You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

533 lines
17 KiB

  1. //----------------------------------------------------------------------------
  2. // Copyright (C) 2004-2025 by EMGU Corporation. All rights reserved.
  3. //----------------------------------------------------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using Emgu.CV;
  12. using Emgu.CV.Util;
  13. using Emgu.CV.CvEnum;
  14. using Emgu.CV.Structure;
  15. using Emgu.Util.TypeEnum;
  16. using System.Runtime.InteropServices;
  17. using Microsoft.Maui.Controls.PlatformConfiguration;
  18. #if __MACCATALYST__
  19. using AppKit;
  20. using CoreGraphics;
  21. #elif __IOS__
  22. using UIKit;
  23. using CoreGraphics;
  24. using Microsoft.Maui.Controls.Compatibility.Platform.iOS;
  25. #elif __ANDROID__
  26. using Android.Graphics;
  27. using Android.Views;
  28. using Android.Widget;
  29. using Android.Hardware.Lights;
  30. #elif WINDOWS
  31. using Visibility = Microsoft.UI.Xaml.Visibility;
  32. using Microsoft.UI.Xaml.Media.Imaging;
  33. #endif
  34. namespace Emgu.CV.Platform.Maui.UI
  35. {
  36. /// <summary>
  37. /// A simple page with a button, message text and an image
  38. /// </summary>
  39. public class ButtonTextImagePage
  40. #if __IOS__
  41. : Emgu.Util.AvCaptureSessionPage
  42. #elif __ANDROID__
  43. : AndroidCameraPage
  44. #else
  45. : ContentPage
  46. #endif
  47. {
  48. private Picker _picker = new Picker();
  49. /// <summary>
  50. /// Get the file picker
  51. /// </summary>
  52. public Picker Picker
  53. {
  54. get { return _picker; }
  55. }
  56. private Microsoft.Maui.Controls.Button _topButton = new Microsoft.Maui.Controls.Button();
  57. /// <summary>
  58. /// Get the button at the top
  59. /// </summary>
  60. public Microsoft.Maui.Controls.Button TopButton
  61. {
  62. get { return _topButton; }
  63. }
  64. private Label _messageLabel = new Label();
  65. /// <summary>
  66. /// Get the message label
  67. /// </summary>
  68. public Label MessageLabel
  69. {
  70. get { return _messageLabel; }
  71. }
  72. private Editor _logEditor = new Editor();
  73. /// <summary>
  74. /// The log message editor
  75. /// </summary>
  76. public Editor LogEditor
  77. {
  78. get { return _logEditor; }
  79. }
  80. private CvImageView _displayImage = new CvImageView();
  81. /// <summary>
  82. /// Get the displayed image
  83. /// </summary>
  84. public CvImageView DisplayImage
  85. {
  86. get { return _displayImage; }
  87. //set { _displayImage = value; }
  88. }
  89. /// <summary>
  90. /// Set the image to be displayed
  91. /// </summary>
  92. /// <param name="image">The image to be displayed</param>
  93. public virtual void SetImage(IInputArray image)
  94. {
  95. //No need to dispatch here, ImageView will handle the proper dispatch
  96. this.DisplayImage.SetImage(image);
  97. }
  98. private StackLayout _mainLayout = new StackLayout();
  99. /// <summary>
  100. /// Get the main layout
  101. /// </summary>
  102. public StackLayout MainLayout
  103. {
  104. get { return _mainLayout; }
  105. }
  106. private Microsoft.Maui.Controls.Button[] _additionalButtons;
  107. /// <summary>
  108. /// Get the list of additional buttons
  109. /// </summary>
  110. public Microsoft.Maui.Controls.Button[] AdditionalButtons
  111. {
  112. get
  113. {
  114. return _additionalButtons;
  115. }
  116. }
  117. /// <summary>
  118. /// Create a simple page with a button, message label and image.
  119. /// </summary>
  120. /// <param name="additionalButtons">Additional buttons added below the standard button.</param>
  121. public ButtonTextImagePage(Microsoft.Maui.Controls.Button[] additionalButtons = null)
  122. {
  123. var horizontalLayoutOptions = LayoutOptions.Center;
  124. TopButton.Text = "Click me";
  125. TopButton.IsEnabled = true;
  126. TopButton.HorizontalOptions = horizontalLayoutOptions;
  127. MessageLabel.Text = "";
  128. MessageLabel.HorizontalOptions = horizontalLayoutOptions;
  129. //DisplayImage.HeightRequest = 100;
  130. //DisplayImage.WidthRequest = 100;
  131. //DisplayImage.MinimumHeightRequest = 10;
  132. //StackLayout mainLayout = new StackLayout();
  133. _mainLayout.Children.Add(Picker);
  134. Picker.IsVisible = false;
  135. _mainLayout.Children.Add(TopButton);
  136. if (additionalButtons != null)
  137. {
  138. foreach (Microsoft.Maui.Controls.Button button in additionalButtons)
  139. {
  140. button.HorizontalOptions = horizontalLayoutOptions;
  141. _mainLayout.Children.Add(button);
  142. }
  143. }
  144. _additionalButtons = additionalButtons;
  145. _mainLayout.Children.Add(MessageLabel);
  146. //MessageLabel.BackgroundColor = Color.AliceBlue;
  147. //DisplayImage.BackgroundColor = Color.Aqua;
  148. //_mainLayout.BackgroundColor = Color.Blue;
  149. _mainLayout.Children.Add(DisplayImage);
  150. DisplayImage.BackgroundColor =
  151. Microsoft.Maui.Graphics.Color.FromRgb(1.0, 0.0, 0.0);
  152. //_mainLayout.Children.Add(MessageLabel);
  153. LogEditor.Text = "";
  154. LogEditor.HorizontalOptions = LayoutOptions.Center;
  155. LogEditor.VerticalOptions = LayoutOptions.Center;
  156. LogEditor.FontSize = LogEditor.FontSize / 2;
  157. _mainLayout.Children.Add(LogEditor);
  158. SetLog(null);
  159. _mainLayout.Padding = new Thickness(10, 10, 10, 10);
  160. this.Content = new Microsoft.Maui.Controls.ScrollView()
  161. {
  162. Content = _mainLayout
  163. };
  164. }
  165. /// <summary>
  166. /// A flag that indicates if the file picker allow getting image from camera
  167. /// </summary>
  168. private bool? _hasCameraOption = null;
  169. /// <summary>
  170. /// Get the default camera option
  171. /// </summary>
  172. /// <returns>A flag that indicates if the file picker allow getting image from camera, will be used by HasCameraOption getter if no value is set.</returns>
  173. protected virtual bool GetDefaultCameraOption()
  174. {
  175. return false;
  176. }
  177. /// <summary>
  178. /// A flag that indicates if the file picker allow getting image from camera
  179. /// </summary>
  180. public bool HasCameraOption
  181. {
  182. get
  183. {
  184. if (_hasCameraOption == null)
  185. {
  186. _hasCameraOption = GetDefaultCameraOption();
  187. }
  188. return _hasCameraOption.Value;
  189. }
  190. set
  191. {
  192. _hasCameraOption = value;
  193. }
  194. }
  195. /// <summary>
  196. /// Load the images and return them asynchronously
  197. /// </summary>
  198. /// <param name="imageNames">The name of the images</param>
  199. /// <param name="labels">The labels of the images</param>
  200. /// <returns>The images loaded</returns>
  201. public virtual async Task<Mat[]> LoadImages(String[] imageNames, String[] labels = null)
  202. {
  203. Mat[] mats = new Mat[imageNames.Length];
  204. SetMessage("Checking if camera option is available, please wait...");
  205. //Run it once in case it need to check if camera is available, which could take a long time to run
  206. await Task.Run(() => { bool cameraOption = this.HasCameraOption; });
  207. SetMessage(null);
  208. bool captureSupported;
  209. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI
  210. || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.macOS)
  211. {
  212. //Pick image from camera is not implemented on WPF.
  213. captureSupported = false;
  214. }
  215. else
  216. {
  217. captureSupported = MediaPicker.IsCaptureSupported;
  218. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android)
  219. {
  220. //Overwrite MediaPicker if there is no camera on this Android device.
  221. if (!this.HasCameraOption)
  222. {
  223. captureSupported = false;
  224. }
  225. }
  226. }
  227. for (int i = 0; i < mats.Length; i++)
  228. {
  229. String pickImgString = "Use Image from";
  230. if (labels != null && labels.Length > i)
  231. pickImgString = labels[i];
  232. String action;
  233. List<String> options = new List<string>();
  234. options.Add("Default");
  235. options.Add("Photo Library");
  236. if (captureSupported)
  237. options.Add("Photo from Camera");
  238. if (this.HasCameraOption)
  239. {
  240. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android)
  241. {
  242. if (captureSupported)
  243. {
  244. options.Add("Camera");
  245. /*
  246. #if __ANDROID__
  247. if (this.CameraBackend == AndroidCameraBackend.AndroidCamera2)
  248. {
  249. foreach (String cameraId in AndroidCameraManager.GetAvailableCameraIds())
  250. {
  251. options.Add(String.Format("Camera {0}", cameraId));
  252. }
  253. }
  254. else
  255. {
  256. options.Add("Camera");
  257. }
  258. #else
  259. options.Add("Camera");
  260. #endif
  261. */
  262. }
  263. } else if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.iOS)
  264. {
  265. if (captureSupported)
  266. options.Add("Camera");
  267. }
  268. else if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI
  269. || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.macOS)
  270. {
  271. var openCVConfigDict = CvInvoke.ConfigDict;
  272. bool haveVideoio = (openCVConfigDict["HAVE_OPENCV_VIDEOIO"] != 0);
  273. if (haveVideoio)
  274. options.Add("Camera");
  275. }
  276. }
  277. if (options.Count == 1)
  278. {
  279. action = "Default";
  280. }
  281. else
  282. {
  283. action = await DisplayActionSheet(pickImgString, "Cancel", null, options.ToArray());
  284. if (
  285. action == null //user clicked outside of action sheet
  286. || action.Equals("Cancel") // user cancel
  287. )
  288. return null;
  289. }
  290. if (action.Equals("Default"))
  291. {
  292. using (Stream stream = await FileSystem.OpenAppPackageFileAsync(imageNames[i]))
  293. using (MemoryStream ms = new MemoryStream())
  294. {
  295. stream.CopyTo(ms);
  296. Mat m = new Mat();
  297. CvInvoke.Imdecode(ms.ToArray(), ImreadModes.ColorBgr, m);
  298. mats[i] = m;
  299. }
  300. }
  301. else if (action.Equals("Photo Library"))
  302. {
  303. /*
  304. if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI)
  305. {
  306. #if !(__MACCATALYST__ || __ANDROID__ || __IOS__ || NETFX_CORE)
  307. Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog();
  308. dialog.Multiselect = false;
  309. dialog.Title = "Select an Image File";
  310. dialog.Filter = "Image | *.jpg;*.jpeg;*.png;*.bmp;*.gif | All Files | *";
  311. if (dialog.ShowDialog() == false)
  312. return null;
  313. mats[i] = CvInvoke.Imread(dialog.FileName, ImreadModes.AnyColor);
  314. #endif
  315. }
  316. else
  317. */
  318. {
  319. FileResult fileResult = await FilePicker.PickAsync(PickOptions.Images);
  320. if (fileResult == null) //canceled
  321. return null;
  322. using (Stream s = await fileResult.OpenReadAsync())
  323. mats[i] = await ReadStream(s);
  324. }
  325. }
  326. else if (action.Equals("Photo from Camera"))
  327. {
  328. PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.Camera>();
  329. if (status == PermissionStatus.Denied)
  330. {
  331. this.SetMessage("Please grant permission to use camera");
  332. }
  333. else
  334. {
  335. var takePhotoResult = await MediaPicker.CapturePhotoAsync();
  336. if (takePhotoResult == null) //canceled
  337. return null;
  338. using (Stream stream = await takePhotoResult.OpenReadAsync())
  339. mats[i] = await ReadStream(stream);
  340. }
  341. }
  342. else if (action.Equals("Camera"))
  343. {
  344. mats = new Mat[0];
  345. #if __ANDROID__
  346. String cameraIdCandidate = action.Replace("Camera ", "");
  347. if (AndroidCameraManager.GetAvailableCameraIds().Contains(cameraIdCandidate))
  348. {
  349. _preferredCameraId = cameraIdCandidate;
  350. }
  351. else
  352. {
  353. _preferredCameraId = null;
  354. }
  355. #endif
  356. }
  357. }
  358. return mats;
  359. }
  360. private static async Task<Mat> ReadStream(Stream stream)
  361. {
  362. using (MemoryStream ms = new MemoryStream())
  363. {
  364. await stream.CopyToAsync(ms);
  365. byte[] data = ms.ToArray();
  366. Mat m = new Mat();
  367. CvInvoke.Imdecode(data, ImreadModes.ColorBgr, m);
  368. return m;
  369. }
  370. }
  371. /// <summary>
  372. /// Get the message label UI
  373. /// </summary>
  374. /// <returns>The message label ui</returns>
  375. public Label GetLabel()
  376. {
  377. //return null;
  378. return this.MessageLabel;
  379. }
  380. /// <summary>
  381. /// Set the message to be displayed
  382. /// </summary>
  383. /// <param name="message">The message to be displayed</param>
  384. public void SetMessage(String message)
  385. {
  386. this.Dispatcher.Dispatch(
  387. () =>
  388. {
  389. Label label = GetLabel();
  390. label.Text = message;
  391. label.LineBreakMode = LineBreakMode.WordWrap;
  392. //label.WidthRequest = this.Width;
  393. }
  394. );
  395. }
  396. private String _log = String.Empty;
  397. /// <summary>
  398. /// Clear the log
  399. /// </summary>
  400. public void ClearLog()
  401. {
  402. SetLog(String.Empty);
  403. }
  404. /// <summary>
  405. /// Set the log
  406. /// </summary>
  407. /// <param name="log">The log</param>
  408. public void SetLog(String log)
  409. {
  410. _log = log;
  411. RenderLog(_log);
  412. }
  413. /// <summary>
  414. /// Append text to the log
  415. /// </summary>
  416. /// <param name="log">The text to be append to the log</param>
  417. public void AppendLog(String log)
  418. {
  419. if (!String.IsNullOrEmpty(_log))
  420. _log = log + _log;
  421. RenderLog(_log);
  422. }
  423. /// <summary>
  424. /// Render the log
  425. /// </summary>
  426. /// <param name="log">The log to be rendered</param>
  427. private void RenderLog(String log)
  428. {
  429. this.Dispatcher.Dispatch(
  430. () =>
  431. {
  432. if (String.IsNullOrEmpty(log))
  433. {
  434. this.LogEditor.IsVisible = false;
  435. }
  436. else
  437. {
  438. this.LogEditor.IsVisible = true;
  439. }
  440. this.LogEditor.Text = log;
  441. this.LogEditor.WidthRequest = this.Width;
  442. this.LogEditor.HeightRequest = 120;
  443. //this.LogLabel.LineBreakMode = LineBreakMode.WordWrap;
  444. this.LogEditor.Focus();
  445. }
  446. );
  447. }
  448. /// <summary>
  449. /// Get the main button
  450. /// </summary>
  451. /// <returns>The main button</returns>
  452. public Microsoft.Maui.Controls.Button GetButton()
  453. {
  454. //return null;
  455. return this.TopButton;
  456. }
  457. }
  458. }