//---------------------------------------------------------------------------- // Copyright (C) 2004-2025 by EMGU Corporation. All rights reserved. //---------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Emgu.CV; using Emgu.CV.Util; using Emgu.CV.CvEnum; using Emgu.CV.Structure; using Emgu.Util.TypeEnum; using System.Runtime.InteropServices; using Microsoft.Maui.Controls.PlatformConfiguration; #if __MACCATALYST__ using AppKit; using CoreGraphics; #elif __IOS__ using UIKit; using CoreGraphics; using Microsoft.Maui.Controls.Compatibility.Platform.iOS; #elif __ANDROID__ using Android.Graphics; using Android.Views; using Android.Widget; using Android.Hardware.Lights; #elif WINDOWS using Visibility = Microsoft.UI.Xaml.Visibility; using Microsoft.UI.Xaml.Media.Imaging; #endif namespace Emgu.CV.Platform.Maui.UI { /// /// A simple page with a button, message text and an image /// public class ButtonTextImagePage #if __IOS__ : Emgu.Util.AvCaptureSessionPage #elif __ANDROID__ : AndroidCameraPage #else : ContentPage #endif { private Picker _picker = new Picker(); /// /// Get the file picker /// public Picker Picker { get { return _picker; } } private Microsoft.Maui.Controls.Button _topButton = new Microsoft.Maui.Controls.Button(); /// /// Get the button at the top /// public Microsoft.Maui.Controls.Button TopButton { get { return _topButton; } } private Label _messageLabel = new Label(); /// /// Get the message label /// public Label MessageLabel { get { return _messageLabel; } } private Editor _logEditor = new Editor(); /// /// The log message editor /// public Editor LogEditor { get { return _logEditor; } } private CvImageView _displayImage = new CvImageView(); /// /// Get the displayed image /// public CvImageView DisplayImage { get { return _displayImage; } //set { _displayImage = value; } } /// /// Set the image to be displayed /// /// The image to be displayed public virtual void SetImage(IInputArray image) { //No need to dispatch here, ImageView will handle the proper dispatch this.DisplayImage.SetImage(image); } private StackLayout _mainLayout = new StackLayout(); /// /// Get the main layout /// public StackLayout MainLayout { get { return _mainLayout; } } private Microsoft.Maui.Controls.Button[] _additionalButtons; /// /// Get the list of additional buttons /// public Microsoft.Maui.Controls.Button[] AdditionalButtons { get { return _additionalButtons; } } /// /// Create a simple page with a button, message label and image. /// /// Additional buttons added below the standard button. public ButtonTextImagePage(Microsoft.Maui.Controls.Button[] additionalButtons = null) { var horizontalLayoutOptions = LayoutOptions.Center; TopButton.Text = "Click me"; TopButton.IsEnabled = true; TopButton.HorizontalOptions = horizontalLayoutOptions; MessageLabel.Text = ""; MessageLabel.HorizontalOptions = horizontalLayoutOptions; //DisplayImage.HeightRequest = 100; //DisplayImage.WidthRequest = 100; //DisplayImage.MinimumHeightRequest = 10; //StackLayout mainLayout = new StackLayout(); _mainLayout.Children.Add(Picker); Picker.IsVisible = false; _mainLayout.Children.Add(TopButton); if (additionalButtons != null) { foreach (Microsoft.Maui.Controls.Button button in additionalButtons) { button.HorizontalOptions = horizontalLayoutOptions; _mainLayout.Children.Add(button); } } _additionalButtons = additionalButtons; _mainLayout.Children.Add(MessageLabel); //MessageLabel.BackgroundColor = Color.AliceBlue; //DisplayImage.BackgroundColor = Color.Aqua; //_mainLayout.BackgroundColor = Color.Blue; _mainLayout.Children.Add(DisplayImage); DisplayImage.BackgroundColor = Microsoft.Maui.Graphics.Color.FromRgb(1.0, 0.0, 0.0); //_mainLayout.Children.Add(MessageLabel); LogEditor.Text = ""; LogEditor.HorizontalOptions = LayoutOptions.Center; LogEditor.VerticalOptions = LayoutOptions.Center; LogEditor.FontSize = LogEditor.FontSize / 2; _mainLayout.Children.Add(LogEditor); SetLog(null); _mainLayout.Padding = new Thickness(10, 10, 10, 10); this.Content = new Microsoft.Maui.Controls.ScrollView() { Content = _mainLayout }; } /// /// A flag that indicates if the file picker allow getting image from camera /// private bool? _hasCameraOption = null; /// /// Get the default camera option /// /// A flag that indicates if the file picker allow getting image from camera, will be used by HasCameraOption getter if no value is set. protected virtual bool GetDefaultCameraOption() { return false; } /// /// A flag that indicates if the file picker allow getting image from camera /// public bool HasCameraOption { get { if (_hasCameraOption == null) { _hasCameraOption = GetDefaultCameraOption(); } return _hasCameraOption.Value; } set { _hasCameraOption = value; } } /// /// Load the images and return them asynchronously /// /// The name of the images /// The labels of the images /// The images loaded public virtual async Task LoadImages(String[] imageNames, String[] labels = null) { Mat[] mats = new Mat[imageNames.Length]; SetMessage("Checking if camera option is available, please wait..."); //Run it once in case it need to check if camera is available, which could take a long time to run await Task.Run(() => { bool cameraOption = this.HasCameraOption; }); SetMessage(null); bool captureSupported; if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.macOS) { //Pick image from camera is not implemented on WPF. captureSupported = false; } else { captureSupported = MediaPicker.IsCaptureSupported; if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android) { //Overwrite MediaPicker if there is no camera on this Android device. if (!this.HasCameraOption) { captureSupported = false; } } } for (int i = 0; i < mats.Length; i++) { String pickImgString = "Use Image from"; if (labels != null && labels.Length > i) pickImgString = labels[i]; String action; List options = new List(); options.Add("Default"); options.Add("Photo Library"); if (captureSupported) options.Add("Photo from Camera"); if (this.HasCameraOption) { if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.Android) { if (captureSupported) { options.Add("Camera"); /* #if __ANDROID__ if (this.CameraBackend == AndroidCameraBackend.AndroidCamera2) { foreach (String cameraId in AndroidCameraManager.GetAvailableCameraIds()) { options.Add(String.Format("Camera {0}", cameraId)); } } else { options.Add("Camera"); } #else options.Add("Camera"); #endif */ } } else if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.iOS) { if (captureSupported) options.Add("Camera"); } else if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI || Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.macOS) { var openCVConfigDict = CvInvoke.ConfigDict; bool haveVideoio = (openCVConfigDict["HAVE_OPENCV_VIDEOIO"] != 0); if (haveVideoio) options.Add("Camera"); } } if (options.Count == 1) { action = "Default"; } else { action = await DisplayActionSheet(pickImgString, "Cancel", null, options.ToArray()); if ( action == null //user clicked outside of action sheet || action.Equals("Cancel") // user cancel ) return null; } if (action.Equals("Default")) { using (Stream stream = await FileSystem.OpenAppPackageFileAsync(imageNames[i])) using (MemoryStream ms = new MemoryStream()) { stream.CopyTo(ms); Mat m = new Mat(); CvInvoke.Imdecode(ms.ToArray(), ImreadModes.ColorBgr, m); mats[i] = m; } } else if (action.Equals("Photo Library")) { /* if (Microsoft.Maui.Devices.DeviceInfo.Platform == DevicePlatform.WinUI) { #if !(__MACCATALYST__ || __ANDROID__ || __IOS__ || NETFX_CORE) Microsoft.Win32.OpenFileDialog dialog = new Microsoft.Win32.OpenFileDialog(); dialog.Multiselect = false; dialog.Title = "Select an Image File"; dialog.Filter = "Image | *.jpg;*.jpeg;*.png;*.bmp;*.gif | All Files | *"; if (dialog.ShowDialog() == false) return null; mats[i] = CvInvoke.Imread(dialog.FileName, ImreadModes.AnyColor); #endif } else */ { FileResult fileResult = await FilePicker.PickAsync(PickOptions.Images); if (fileResult == null) //canceled return null; using (Stream s = await fileResult.OpenReadAsync()) mats[i] = await ReadStream(s); } } else if (action.Equals("Photo from Camera")) { PermissionStatus status = await Permissions.CheckStatusAsync(); if (status == PermissionStatus.Denied) { this.SetMessage("Please grant permission to use camera"); } else { var takePhotoResult = await MediaPicker.CapturePhotoAsync(); if (takePhotoResult == null) //canceled return null; using (Stream stream = await takePhotoResult.OpenReadAsync()) mats[i] = await ReadStream(stream); } } else if (action.Equals("Camera")) { mats = new Mat[0]; #if __ANDROID__ String cameraIdCandidate = action.Replace("Camera ", ""); if (AndroidCameraManager.GetAvailableCameraIds().Contains(cameraIdCandidate)) { _preferredCameraId = cameraIdCandidate; } else { _preferredCameraId = null; } #endif } } return mats; } private static async Task ReadStream(Stream stream) { using (MemoryStream ms = new MemoryStream()) { await stream.CopyToAsync(ms); byte[] data = ms.ToArray(); Mat m = new Mat(); CvInvoke.Imdecode(data, ImreadModes.ColorBgr, m); return m; } } /// /// Get the message label UI /// /// The message label ui public Label GetLabel() { //return null; return this.MessageLabel; } /// /// Set the message to be displayed /// /// The message to be displayed public void SetMessage(String message) { this.Dispatcher.Dispatch( () => { Label label = GetLabel(); label.Text = message; label.LineBreakMode = LineBreakMode.WordWrap; //label.WidthRequest = this.Width; } ); } private String _log = String.Empty; /// /// Clear the log /// public void ClearLog() { SetLog(String.Empty); } /// /// Set the log /// /// The log public void SetLog(String log) { _log = log; RenderLog(_log); } /// /// Append text to the log /// /// The text to be append to the log public void AppendLog(String log) { if (!String.IsNullOrEmpty(_log)) _log = log + _log; RenderLog(_log); } /// /// Render the log /// /// The log to be rendered private void RenderLog(String log) { this.Dispatcher.Dispatch( () => { if (String.IsNullOrEmpty(log)) { this.LogEditor.IsVisible = false; } else { this.LogEditor.IsVisible = true; } this.LogEditor.Text = log; this.LogEditor.WidthRequest = this.Width; this.LogEditor.HeightRequest = 120; //this.LogLabel.LineBreakMode = LineBreakMode.WordWrap; this.LogEditor.Focus(); } ); } /// /// Get the main button /// /// The main button public Microsoft.Maui.Controls.Button GetButton() { //return null; return this.TopButton; } } }