diff --git a/Camera.MAUI.Plugin.MLKit/Camera.MAUI.Plugin.MLKit.csproj b/Camera.MAUI.Plugin.MLKit/Camera.MAUI.Plugin.MLKit.csproj
new file mode 100644
index 0000000..51a48b8
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/Camera.MAUI.Plugin.MLKit.csproj
@@ -0,0 +1,62 @@
+
+
+
+ net7.0-android;net7.0-ios;
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 11.0
+ 13.1
+ 26.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+ programatix
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.8.1.1
+
+
+ 1.8.1.1
+
+
+ 1.3.0.2
+
+
+ 1.3.0.2
+
+
+ 2.6.2.3
+
+
+ 117.2.0.2
+
+
+ 117.0.0.7
+
+
+ 118.3.0.2
+
+
+
diff --git a/Camera.MAUI.Plugin.MLKit/Extensions.cs b/Camera.MAUI.Plugin.MLKit/Extensions.cs
new file mode 100644
index 0000000..5386f47
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/Extensions.cs
@@ -0,0 +1,6 @@
+namespace Camera.MAUI.Plugin.MLKit
+{
+ internal static class Extensions
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoder.cs b/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoder.cs
new file mode 100644
index 0000000..b187390
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoder.cs
@@ -0,0 +1,107 @@
+#if IOS || MACCATALYST
+using Foundation;
+using global::MLKit.BarcodeScanning;
+using global::MLKit.Core;
+using DecodeDataType = UIKit.UIImage;
+#elif ANDROID
+
+using Xamarin.Google.MLKit.Vision.BarCode;
+using Xamarin.Google.MLKit.Vision.Common;
+using static Xamarin.Google.MLKit.Vision.Barcode.Common.Barcode;
+using BarcodeScanner = Xamarin.Google.MLKit.Vision.BarCode.IBarcodeScanner;
+using DecodeDataType = Android.Graphics.Bitmap;
+
+#elif WINDOWS
+using BarcodeScanner = System.Object;
+using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap;
+#endif
+
+namespace Camera.MAUI.Plugin.MLKit
+{
+ public class MLKitBarcodeDecoder : PluginDecoder
+ {
+ #region Private Fields
+
+ private BarcodeScanner barcodeScanner;
+
+ #endregion Private Fields
+
+ #region Public Methods
+
+ public override void ClearResults()
+ {
+ }
+
+ public
+#if ANDROID
+ async
+#endif
+ override void Decode(DecodeDataType data)
+ {
+ try
+ {
+#if ANDROID
+ var image = InputImage.FromBitmap(data, 0);
+ var result = await barcodeScanner.Process(image).ToAwaitableTask();
+ var results = Methods.ProcessBarcodeResult(result);
+ if (results?.Count > 0)
+ {
+ OnDecoded(new PluginDecodedEventArgs { Results = results.ToArray() });
+ }
+ image.Dispose();
+#elif IOS
+ var image = new MLImage(data) { Orientation = UIKit.UIImageOrientation.Up };
+ barcodeScanner.ProcessImage(image, (barcodes, error) =>
+ {
+ var results = new List();
+ foreach (var barcode in barcodes)
+ results.Add(Methods.ProcessBarcodeResult(barcode));
+
+ if (results.Count > 0)
+ {
+ OnDecoded(new PluginDecodedEventArgs { Results = results.ToArray() });
+ }
+
+ image.Dispose();
+ });
+#else
+ throw new NotImplementedException();
+#endif
+ }
+ catch { }
+ finally
+ {
+ }
+ }
+
+ #endregion Public Methods
+
+ #region Protected Methods
+
+ protected override void OnOptionsChanged(object oldValue, object newValue)
+ {
+ if (newValue is BarcodeDecoderOptions barcodeOptions)
+ {
+#if ANDROID || IOS
+ var platformFormats = barcodeOptions.PossibleFormats?.Count > 0
+ ? barcodeOptions.PossibleFormats.Select(x => x.ToPlatform()).Aggregate((r, x) => r |= x)
+ : default;
+#if ANDROID
+ barcodeScanner = BarcodeScanning.GetClient(new BarcodeScannerOptions.Builder()
+ .SetBarcodeFormats(platformFormats == default ? FormatAllFormats : platformFormats)
+ .Build()
+ );
+#elif IOS
+ var scannerOptions = new BarcodeScannerOptions(platformFormats == default ? global::MLKit.BarcodeScanning.BarcodeFormat.Unknown : platformFormats);
+ barcodeScanner = BarcodeScanner.BarcodeScannerWithOptions(scannerOptions);
+#endif
+#endif
+ }
+ if (newValue is MLKitBarcodeDecoderOptions mlkitOptions)
+ {
+ }
+ }
+
+ #endregion Protected Methods
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoderOptions.cs b/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoderOptions.cs
new file mode 100644
index 0000000..c165238
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/MLKitBarcodeDecoderOptions.cs
@@ -0,0 +1,6 @@
+namespace Camera.MAUI.Plugin.MLKit
+{
+ public record MLKitBarcodeDecoderOptions : BarcodeDecoderOptions
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.MLKit/Platforms/Android/Methods.cs b/Camera.MAUI.Plugin.MLKit/Platforms/Android/Methods.cs
new file mode 100644
index 0000000..3858719
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/Platforms/Android/Methods.cs
@@ -0,0 +1,83 @@
+using Android.Runtime;
+using Java.Util;
+using Xamarin.Google.MLKit.Vision.Barcode.Common;
+
+namespace Camera.MAUI.Plugin.MLKit
+{
+ internal static class Methods
+ {
+ internal static int ToPlatform(this BarcodeFormat format)
+ {
+ return format switch
+ {
+ BarcodeFormat.AZTEC => Barcode.FormatAztec,
+ BarcodeFormat.CODABAR => Barcode.FormatCodabar,
+ BarcodeFormat.CODE_128 => Barcode.FormatCode128,
+ BarcodeFormat.CODE_39 => Barcode.FormatCode39,
+ BarcodeFormat.CODE_93 => Barcode.FormatCode93,
+ BarcodeFormat.DATA_MATRIX => Barcode.FormatDataMatrix,
+ BarcodeFormat.EAN_13 => Barcode.FormatEan13,
+ BarcodeFormat.EAN_8 => Barcode.FormatEan8,
+ BarcodeFormat.ITF => Barcode.FormatItf,
+ BarcodeFormat.PDF_417 => Barcode.FormatPdf417,
+ BarcodeFormat.QR_CODE => Barcode.FormatQrCode,
+ BarcodeFormat.UPC_A => Barcode.FormatUpcA,
+ BarcodeFormat.UPC_E => Barcode.FormatUpcE,
+ _ => Barcode.FormatUnknown,
+ };
+ }
+
+ internal static BarcodeFormat BarcodeFormatToNative(this int format)
+ {
+ return format switch
+ {
+ Barcode.FormatAztec => BarcodeFormat.AZTEC,
+ Barcode.FormatCodabar => BarcodeFormat.CODABAR,
+ Barcode.FormatCode128 => BarcodeFormat.CODE_128,
+ Barcode.FormatCode39 => BarcodeFormat.CODE_39,
+ Barcode.FormatCode93 => BarcodeFormat.CODE_93,
+ Barcode.FormatDataMatrix => BarcodeFormat.DATA_MATRIX,
+ Barcode.FormatEan13 => BarcodeFormat.EAN_13,
+ Barcode.FormatEan8 => BarcodeFormat.EAN_8,
+ Barcode.FormatItf => BarcodeFormat.ITF,
+ Barcode.FormatPdf417 => BarcodeFormat.PDF_417,
+ Barcode.FormatQrCode => BarcodeFormat.QR_CODE,
+ Barcode.FormatUpcA => BarcodeFormat.UPC_A,
+ Barcode.FormatUpcE => BarcodeFormat.UPC_E,
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ internal static List ProcessBarcodeResult(Java.Lang.Object result)
+ {
+ if (result == null)
+ return null;
+ var javaList = result.JavaCast();
+ if (javaList.IsEmpty)
+ return null;
+ var resultList = new List();
+
+ foreach (var barcode in javaList.ToArray())
+ {
+ var mapped = barcode.JavaCast();
+ var cornerPoints = new List();
+
+ foreach (var cornerPoint in mapped.GetCornerPoints())
+ cornerPoints.Add(new Point(cornerPoint.X, cornerPoint.Y));
+
+ resultList.Add(new BarcodeResult(mapped.DisplayValue, mapped.GetRawBytes(), cornerPoints.ToArray(), mapped.Format.BarcodeFormatToNative()));
+ }
+
+ return resultList;
+ }
+
+ internal static Task ToAwaitableTask(this global::Android.Gms.Tasks.Task task)
+ {
+ var taskCompletionSource = new TaskCompletionSource();
+ var taskCompleteListener = new TaskCompleteListener(taskCompletionSource);
+ task.AddOnCompleteListener(taskCompleteListener);
+
+ return taskCompletionSource.Task;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.MLKit/Platforms/Android/TaskCompleteListener.cs b/Camera.MAUI.Plugin.MLKit/Platforms/Android/TaskCompleteListener.cs
new file mode 100644
index 0000000..0c7e2a5
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/Platforms/Android/TaskCompleteListener.cs
@@ -0,0 +1,30 @@
+using Android.Gms.Tasks;
+
+namespace Camera.MAUI.Plugin.MLKit
+{
+ internal class TaskCompleteListener : Java.Lang.Object, IOnCompleteListener
+ {
+ private readonly TaskCompletionSource _taskCompletionSource;
+
+ public TaskCompleteListener(TaskCompletionSource tcs)
+ {
+ _taskCompletionSource = tcs;
+ }
+
+ public void OnComplete(global::Android.Gms.Tasks.Task task)
+ {
+ if (task.IsCanceled)
+ {
+ _taskCompletionSource.SetCanceled();
+ }
+ else if (task.IsSuccessful)
+ {
+ _taskCompletionSource.SetResult(task.Result);
+ }
+ else
+ {
+ _taskCompletionSource.SetException(task.Exception);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.MLKit/Platforms/iOS/Methods.cs b/Camera.MAUI.Plugin.MLKit/Platforms/iOS/Methods.cs
new file mode 100644
index 0000000..9a25a73
--- /dev/null
+++ b/Camera.MAUI.Plugin.MLKit/Platforms/iOS/Methods.cs
@@ -0,0 +1,57 @@
+namespace Camera.MAUI.Plugin.MLKit
+{
+ internal static class Methods
+ {
+ internal static global::MLKit.BarcodeScanning.BarcodeFormat ToPlatform(this BarcodeFormat format)
+ {
+ return format switch
+ {
+ BarcodeFormat.AZTEC => global::MLKit.BarcodeScanning.BarcodeFormat.Aztec,
+ BarcodeFormat.CODABAR => global::MLKit.BarcodeScanning.BarcodeFormat.CodaBar,
+ BarcodeFormat.CODE_128 => global::MLKit.BarcodeScanning.BarcodeFormat.Code128,
+ BarcodeFormat.CODE_39 => global::MLKit.BarcodeScanning.BarcodeFormat.Code39,
+ BarcodeFormat.CODE_93 => global::MLKit.BarcodeScanning.BarcodeFormat.Code93,
+ BarcodeFormat.DATA_MATRIX => global::MLKit.BarcodeScanning.BarcodeFormat.DataMatrix,
+ BarcodeFormat.EAN_13 => global::MLKit.BarcodeScanning.BarcodeFormat.Ean13,
+ BarcodeFormat.EAN_8 => global::MLKit.BarcodeScanning.BarcodeFormat.Ean8,
+ BarcodeFormat.ITF => global::MLKit.BarcodeScanning.BarcodeFormat.Itf,
+ BarcodeFormat.PDF_417 => global::MLKit.BarcodeScanning.BarcodeFormat.Pdf417,
+ BarcodeFormat.QR_CODE => global::MLKit.BarcodeScanning.BarcodeFormat.QrCode,
+ BarcodeFormat.UPC_A => global::MLKit.BarcodeScanning.BarcodeFormat.Upca,
+ BarcodeFormat.UPC_E => global::MLKit.BarcodeScanning.BarcodeFormat.Upce,
+ _ => global::MLKit.BarcodeScanning.BarcodeFormat.Unknown,
+ };
+ }
+
+ internal static BarcodeFormat BarcodeFormatToNative(this global::MLKit.BarcodeScanning.BarcodeFormat format)
+ {
+ return format switch
+ {
+ global::MLKit.BarcodeScanning.BarcodeFormat.Aztec => BarcodeFormat.AZTEC,
+ global::MLKit.BarcodeScanning.BarcodeFormat.CodaBar => BarcodeFormat.CODABAR,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Code128 => BarcodeFormat.CODE_128,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Code39 => BarcodeFormat.CODE_39,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Code93 => BarcodeFormat.CODE_93,
+ global::MLKit.BarcodeScanning.BarcodeFormat.DataMatrix => BarcodeFormat.DATA_MATRIX,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Ean13 => BarcodeFormat.EAN_13,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Ean8 => BarcodeFormat.EAN_8,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Itf => BarcodeFormat.ITF,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Pdf417 => BarcodeFormat.PDF_417,
+ global::MLKit.BarcodeScanning.BarcodeFormat.QrCode => BarcodeFormat.QR_CODE,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Upca => BarcodeFormat.UPC_A,
+ global::MLKit.BarcodeScanning.BarcodeFormat.Upce => BarcodeFormat.UPC_E,
+ _ => throw new NotSupportedException()
+ };
+ }
+
+ internal static BarcodeResult ProcessBarcodeResult(global::MLKit.BarcodeScanning.Barcode barcode)
+ {
+ var cornerPoints = new List();
+
+ foreach (var cornerPoint in barcode.CornerPoints)
+ cornerPoints.Add(new Point(cornerPoint.CGPointValue.X, cornerPoint.CGPointValue.Y));
+
+ return new BarcodeResult(barcode.DisplayValue, barcode.RawData.ToArray(), cornerPoints.ToArray(), barcode.Format.BarcodeFormatToNative());
+ }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/Camera.MAUI.Plugin.ZXing.csproj b/Camera.MAUI.Plugin.ZXing/Camera.MAUI.Plugin.ZXing.csproj
new file mode 100644
index 0000000..6611e7a
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/Camera.MAUI.Plugin.ZXing.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 11.0
+ 13.1
+ 26.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+ hjam40,programatix
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Camera.MAUI.Plugin.ZXing/Extensions.cs b/Camera.MAUI.Plugin.ZXing/Extensions.cs
new file mode 100644
index 0000000..4ea57e4
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/Extensions.cs
@@ -0,0 +1,79 @@
+using ZXing;
+
+namespace Camera.MAUI.Plugin.ZXing
+{
+ internal static class Extensions
+ {
+ internal static global::ZXing.BarcodeFormat ToPlatform(this BarcodeFormat format)
+ {
+ return format switch
+ {
+ BarcodeFormat.AZTEC => global::ZXing.BarcodeFormat.AZTEC,
+ BarcodeFormat.CODABAR => global::ZXing.BarcodeFormat.CODABAR,
+ BarcodeFormat.CODE_39 => global::ZXing.BarcodeFormat.CODE_39,
+ BarcodeFormat.CODE_93 => global::ZXing.BarcodeFormat.CODE_93,
+ BarcodeFormat.CODE_128 => global::ZXing.BarcodeFormat.CODE_128,
+ BarcodeFormat.DATA_MATRIX => global::ZXing.BarcodeFormat.DATA_MATRIX,
+ BarcodeFormat.EAN_8 => global::ZXing.BarcodeFormat.EAN_8,
+ BarcodeFormat.EAN_13 => global::ZXing.BarcodeFormat.EAN_13,
+ BarcodeFormat.ITF => global::ZXing.BarcodeFormat.ITF,
+ BarcodeFormat.MAXICODE => global::ZXing.BarcodeFormat.MAXICODE,
+ BarcodeFormat.PDF_417 => global::ZXing.BarcodeFormat.PDF_417,
+ BarcodeFormat.QR_CODE => global::ZXing.BarcodeFormat.QR_CODE,
+ BarcodeFormat.RSS_14 => global::ZXing.BarcodeFormat.RSS_14,
+ BarcodeFormat.RSS_EXPANDED => global::ZXing.BarcodeFormat.RSS_EXPANDED,
+ BarcodeFormat.UPC_A => global::ZXing.BarcodeFormat.UPC_A,
+ BarcodeFormat.UPC_E => global::ZXing.BarcodeFormat.UPC_E,
+ BarcodeFormat.UPC_EAN_EXTENSION => global::ZXing.BarcodeFormat.UPC_EAN_EXTENSION,
+ BarcodeFormat.MSI => global::ZXing.BarcodeFormat.MSI,
+ BarcodeFormat.PLESSEY => global::ZXing.BarcodeFormat.PLESSEY,
+ BarcodeFormat.IMB => global::ZXing.BarcodeFormat.IMB,
+ BarcodeFormat.PHARMA_CODE => global::ZXing.BarcodeFormat.PHARMA_CODE,
+ BarcodeFormat.All_1D => global::ZXing.BarcodeFormat.All_1D,
+ _ => throw new NotSupportedException(),
+ };
+ }
+
+ internal static BarcodeFormat ToNative(this global::ZXing.BarcodeFormat format)
+ {
+ return format switch
+ {
+ global::ZXing.BarcodeFormat.AZTEC => BarcodeFormat.AZTEC,
+ global::ZXing.BarcodeFormat.CODABAR => BarcodeFormat.CODABAR,
+ global::ZXing.BarcodeFormat.CODE_39 => BarcodeFormat.CODE_39,
+ global::ZXing.BarcodeFormat.CODE_93 => BarcodeFormat.CODE_93,
+ global::ZXing.BarcodeFormat.CODE_128 => BarcodeFormat.CODE_128,
+ global::ZXing.BarcodeFormat.DATA_MATRIX => BarcodeFormat.DATA_MATRIX,
+ global::ZXing.BarcodeFormat.EAN_8 => BarcodeFormat.EAN_8,
+ global::ZXing.BarcodeFormat.EAN_13 => BarcodeFormat.EAN_13,
+ global::ZXing.BarcodeFormat.ITF => BarcodeFormat.ITF,
+ global::ZXing.BarcodeFormat.MAXICODE => BarcodeFormat.MAXICODE,
+ global::ZXing.BarcodeFormat.PDF_417 => BarcodeFormat.PDF_417,
+ global::ZXing.BarcodeFormat.QR_CODE => BarcodeFormat.QR_CODE,
+ global::ZXing.BarcodeFormat.RSS_14 => BarcodeFormat.RSS_14,
+ global::ZXing.BarcodeFormat.RSS_EXPANDED => BarcodeFormat.RSS_EXPANDED,
+ global::ZXing.BarcodeFormat.UPC_A => BarcodeFormat.UPC_A,
+ global::ZXing.BarcodeFormat.UPC_E => BarcodeFormat.UPC_E,
+ global::ZXing.BarcodeFormat.UPC_EAN_EXTENSION => BarcodeFormat.UPC_EAN_EXTENSION,
+ global::ZXing.BarcodeFormat.MSI => BarcodeFormat.MSI,
+ global::ZXing.BarcodeFormat.PLESSEY => BarcodeFormat.PLESSEY,
+ global::ZXing.BarcodeFormat.IMB => BarcodeFormat.IMB,
+ global::ZXing.BarcodeFormat.PHARMA_CODE => BarcodeFormat.PHARMA_CODE,
+ global::ZXing.BarcodeFormat.All_1D => BarcodeFormat.All_1D,
+ _ => throw new NotSupportedException(),
+ };
+ }
+
+ internal static ZXingResult ToNative(this Result result)
+ {
+ return new ZXingResult(
+ result.Text,
+ result.RawBytes,
+ result.ResultPoints.Select(x => new Point(x.X, x.Y)).ToArray(),
+ result.BarcodeFormat.ToNative(),
+ result.ResultMetadata.ToDictionary(k => k.Key.ToString(), v => v.Value),
+ result.NumBits,
+ result.Timestamp);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/MaciOS/BitmapRenderer.cs b/Camera.MAUI.Plugin.ZXing/MaciOS/BitmapRenderer.cs
new file mode 100644
index 0000000..964ae49
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/MaciOS/BitmapRenderer.cs
@@ -0,0 +1,79 @@
+#if IOS || MACCATALYST
+using CoreGraphics;
+using UIKit;
+using ZXing.Common;
+using ZXing.Rendering;
+
+namespace Camera.MAUI.Plugin.ZXing.Platforms.MaciOS;
+
+public class BitmapRenderer : IBarcodeRenderer
+{
+ private CGColor foreground;
+ private CGColor background;
+ private UIColor foregroundUIColor;
+ private UIColor backgroundUIColor;
+
+ public CGColor Foreground
+ {
+ get => foreground;
+ set { foreground = value; foregroundUIColor = new UIColor(value); }
+ }
+
+ public CGColor Background
+ {
+ get => background;
+ set { background = value; backgroundUIColor = new UIColor(value); }
+ }
+
+ public BitmapRenderer()
+ {
+ Foreground = new CGColor(0f, 0f, 0f);
+ Background = new CGColor(1.0f, 1.0f, 1.0f);
+ }
+ public UIImage Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content)
+ {
+ return Render(matrix, format, content, new EncodingOptions());
+ }
+
+ public UIImage Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content, EncodingOptions options)
+ {
+#if (IOS17_0_OR_GREATER || MACCATALYST17_0_OR_GREATER || TRUE)
+ var renderer = new UIGraphicsImageRenderer(new CGSize(matrix.Width, matrix.Height));
+ var img = renderer.CreateImage((UIGraphicsImageRendererContext context) =>
+ {
+ for (int x = 0; x < matrix.Width; x++)
+ {
+ for (int y = 0; y < matrix.Height; y++)
+ {
+ if (matrix[x, y])
+ foregroundUIColor.SetFill();
+ else
+ backgroundUIColor.SetFill();
+
+ context.FillRect(new CGRect(x, y, 1, 1));
+ }
+ }
+ });
+ return img;
+#else
+ UIGraphics.BeginImageContext(new CGSize(matrix.Width, matrix.Height));
+ var context = UIGraphics.GetCurrentContext();
+
+ for (int x = 0; x < matrix.Width; x++)
+ {
+ for (int y = 0; y < matrix.Height; y++)
+ {
+ context.SetFillColor(matrix[x, y] ? Foreground : Background);
+ context.FillRect(new CGRect(x, y, 1, 1));
+ }
+ }
+
+ var img = UIGraphics.GetImageFromCurrentImageContext();
+
+ UIGraphics.EndImageContext();
+
+ return img;
+#endif
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Camera.MAUI/Apple/RGBLuminanceSource.cs b/Camera.MAUI.Plugin.ZXing/MaciOS/RGBLuminanceSource.cs
similarity index 90%
rename from Camera.MAUI/Apple/RGBLuminanceSource.cs
rename to Camera.MAUI.Plugin.ZXing/MaciOS/RGBLuminanceSource.cs
index ad77bf3..7ed7b3b 100644
--- a/Camera.MAUI/Apple/RGBLuminanceSource.cs
+++ b/Camera.MAUI.Plugin.ZXing/MaciOS/RGBLuminanceSource.cs
@@ -3,9 +3,9 @@
using System.Runtime.InteropServices;
using UIKit;
-namespace Camera.MAUI.ZXingHelper;
+namespace Camera.MAUI.Plugin.ZXing.Platforms.MaciOS;
-internal partial class RGBLuminanceSource
+public class RGBLuminanceSource : Plugin.ZXing.RGBLuminanceSource
{
public RGBLuminanceSource(UIImage d)
: base((int)d.CGImage.Width, (int)d.CGImage.Height)
diff --git a/Camera.MAUI/Platforms/Android/BitmapLuminanceSource.cs b/Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapLuminanceSource.cs
similarity index 94%
rename from Camera.MAUI/Platforms/Android/BitmapLuminanceSource.cs
rename to Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapLuminanceSource.cs
index 3a17599..b3cb87f 100644
--- a/Camera.MAUI/Platforms/Android/BitmapLuminanceSource.cs
+++ b/Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapLuminanceSource.cs
@@ -1,7 +1,7 @@
using Android.Graphics;
using ZXing;
-namespace Camera.MAUI.Platforms.Android;
+namespace Camera.MAUI.Plugin.ZXing.Platforms.Android;
public class BitmapLuminanceSource : RGBLuminanceSource
{
@@ -29,4 +29,4 @@ protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, i
{
return new BitmapLuminanceSource(width, height) { luminances = newLuminances };
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Platforms/Android/BitmapRenderer.cs b/Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapRenderer.cs
similarity index 80%
rename from Camera.MAUI/Platforms/Android/BitmapRenderer.cs
rename to Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapRenderer.cs
index 662696b..b57d867 100644
--- a/Camera.MAUI/Platforms/Android/BitmapRenderer.cs
+++ b/Camera.MAUI.Plugin.ZXing/Platforms/Android/BitmapRenderer.cs
@@ -1,10 +1,9 @@
using Android.Graphics;
-using ZXing;
using ZXing.Common;
using ZXing.Rendering;
using Color = Android.Graphics.Color;
-namespace Camera.MAUI.Platforms.Android;
+namespace Camera.MAUI.Plugin.ZXing.Platforms.Android;
public class BitmapRenderer : IBarcodeRenderer
{
@@ -18,12 +17,12 @@ public BitmapRenderer()
Background = Color.White;
}
- public Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content)
+ public Bitmap Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content)
{
return Render(matrix, format, content, new EncodingOptions());
}
- public Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options)
+ public Bitmap Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content, EncodingOptions options)
{
var width = matrix.Width;
var height = matrix.Height;
@@ -45,4 +44,4 @@ public Bitmap Render(BitMatrix matrix, BarcodeFormat format, string content, Enc
bitmap.SetPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Platforms/Windows/SoftwareBitmapLuminanceSource.cs b/Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs
similarity index 94%
rename from Camera.MAUI/Platforms/Windows/SoftwareBitmapLuminanceSource.cs
rename to Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs
index fc34d3f..2263d42 100644
--- a/Camera.MAUI/Platforms/Windows/SoftwareBitmapLuminanceSource.cs
+++ b/Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs
@@ -2,7 +2,7 @@
using Windows.Graphics.Imaging;
using ZXing;
-namespace Camera.MAUI.Platforms.Windows;
+namespace Camera.MAUI.Plugin.ZXing.Platforms.Windows;
internal class SoftwareBitmapLuminanceSource : BaseLuminanceSource
{
@@ -27,4 +27,4 @@ protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, i
{
return new SoftwareBitmapLuminanceSource(width, height) { luminances = newLuminances };
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs b/Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs
similarity index 61%
rename from Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs
rename to Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs
index 1b89a54..21a7237 100644
--- a/Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs
+++ b/Camera.MAUI.Plugin.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs
@@ -1,15 +1,11 @@
-using Microsoft.UI.Xaml.Media;
-using Microsoft.UI.Xaml.Media.Imaging;
-using System.Runtime.InteropServices;
+using Microsoft.UI.Xaml.Media.Imaging;
+using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
-using Windows.UI;
-using ZXing;
using ZXing.Common;
using ZXing.Rendering;
-using System.Runtime.InteropServices.WindowsRuntime;
using Color = Windows.UI.Color;
-namespace Camera.MAUI.Platforms.Windows
+namespace Camera.MAUI.Plugin.ZXing.Platforms.Windows
{
public class SoftwareBitmapRenderer : IBarcodeRenderer
{
@@ -18,29 +14,29 @@ public class SoftwareBitmapRenderer : IBarcodeRenderer
public SoftwareBitmapRenderer()
{
- Foreground = Color.FromArgb(1,0,0,0);
+ Foreground = Color.FromArgb(1, 0, 0, 0);
Background = Color.FromArgb(1, 255, 255, 255);
}
- public SoftwareBitmap Render(BitMatrix matrix, BarcodeFormat format, string content)
+ public SoftwareBitmap Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content)
{
return Render(matrix, format, content, null);
}
- public virtual SoftwareBitmap Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options)
+ public virtual SoftwareBitmap Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content, EncodingOptions options)
{
int width = matrix.Width;
int height = matrix.Height;
bool outputContent = (options == null || !options.PureBarcode) &&
- !String.IsNullOrEmpty(content) && (format == BarcodeFormat.CODE_39 ||
- format == BarcodeFormat.CODE_128 ||
- format == BarcodeFormat.EAN_13 ||
- format == BarcodeFormat.EAN_8 ||
- format == BarcodeFormat.CODABAR ||
- format == BarcodeFormat.ITF ||
- format == BarcodeFormat.UPC_A ||
- format == BarcodeFormat.MSI ||
- format == BarcodeFormat.PLESSEY);
+ !string.IsNullOrEmpty(content) && (format == global::ZXing.BarcodeFormat.CODE_39 ||
+ format == global::ZXing.BarcodeFormat.CODE_128 ||
+ format == global::ZXing.BarcodeFormat.EAN_13 ||
+ format == global::ZXing.BarcodeFormat.EAN_8 ||
+ format == global::ZXing.BarcodeFormat.CODABAR ||
+ format == global::ZXing.BarcodeFormat.ITF ||
+ format == global::ZXing.BarcodeFormat.UPC_A ||
+ format == global::ZXing.BarcodeFormat.MSI ||
+ format == global::ZXing.BarcodeFormat.PLESSEY);
int emptyArea = outputContent ? 16 : 0;
int pixelsize = 1;
@@ -67,36 +63,36 @@ public virtual SoftwareBitmap Render(BitMatrix matrix, BarcodeFormat format, str
var writableBitmap = new WriteableBitmap(width, height);
- using(var stream = writableBitmap.PixelBuffer.AsStream())
+ using (var stream = writableBitmap.PixelBuffer.AsStream())
{
- for (int y = 0; y < matrix.Height - emptyArea; y++)
+ for (int y = 0; y < matrix.Height - emptyArea; y++)
+ {
+ for (var pixelsizeHeight = 0; pixelsizeHeight < pixelsize; pixelsizeHeight++)
{
- for (var pixelsizeHeight = 0; pixelsizeHeight < pixelsize; pixelsizeHeight++)
+ for (var x = 0; x < matrix.Width; x++)
{
- for (var x = 0; x < matrix.Width; x++)
+ var color = matrix[x, y] ? foreground : background;
+ for (var pixelsizeWidth = 0; pixelsizeWidth < pixelsize; pixelsizeWidth++)
{
- var color = matrix[x, y] ? foreground : background;
- for (var pixelsizeWidth = 0; pixelsizeWidth < pixelsize; pixelsizeWidth++)
- {
stream.Write(color, 0, 4);
- }
}
- for (var x = pixelsize * matrix.Width; x < width; x++)
- {
+ }
+ for (var x = pixelsize * matrix.Width; x < width; x++)
+ {
stream.Write(background, 0, 4);
- }
}
}
- for (int y = matrix.Height * pixelsize - emptyArea; y < height; y++)
+ }
+ for (int y = matrix.Height * pixelsize - emptyArea; y < height; y++)
+ {
+ for (var x = 0; x < width; x++)
{
- for (var x = 0; x < width; x++)
- {
stream.Write(background, 0, 4);
}
- }
+ }
}
writableBitmap.Invalidate();
return SoftwareBitmap.CreateCopyFromBuffer(writableBitmap.PixelBuffer, BitmapPixelFormat.Bgra8, width, height);
}
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/ZXingHelper/RGBLuminanceSource.cs b/Camera.MAUI.Plugin.ZXing/RGBLuminanceSource.cs
similarity index 82%
rename from Camera.MAUI/ZXingHelper/RGBLuminanceSource.cs
rename to Camera.MAUI.Plugin.ZXing/RGBLuminanceSource.cs
index fef317c..ff01a98 100644
--- a/Camera.MAUI/ZXingHelper/RGBLuminanceSource.cs
+++ b/Camera.MAUI.Plugin.ZXing/RGBLuminanceSource.cs
@@ -1,8 +1,8 @@
using ZXing;
-namespace Camera.MAUI.ZXingHelper;
+namespace Camera.MAUI.Plugin.ZXing;
-internal partial class RGBLuminanceSource : BaseLuminanceSource
+public class RGBLuminanceSource : BaseLuminanceSource
{
public enum BitmapFormat
{
@@ -25,10 +25,12 @@ protected RGBLuminanceSource(int width, int height)
: base(width, height)
{
}
+
protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, int width, int height)
{
return new RGBLuminanceSource(width, height) { luminances = newLuminances };
}
+
private static BitmapFormat DetermineBitmapFormat(byte[] rgbRawBytes, int width, int height)
{
var square = width * height;
@@ -43,6 +45,7 @@ private static BitmapFormat DetermineBitmapFormat(byte[] rgbRawBytes, int width,
_ => throw new ArgumentException("The bitmap format could not be determined. Please specify the correct value."),
};
}
+
protected void CalculateLuminance(byte[] rgbRawBytes, BitmapFormat bitmapFormat)
{
if (bitmapFormat == BitmapFormat.Unknown)
@@ -54,39 +57,51 @@ protected void CalculateLuminance(byte[] rgbRawBytes, BitmapFormat bitmapFormat)
case BitmapFormat.Gray8:
Buffer.BlockCopy(rgbRawBytes, 0, luminances, 0, rgbRawBytes.Length < luminances.Length ? rgbRawBytes.Length : luminances.Length);
break;
+
case BitmapFormat.Gray16:
CalculateLuminanceGray16(rgbRawBytes);
break;
+
case BitmapFormat.RGB24:
CalculateLuminanceRGB24(rgbRawBytes);
break;
+
case BitmapFormat.BGR24:
CalculateLuminanceBGR24(rgbRawBytes);
break;
+
case BitmapFormat.RGB32:
CalculateLuminanceRGB32(rgbRawBytes);
break;
+
case BitmapFormat.BGR32:
CalculateLuminanceBGR32(rgbRawBytes);
break;
+
case BitmapFormat.RGBA32:
CalculateLuminanceRGBA32(rgbRawBytes);
break;
+
case BitmapFormat.ARGB32:
CalculateLuminanceARGB32(rgbRawBytes);
break;
+
case BitmapFormat.BGRA32:
CalculateLuminanceBGRA32(rgbRawBytes);
break;
+
case BitmapFormat.RGB565:
CalculateLuminanceRGB565(rgbRawBytes);
break;
+
case BitmapFormat.UYVY:
CalculateLuminanceUYVY(rgbRawBytes);
break;
+
case BitmapFormat.YUYV:
CalculateLuminanceYUYV(rgbRawBytes);
break;
+
default:
throw new ArgumentException("The bitmap format isn't supported.", bitmapFormat.ToString());
}
@@ -101,13 +116,13 @@ private void CalculateLuminanceRGB565(byte[] rgb565RawData)
var byte2 = rgb565RawData[index + 1];
var b5 = byte1 & 0x1F;
- var g5 = (((byte1 & 0xE0) >> 5) | ((byte2 & 0x03) << 3)) & 0x1F;
- var r5 = (byte2 >> 2) & 0x1F;
- var r8 = (r5 * 527 + 23) >> 6;
- var g8 = (g5 * 527 + 23) >> 6;
- var b8 = (b5 * 527 + 23) >> 6;
+ var g5 = ((byte1 & 0xE0) >> 5 | (byte2 & 0x03) << 3) & 0x1F;
+ var r5 = byte2 >> 2 & 0x1F;
+ var r8 = r5 * 527 + 23 >> 6;
+ var g8 = g5 * 527 + 23 >> 6;
+ var b8 = b5 * 527 + 23 >> 6;
- luminances[luminanceIndex] = (byte)((RChannelWeight * r8 + GChannelWeight * g8 + BChannelWeight * b8) >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)(RChannelWeight * r8 + GChannelWeight * g8 + BChannelWeight * b8 >> ChannelWeight);
}
}
@@ -119,7 +134,7 @@ private void CalculateLuminanceRGB24(byte[] rgbRawBytes)
int r = rgbRawBytes[rgbIndex++];
int g = rgbRawBytes[rgbIndex++];
int b = rgbRawBytes[rgbIndex++];
- luminances[luminanceIndex] = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
}
}
@@ -131,7 +146,7 @@ private void CalculateLuminanceBGR24(byte[] rgbRawBytes)
int b = rgbRawBytes[rgbIndex++];
int g = rgbRawBytes[rgbIndex++];
int r = rgbRawBytes[rgbIndex++];
- luminances[luminanceIndex] = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
}
}
@@ -144,7 +159,7 @@ private void CalculateLuminanceRGB32(byte[] rgbRawBytes)
int g = rgbRawBytes[rgbIndex++];
int b = rgbRawBytes[rgbIndex++];
rgbIndex++;
- luminances[luminanceIndex] = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
}
}
@@ -157,7 +172,7 @@ private void CalculateLuminanceBGR32(byte[] rgbRawBytes)
int g = rgbRawBytes[rgbIndex++];
int r = rgbRawBytes[rgbIndex++];
rgbIndex++;
- luminances[luminanceIndex] = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
}
}
@@ -170,8 +185,8 @@ private void CalculateLuminanceBGRA32(byte[] rgbRawBytes)
var g = rgbRawBytes[rgbIndex++];
var r = rgbRawBytes[rgbIndex++];
var alpha = rgbRawBytes[rgbIndex++];
- var luminance = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
- luminances[luminanceIndex] = (byte)(((luminance * alpha) >> 8) + (255 * (255 - alpha) >> 8));
+ var luminance = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)((luminance * alpha >> 8) + (255 * (255 - alpha) >> 8));
}
}
@@ -184,8 +199,8 @@ private void CalculateLuminanceRGBA32(byte[] rgbRawBytes)
var g = rgbRawBytes[rgbIndex++];
var b = rgbRawBytes[rgbIndex++];
var alpha = rgbRawBytes[rgbIndex++];
- var luminance = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
- luminances[luminanceIndex] = (byte)(((luminance * alpha) >> 8) + (255 * (255 - alpha) >> 8));
+ var luminance = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)((luminance * alpha >> 8) + (255 * (255 - alpha) >> 8));
}
}
@@ -198,8 +213,8 @@ private void CalculateLuminanceARGB32(byte[] rgbRawBytes)
var r = rgbRawBytes[rgbIndex++];
var g = rgbRawBytes[rgbIndex++];
var b = rgbRawBytes[rgbIndex++];
- var luminance = (byte)((RChannelWeight * r + GChannelWeight * g + BChannelWeight * b) >> ChannelWeight);
- luminances[luminanceIndex] = (byte)(((luminance * alpha) >> 8) + (255 * (255 - alpha) >> 8));
+ var luminance = (byte)(RChannelWeight * r + GChannelWeight * g + BChannelWeight * b >> ChannelWeight);
+ luminances[luminanceIndex] = (byte)((luminance * alpha >> 8) + (255 * (255 - alpha) >> 8));
}
}
diff --git a/Camera.MAUI.Plugin.ZXing/ZXingDecoder.cs b/Camera.MAUI.Plugin.ZXing/ZXingDecoder.cs
new file mode 100644
index 0000000..bcff899
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/ZXingDecoder.cs
@@ -0,0 +1,339 @@
+using ZXing;
+
+#if IOS || MACCATALYST
+using DecodeDataType = UIKit.UIImage;
+#elif ANDROID
+using DecodeDataType = Android.Graphics.Bitmap;
+#elif WINDOWS
+using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap;
+#else
+
+using DecodeDataType = System.Object;
+
+#endif
+
+namespace Camera.MAUI.Plugin.ZXing
+{
+ public class ZXingDecoder : PluginDecoder
+ {
+ #region Private Fields
+
+ private readonly BarcodeReaderGeneric BarcodeReader = new();
+
+ #endregion Private Fields
+
+ #region Public Properties
+
+ ///
+ /// If true Decoded event will invoke only if a Results is diferent from preview Results
+ ///
+ public bool ControlBarcodeResultDuplicate { get; set; } = false;
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ public override void ClearResults()
+ {
+ Results = null;
+ }
+
+ public override void Decode(DecodeDataType data)
+ {
+ System.Diagnostics.Debug.WriteLine("Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
+
+ LuminanceSource lumSource = default;
+#if ANDROID
+ lumSource = new Platforms.Android.BitmapLuminanceSource(data);
+#elif IOS || MACCATALYST
+ lumSource = new Platforms.MaciOS.RGBLuminanceSource(data);
+#elif WINDOWS
+ lumSource = new Platforms.Windows.SoftwareBitmapLuminanceSource(data);
+#endif
+ System.Diagnostics.Debug.WriteLine("End Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
+
+ try
+ {
+ Result[] results = null;
+ if (Options is ZXingDecoderOptions zxingOptions && zxingOptions.ReadMultipleCodes)
+ {
+ results = BarcodeReader.DecodeMultiple(lumSource);
+ }
+ else
+ {
+ var result = BarcodeReader.Decode(lumSource);
+ if (result != null) results = new Result[] { result };
+ }
+ if (results?.Length > 0)
+ {
+ var nativeResults = results.Select(x => x.ToNative()).ToArray();
+ bool refresh = true;
+ if (ControlBarcodeResultDuplicate)
+ {
+ if (Results != null)
+ {
+ foreach (var result in nativeResults)
+ {
+ refresh = Results.FirstOrDefault(b => b.Text == result.Text && b.BarcodeFormat == result.BarcodeFormat) == null;
+ if (refresh) break;
+ }
+ }
+ }
+ if (refresh)
+ {
+ Results = nativeResults;
+ OnDecoded(new PluginDecodedEventArgs { Results = Results });
+ }
+ }
+ }
+ catch { }
+ }
+
+ #endregion Public Methods
+
+ #region Protected Methods
+
+ protected override void OnOptionsChanged(object oldValue, object newValue)
+ {
+ if (newValue is BarcodeDecoderOptions barcodeOptions)
+ {
+ BarcodeReader.Options.PossibleFormats = barcodeOptions.PossibleFormats?.Select(x => x.ToPlatform()).ToList();
+ }
+ if (newValue is ZXingDecoderOptions zxingOptions)
+ {
+ BarcodeReader.AutoRotate = zxingOptions.AutoRotate;
+ if (zxingOptions.CharacterSet != string.Empty)
+ BarcodeReader.Options.CharacterSet = zxingOptions.CharacterSet;
+
+ BarcodeReader.Options.TryHarder = zxingOptions.TryHarder;
+ BarcodeReader.Options.TryInverted = zxingOptions.TryInverted;
+ BarcodeReader.Options.PureBarcode = zxingOptions.PureBarcode;
+ }
+ }
+
+ #endregion Protected Methods
+
+ /*
+ internal void DecodeBarcode(DecodeDataType data)
+ {
+ System.Diagnostics.Debug.WriteLine("Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
+
+ LuminanceSource lumSource = default;
+#if ANDROID
+ lumSource = new Platforms.Android.BitmapLuminanceSource(data);
+#elif IOS || MACCATALYST
+ lumSource = new Platforms.MaciOS.RGBLuminanceSource(data);
+#elif WINDOWS
+ lumSource = new Platforms.Windows.SoftwareBitmapLuminanceSource(data);
+#endif
+ System.Diagnostics.Debug.WriteLine("End Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
+
+ try
+ {
+ Result[] results = null;
+ if (Options.ReadMultipleCodes)
+ results = BarcodeReader.DecodeMultiple(lumSource);
+ else
+ {
+ var result = BarcodeReader.Decode(lumSource);
+ if (result != null) results = new Result[] { result };
+ }
+ if (results?.Length > 0)
+ {
+ bool refresh = true;
+ if (ControlBarcodeResultDuplicate)
+ {
+ if (BarCodeResults != null)
+ {
+ foreach (var result in results)
+ {
+ refresh = BarCodeResults.FirstOrDefault(b => b.Text == result.Text && b.BarcodeFormat == result.BarcodeFormat) == null;
+ if (refresh) break;
+ }
+ }
+ }
+ if (refresh)
+ {
+ int width;
+ int height;
+#if ANDROID
+ width = data.Width;
+ height = data.Height;
+#else
+ width = 0;
+ height = 0;
+#endif
+
+ var images = new List();
+ foreach (var result in results)
+ {
+ var points = new List();
+
+ //foreach (var p in result.ResultPoints)
+ for (int i = 0; i < result.ResultPoints.Length; i++)
+ {
+ var p = result.ResultPoints[i];
+
+ float msize;
+ if (p is ZXing.QrCode.Internal.FinderPattern fp)
+ msize = fp.EstimatedModuleSize * 3;
+ else
+ msize = 0;
+
+ float adjX;
+ float adjY;
+ switch (i)
+ {
+ case 0:
+ adjX = msize;
+ adjY = -msize;
+ break;
+
+ case 1:
+ adjX = msize;
+ adjY = msize;
+ break;
+
+ case 2:
+ adjX = -msize;
+ adjY = msize;
+ break;
+
+ default:
+ adjX = adjY = 0;
+ break;
+ }
+
+ ResultPoint pNew = result.ResultMetadata[ResultMetadataType.ORIENTATION] switch
+ {
+ 0 => new ResultPoint(p.X + adjX, p.Y + adjY),
+ 90 => new ResultPoint(width - p.Y + adjX, p.X + adjY),
+ 180 => new ResultPoint(width - p.X + adjX, height - p.Y + adjY),
+ 270 => new ResultPoint(p.Y + adjX, height - p.X + adjY),
+ _ => null,
+ };
+
+ points.Add(pNew);
+ }
+
+ float? p0_x = null, p0_y = null;
+ float? p1_x = null, p1_y = null;
+ float? p2_x = null, p2_y = null;
+ foreach (var p in points)
+ {
+ if (!p1_x.HasValue || p.X < p1_x) p1_x = p.X;
+ if (!p1_y.HasValue || p.Y < p1_y) p1_y = p.Y;
+ if (!p2_x.HasValue || p.X > p2_x) p2_x = p.X;
+ if (!p2_y.HasValue || p.Y > p2_y) p2_y = p.Y;
+ }
+ p0_x = ((p2_x - p1_x) / 2) + p1_x;
+ p0_y = ((p2_y - p1_y) / 2) + p1_y;
+
+ var stream = new MemoryStream();
+#if ANDROID
+#if REMOVED
+ var cropped = DecodeDataType.CreateBitmap(
+ data,
+ (int)p1_x, (int)p1_y,
+ (int)(p2_x - p1_x), (int)(p2_y - p1_y));
+
+ cropped.Compress(DecodeDataType.CompressFormat.Jpeg, 100, stream);
+#endif
+
+ var paint = new Android.Graphics.Paint
+ {
+ AntiAlias = true,
+ Color = Android.Graphics.Color.Red,
+ StrokeWidth = 4
+ };
+ var paint2 = new Android.Graphics.Paint
+ {
+ AntiAlias = true,
+ Color = Android.Graphics.Color.Yellow,
+ StrokeWidth = 1,
+ };
+ paint2.SetStyle(Android.Graphics.Paint.Style.Stroke);
+
+ var canvas = new Android.Graphics.Canvas(data);
+ canvas.DrawLine(points[0].X, points[0].Y, points[1].X, points[1].Y, paint);
+ canvas.DrawLine(points[1].X, points[1].Y, points[2].X, points[2].Y, paint);
+ canvas.DrawLine(points[2].X, points[2].Y, points[3].X, points[3].Y, paint);
+ canvas.DrawLine(points[3].X, points[3].Y, points[0].X, points[0].Y, paint);
+
+ var modsize = (result.ResultPoints[0] as ZXing.QrCode.Internal.FinderPattern).EstimatedModuleSize * 3;
+ canvas.DrawRect(points[0].X - modsize, points[0].Y - modsize, points[0].X + modsize, points[0].Y + modsize, paint2);
+
+ //data.Compress(DecodeDataType.CompressFormat.Jpeg, 100, stream);
+
+ var src = new List();
+ var dst = new List();
+ foreach (var p in points)
+ {
+#if REMOVED
+ // X and Y values are "flattened" into the array.
+ src.Add(p.X - p1_x.Value);
+ src.Add(p.Y - p1_y.Value);
+
+ // set up a dest polygon which is just a rectangle
+ if (p.X < p0_x)
+ dst.Add(0);
+ else
+ dst.Add(p2_x.Value - p1_x.Value);
+ if (p.Y < p0_y)
+ dst.Add(0);
+ else
+ dst.Add(p2_y.Value - p1_y.Value);
+#endif
+
+ // X and Y values are "flattened" into the array.
+ src.Add(p.X);
+ src.Add(p.Y);
+
+ // set up a dest polygon which is just a rectangle
+ if (p.X < p0_x)
+ dst.Add(0);
+ else
+ dst.Add(330);
+ if (p.Y < p0_y)
+ dst.Add(0);
+ else
+ dst.Add(330);
+ }
+
+ var matrix = new Android.Graphics.Matrix();
+ // set the matrix to map the source values to the dest values.
+ matrix.SetPolyToPoly(src.ToArray(), 0, dst.ToArray(), 0, 4);
+ //var cropped = DecodeDataType.CreateBitmap(data, (int)p1_x, (int)p1_y, (int)(p2_x - p1_x), (int)(p2_y - p1_y), matrix, true);
+ //var cropped = DecodeDataType.CreateBitmap(data, 0, 0, width, height, matrix, true);
+ //var cropped = DecodeDataType.CreateBitmap(data, (int)p1_x, (int)p1_y, (int)(p2_x - p1_x), (int)(p2_y - p1_y));
+
+ var cropped = DecodeDataType.CreateBitmap(300, 300, DecodeDataType.Config.Argb8888);
+ canvas = new Android.Graphics.Canvas(cropped);
+ canvas.DrawBitmap(data, matrix, null);
+
+ cropped.Compress(DecodeDataType.CompressFormat.Jpeg, 100, stream);
+#endif
+ stream.Seek(0, SeekOrigin.Begin);
+ images.Add(stream.ToArray());
+ }
+
+ BarCodeResults = results;
+ OnPropertyChanged(nameof(BarCodeResults));
+ BarcodeDetected?.Invoke(this, new BarcodeEventArgs { Result = results, Images = images.ToArray() });
+ }
+ }
+ }
+ catch { }
+ }
+
+ private enum TransformMode
+ {
+ None,
+ RotateLeft,
+ RotateRight,
+ FlipVertical,
+ FlipHorizontal,
+ }
+ */
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/ZXingDecoderOptions.cs b/Camera.MAUI.Plugin.ZXing/ZXingDecoderOptions.cs
new file mode 100644
index 0000000..bdb80f6
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/ZXingDecoderOptions.cs
@@ -0,0 +1,12 @@
+namespace Camera.MAUI.Plugin.ZXing
+{
+ public record ZXingDecoderOptions : BarcodeDecoderOptions
+ {
+ public bool AutoRotate { get; init; } = true;
+ public string CharacterSet { get; init; } = string.Empty;
+ public bool PureBarcode { get; init; } = false;
+ public bool ReadMultipleCodes { get; init; } = false;
+ public bool TryHarder { get; init; } = true;
+ public bool TryInverted { get; init; } = true;
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/ZXingEncoderOptions.cs b/Camera.MAUI.Plugin.ZXing/ZXingEncoderOptions.cs
new file mode 100644
index 0000000..a1bb867
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/ZXingEncoderOptions.cs
@@ -0,0 +1,9 @@
+namespace Camera.MAUI.Plugin.ZXing
+{
+ public class ZXingEncoderOptions : BarcodeEncoderOptions
+ {
+ public ZXingEncoderOptions(BarcodeFormat format = BarcodeFormat.QR_CODE, int width = 400, int height = 400, int margin = 5)
+ : base(format, width, height, margin)
+ { }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/ZXingRenderer.cs b/Camera.MAUI.Plugin.ZXing/ZXingRenderer.cs
new file mode 100644
index 0000000..64fd1f4
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/ZXingRenderer.cs
@@ -0,0 +1,95 @@
+using ZXing.Common;
+using ZXing;
+
+#if WINDOWS
+using Windows.Graphics.Imaging;
+using CustomRenderer = Camera.MAUI.Plugin.ZXing.Platforms.Windows.SoftwareBitmapRenderer;
+#elif IOS || MACCATALYST
+using CustomRenderer = Camera.MAUI.Plugin.ZXing.Platforms.MaciOS.BitmapRenderer;
+#elif ANDROID
+using CustomRenderer = Camera.MAUI.Plugin.ZXing.Platforms.Android.BitmapRenderer;
+#else
+
+using CustomRenderer = System.Object;
+
+#endif
+
+namespace Camera.MAUI.Plugin.ZXing
+{
+ public class ZXingRenderer : IPluginRenderer
+ {
+ #region Private Fields
+
+ private readonly CustomRenderer customRenderer = new();
+ private readonly BarcodeWriterPixelData writer = new();
+
+ #endregion Private Fields
+
+ #region Public Properties
+
+ public Color Background { get; set; } = Colors.White;
+ public Color Foreground { get; set; } = Colors.Black;
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ public ImageSource EncodeBarcode(string code, IPluginRendererOptions options)
+ {
+ ImageSource imageSource = null;
+ if (options is BarcodeEncoderOptions noptions)
+ {
+ writer.Options = new EncodingOptions { Width = noptions.Width, Height = noptions.Height, Margin = noptions.Margin };
+ writer.Format = noptions.Format.ToPlatform();
+ }
+ else
+ {
+ writer.Options = new EncodingOptions { Width = 400, Height = 400, Margin = 5 };
+ writer.Format = global::ZXing.BarcodeFormat.QR_CODE;
+ }
+
+ try
+ {
+ var bitMatrix = writer.Encode(code);
+ if (bitMatrix != null)
+ {
+ var stream = new MemoryStream();
+#if WINDOWS
+ Foreground.ToRgba(out byte r, out byte g, out byte b, out byte a);
+ customRenderer.Foreground = Windows.UI.Color.FromArgb(a, r, g, b);
+ Background.ToRgba(out r, out g, out b, out a);
+ customRenderer.Background = Windows.UI.Color.FromArgb(a, r, g, b);
+ var bitmap = customRenderer.Render(bitMatrix, writer.Format, code);
+ BitmapEncoder encoder = BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream.AsRandomAccessStream()).GetAwaiter().GetResult();
+ encoder.SetSoftwareBitmap(bitmap);
+ encoder.FlushAsync().GetAwaiter().GetResult();
+ stream.Position = 0;
+ imageSource = ImageSource.FromStream(() => stream);
+#elif IOS || MACCATALYST
+ customRenderer.Foreground = new CoreGraphics.CGColor(Foreground.Red, Foreground.Green, Foreground.Blue, Foreground.Alpha);
+ customRenderer.Background = new CoreGraphics.CGColor(Background.Red, Background.Green, Background.Blue, Background.Alpha);
+ var bitmap = customRenderer.Render(bitMatrix, writer.Format, code);
+ bitmap.AsPNG().AsStream().CopyTo(stream);
+ stream.Position = 0;
+ imageSource = ImageSource.FromStream(() => stream);
+#elif ANDROID
+ Foreground.ToRgba(out byte r, out byte g, out byte b, out byte a);
+ customRenderer.Foreground = new Android.Graphics.Color(r, g, b, a);
+ Background.ToRgba(out r, out g, out b, out a);
+ customRenderer.Background = new Android.Graphics.Color(r, g, b, a);
+ var bitmap = customRenderer.Render(bitMatrix, writer.Format, code);
+ bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 100, stream);
+ stream.Position = 0;
+ imageSource = ImageSource.FromStream(() => stream);
+#endif
+ }
+ }
+ catch
+ {
+ }
+ return imageSource;
+ }
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin.ZXing/ZXingResult.cs b/Camera.MAUI.Plugin.ZXing/ZXingResult.cs
new file mode 100644
index 0000000..06552d6
--- /dev/null
+++ b/Camera.MAUI.Plugin.ZXing/ZXingResult.cs
@@ -0,0 +1,30 @@
+namespace Camera.MAUI.Plugin.ZXing
+{
+ public class ZXingResult : BarcodeResult
+ {
+ public ZXingResult(string text, byte[] rawBytes, Point[] resultPoints, BarcodeFormat barcodeFormat, IDictionary resultMetadata, int numBits, long timestamp) : base(text, rawBytes, resultPoints, barcodeFormat)
+ {
+ ResultMetadata = resultMetadata;
+ NumBits = numBits;
+ Timestamp = timestamp;
+ }
+
+ //
+ // Returns:
+ // {@link Hashtable} mapping {@link ResultMetadataType} keys to values. May be
+ // null
+ // . This contains optional metadata about what was detected about the barcode,
+ // like orientation.
+ public IDictionary ResultMetadata { get; private set; }
+
+ //
+ // Summary:
+ // Gets the timestamp.
+ public long Timestamp { get; private set; }
+
+ //
+ // Summary:
+ // how many bits of ZXing.Result.RawBytes are valid; typically 8 times its length
+ public int NumBits { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/BarcodeDecoderOptions.cs b/Camera.MAUI.Plugin/BarcodeDecoderOptions.cs
new file mode 100644
index 0000000..cc2480d
--- /dev/null
+++ b/Camera.MAUI.Plugin/BarcodeDecoderOptions.cs
@@ -0,0 +1,7 @@
+namespace Camera.MAUI.Plugin
+{
+ public record BarcodeDecoderOptions : IPluginDecoderOptions
+ {
+ public IList PossibleFormats { get; init; } = new List { BarcodeFormat.QR_CODE };
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/BarcodeEncoderOptions.cs b/Camera.MAUI.Plugin/BarcodeEncoderOptions.cs
new file mode 100644
index 0000000..3a14603
--- /dev/null
+++ b/Camera.MAUI.Plugin/BarcodeEncoderOptions.cs
@@ -0,0 +1,18 @@
+namespace Camera.MAUI.Plugin
+{
+ public class BarcodeEncoderOptions : IPluginRendererOptions
+ {
+ public BarcodeFormat Format { get; set; }
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public int Margin { get; set; }
+
+ public BarcodeEncoderOptions(BarcodeFormat format = BarcodeFormat.QR_CODE, int width = 400, int height = 400, int margin = 5)
+ {
+ Format = format;
+ Width = width;
+ Height = height;
+ Margin = margin;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/BarcodeFormat.cs b/Camera.MAUI.Plugin/BarcodeFormat.cs
new file mode 100644
index 0000000..f7e6331
--- /dev/null
+++ b/Camera.MAUI.Plugin/BarcodeFormat.cs
@@ -0,0 +1,121 @@
+namespace Camera.MAUI.Plugin
+{
+ //
+ // Summary:
+ // Enumerates barcode formats known to this package.
+ [Flags]
+ public enum BarcodeFormat
+ {
+ //
+ // Summary:
+ // Aztec 2D barcode format.
+ AZTEC = 0x1,
+
+ //
+ // Summary:
+ // CODABAR 1D format.
+ CODABAR = 0x2,
+
+ //
+ // Summary:
+ // Code 39 1D format.
+ CODE_39 = 0x4,
+
+ //
+ // Summary:
+ // Code 93 1D format.
+ CODE_93 = 0x8,
+
+ //
+ // Summary:
+ // Code 128 1D format.
+ CODE_128 = 0x10,
+
+ //
+ // Summary:
+ // Data Matrix 2D barcode format.
+ DATA_MATRIX = 0x20,
+
+ //
+ // Summary:
+ // EAN-8 1D format.
+ EAN_8 = 0x40,
+
+ //
+ // Summary:
+ // EAN-13 1D format.
+ EAN_13 = 0x80,
+
+ //
+ // Summary:
+ // ITF (Interleaved Two of Five) 1D format.
+ ITF = 0x100,
+
+ //
+ // Summary:
+ // MaxiCode 2D barcode format.
+ MAXICODE = 0x200,
+
+ //
+ // Summary:
+ // PDF417 format.
+ PDF_417 = 0x400,
+
+ //
+ // Summary:
+ // QR Code 2D barcode format.
+ QR_CODE = 0x800,
+
+ //
+ // Summary:
+ // RSS 14
+ RSS_14 = 0x1000,
+
+ //
+ // Summary:
+ // RSS EXPANDED
+ RSS_EXPANDED = 0x2000,
+
+ //
+ // Summary:
+ // UPC-A 1D format.
+ UPC_A = 0x4000,
+
+ //
+ // Summary:
+ // UPC-E 1D format.
+ UPC_E = 0x8000,
+
+ //
+ // Summary:
+ // UPC/EAN extension format. Not a stand-alone format.
+ UPC_EAN_EXTENSION = 0x10000,
+
+ //
+ // Summary:
+ // MSI
+ MSI = 0x20000,
+
+ //
+ // Summary:
+ // Plessey
+ PLESSEY = 0x40000,
+
+ //
+ // Summary:
+ // Intelligent Mail barcode
+ IMB = 0x80000,
+
+ //
+ // Summary:
+ // Pharmacode format.
+ PHARMA_CODE = 0x100000,
+
+ //
+ // Summary:
+ // UPC_A | UPC_E | EAN_13 | EAN_8 | CODABAR | CODE_39 | CODE_93 | CODE_128 | ITF
+ // | RSS_14 | RSS_EXPANDED without MSI (to many false-positives) and IMB (not enough
+ // tested, and it looks more like a 2D)
+ All_1D = 0xF1DE
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/BarcodeResult.cs b/Camera.MAUI.Plugin/BarcodeResult.cs
new file mode 100644
index 0000000..bad3090
--- /dev/null
+++ b/Camera.MAUI.Plugin/BarcodeResult.cs
@@ -0,0 +1,37 @@
+namespace Camera.MAUI.Plugin
+{
+ public class BarcodeResult : IPluginResult
+ {
+ public BarcodeResult(string text, byte[] rawBytes, Point[] resultPoints, BarcodeFormat barcodeFormat)
+ {
+ Text = text;
+ RawBytes = rawBytes;
+ ResultPoints = resultPoints;
+ BarcodeFormat = barcodeFormat;
+ }
+
+ //
+ // Returns:
+ // raw text encoded by the barcode, if applicable, otherwise
+ // null
+ public string Text { get; private set; }
+
+ //
+ // Returns:
+ // raw bytes encoded by the barcode, if applicable, otherwise
+ // null
+ public byte[] RawBytes { get; private set; }
+
+ //
+ // Returns:
+ // points related to the barcode in the image. These are typically points identifying
+ // finder patterns or the corners of the barcode. The exact meaning is specific
+ // to the type of barcode that was decoded.
+ public Point[] ResultPoints { get; private set; }
+
+ //
+ // Returns:
+ // {@link BarcodeFormat} representing the format of the barcode that was decoded
+ public BarcodeFormat BarcodeFormat { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/Camera.MAUI.Plugin.csproj b/Camera.MAUI.Plugin/Camera.MAUI.Plugin.csproj
new file mode 100644
index 0000000..17ef128
--- /dev/null
+++ b/Camera.MAUI.Plugin/Camera.MAUI.Plugin.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst
+ $(TargetFrameworks);net7.0-windows10.0.19041.0
+
+
+ true
+ true
+ enable
+
+ 11.0
+ 13.1
+ 26.0
+ 10.0.17763.0
+ 10.0.17763.0
+ 6.5
+
+ programatix
+
+
+
diff --git a/Camera.MAUI.Plugin/IPluginDecoder.cs b/Camera.MAUI.Plugin/IPluginDecoder.cs
new file mode 100644
index 0000000..879e734
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginDecoder.cs
@@ -0,0 +1,44 @@
+using System.Windows.Input;
+
+#if IOS || MACCATALYST
+using DecodeDataType = UIKit.UIImage;
+#elif ANDROID
+using DecodeDataType = Android.Graphics.Bitmap;
+#elif WINDOWS
+using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap;
+#else
+
+using DecodeDataType = System.Object;
+
+#endif
+
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginDecoder
+ {
+ #region Public Events
+
+ ///
+ /// Event launched every time a successful decode occurs in the image if "Camera.MAUI.CameraView.PluginProcessingEnabled" is set to true.
+ ///
+ event PluginDecoderResultHandler Decoded;
+
+ #endregion Public Events
+
+ #region Public Properties
+
+ ICommand OnDecodedCommand { get; set; }
+
+ bool VibrateOnDetected { get; set; }
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ void ClearResults();
+
+ void Decode(DecodeDataType data);
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/IPluginDecoderOfTOptions.cs b/Camera.MAUI.Plugin/IPluginDecoderOfTOptions.cs
new file mode 100644
index 0000000..d703da6
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginDecoderOfTOptions.cs
@@ -0,0 +1,21 @@
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginDecoder : IPluginDecoder
+ where TOptions : IPluginDecoderOptions
+ where TResult : IPluginResult
+ {
+ #region Public Properties
+
+ ///
+ /// Options for the plugin.
+ ///
+ TOptions Options { get; set; }
+
+ ///
+ /// It refresh each time a successful decode occurs, if "Camera.MAUI.CameraView.PluginProcessingEnabled" property is true.
+ ///
+ TResult[] Results { get; set; }
+
+ #endregion Public Properties
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/IPluginDecoderOptions.cs b/Camera.MAUI.Plugin/IPluginDecoderOptions.cs
new file mode 100644
index 0000000..9d22807
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginDecoderOptions.cs
@@ -0,0 +1,6 @@
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginDecoderOptions
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/IPluginRenderer.cs b/Camera.MAUI.Plugin/IPluginRenderer.cs
new file mode 100644
index 0000000..a7c4b79
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginRenderer.cs
@@ -0,0 +1,18 @@
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginRenderer
+ {
+ #region Public Properties
+
+ Color Background { get; set; }
+ Color Foreground { get; set; }
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ ImageSource EncodeBarcode(string code, IPluginRendererOptions options);
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/IPluginRendererOptions.cs b/Camera.MAUI.Plugin/IPluginRendererOptions.cs
new file mode 100644
index 0000000..e18db85
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginRendererOptions.cs
@@ -0,0 +1,6 @@
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginRendererOptions
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/IPluginResult.cs b/Camera.MAUI.Plugin/IPluginResult.cs
new file mode 100644
index 0000000..d8da98c
--- /dev/null
+++ b/Camera.MAUI.Plugin/IPluginResult.cs
@@ -0,0 +1,6 @@
+namespace Camera.MAUI.Plugin
+{
+ public interface IPluginResult
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/PluginDecodedEventArgs.cs b/Camera.MAUI.Plugin/PluginDecodedEventArgs.cs
new file mode 100644
index 0000000..6726689
--- /dev/null
+++ b/Camera.MAUI.Plugin/PluginDecodedEventArgs.cs
@@ -0,0 +1,7 @@
+namespace Camera.MAUI.Plugin
+{
+ public record PluginDecodedEventArgs
+ {
+ public IPluginResult[] Results { get; init; }
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/PluginDecoder.cs b/Camera.MAUI.Plugin/PluginDecoder.cs
new file mode 100644
index 0000000..0a0b2e0
--- /dev/null
+++ b/Camera.MAUI.Plugin/PluginDecoder.cs
@@ -0,0 +1,118 @@
+using System.Windows.Input;
+
+#if IOS || MACCATALYST
+using DecodeDataType = UIKit.UIImage;
+#elif ANDROID
+using DecodeDataType = Android.Graphics.Bitmap;
+#elif WINDOWS
+using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap;
+#else
+
+using DecodeDataType = System.Object;
+
+#endif
+
+namespace Camera.MAUI.Plugin
+{
+ public abstract class PluginDecoder : BindableObject, IPluginDecoder
+ where TOptions : IPluginDecoderOptions
+ where TResult : IPluginResult
+ {
+ #region Public Fields
+
+ public static readonly BindableProperty OnDecodedCommandProperty = BindableProperty.Create(nameof(OnDecodedCommand), typeof(ICommand), typeof(PluginDecoder), null, defaultBindingMode: BindingMode.TwoWay);
+ public static readonly BindableProperty OptionsProperty = BindableProperty.Create(nameof(Options), typeof(TOptions), typeof(PluginDecoder), default, propertyChanged: OptionsChanged);
+ public static readonly BindableProperty ResultsProperty = BindableProperty.Create(nameof(Results), typeof(TResult[]), typeof(PluginDecoder), null, BindingMode.OneWayToSource);
+ public static readonly BindableProperty VibrateOnDetectedProperty = BindableProperty.Create(nameof(VibrateOnDetected), typeof(bool), typeof(PluginDecoder), true, defaultBindingMode: BindingMode.TwoWay);
+
+ #endregion Public Fields
+
+ #region Protected Constructors
+
+ protected PluginDecoder()
+ {
+ Options = (TOptions)Activator.CreateInstance(typeof(TOptions));
+ }
+
+ #endregion Protected Constructors
+
+ #region Public Events
+
+ public event PluginDecoderResultHandler Decoded;
+
+ #endregion Public Events
+
+ #region Public Properties
+
+ ///
+ public ICommand OnDecodedCommand
+ {
+ get { return (ICommand)GetValue(OnDecodedCommandProperty); }
+ set { SetValue(OnDecodedCommandProperty, value); }
+ }
+
+ ///
+ public TOptions Options
+ {
+ get { return (TOptions)GetValue(OptionsProperty); }
+ set { SetValue(OptionsProperty, value); }
+ }
+
+ ///
+ public TResult[] Results
+ {
+ get { return (TResult[])GetValue(ResultsProperty); }
+ set { SetValue(ResultsProperty, value); }
+ }
+
+ ///
+ public bool VibrateOnDetected
+ {
+ get => (bool)GetValue(VibrateOnDetectedProperty);
+ set => SetValue(VibrateOnDetectedProperty, value);
+ }
+
+ #endregion Public Properties
+
+ #region Public Methods
+
+ public abstract void ClearResults();
+
+ public abstract void Decode(DecodeDataType data);
+
+ #endregion Public Methods
+
+ #region Protected Methods
+
+ protected void OnDecoded(PluginDecodedEventArgs args)
+ {
+ if (VibrateOnDetected)
+ {
+ try
+ {
+ Vibration.Vibrate(200);
+ }
+ catch
+ { }
+ }
+ Decoded?.Invoke(this, args);
+ OnDecodedCommand?.Execute(args);
+ }
+
+ protected abstract void OnOptionsChanged(object oldValue, object newValue);
+
+ #endregion Protected Methods
+
+ #region Private Methods
+
+ private static void OptionsChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (newValue != null && oldValue != newValue && bindable is PluginDecoder decoder)
+ {
+ decoder.OnOptionsChanged(oldValue, newValue);
+ }
+ }
+
+ #endregion Private Methods
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/PluginDecoderCollection.cs b/Camera.MAUI.Plugin/PluginDecoderCollection.cs
new file mode 100644
index 0000000..a56ab00
--- /dev/null
+++ b/Camera.MAUI.Plugin/PluginDecoderCollection.cs
@@ -0,0 +1,8 @@
+using System.Collections.ObjectModel;
+
+namespace Camera.MAUI.Plugin
+{
+ public class PluginDecoderCollection : ObservableCollection
+ {
+ }
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Plugin/PluginDecoderResultHandler.cs b/Camera.MAUI.Plugin/PluginDecoderResultHandler.cs
new file mode 100644
index 0000000..c6b9caa
--- /dev/null
+++ b/Camera.MAUI.Plugin/PluginDecoderResultHandler.cs
@@ -0,0 +1,4 @@
+namespace Camera.MAUI.Plugin
+{
+ public delegate void PluginDecoderResultHandler(object sender, PluginDecodedEventArgs args);
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Test/BarcodeGenerationPage.xaml b/Camera.MAUI.Test/BarcodeGenerationPage.xaml
index dce0e24..cea888c 100644
--- a/Camera.MAUI.Test/BarcodeGenerationPage.xaml
+++ b/Camera.MAUI.Test/BarcodeGenerationPage.xaml
@@ -3,19 +3,24 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Camera.MAUI.Test.BarcodeGenerationPage"
xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
+ xmlns:bc="clr-namespace:Camera.MAUI.Plugin.ZXing;assembly=Camera.MAUI.Plugin.ZXing"
Title="BarcodeGenerationPage">
-
+
+ BarcodeFormat="QR_CODE">
+
+
+
+
\ No newline at end of file
diff --git a/Camera.MAUI.Test/BarcodeGenerationPage.xaml.cs b/Camera.MAUI.Test/BarcodeGenerationPage.xaml.cs
index 769af19..53e27e6 100644
--- a/Camera.MAUI.Test/BarcodeGenerationPage.xaml.cs
+++ b/Camera.MAUI.Test/BarcodeGenerationPage.xaml.cs
@@ -2,16 +2,16 @@ namespace Camera.MAUI.Test;
public partial class BarcodeGenerationPage : ContentPage
{
- public BarcodeGenerationPage()
- {
- InitializeComponent();
- }
+ public BarcodeGenerationPage()
+ {
+ InitializeComponent();
+ }
private void Button_Clicked(object sender, EventArgs e)
{
- if (!string.IsNullOrEmpty(codeEntry.Text))
- {
- barcodeImage.Barcode = codeEntry.Text;
+ if (!string.IsNullOrEmpty(codeEntry.Text))
+ {
+ barcodeImage.Barcode = codeEntry.Text;
}
}
}
\ No newline at end of file
diff --git a/Camera.MAUI.Test/Camera.MAUI.Test.csproj b/Camera.MAUI.Test/Camera.MAUI.Test.csproj
index a656ee8..1e5fab3 100644
--- a/Camera.MAUI.Test/Camera.MAUI.Test.csproj
+++ b/Camera.MAUI.Test/Camera.MAUI.Test.csproj
@@ -24,7 +24,7 @@
11.0
13.1
- 21.0
+ 26.0
10.0.17763.0
10.0.17763.0
6.5
@@ -53,12 +53,14 @@
-
-
+
+
+
+
diff --git a/Camera.MAUI.Test/FullScreenPage.xaml b/Camera.MAUI.Test/FullScreenPage.xaml
index 987f56a..c162134 100644
--- a/Camera.MAUI.Test/FullScreenPage.xaml
+++ b/Camera.MAUI.Test/FullScreenPage.xaml
@@ -5,7 +5,7 @@
x:Class="Camera.MAUI.Test.FullScreenPage"
Title="FullScreenPage">
-
-
+
+
\ No newline at end of file
diff --git a/Camera.MAUI.Test/FullScreenPage.xaml.cs b/Camera.MAUI.Test/FullScreenPage.xaml.cs
index 8a0d312..2f15516 100644
--- a/Camera.MAUI.Test/FullScreenPage.xaml.cs
+++ b/Camera.MAUI.Test/FullScreenPage.xaml.cs
@@ -2,12 +2,14 @@ namespace Camera.MAUI.Test;
public partial class FullScreenPage : ContentPage
{
- bool playing = false;
- public FullScreenPage()
- {
- InitializeComponent();
+ private bool playing = false;
+
+ public FullScreenPage()
+ {
+ InitializeComponent();
cameraView.CamerasLoaded += CameraView_CamerasLoaded;
}
+
private void CameraView_CamerasLoaded(object sender, EventArgs e)
{
if (cameraView.Cameras.Count > 0)
@@ -25,18 +27,25 @@ private void CameraView_CamerasLoaded(object sender, EventArgs e)
*/
}
}
+
private async void Button_Clicked(object sender, EventArgs e)
{
cameraView.Camera = cameraView.Cameras.First();
if (playing)
{
var result = await cameraView.StopCameraAsync();
- controlButton.Text = "Play";
+ if (result == CameraResult.Success)
+ {
+ controlButton.Text = "Play";
+ }
}
else
{
var result = await cameraView.StartCameraAsync();
- controlButton.Text = "Stop";
+ if (result == CameraResult.Success)
+ {
+ controlButton.Text = "Stop";
+ }
}
playing = !playing;
}
diff --git a/Camera.MAUI.Test/MVVM/CameraViewModel.cs b/Camera.MAUI.Test/MVVM/CameraViewModel.cs
index 8b04dce..63bc73c 100644
--- a/Camera.MAUI.Test/MVVM/CameraViewModel.cs
+++ b/Camera.MAUI.Test/MVVM/CameraViewModel.cs
@@ -1,35 +1,30 @@
-using System;
-using System.Collections.Generic;
+using Camera.MAUI.Plugin;
+using Camera.MAUI.Plugin.ZXing;
+using CommunityToolkit.Maui.Views;
using System.Collections.ObjectModel;
using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using ZXing;
-using System.Windows.Markup;
-using System.Collections.Specialized;
-using Camera.MAUI.ZXingHelper;
-using CommunityToolkit.Maui.Views;
namespace Camera.MAUI.Test;
public class CameraViewModel : INotifyPropertyChanged
{
private CameraInfo camera = null;
- public CameraInfo Camera
+ private bool takeSnapshot = false;
+ private bool autoStartPreview = false;
+ private bool autoStartRecording = false;
+
+ public CameraInfo Camera
{
get => camera;
set
{
camera = value;
OnPropertyChanged(nameof(Camera));
- AutoStartPreview = false;
- OnPropertyChanged(nameof(AutoStartPreview));
- AutoStartPreview = true;
- OnPropertyChanged(nameof(AutoStartPreview));
}
}
+
private ObservableCollection cameras = new();
+
public ObservableCollection Cameras
{
get => cameras;
@@ -39,6 +34,7 @@ public ObservableCollection Cameras
OnPropertyChanged(nameof(Cameras));
}
}
+
public int NumCameras
{
set
@@ -47,7 +43,9 @@ public int NumCameras
Camera = Cameras.First();
}
}
+
private MicrophoneInfo micro = null;
+
public MicrophoneInfo Microphone
{
get => micro;
@@ -57,7 +55,9 @@ public MicrophoneInfo Microphone
OnPropertyChanged(nameof(Microphone));
}
}
+
private ObservableCollection micros = new();
+
public ObservableCollection Microphones
{
get => micros;
@@ -67,6 +67,7 @@ public ObservableCollection Microphones
OnPropertyChanged(nameof(Microphones));
}
}
+
public int NumMicrophones
{
set
@@ -75,36 +76,63 @@ public int NumMicrophones
Microphone = Microphones.First();
}
}
+
+ public bool AutoStartPreview
+ {
+ get => autoStartPreview;
+ set
+ {
+ autoStartPreview = value;
+ OnPropertyChanged(nameof(AutoStartPreview));
+ }
+ }
+
+ public bool AutoStartRecording
+ {
+ get => autoStartRecording;
+ set
+ {
+ autoStartRecording = value;
+ OnPropertyChanged(nameof(AutoStartRecording));
+ }
+ }
+
public MediaSource VideoSource { get; set; }
- public BarcodeDecodeOptions BarCodeOptions { get; set; }
+ public BarcodeDecoderOptions BarCodeOptions { get; set; }
public string BarcodeText { get; set; } = "No barcode detected";
- public bool AutoStartPreview { get; set; } = false;
- public bool AutoStartRecording { get; set; } = false;
- private Result[] barCodeResults;
- public Result[] BarCodeResults
+ private IPluginResult[] barCodeResults;
+
+ public IPluginResult[] BarCodeResults
{
get => barCodeResults;
set
{
barCodeResults = value;
if (barCodeResults != null && barCodeResults.Length > 0)
- BarcodeText = barCodeResults[0].Text;
+ {
+ if (barCodeResults is BarcodeResult[] results)
+ {
+ BarcodeText = results[0].Text;
+ }
+ }
else
BarcodeText = "No barcode detected";
OnPropertyChanged(nameof(BarcodeText));
}
}
- private bool takeSnapshot = false;
- public bool TakeSnapshot
- {
+
+ public bool TakeSnapshot
+ {
get => takeSnapshot;
set
{
takeSnapshot = value;
OnPropertyChanged(nameof(TakeSnapshot));
- }
+ }
}
+
public float SnapshotSeconds { get; set; } = 0f;
+
public string Seconds
{
get => SnapshotSeconds.ToString();
@@ -117,6 +145,7 @@ public string Seconds
}
}
}
+
public Command StartCamera { get; set; }
public Command StopCamera { get; set; }
public Command TakeSnapshotCmd { get; set; }
@@ -130,12 +159,13 @@ protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
public CameraViewModel()
{
- BarCodeOptions = new ZXingHelper.BarcodeDecodeOptions
+ BarCodeOptions = new ZXingDecoderOptions
{
AutoRotate = true,
- PossibleFormats = { ZXing.BarcodeFormat.QR_CODE },
+ PossibleFormats = { Plugin.BarcodeFormat.QR_CODE },
ReadMultipleCodes = false,
TryHarder = true,
TryInverted = true
@@ -144,12 +174,10 @@ public CameraViewModel()
StartCamera = new Command(() =>
{
AutoStartPreview = true;
- OnPropertyChanged(nameof(AutoStartPreview));
});
StopCamera = new Command(() =>
{
AutoStartPreview = false;
- OnPropertyChanged(nameof(AutoStartPreview));
});
TakeSnapshotCmd = new Command(() =>
{
@@ -165,12 +193,10 @@ public CameraViewModel()
StartRecording = new Command(() =>
{
AutoStartRecording = true;
- OnPropertyChanged(nameof(AutoStartRecording));
});
StopRecording = new Command(() =>
{
AutoStartRecording = false;
- OnPropertyChanged(nameof(AutoStartRecording));
VideoSource = MediaSource.FromFile(RecordingFile);
OnPropertyChanged(nameof(VideoSource));
});
@@ -180,4 +206,4 @@ public CameraViewModel()
OnPropertyChanged(nameof(StartRecording));
OnPropertyChanged(nameof(StopRecording));
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI.Test/MVVM/MVVMPage.xaml b/Camera.MAUI.Test/MVVM/MVVMPage.xaml
index 453d222..0157446 100644
--- a/Camera.MAUI.Test/MVVM/MVVMPage.xaml
+++ b/Camera.MAUI.Test/MVVM/MVVMPage.xaml
@@ -5,6 +5,7 @@
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:local="clr-namespace:Camera.MAUI.Test"
xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
+ xmlns:bc="clr-namespace:Camera.MAUI.Plugin.ZXing;assembly=Camera.MAUI.Plugin.ZXing"
Title="MVVMPage" Background="white">
@@ -12,37 +13,49 @@
-
+ Microphones="{Binding Microphones, Mode=OneWayToSource}" Microphone="{Binding Microphone}" NumMicrophonesDetected="{Binding NumMicrophones, Mode=OneWayToSource}"
+ AutoRecordingFile="{Binding RecordingFile}" AutoStartRecording="{Binding AutoStartRecording}">
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
+
@@ -60,5 +73,4 @@
-
\ No newline at end of file
diff --git a/Camera.MAUI.Test/MVVM/MVVMPage.xaml.cs b/Camera.MAUI.Test/MVVM/MVVMPage.xaml.cs
index ac1b73c..81c4381 100644
--- a/Camera.MAUI.Test/MVVM/MVVMPage.xaml.cs
+++ b/Camera.MAUI.Test/MVVM/MVVMPage.xaml.cs
@@ -2,8 +2,8 @@ namespace Camera.MAUI.Test;
public partial class MVVMPage : ContentPage
{
- public MVVMPage()
- {
- InitializeComponent();
- }
+ public MVVMPage()
+ {
+ InitializeComponent();
+ }
}
\ No newline at end of file
diff --git a/Camera.MAUI.Test/MainPage.xaml b/Camera.MAUI.Test/MainPage.xaml
index 6b44831..10e50bc 100644
--- a/Camera.MAUI.Test/MainPage.xaml
+++ b/Camera.MAUI.Test/MainPage.xaml
@@ -7,11 +7,11 @@
-
-
-
-
+
+
+
+
-
+
\ No newline at end of file
diff --git a/Camera.MAUI.Test/MauiProgram.cs b/Camera.MAUI.Test/MauiProgram.cs
index a16c0cf..f582afc 100644
--- a/Camera.MAUI.Test/MauiProgram.cs
+++ b/Camera.MAUI.Test/MauiProgram.cs
@@ -1,7 +1,5 @@
-using Microsoft.Extensions.Logging;
-using Camera.MAUI;
-using Microsoft.Maui.Hosting;
-using CommunityToolkit.Maui;
+using CommunityToolkit.Maui;
+using Microsoft.Extensions.Logging;
namespace Camera.MAUI.Test
{
@@ -22,7 +20,7 @@ public static MauiApp CreateMauiApp()
});
#if DEBUG
- builder.Logging.AddDebug();
+ builder.Logging.AddDebug();
#endif
builder.Services.AddSingleton();
return builder.Build();
diff --git a/Camera.MAUI.Test/Platforms/Android/AndroidManifest.xml b/Camera.MAUI.Test/Platforms/Android/AndroidManifest.xml
index 5136136..3e55689 100644
--- a/Camera.MAUI.Test/Platforms/Android/AndroidManifest.xml
+++ b/Camera.MAUI.Test/Platforms/Android/AndroidManifest.xml
@@ -3,9 +3,10 @@
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Camera.MAUI.Test/Platforms/Android/MainActivity.cs b/Camera.MAUI.Test/Platforms/Android/MainActivity.cs
index ece9a52..e9408be 100644
--- a/Camera.MAUI.Test/Platforms/Android/MainActivity.cs
+++ b/Camera.MAUI.Test/Platforms/Android/MainActivity.cs
@@ -1,6 +1,5 @@
using Android.App;
using Android.Content.PM;
-using Android.OS;
namespace Camera.MAUI.Test
{
diff --git a/Camera.MAUI.Test/Platforms/MacCatalyst/Program.cs b/Camera.MAUI.Test/Platforms/MacCatalyst/Program.cs
index 9e93a67..06065eb 100644
--- a/Camera.MAUI.Test/Platforms/MacCatalyst/Program.cs
+++ b/Camera.MAUI.Test/Platforms/MacCatalyst/Program.cs
@@ -1,5 +1,4 @@
-using ObjCRuntime;
-using UIKit;
+using UIKit;
namespace Camera.MAUI.Test
{
diff --git a/Camera.MAUI.Test/Platforms/Windows/App.xaml.cs b/Camera.MAUI.Test/Platforms/Windows/App.xaml.cs
index 5e661e7..d176fa2 100644
--- a/Camera.MAUI.Test/Platforms/Windows/App.xaml.cs
+++ b/Camera.MAUI.Test/Platforms/Windows/App.xaml.cs
@@ -1,6 +1,4 @@
-using Microsoft.UI.Xaml;
-
-// To learn more about WinUI, the WinUI project structure,
+// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace Camera.MAUI.Test.WinUI
diff --git a/Camera.MAUI.Test/Platforms/iOS/Program.cs b/Camera.MAUI.Test/Platforms/iOS/Program.cs
index 9e93a67..06065eb 100644
--- a/Camera.MAUI.Test/Platforms/iOS/Program.cs
+++ b/Camera.MAUI.Test/Platforms/iOS/Program.cs
@@ -1,5 +1,4 @@
-using ObjCRuntime;
-using UIKit;
+using UIKit;
namespace Camera.MAUI.Test
{
diff --git a/Camera.MAUI.Test/SizedPage.xaml b/Camera.MAUI.Test/SizedPage.xaml
index 834c644..57e3427 100644
--- a/Camera.MAUI.Test/SizedPage.xaml
+++ b/Camera.MAUI.Test/SizedPage.xaml
@@ -4,36 +4,41 @@
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
x:Class="Camera.MAUI.Test.SizedPage"
xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
+ xmlns:bc="clr-namespace:Camera.MAUI.Plugin.ZXing;assembly=Camera.MAUI.Plugin.ZXing"
Title="SizedPage" Background="white" x:Name="sizedPage">
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
@@ -49,7 +54,7 @@
-
+
diff --git a/Camera.MAUI.Test/SizedPage.xaml.cs b/Camera.MAUI.Test/SizedPage.xaml.cs
index 63233a8..bc039c0 100644
--- a/Camera.MAUI.Test/SizedPage.xaml.cs
+++ b/Camera.MAUI.Test/SizedPage.xaml.cs
@@ -1,7 +1,7 @@
+using Camera.MAUI.Plugin;
+using Camera.MAUI.Plugin.ZXing;
using CommunityToolkit.Maui.Views;
using System.Diagnostics;
-using ZXing;
-using ZXing.QrCode.Internal;
namespace Camera.MAUI.Test;
@@ -19,25 +19,36 @@ private static void SetStream(BindableObject bindable, object oldValue, object n
control.snapPreview.Source = ImageSource.FromStream(() => str);
}
}
+
public string BarcodeText { get; set; } = "No barcode detected";
- public Stream Stream {
+
+ public Stream Stream
+ {
get { return (Stream)GetValue(StreamProperty); }
- set { SetValue(StreamProperty, value); }
+ set { SetValue(StreamProperty, value); }
}
- public SizedPage()
- {
- InitializeComponent();
+
+ public SizedPage()
+ {
+ InitializeComponent();
cameraView.CamerasLoaded += CameraView_CamerasLoaded;
cameraView.MicrophonesLoaded += CameraView_MicrophonesLoaded;
- cameraView.BarcodeDetected += CameraView_BarcodeDetected;
- cameraView.BarCodeOptions = new ZXingHelper.BarcodeDecodeOptions
+ if (cameraView.PluginDecoder != null)
{
- AutoRotate = true,
- PossibleFormats = { BarcodeFormat.EAN_13 },
- ReadMultipleCodes = false,
- TryHarder = false,
- TryInverted = true
- };
+ cameraView.PluginDecoder.Decoded += CameraView_BarcodeDetected;
+ if (cameraView.PluginDecoder is ZXingDecoder decoder)
+ {
+ decoder.Options = new ZXingDecoderOptions
+ {
+ AutoRotate = true,
+ PossibleFormats = { BarcodeFormat.EAN_13 },
+ ReadMultipleCodes = false,
+ TryHarder = false,
+ TryInverted = true
+ };
+ decoder.ControlBarcodeResultDuplicate = true;
+ }
+ }
BindingContext = cameraView;
}
@@ -47,11 +58,14 @@ private void CameraView_MicrophonesLoaded(object sender, EventArgs e)
microPicker.SelectedIndex = 0;
}
- private void CameraView_BarcodeDetected(object sender, ZXingHelper.BarcodeEventArgs args)
+ private void CameraView_BarcodeDetected(object sender, PluginDecodedEventArgs args)
{
- BarcodeText = "Barcode: " + args.Result[0].Text;
- OnPropertyChanged(nameof(BarcodeText));
- Debug.WriteLine("BarcodeText=" + args.Result[0].Text);
+ if (args.Results is BarcodeResult[] results)
+ {
+ BarcodeText = "Barcode: " + results[0].Text;
+ OnPropertyChanged(nameof(BarcodeText));
+ Debug.WriteLine("BarcodeText=" + results[0].Text);
+ }
}
private void CameraView_CamerasLoaded(object sender, EventArgs e)
@@ -74,15 +88,16 @@ private async void OnStartClicked(object sender, EventArgs e)
cameraLabel.BackgroundColor = Colors.Red;
}
}
+
private async void OnStartRecordingClicked(object sender, EventArgs e)
{
if (cameraPicker.SelectedItem != null && cameraPicker.SelectedItem is CameraInfo camera)
{
//if (microPicker.SelectedItem != null && microPicker.SelectedItem is MicrophoneInfo micro)
//{
- cameraLabel.BackgroundColor = Colors.White;
- microLabel.BackgroundColor = Colors.White;
- cameraView.Camera = camera;
+ cameraLabel.BackgroundColor = Colors.White;
+ microLabel.BackgroundColor = Colors.White;
+ cameraView.Camera = camera;
//cameraView.Microphone = micro;
#if IOS
var result = await cameraView.StartRecordingAsync(Path.Combine(FileSystem.Current.CacheDirectory, "Video.mov"));
@@ -97,6 +112,7 @@ private async void OnStartRecordingClicked(object sender, EventArgs e)
else
cameraLabel.BackgroundColor = Colors.Red;
}
+
private async void OnStopRecordingClicked(object sender, EventArgs e)
{
var result = await cameraView.StopRecordingAsync();
@@ -107,13 +123,15 @@ private async void OnStopRecordingClicked(object sender, EventArgs e)
player.Source = MediaSource.FromFile(Path.Combine(FileSystem.Current.CacheDirectory, "Video.mp4"));
#endif
}
+
private async void OnStopClicked(object sender, EventArgs e)
{
var result = await cameraView.StopCameraAsync();
Debug.WriteLine("Stop camera result " + result);
}
+
private void OnSnapClicked(object sender, EventArgs e)
- {
+ {
var result = cameraView.GetSnapShot(ImageFormat.PNG);
if (result != null)
snapPreview.Source = result;
@@ -123,13 +141,15 @@ private void CheckBox_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
cameraView.MirroredImage = e.Value;
}
+
private void CheckBox4_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
cameraView.TorchEnabled = e.Value;
}
+
private void CheckBox3_CheckedChanged(object sender, CheckedChangedEventArgs e)
{
- cameraView.BarCodeDetectionEnabled = e.Value;
+ cameraView.PluginProcessingEnabled = e.Value;
}
private void Stepper_ValueChanged(object sender, ValueChangedEventArgs e)
@@ -146,7 +166,8 @@ private void CameraPicker_SelectedIndexChanged(object sender, EventArgs e)
{
zoomLabel.IsEnabled = zoomStepper.IsEnabled = true;
zoomStepper.Maximum = camera.MaxZoomFactor;
- }else
+ }
+ else
zoomLabel.IsEnabled = zoomStepper.IsEnabled = true;
cameraView.Camera = camera;
}
diff --git a/Camera.MAUI.sln b/Camera.MAUI.sln
index f70ac7c..57dc8a1 100644
--- a/Camera.MAUI.sln
+++ b/Camera.MAUI.sln
@@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camera.MAUI", "Camera.MAUI\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camera.MAUI.Test", "Camera.MAUI.Test\Camera.MAUI.Test.csproj", "{AFE92281-679D-4DC3-AD37-C2BAA6D1C2BD}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camera.MAUI.Plugin", "Camera.MAUI.Plugin\Camera.MAUI.Plugin.csproj", "{D4ABAD07-DAA6-4062-A7FA-B25D1E2C37F4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camera.MAUI.Plugin.ZXing", "Camera.MAUI.Plugin.ZXing\Camera.MAUI.Plugin.ZXing.csproj", "{DB6CCC62-6911-419E-B393-48E3097E0ACA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Camera.MAUI.Plugin.MLKit", "Camera.MAUI.Plugin.MLKit\Camera.MAUI.Plugin.MLKit.csproj", "{27BF5876-E36F-42C7-BD31-C89D69B2888E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -23,6 +29,18 @@ Global
{AFE92281-679D-4DC3-AD37-C2BAA6D1C2BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AFE92281-679D-4DC3-AD37-C2BAA6D1C2BD}.Release|Any CPU.Build.0 = Release|Any CPU
{AFE92281-679D-4DC3-AD37-C2BAA6D1C2BD}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {D4ABAD07-DAA6-4062-A7FA-B25D1E2C37F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4ABAD07-DAA6-4062-A7FA-B25D1E2C37F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4ABAD07-DAA6-4062-A7FA-B25D1E2C37F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4ABAD07-DAA6-4062-A7FA-B25D1E2C37F4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DB6CCC62-6911-419E-B393-48E3097E0ACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DB6CCC62-6911-419E-B393-48E3097E0ACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DB6CCC62-6911-419E-B393-48E3097E0ACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DB6CCC62-6911-419E-B393-48E3097E0ACA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27BF5876-E36F-42C7-BD31-C89D69B2888E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27BF5876-E36F-42C7-BD31-C89D69B2888E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27BF5876-E36F-42C7-BD31-C89D69B2888E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27BF5876-E36F-42C7-BD31-C89D69B2888E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Camera.MAUI/AppBuilderExtensions.cs b/Camera.MAUI/AppBuilderExtensions.cs
index bd7f845..108c60d 100644
--- a/Camera.MAUI/AppBuilderExtensions.cs
+++ b/Camera.MAUI/AppBuilderExtensions.cs
@@ -1,4 +1,6 @@
-namespace Camera.MAUI;
+using Microsoft.Maui.LifecycleEvents;
+
+namespace Camera.MAUI;
public static class AppBuilderExtensions
{
@@ -8,6 +10,19 @@ public static MauiAppBuilder UseMauiCameraView(this MauiAppBuilder builder)
{
h.AddHandler(typeof(CameraView), typeof(CameraViewHandler));
});
+ builder.ConfigureLifecycleEvents(events =>
+ {
+#if ANDROID
+ events.AddAndroid(android => android
+ .OnPause((activity) => CameraViewHandler.Current?.StopCameraAsync(false))
+ .OnResume((activity) => CameraViewHandler.Current?.StartCameraAsync(CameraViewHandler.CurrentResolution)));
+#elif IOS || MACCATALYST
+ events.AddiOS(ios => ios
+ .OnResignActivation((app) => CameraViewHandler.Current?.StopCameraAsync(false))
+ .WillEnterForeground((app) => CameraViewHandler.Current?.StartCameraAsync(CameraViewHandler.CurrentResolution)));
+#elif WINDOWS
+#endif
+ });
return builder;
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Apple/BitmapRenderer.cs b/Camera.MAUI/Apple/BitmapRenderer.cs
deleted file mode 100644
index 266cc0d..0000000
--- a/Camera.MAUI/Apple/BitmapRenderer.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-#if IOS || MACCATALYST
-using CoreGraphics;
-using UIKit;
-using ZXing.Common;
-using ZXing.Rendering;
-using ZXing;
-
-namespace Camera.MAUI.Platforms.Apple;
-
-public class BitmapRenderer : IBarcodeRenderer
-{
- public CGColor Foreground { get; set; }
- public CGColor Background { get; set; }
-
- public BitmapRenderer()
- {
- Foreground = new CGColor(0f, 0f, 0f);
- Background = new CGColor(1.0f, 1.0f, 1.0f);
- }
- public UIImage Render(BitMatrix matrix, BarcodeFormat format, string content)
- {
- return Render(matrix, format, content, new EncodingOptions());
- }
-
- public UIImage Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options)
- {
- UIGraphics.BeginImageContext(new CGSize(matrix.Width, matrix.Height));
- var context = UIGraphics.GetCurrentContext();
-
- for (int x = 0; x < matrix.Width; x++)
- {
- for (int y = 0; y < matrix.Height; y++)
- {
- context.SetFillColor(matrix[x, y] ? Foreground : Background);
- context.FillRect(new CGRect(x, y, 1, 1));
- }
- }
-
-
- var img = UIGraphics.GetImageFromCurrentImageContext();
-
- UIGraphics.EndImageContext();
-
- return img;
- }
-}
-#endif
diff --git a/Camera.MAUI/BarcodeImage.xaml.cs b/Camera.MAUI/BarcodeImage.xaml.cs
index bb03f91..ad5e97d 100644
--- a/Camera.MAUI/BarcodeImage.xaml.cs
+++ b/Camera.MAUI/BarcodeImage.xaml.cs
@@ -1,10 +1,10 @@
-using ZXing;
+using Camera.MAUI.Plugin;
namespace Camera.MAUI;
public partial class BarcodeImage : ContentView
{
- public static readonly BindableProperty BarcodeForegroundProperty = BindableProperty.Create(nameof(BarcodeForeground), typeof(Color), typeof(BarcodeImage), Colors.Black, propertyChanged:RefreshRender);
+ public static readonly BindableProperty BarcodeForegroundProperty = BindableProperty.Create(nameof(BarcodeForeground), typeof(Color), typeof(BarcodeImage), Colors.Black, propertyChanged: RefreshRender);
public static readonly BindableProperty BarcodeBackgroundProperty = BindableProperty.Create(nameof(BarcodeBackground), typeof(Color), typeof(BarcodeImage), Colors.White, propertyChanged: RefreshRender);
public static readonly BindableProperty BarcodeWidthProperty = BindableProperty.Create(nameof(BarcodeWidth), typeof(int), typeof(BarcodeImage), 200, propertyChanged: RefreshRender);
public static readonly BindableProperty BarcodeHeightProperty = BindableProperty.Create(nameof(BarcodeHeight), typeof(int), typeof(BarcodeImage), 200, propertyChanged: RefreshRender);
@@ -12,6 +12,7 @@ public partial class BarcodeImage : ContentView
public static readonly BindableProperty BarcodeFormatProperty = BindableProperty.Create(nameof(BarcodeFormat), typeof(BarcodeFormat), typeof(BarcodeImage), BarcodeFormat.QR_CODE, propertyChanged: RefreshRender);
public static readonly BindableProperty BarcodeProperty = BindableProperty.Create(nameof(Barcode), typeof(string), typeof(BarcodeImage), string.Empty, propertyChanged: RefreshRender);
public static readonly BindableProperty AspectProperty = BindableProperty.Create(nameof(Aspect), typeof(Aspect), typeof(BarcodeImage), Aspect.AspectFit);
+ public static readonly BindableProperty BarcodeRendererProperty = BindableProperty.Create(nameof(BarcodeRenderer), typeof(IPluginRenderer), typeof(BarcodeImage), null);
///
/// Foreground color for Codebar generation. This is a bindable property.
@@ -21,6 +22,7 @@ public Color BarcodeForeground
get { return (Color)GetValue(BarcodeForegroundProperty); }
set { SetValue(BarcodeForegroundProperty, value); }
}
+
///
/// Background color for Codebar generation. This is a bindable property.
///
@@ -29,6 +31,7 @@ public Color BarcodeBackground
get { return (Color)GetValue(BarcodeBackgroundProperty); }
set { SetValue(BarcodeBackgroundProperty, value); }
}
+
///
/// Width of generated Barcode image. This is a bindable property.
///
@@ -37,6 +40,7 @@ public int BarcodeWidth
get { return (int)GetValue(BarcodeWidthProperty); }
set { SetValue(BarcodeWidthProperty, value); }
}
+
///
/// Height of generated Barcode image. This is a bindable property.
///
@@ -45,6 +49,7 @@ public int BarcodeHeight
get { return (int)GetValue(BarcodeHeightProperty); }
set { SetValue(BarcodeHeightProperty, value); }
}
+
///
/// Margin of generated Barcode image. This is a bindable property.
///
@@ -53,6 +58,7 @@ public int BarcodeMargin
get { return (int)GetValue(BarcodeMarginProperty); }
set { SetValue(BarcodeMarginProperty, value); }
}
+
///
/// Barcode Format for the generated image. This is a bindable property.
///
@@ -61,6 +67,7 @@ public BarcodeFormat BarcodeFormat
get { return (BarcodeFormat)GetValue(BarcodeFormatProperty); }
set { SetValue(BarcodeFormatProperty, value); }
}
+
///
/// Barcode string to Encode. This is a bindable property.
///
@@ -69,6 +76,7 @@ public string Barcode
get { return (string)GetValue(BarcodeProperty); }
set { SetValue(BarcodeProperty, value); }
}
+
///
/// Scale mode for the image. This is a bindable property.
///
@@ -77,24 +85,33 @@ public Aspect Aspect
get { return (Aspect)GetValue(AspectProperty); }
set { SetValue(AspectProperty, value); }
}
- private BarcodeRenderer barcodeRenderer = new BarcodeRenderer();
+
+ ///
+ /// Barcode renderer. This is a bindable property.
+ ///
+ public IPluginRenderer BarcodeRenderer
+ {
+ get { return (IPluginRenderer)GetValue(BarcodeRendererProperty); }
+ set { SetValue(BarcodeRendererProperty, value); }
+ }
public BarcodeImage()
- {
- InitializeComponent();
- }
+ {
+ InitializeComponent();
+ }
+
private static void RefreshRender(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue != newValue && bindable is BarcodeImage barcodeImage)
{
- barcodeImage.barcodeRenderer.Foreground = barcodeImage.BarcodeForeground;
- barcodeImage.barcodeRenderer.Background = barcodeImage.BarcodeBackground;
+ barcodeImage.BarcodeRenderer.Foreground = barcodeImage.BarcodeForeground;
+ barcodeImage.BarcodeRenderer.Background = barcodeImage.BarcodeBackground;
if (!string.IsNullOrEmpty(barcodeImage.Barcode) && barcodeImage.BarcodeWidth > 0 && barcodeImage.BarcodeHeight > 0)
{
- var imageSource = barcodeImage.barcodeRenderer.EncodeBarcode(barcodeImage.Barcode, barcodeImage.BarcodeFormat, barcodeImage.BarcodeWidth, barcodeImage.BarcodeHeight, barcodeImage.BarcodeMargin);
+ var options = new BarcodeEncoderOptions(barcodeImage.BarcodeFormat, barcodeImage.BarcodeWidth, barcodeImage.BarcodeHeight, barcodeImage.BarcodeMargin);
+ var imageSource = barcodeImage.BarcodeRenderer.EncodeBarcode(barcodeImage.Barcode, options);
if (imageSource != null) barcodeImage.image.Source = imageSource;
}
}
}
-
}
\ No newline at end of file
diff --git a/Camera.MAUI/BarcodeRenderer.cs b/Camera.MAUI/BarcodeRenderer.cs
deleted file mode 100644
index ede671f..0000000
--- a/Camera.MAUI/BarcodeRenderer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using ZXing.Common;
-using ZXing;
-#if WINDOWS
-using Windows.Graphics.Imaging;
-using CustomRenderer = Camera.MAUI.Platforms.Windows.SoftwareBitmapRenderer;
-#elif IOS || MACCATALYST
-using CustomRenderer = Camera.MAUI.Platforms.Apple.BitmapRenderer;
-#elif ANDROID
-using CustomRenderer = Camera.MAUI.Platforms.Android.BitmapRenderer;
-#else
-using CustomRenderer = System.Object;
-#endif
-
-namespace Camera.MAUI;
-
-public class BarcodeRenderer
-{
- public Color Foreground { get; set; } = Colors.Black;
- public Color Background { get; set; } = Colors.White;
-
- private BarcodeWriterPixelData writer = new();
- private CustomRenderer customRenderer = new();
- public ImageSource EncodeBarcode(string code, BarcodeFormat format = BarcodeFormat.QR_CODE, int width = 400, int height = 400, int margin = 5)
- {
- ImageSource imageSource = null;
- writer.Options = new EncodingOptions { Width = width, Height = height, Margin = margin };
- writer.Format = format;
- try
- {
- var bitMatrix = writer.Encode(code);
- if (bitMatrix != null)
- {
- MemoryStream stream = new MemoryStream();
-#if WINDOWS
- byte a, r, g, b;
- Foreground.ToRgba(out r, out g, out b, out a);
- customRenderer.Foreground = Windows.UI.Color.FromArgb(a, r, g, b);
- Background.ToRgba(out r, out g, out b, out a);
- customRenderer.Background = Windows.UI.Color.FromArgb(a, r, g, b);
- var bitmap = customRenderer.Render(bitMatrix, format, code);
- BitmapEncoder encoder = BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream.AsRandomAccessStream()).GetAwaiter().GetResult();
- encoder.SetSoftwareBitmap(bitmap);
- encoder.FlushAsync().GetAwaiter().GetResult();
- stream.Position = 0;
- imageSource = ImageSource.FromStream(()=>stream);
-#elif IOS || MACCATALYST
- customRenderer.Foreground = new CoreGraphics.CGColor(Foreground.Red, Foreground.Green, Foreground.Blue, Foreground.Alpha);
- customRenderer.Background = new CoreGraphics.CGColor(Background.Red, Background.Green, Background.Blue, Background.Alpha);
- var bitmap = customRenderer.Render(bitMatrix, format, code);
- bitmap.AsPNG().AsStream().CopyTo(stream);
- stream.Position = 0;
- imageSource = ImageSource.FromStream(() => stream);
-#elif ANDROID
- byte a, r, g, b;
- Foreground.ToRgba(out r, out g, out b, out a);
- customRenderer.Foreground = new Android.Graphics.Color(r, g, b, a);
- Background.ToRgba(out r, out g, out b, out a);
- customRenderer.Background = new Android.Graphics.Color(r, g, b, a);
- var bitmap = customRenderer.Render(bitMatrix, format, code);
- bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 100, stream);
- stream.Position = 0;
- imageSource = ImageSource.FromStream(() => stream);
-#endif
- }
- }
- catch
- {
- }
- return imageSource;
- }
-}
diff --git a/Camera.MAUI/Camera.MAUI.csproj b/Camera.MAUI/Camera.MAUI.csproj
index 7a5c608..ad9ec3d 100644
--- a/Camera.MAUI/Camera.MAUI.csproj
+++ b/Camera.MAUI/Camera.MAUI.csproj
@@ -26,12 +26,11 @@
https://github.com/hjam40/Camera.MAUI
git
csharp; dotnet; cross-platform; camera; cameraview; codebar; qr; qr-decoder; codebar-encoder; codebar-decoder; camera-component; camera-view; plugin; maui; dotnet-maui;
- hjam40
+ hjam40,programatix
MIT
True
1.4.4
- Fix problems with Android rotate device. Fix iOS problems.
-
+ Fix problems with Android rotate device. Fix iOS problems.
@@ -39,12 +38,13 @@
-
-
-
+
-
-
+
+
+
+
+
@@ -59,14 +59,4 @@
-
-
-
-
-
-
- MSBuild:Compile
-
-
-
diff --git a/Camera.MAUI/CameraInfo.cs b/Camera.MAUI/CameraInfo.cs
index 1574c86..ac36980 100644
--- a/Camera.MAUI/CameraInfo.cs
+++ b/Camera.MAUI/CameraInfo.cs
@@ -10,10 +10,14 @@ public class CameraInfo
public float MaxZoomFactor { get; internal set; }
public float HorizontalViewAngle { get; internal set; }
public float VerticalViewAngle { get; internal set; }
-
public List AvailableResolutions { get; internal set; }
+
+#if ANDROID30_0_OR_GREATER
+ public bool UseZoomRatio { get; internal set; }
+#endif
+
public override string ToString()
{
return Name;
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/CameraView.cs b/Camera.MAUI/CameraView.cs
index 91e6a3e..cdbfe52 100644
--- a/Camera.MAUI/CameraView.cs
+++ b/Camera.MAUI/CameraView.cs
@@ -1,18 +1,5 @@
-using Camera.MAUI.ZXingHelper;
-using Microsoft.Maui.Controls;
+using Camera.MAUI.Plugin;
using System.Collections.ObjectModel;
-using System.Diagnostics;
-using ZXing;
-using static Microsoft.Maui.ApplicationModel.Permissions;
-#if IOS || MACCATALYST
-using DecodeDataType = UIKit.UIImage;
-#elif ANDROID
-using DecodeDataType = Android.Graphics.Bitmap;
-#elif WINDOWS
-using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap;
-#else
-using DecodeDataType = System.Object;
-#endif
namespace Camera.MAUI;
@@ -21,27 +8,27 @@ public class CameraView : View, ICameraView
public static readonly BindableProperty SelfProperty = BindableProperty.Create(nameof(Self), typeof(CameraView), typeof(CameraView), null, BindingMode.OneWayToSource);
public static readonly BindableProperty FlashModeProperty = BindableProperty.Create(nameof(FlashMode), typeof(FlashMode), typeof(CameraView), FlashMode.Disabled);
public static readonly BindableProperty TorchEnabledProperty = BindableProperty.Create(nameof(TorchEnabled), typeof(bool), typeof(CameraView), false);
- public static readonly BindableProperty CamerasProperty = BindableProperty.Create(nameof(Cameras), typeof(ObservableCollection), typeof(CameraView), new ObservableCollection());
+ public static readonly BindableProperty CamerasProperty = BindableProperty.Create(nameof(Cameras), typeof(ObservableCollection), typeof(CameraView), new ObservableCollection(), defaultBindingMode: BindingMode.OneWayToSource);
public static readonly BindableProperty NumCamerasDetectedProperty = BindableProperty.Create(nameof(NumCamerasDetected), typeof(int), typeof(CameraView), 0);
- public static readonly BindableProperty CameraProperty = BindableProperty.Create(nameof(Camera), typeof(CameraInfo), typeof(CameraView), null, propertyChanged:CameraChanged);
- public static readonly BindableProperty MicrophonesProperty = BindableProperty.Create(nameof(Microphones), typeof(ObservableCollection), typeof(CameraView), new ObservableCollection());
+ public static readonly BindableProperty CameraProperty = BindableProperty.Create(nameof(Camera), typeof(CameraInfo), typeof(CameraView), null, propertyChanged: CameraChanged, defaultBindingMode: BindingMode.TwoWay);
+ public static readonly BindableProperty MicrophonesProperty = BindableProperty.Create(nameof(Microphones), typeof(ObservableCollection), typeof(CameraView), new ObservableCollection(), defaultBindingMode: BindingMode.OneWayToSource);
public static readonly BindableProperty NumMicrophonesDetectedProperty = BindableProperty.Create(nameof(NumMicrophonesDetected), typeof(int), typeof(CameraView), 0);
public static readonly BindableProperty MicrophoneProperty = BindableProperty.Create(nameof(Microphone), typeof(MicrophoneInfo), typeof(CameraView), null);
public static readonly BindableProperty MirroredImageProperty = BindableProperty.Create(nameof(MirroredImage), typeof(bool), typeof(CameraView), false);
- public static readonly BindableProperty BarCodeDetectionEnabledProperty = BindableProperty.Create(nameof(BarCodeDetectionEnabled), typeof(bool), typeof(CameraView), false);
- public static readonly BindableProperty BarCodeDetectionFrameRateProperty = BindableProperty.Create(nameof(BarCodeDetectionFrameRate), typeof(int), typeof(CameraView), 10);
- public static readonly BindableProperty BarCodeOptionsProperty = BindableProperty.Create(nameof(BarCodeOptions), typeof(BarcodeDecodeOptions), typeof(CameraView), new BarcodeDecodeOptions(), propertyChanged:BarCodeOptionsChanged);
- public static readonly BindableProperty BarCodeResultsProperty = BindableProperty.Create(nameof(BarCodeResults), typeof(Result[]), typeof(CameraView), null, BindingMode.OneWayToSource);
+ public static readonly BindableProperty PluginProcessingEnabledProperty = BindableProperty.Create(nameof(PluginProcessingEnabled), typeof(bool), typeof(CameraView), false);
+ public static readonly BindableProperty PluginProcessingSkipFramesProperty = BindableProperty.Create(nameof(PluginProcessingSkipFrames), typeof(int), typeof(CameraView), 10);
public static readonly BindableProperty ZoomFactorProperty = BindableProperty.Create(nameof(ZoomFactor), typeof(float), typeof(CameraView), 1f);
public static readonly BindableProperty AutoSnapShotSecondsProperty = BindableProperty.Create(nameof(AutoSnapShotSeconds), typeof(float), typeof(CameraView), 0f);
public static readonly BindableProperty AutoSnapShotFormatProperty = BindableProperty.Create(nameof(AutoSnapShotFormat), typeof(ImageFormat), typeof(CameraView), ImageFormat.PNG);
public static readonly BindableProperty SnapShotProperty = BindableProperty.Create(nameof(SnapShot), typeof(ImageSource), typeof(CameraView), null, BindingMode.OneWayToSource);
public static readonly BindableProperty SnapShotStreamProperty = BindableProperty.Create(nameof(SnapShotStream), typeof(Stream), typeof(CameraView), null, BindingMode.OneWayToSource);
- public static readonly BindableProperty TakeAutoSnapShotProperty = BindableProperty.Create(nameof(TakeAutoSnapShot), typeof(bool), typeof(CameraView), false, propertyChanged:TakeAutoSnapShotChanged);
+ public static readonly BindableProperty TakeAutoSnapShotProperty = BindableProperty.Create(nameof(TakeAutoSnapShot), typeof(bool), typeof(CameraView), false, propertyChanged: TakeAutoSnapShotChanged);
public static readonly BindableProperty AutoSnapShotAsImageSourceProperty = BindableProperty.Create(nameof(AutoSnapShotAsImageSource), typeof(bool), typeof(CameraView), false);
- public static readonly BindableProperty AutoStartPreviewProperty = BindableProperty.Create(nameof(AutoStartPreview), typeof(bool), typeof(CameraView), false, propertyChanged: AutoStartPreviewChanged);
+ public static readonly BindableProperty AutoStartPreviewProperty = BindableProperty.Create(nameof(AutoStartPreview), typeof(bool), typeof(CameraView), false, propertyChanged: AutoStartPreviewChanged, defaultBindingMode: BindingMode.TwoWay);
public static readonly BindableProperty AutoRecordingFileProperty = BindableProperty.Create(nameof(AutoRecordingFile), typeof(string), typeof(CameraView), string.Empty);
- public static readonly BindableProperty AutoStartRecordingProperty = BindableProperty.Create(nameof(AutoStartRecording), typeof(bool), typeof(CameraView), false, propertyChanged: AutoStartRecordingChanged);
+ public static readonly BindableProperty AutoStartRecordingProperty = BindableProperty.Create(nameof(AutoStartRecording), typeof(bool), typeof(CameraView), false, propertyChanged: AutoStartRecordingChanged, defaultBindingMode: BindingMode.TwoWay);
+ public static readonly BindableProperty PluginDecoderProperty = BindableProperty.Create(nameof(PluginDecoder), typeof(IPluginDecoder), typeof(CameraView), null, propertyChanged: PluginDecoderChanged);
+ public static readonly BindableProperty PluginDecodersProperty = BindableProperty.Create(nameof(PluginDecoders), typeof(PluginDecoderCollection), typeof(CameraView), null, propertyChanged: PluginDecodersChanged);
///
/// Binding property for use this control in MVVM.
@@ -51,6 +38,7 @@ public CameraView Self
get { return (CameraView)GetValue(SelfProperty); }
set { SetValue(SelfProperty, value); }
}
+
///
/// Flash mode for take a photo. This is a bindable property.
///
@@ -59,6 +47,7 @@ public FlashMode FlashMode
get { return (FlashMode)GetValue(FlashModeProperty); }
set { SetValue(FlashModeProperty, value); }
}
+
///
/// Turns the camera torch on and off if available. This is a bindable property.
///
@@ -67,6 +56,7 @@ public bool TorchEnabled
get { return (bool)GetValue(TorchEnabledProperty); }
set { SetValue(TorchEnabledProperty, value); }
}
+
///
/// List of available cameras in the device. This is a bindable property.
///
@@ -75,6 +65,7 @@ public ObservableCollection Cameras
get { return (ObservableCollection)GetValue(CamerasProperty); }
set { SetValue(CamerasProperty, value); }
}
+
///
/// Indicates the number of available cameras in the device.
///
@@ -83,6 +74,7 @@ public int NumCamerasDetected
get { return (int)GetValue(NumCamerasDetectedProperty); }
set { SetValue(NumCamerasDetectedProperty, value); }
}
+
///
/// Set the camera to use by the controler. This is a bindable property.
///
@@ -91,6 +83,7 @@ public CameraInfo Camera
get { return (CameraInfo)GetValue(CameraProperty); }
set { SetValue(CameraProperty, value); }
}
+
///
/// List of available microphones in the device. This is a bindable property.
///
@@ -99,6 +92,7 @@ public ObservableCollection Microphones
get { return (ObservableCollection)GetValue(MicrophonesProperty); }
set { SetValue(MicrophonesProperty, value); }
}
+
///
/// Indicates the number of available microphones in the device.
///
@@ -107,6 +101,7 @@ public int NumMicrophonesDetected
get { return (int)GetValue(NumMicrophonesDetectedProperty); }
set { SetValue(NumMicrophonesDetectedProperty, value); }
}
+
///
/// Set the microphone to use by the controler. This is a bindable property.
///
@@ -115,6 +110,7 @@ public MicrophoneInfo Microphone
get { return (MicrophoneInfo)GetValue(MicrophoneProperty); }
set { SetValue(MicrophoneProperty, value); }
}
+
///
/// Turns a mirror image of the camera on and off. This is a bindable property.
///
@@ -123,44 +119,33 @@ public bool MirroredImage
get { return (bool)GetValue(MirroredImageProperty); }
set { SetValue(MirroredImageProperty, value); }
}
+
///
- /// Turns on and off the barcode detection. This is a bindable property.
+ /// Turns on and off the plugin(s) processing. This is a bindable property.
///
- public bool BarCodeDetectionEnabled
+ public bool PluginProcessingEnabled
{
- get { return (bool)GetValue(BarCodeDetectionEnabledProperty); }
- set { SetValue(BarCodeDetectionEnabledProperty, value); }
+ get { return (bool)GetValue(PluginProcessingEnabledProperty); }
+ set { SetValue(PluginProcessingEnabledProperty, value); }
}
+
///
- /// Indicates every how many frames the control tries to detect a barcode in the image. This is a bindable property.
+ /// Indicates every how many frames to skip before delivering it to the plugin(s). This is a bindable property.
///
- public int BarCodeDetectionFrameRate
+ public int PluginProcessingSkipFrames
{
- get { return (int)GetValue(BarCodeDetectionFrameRateProperty); }
- set { SetValue(BarCodeDetectionFrameRateProperty, value); }
+ get { return (int)GetValue(PluginProcessingSkipFramesProperty); }
+ set { SetValue(PluginProcessingSkipFramesProperty, value); }
}
+
///
- /// Indicates the maximun number of simultaneous running threads for barcode detection
+ /// Indicates the maximun number of simultaneous running threads for plugin(s) execution.
///
- public int BarCodeDetectionMaxThreads { get; set; } = 3;
+ public int PluginProcessingMaxThreads { get; set; } = 3;
+
internal int currentThreads = 0;
internal object currentThreadsLocker = new();
- ///
- /// Options for the barcode detection. This is a bindable property.
- ///
- public BarcodeDecodeOptions BarCodeOptions
- {
- get { return (BarcodeDecodeOptions)GetValue(BarCodeOptionsProperty); }
- set { SetValue(BarCodeOptionsProperty, value); }
- }
- ///
- /// It refresh each time a barcode is detected if BarCodeDetectionEnabled porperty is true
- ///
- public Result[] BarCodeResults
- {
- get { return (Result[])GetValue(BarCodeResultsProperty); }
- set { SetValue(BarCodeResultsProperty, value); }
- }
+
///
/// The zoom factor for the current camera in use. This is a bindable property.
///
@@ -169,6 +154,7 @@ public float ZoomFactor
get { return (float)GetValue(ZoomFactorProperty); }
set { SetValue(ZoomFactorProperty, value); }
}
+
///
/// Indicates the minimum zoom factor for the camera in use. This property is refreshed when the "Camera" property change.
///
@@ -182,6 +168,7 @@ public float MinZoomFactor
return 1f;
}
}
+
///
/// Indicates the maximum zoom factor for the camera in use. This property is refreshed when the "Camera" property change.
///
@@ -195,6 +182,7 @@ public float MaxZoomFactor
return 1f;
}
}
+
///
/// Sets how often the SnapShot property is updated in seconds.
/// Default 0: no snapshots are taken
@@ -205,6 +193,7 @@ public float AutoSnapShotSeconds
get { return (float)GetValue(AutoSnapShotSecondsProperty); }
set { SetValue(AutoSnapShotSecondsProperty, value); }
}
+
///
/// Sets the snaphost image format
///
@@ -213,6 +202,7 @@ public ImageFormat AutoSnapShotFormat
get { return (ImageFormat)GetValue(AutoSnapShotFormatProperty); }
set { SetValue(AutoSnapShotFormatProperty, value); }
}
+
///
/// Refreshes according to the frequency set in the AutoSnapShotSeconds property (if AutoSnapShotAsImageSource is set to true)
/// or when GetSnapShot is called or TakeAutoSnapShot is set to true
@@ -222,6 +212,7 @@ public ImageSource SnapShot
get { return (ImageSource)GetValue(SnapShotProperty); }
private set { SetValue(SnapShotProperty, value); }
}
+
///
/// Refreshes according to the frequency set in the AutoSnapShotSeconds property or when GetSnapShot is called.
/// WARNING. Each time a snapshot is made, the previous stream is disposed.
@@ -231,6 +222,7 @@ public Stream SnapShotStream
get { return (Stream)GetValue(SnapShotStreamProperty); }
internal set { SetValue(SnapShotStreamProperty, value); }
}
+
///
/// Change from false to true refresh SnapShot property
///
@@ -239,6 +231,7 @@ public bool TakeAutoSnapShot
get { return (bool)GetValue(TakeAutoSnapShotProperty); }
set { SetValue(TakeAutoSnapShotProperty, value); }
}
+
///
/// If true SnapShot property is refreshed according to the frequency set in the AutoSnapShotSeconds property
///
@@ -247,6 +240,7 @@ public bool AutoSnapShotAsImageSource
get { return (bool)GetValue(AutoSnapShotAsImageSourceProperty); }
set { SetValue(AutoSnapShotAsImageSourceProperty, value); }
}
+
///
/// Starts/Stops the Preview if camera property has been set
///
@@ -255,6 +249,7 @@ public bool AutoStartPreview
get { return (bool)GetValue(AutoStartPreviewProperty); }
set { SetValue(AutoStartPreviewProperty, value); }
}
+
///
/// Full path to file where record video will be recorded.
///
@@ -263,6 +258,7 @@ public string AutoRecordingFile
get { return (string)GetValue(AutoRecordingFileProperty); }
set { SetValue(AutoRecordingFileProperty, value); }
}
+
///
/// Starts/Stops record video to AutoRecordingFile if camera and microphone properties have been set
///
@@ -271,38 +267,49 @@ public bool AutoStartRecording
get { return (bool)GetValue(AutoStartRecordingProperty); }
set { SetValue(AutoStartRecordingProperty, value); }
}
+
///
- /// If true BarcodeDetected event will invoke only if a Results is diferent from preview Results
+ /// Plugin to use
///
- public bool ControlBarcodeResultDuplicate { get; set; } = false;
- public delegate void BarcodeResultHandler(object sender, BarcodeEventArgs args);
+ public IPluginDecoder PluginDecoder
+ {
+ get { return (IPluginDecoder)GetValue(PluginDecoderProperty); }
+ set { SetValue(PluginDecoderProperty, value); }
+ }
+
///
- /// Event launched every time a code is detected in the image if "BarCodeDetectionEnabled" is set to true.
+ /// Plugins to use
///
- public event BarcodeResultHandler BarcodeDetected;
+ public PluginDecoderCollection PluginDecoders
+ {
+ get { return (PluginDecoderCollection)GetValue(PluginDecodersProperty); }
+ set { SetValue(PluginDecodersProperty, value); }
+ }
+
///
/// Event launched when "Cameras" property has been loaded.
///
public event EventHandler CamerasLoaded;
+
///
/// Event launched when "Microphones" property has been loaded.
///
public event EventHandler MicrophonesLoaded;
+
///
/// A static reference to the last CameraView created.
///
public static CameraView Current { get; set; }
- private readonly BarcodeReaderGeneric BarcodeReader;
internal DateTime lastSnapshot = DateTime.Now;
internal Size PhotosResolution = new(0, 0);
public CameraView()
{
- BarcodeReader = new BarcodeReaderGeneric();
HandlerChanged += CameraView_HandlerChanged;
Current = this;
}
+
private void CameraView_HandlerChanged(object sender, EventArgs e)
{
if (Handler != null)
@@ -312,6 +319,7 @@ private void CameraView_HandlerChanged(object sender, EventArgs e)
Self = this;
}
}
+
internal void RefreshSnapshot(ImageSource img)
{
if (AutoSnapShotAsImageSource)
@@ -323,62 +331,24 @@ internal void RefreshSnapshot(ImageSource img)
lastSnapshot = DateTime.Now;
}
- internal void DecodeBarcode(DecodeDataType data)
- {
- System.Diagnostics.Debug.WriteLine("Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
-
- LuminanceSource lumSource = default;
-#if ANDROID
- lumSource = new Camera.MAUI.Platforms.Android.BitmapLuminanceSource(data);
-#elif IOS || MACCATALYST
- lumSource = new Camera.MAUI.ZXingHelper.RGBLuminanceSource(data);
-#elif WINDOWS
- lumSource = new Camera.MAUI.Platforms.Windows.SoftwareBitmapLuminanceSource(data);
-#endif
- System.Diagnostics.Debug.WriteLine("End Calculate Luminance " + DateTime.Now.ToString("mm:ss:fff"));
-
- try
- {
- Result[] results = null;
- if (BarCodeOptions.ReadMultipleCodes)
- results = BarcodeReader.DecodeMultiple(lumSource);
- else
- {
- var result = BarcodeReader.Decode(lumSource);
- if (result != null) results = new Result[] { result };
- }
- if (results?.Length > 0)
- {
- bool refresh = true;
- if (ControlBarcodeResultDuplicate)
- {
- if (BarCodeResults != null)
- {
- foreach (var result in results)
- {
- refresh = BarCodeResults.FirstOrDefault(b => b.Text == result.Text && b.BarcodeFormat == result.BarcodeFormat) == null;
- if (refresh) break;
- }
- }
- }
- if (refresh)
- {
- BarCodeResults = results;
- OnPropertyChanged(nameof(BarCodeResults));
- BarcodeDetected?.Invoke(this, new BarcodeEventArgs { Result = results });
- }
- }
- }
- catch {}
- }
private static void CameraChanged(BindableObject bindable, object oldValue, object newValue)
{
if (newValue != null && oldValue != newValue && bindable is CameraView cameraView && newValue is CameraInfo)
{
cameraView.OnPropertyChanged(nameof(MinZoomFactor));
cameraView.OnPropertyChanged(nameof(MaxZoomFactor));
+ if (cameraView.AutoStartPreview)
+ {
+ cameraView.StartCameraAsync().Wait();
+ }
+ else if (cameraView.AutoStartRecording)
+ {
+ if (!string.IsNullOrEmpty(cameraView.AutoRecordingFile))
+ cameraView.StartRecordingAsync(cameraView.AutoRecordingFile).Wait();
+ }
}
}
+
private static void TakeAutoSnapShotChanged(BindableObject bindable, object oldValue, object newValue)
{
if ((bool)newValue && !(bool)oldValue && bindable is CameraView cameraView)
@@ -386,6 +356,7 @@ private static void TakeAutoSnapShotChanged(BindableObject bindable, object oldV
cameraView.RefreshSnapshot(cameraView.GetSnapShot(cameraView.AutoSnapShotFormat));
}
}
+
private static async void AutoStartPreviewChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue != newValue && bindable is CameraView control)
@@ -398,9 +369,9 @@ private static async void AutoStartPreviewChanged(BindableObject bindable, objec
await control.StopCameraAsync();
}
catch { }
-
}
}
+
private static async void AutoStartRecordingChanged(BindableObject bindable, object oldValue, object newValue)
{
if (oldValue != newValue && bindable is CameraView control)
@@ -416,21 +387,87 @@ private static async void AutoStartRecordingChanged(BindableObject bindable, obj
await control.StopRecordingAsync();
}
catch { }
+ }
+ }
+
+ private static void PluginDecoderChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != newValue && bindable is CameraView control)
+ {
+ try
+ {
+ if (newValue is BindableObject bo)
+ {
+ SetBindingContext(control, bo);
+ }
+ }
+ catch { }
+ }
+ }
+
+ private static void PluginDecodersChanged(BindableObject bindable, object oldValue, object newValue)
+ {
+ if (oldValue != newValue && bindable is CameraView control)
+ {
+ try
+ {
+ if (oldValue is PluginDecoderCollection oldCollection)
+ {
+ oldCollection.CollectionChanged -= PluginDecoders_CollectionChanged;
+ foreach (var item in oldCollection)
+ {
+ if (item is BindableObject boi)
+ {
+ RemoveBindingContext(boi);
+ }
+ }
+ }
+ if (newValue is PluginDecoderCollection newCollection)
+ {
+ newCollection.CollectionChanged += PluginDecoders_CollectionChanged;
+ foreach (var item in newCollection)
+ {
+ if (item is BindableObject boi)
+ {
+ SetBindingContext(control, boi);
+ }
+ }
+ }
+ }
+ catch { }
+ }
+ }
+ private static void PluginDecoders_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
+ foreach (var item in e.NewItems)
+ SetBindingContext(sender as Element, item as BindableObject);
+ break;
+
+ case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
+ foreach (var item in e.NewItems)
+ RemoveBindingContext(item as BindableObject);
+ break;
}
}
- private static void BarCodeOptionsChanged(BindableObject bindable, object oldValue, object newValue)
+
+ private static void SetBindingContext(Element element, BindableObject bindable)
{
- if (newValue != null && oldValue != newValue && bindable is CameraView cameraView && newValue is BarcodeDecodeOptions options)
+ if (element != null && bindable != null)
{
- cameraView.BarcodeReader.AutoRotate = options.AutoRotate;
- if (options.CharacterSet != string.Empty) cameraView.BarcodeReader.Options.CharacterSet = options.CharacterSet;
- cameraView.BarcodeReader.Options.PossibleFormats = options.PossibleFormats;
- cameraView.BarcodeReader.Options.TryHarder = options.TryHarder;
- cameraView.BarcodeReader.Options.TryInverted = options.TryInverted;
- cameraView.BarcodeReader.Options.PureBarcode = options.PureBarcode;
+ var b = new Binding(nameof(element.BindingContext), source: element);
+ bindable.SetBinding(BindableObject.BindingContextProperty, b);
}
}
+
+ private static void RemoveBindingContext(BindableObject bindable)
+ {
+ bindable?.RemoveBinding(BindableObject.BindingContextProperty);
+ }
+
///
/// Start playback of the selected camera async. "Camera" property must not be null.
/// Indicates the resolution for the preview and photos taken with TakePhotoAsync (must be in Camera.AvailableResolutions). If width or height is 0, max resolution will be taken.
@@ -451,7 +488,8 @@ public async Task StartCameraAsync(Size Resolution = default)
result = await handler.StartCameraAsync(Resolution);
if (result == CameraResult.Success)
{
- BarCodeResults = null;
+ PluginDecoder?.ClearResults();
+ PluginDecoders?.ToList().ForEach(x => x.ClearResults());
OnPropertyChanged(nameof(MinZoomFactor));
OnPropertyChanged(nameof(MaxZoomFactor));
}
@@ -462,6 +500,7 @@ public async Task StartCameraAsync(Size Resolution = default)
return result;
}
+
///
/// Start recording a video async. "Camera" property must not be null.
/// Full path to file where video will be stored.
@@ -482,7 +521,8 @@ public async Task StartRecordingAsync(string file, Size Resolution
result = await handler.StartRecordingAsync(file, Resolution);
if (result == CameraResult.Success)
{
- BarCodeResults = null;
+ PluginDecoder?.ClearResults();
+ PluginDecoders?.ToList().ForEach(x => x.ClearResults());
OnPropertyChanged(nameof(MinZoomFactor));
OnPropertyChanged(nameof(MaxZoomFactor));
}
@@ -493,6 +533,7 @@ public async Task StartRecordingAsync(string file, Size Resolution
return result;
}
+
///
/// Stop playback of the selected camera async.
///
@@ -505,6 +546,7 @@ public async Task StopCameraAsync()
}
return result;
}
+
///
/// Stop recording a video async.
///
@@ -517,6 +559,7 @@ public async Task StopRecordingAsync()
}
return result;
}
+
///
/// Takes a photo from the camera selected.
///
@@ -530,6 +573,7 @@ public async Task TakePhotoAsync(ImageFormat imageFormat = ImageFormat.J
}
return null;
}
+
///
/// Takes a capture form the active camera playback.
///
@@ -542,6 +586,7 @@ public ImageSource GetSnapShot(ImageFormat imageFormat = ImageFormat.PNG)
}
return SnapShot;
}
+
///
/// Saves a capture form the active camera playback in a file
///
@@ -556,6 +601,7 @@ public async Task SaveSnapShot(ImageFormat imageFormat, string SnapFilePat
}
return result;
}
+
///
/// Force execute the camera autofocus trigger.
///
@@ -566,6 +612,7 @@ public void ForceAutoFocus()
handler.ForceAutoFocus();
}
}
+
///
/// Forces the device specific control dispose
///
@@ -576,15 +623,18 @@ public void ForceDisposeHandler()
handler.ForceDispose();
}
}
+
internal void RefreshDevices()
{
- Task.Run(() => {
- OnPropertyChanged(nameof(Cameras));
+ Task.Run(() =>
+ {
+ OnPropertyChanged(nameof(Cameras));
NumCamerasDetected = Cameras.Count;
OnPropertyChanged(nameof(Microphones));
NumMicrophonesDetected = Microphones.Count;
});
}
+
public static async Task RequestPermissions(bool withMic = false, bool withStorageWrite = false)
{
var status = await Permissions.CheckStatusAsync();
diff --git a/Camera.MAUI/CameraViewHandler.cs b/Camera.MAUI/CameraViewHandler.cs
index 4a9036c..dfde7f1 100644
--- a/Camera.MAUI/CameraViewHandler.cs
+++ b/Camera.MAUI/CameraViewHandler.cs
@@ -1,37 +1,45 @@
using Microsoft.Maui.Handlers;
-#if IOS || MACCATALYST
-using PlatformView = Camera.MAUI.Platforms.Apple.MauiCameraView;
-#elif ANDROID
-using PlatformView = Camera.MAUI.Platforms.Android.MauiCameraView;
-#elif WINDOWS
-using PlatformView = Camera.MAUI.Platforms.Windows.MauiCameraView;
-#elif (NETSTANDARD || !PLATFORM) || (NET6_0_OR_GREATER && !IOS && !ANDROID)
+
+#if IOS || MACCATALYST || ANDROID || WINDOWS
+using PlatformView = Camera.MAUI.MauiCameraView;
+#else
+
using PlatformView = System.Object;
+
#endif
namespace Camera.MAUI;
internal partial class CameraViewHandler : ViewHandler
{
+ internal static CameraViewHandler Current = null;
+ internal static Size CurrentResolution = default;
+
public static IPropertyMapper PropertyMapper = new PropertyMapper(ViewMapper)
{
[nameof(CameraView.TorchEnabled)] = MapTorch,
[nameof(CameraView.MirroredImage)] = MapMirroredImage,
[nameof(CameraView.ZoomFactor)] = MapZoomFactor,
};
+
public static CommandMapper CommandMapper = new(ViewCommandMapper)
{
};
+
public CameraViewHandler() : base(PropertyMapper, CommandMapper)
{
}
+
+ protected override PlatformView CreatePlatformView() =>
#if ANDROID
- protected override PlatformView CreatePlatformView() => new(Context, VirtualView);
+ new(Context, VirtualView);
#elif IOS || MACCATALYST || WINDOWS
- protected override PlatformView CreatePlatformView() => new(VirtualView);
+ new(VirtualView);
#else
- protected override PlatformView CreatePlatformView() => new();
+ new();
+
#endif
+
protected override void ConnectHandler(PlatformView platformView)
{
base.ConnectHandler(platformView);
@@ -46,18 +54,21 @@ protected override void DisconnectHandler(PlatformView platformView)
#endif
base.DisconnectHandler(platformView);
}
+
public static void MapTorch(CameraViewHandler handler, CameraView cameraView)
{
#if WINDOWS || ANDROID || IOS || MACCATALYST
handler.PlatformView?.UpdateTorch();
#endif
}
+
public static void MapMirroredImage(CameraViewHandler handler, CameraView cameraView)
{
#if WINDOWS || ANDROID || IOS || MACCATALYST
handler.PlatformView?.UpdateMirroredImage();
#endif
}
+
public static void MapZoomFactor(CameraViewHandler handler, CameraView cameraView)
{
#if WINDOWS || ANDROID || IOS || MACCATALYST
@@ -70,11 +81,14 @@ public Task StartCameraAsync(Size PhotosResolution)
if (PlatformView != null)
{
#if WINDOWS || ANDROID || IOS || MACCATALYST
+ CurrentResolution = PhotosResolution;
+ Current = this;
return PlatformView.StartCameraAsync(PhotosResolution);
#endif
}
return Task.Run(() => { return CameraResult.AccessError; });
}
+
public Task StartRecordingAsync(string file, Size Resolution)
{
if (PlatformView != null)
@@ -85,10 +99,16 @@ public Task StartRecordingAsync(string file, Size Resolution)
}
return Task.Run(() => { return CameraResult.AccessError; });
}
- public Task StopCameraAsync()
+
+ public Task StopCameraAsync(bool isResetCurrent = true)
{
if (PlatformView != null)
{
+ if (isResetCurrent)
+ {
+ Current = null;
+ CurrentResolution = default;
+ }
#if WINDOWS
return PlatformView.StopCameraAsync();
#elif ANDROID || IOS || MACCATALYST
@@ -99,6 +119,7 @@ public Task StopCameraAsync()
}
return Task.Run(() => { return CameraResult.AccessError; });
}
+
public Task StopRecordingAsync()
{
if (PlatformView != null)
@@ -109,6 +130,7 @@ public Task StopRecordingAsync()
}
return Task.Run(() => { return CameraResult.AccessError; });
}
+
public ImageSource GetSnapShot(ImageFormat imageFormat)
{
if (PlatformView != null)
@@ -119,6 +141,7 @@ public ImageSource GetSnapShot(ImageFormat imageFormat)
}
return null;
}
+
public Task TakePhotoAsync(ImageFormat imageFormat)
{
if (PlatformView != null)
@@ -131,6 +154,7 @@ public Task TakePhotoAsync(ImageFormat imageFormat)
}
return Task.Run(() => { Stream result = null; return result; });
}
+
public Task SaveSnapShot(ImageFormat imageFormat, string SnapFilePath)
{
if (PlatformView != null)
@@ -145,16 +169,22 @@ public Task SaveSnapShot(ImageFormat imageFormat, string SnapFilePath)
}
return Task.Run(() => { return false; });
}
+
public void ForceAutoFocus()
{
#if ANDROID || WINDOWS || IOS || MACCATALYST
PlatformView?.ForceAutoFocus();
+#else
+ throw new NotImplementedException();
#endif
}
+
public void ForceDispose()
{
#if ANDROID || WINDOWS || IOS || MACCATALYST
PlatformView?.DisposeControl();
+#else
+ throw new NotImplementedException();
#endif
}
-}
+}
\ No newline at end of file
diff --git a/Camera.MAUI/FlashMode.cs b/Camera.MAUI/FlashMode.cs
index 384c761..282de3f 100644
--- a/Camera.MAUI/FlashMode.cs
+++ b/Camera.MAUI/FlashMode.cs
@@ -1,5 +1,5 @@
-namespace Camera.MAUI;
-public enum FlashMode
+namespace Camera.MAUI;
+public enum FlashMode
{
Auto,
Enabled,
diff --git a/Camera.MAUI/ICameraView.cs b/Camera.MAUI/ICameraView.cs
index 814785d..8b620f3 100644
--- a/Camera.MAUI/ICameraView.cs
+++ b/Camera.MAUI/ICameraView.cs
@@ -1,7 +1,5 @@
-using Microsoft.Maui.Controls;
-
-namespace Camera.MAUI;
-public interface ICameraView
+namespace Camera.MAUI;
+public interface ICameraView
{
public FlashMode FlashMode { get; set; }
}
diff --git a/Camera.MAUI/Apple/MauiCameraView.cs b/Camera.MAUI/MaciOS/MauiCameraView.cs
similarity index 84%
rename from Camera.MAUI/Apple/MauiCameraView.cs
rename to Camera.MAUI/MaciOS/MauiCameraView.cs
index f8e5c66..9e99931 100644
--- a/Camera.MAUI/Apple/MauiCameraView.cs
+++ b/Camera.MAUI/MaciOS/MauiCameraView.cs
@@ -1,5 +1,4 @@
-#if IOS || MACCATALYST
-using AVFoundation;
+using AVFoundation;
using CoreAnimation;
using CoreFoundation;
using CoreGraphics;
@@ -7,11 +6,9 @@
using CoreMedia;
using CoreVideo;
using Foundation;
-using MediaPlayer;
-using System.IO;
using UIKit;
-namespace Camera.MAUI.Platforms.Apple;
+namespace Camera.MAUI;
internal class MauiCameraView : UIView, IAVCaptureVideoDataOutputSampleBufferDelegate, IAVCaptureFileOutputRecordingDelegate, IAVCapturePhotoCaptureDelegate
{
@@ -43,7 +40,7 @@ public MauiCameraView(CameraView cameraView)
this.cameraView = cameraView;
captureSession = new AVCaptureSession
- {
+ {
SessionPreset = AVCaptureSession.PresetPhoto
};
PreviewLayer = new(captureSession)
@@ -64,10 +61,12 @@ public MauiCameraView(CameraView cameraView)
NSNotificationCenter.DefaultCenter.AddObserver(UIDevice.OrientationDidChangeNotification, OrientationChanged);
InitDevices();
}
+
private void OrientationChanged(NSNotification notification)
{
LayoutSubviews();
}
+
private void InitDevices()
{
if (!initiated)
@@ -84,7 +83,7 @@ private void InitDevices()
AVCaptureDevicePosition.Back => CameraPosition.Back,
AVCaptureDevicePosition.Front => CameraPosition.Front,
_ => CameraPosition.Unknow
- };
+ };
cameraView.Cameras.Add(new CameraInfo
{
Name = device.LocalizedName,
@@ -111,6 +110,13 @@ private void InitDevices()
}
}
}
+
+ private bool IsFlipOrientation()
+ {
+ return (cameraView.Camera?.Position == CameraPosition.Back && !cameraView.MirroredImage)
+ || (cameraView.Camera?.Position == CameraPosition.Front && cameraView.MirroredImage);
+ }
+
public async Task StartRecordingAsync(string file, Size Resolution)
{
CameraResult result = CameraResult.Success;
@@ -148,7 +154,7 @@ public async Task StartRecordingAsync(string file, Size Resolution
movieFileOutputConnection.VideoOrientation = (AVCaptureVideoOrientation)UIDevice.CurrentDevice.Orientation;
captureSession.StartRunning();
if (!File.Exists(file)) File.Create(file).Close();
-
+
recordOutput.StartRecordingToOutputFile(NSUrl.FromFilename(file), this);
UpdateMirroredImage();
SetZoomFactor(cameraView.ZoomFactor);
@@ -169,6 +175,7 @@ public async Task StartRecordingAsync(string file, Size Resolution
result = CameraResult.NotInitiated;
return result;
}
+
public Task StopRecordingAsync()
{
return StartCameraAsync(cameraView.PhotosResolution);
@@ -216,10 +223,12 @@ public async Task StartCameraAsync(Size PhotosResolution)
}
else
result = CameraResult.AccessDenied;
- }else
+ }
+ else
result = CameraResult.NotInitiated;
return result;
}
+
public CameraResult StopCamera()
{
CameraResult result = CameraResult.Success;
@@ -252,11 +261,13 @@ public CameraResult StopCamera()
{
result = CameraResult.AccessError;
}
- }else
+ }
+ else
result = CameraResult.NotInitiated;
return result;
}
+
public void DisposeControl()
{
if (started) StopCamera();
@@ -265,6 +276,7 @@ public void DisposeControl()
captureSession?.Dispose();
Dispose();
}
+
public void UpdateMirroredImage()
{
if (cameraView != null && PreviewLayer.Connection != null)
@@ -279,6 +291,7 @@ public void UpdateMirroredImage()
UpdateTorch();
}
}
+
internal void SetZoomFactor(float zoom)
{
if (cameraView.Camera != null && captureDevice != null)
@@ -291,6 +304,7 @@ internal void SetZoomFactor(float zoom)
}
}
}
+
internal void ForceAutoFocus()
{
if (cameraView.Camera != null && captureDevice != null && captureDevice.IsFocusModeSupported(AVCaptureFocusMode.AutoFocus))
@@ -306,6 +320,7 @@ internal void ForceAutoFocus()
}
}
}
+
public void UpdateTorch()
{
if (captureDevice != null && cameraView != null)
@@ -331,15 +346,15 @@ internal async Task TakePhotoAsync(ImageFormat imageFormat)
_ => AVCaptureFlashMode.Off
};
photoOutput.CapturePhoto(photoSettings, this);
- while(!photoTaken && !photoError) await Task.Delay(50);
+ while (!photoTaken && !photoError) await Task.Delay(50);
if (photoError || photo == null)
return null;
else
{
UIImageOrientation orientation = UIDevice.CurrentDevice.Orientation switch
{
- UIDeviceOrientation.LandscapeRight => UIImageOrientation.Down,
- UIDeviceOrientation.LandscapeLeft => UIImageOrientation.Up,
+ UIDeviceOrientation.LandscapeRight => IsFlipOrientation() ? UIImageOrientation.Down : UIImageOrientation.Up,
+ UIDeviceOrientation.LandscapeLeft => IsFlipOrientation() ? UIImageOrientation.Up : UIImageOrientation.Down,
UIDeviceOrientation.PortraitUpsideDown => UIImageOrientation.Left,
_ => UIImageOrientation.Right
};
@@ -351,6 +366,7 @@ internal async Task TakePhotoAsync(ImageFormat imageFormat)
case ImageFormat.JPEG:
photo.AsJPEG().AsStream().CopyTo(stream);
break;
+
default:
photo.AsPNG().AsStream().CopyTo(stream);
break;
@@ -359,6 +375,7 @@ internal async Task TakePhotoAsync(ImageFormat imageFormat)
return stream;
}
}
+
public ImageSource GetSnapShot(ImageFormat imageFormat, bool auto = false)
{
ImageSource result = null;
@@ -389,6 +406,7 @@ public ImageSource GetSnapShot(ImageFormat imageFormat, bool auto = false)
case ImageFormat.JPEG:
image2.AsJPEG().AsStream().CopyTo(stream);
break;
+
default:
image2.AsPNG().AsStream().CopyTo(stream);
break;
@@ -414,13 +432,14 @@ public ImageSource GetSnapShot(ImageFormat imageFormat, bool auto = false)
return result;
}
+
public bool SaveSnapShot(ImageFormat imageFormat, string SnapFilePath)
{
bool result = true;
if (started && lastCapture != null)
{
- if (File.Exists(SnapFilePath)) File.Delete(SnapFilePath);
+ if (File.Exists(SnapFilePath)) File.Delete(SnapFilePath);
MainThread.InvokeOnMainThreadAsync(() =>
{
try
@@ -443,6 +462,7 @@ public bool SaveSnapShot(ImageFormat imageFormat, string SnapFilePath)
case ImageFormat.PNG:
image2.AsPNG().Save(NSUrl.FromFilename(SnapFilePath), true);
break;
+
case ImageFormat.JPEG:
image2.AsJPEG().Save(NSUrl.FromFilename(SnapFilePath), true);
break;
@@ -459,6 +479,7 @@ public bool SaveSnapShot(ImageFormat imageFormat, string SnapFilePath)
result = false;
return result;
}
+
public UIImage CropImage(UIImage originalImage)
{
nfloat x, y, width, height;
@@ -466,17 +487,29 @@ public UIImage CropImage(UIImage originalImage)
if (originalImage.Size.Width <= originalImage.Size.Height)
{
width = originalImage.Size.Width;
- height = (Frame.Size.Height * originalImage.Size.Width) / Frame.Size.Width;
+ height = Frame.Size.Height * originalImage.Size.Width / Frame.Size.Width;
}
else
{
height = originalImage.Size.Height;
- width = (Frame.Size.Width * originalImage.Size.Height) / Frame.Size.Height;
+ width = Frame.Size.Width * originalImage.Size.Height / Frame.Size.Height;
}
x = (nfloat)((originalImage.Size.Width - width) / 2.0);
y = (nfloat)((originalImage.Size.Height - height) / 2.0);
+#if (IOS17_0_OR_GREATER || MACCATALYST17_0_OR_GREATER || TRUE)
+ var renderer = new UIGraphicsImageRenderer(new CGSize(width, height));
+ var croppedImage = renderer.CreateImage((UIGraphicsImageRendererContext context) =>
+ {
+ if (cameraView.MirroredImage)
+ {
+ context.CGContext.ScaleCTM(-1, 1);
+ context.CGContext.TranslateCTM(-width, 0);
+ }
+ originalImage.Draw(new CGPoint(-x, -y));
+ });
+#else
UIGraphics.BeginImageContextWithOptions(originalImage.Size, false, 1);
if (cameraView.MirroredImage)
{
@@ -487,56 +520,73 @@ public UIImage CropImage(UIImage originalImage)
originalImage.Draw(new CGPoint(0, 0));
UIImage croppedImage = UIImage.FromImage(UIGraphics.GetImageFromCurrentImageContext().CGImage.WithImageInRect(new CGRect(new CGPoint(x, y), new CGSize(width, height))));
UIGraphics.EndImageContext();
+#endif
return croppedImage;
}
- private void ProccessQR()
+
+ private async Task ProcessPluginAsync()
{
- MainThread.BeginInvokeOnMainThread(() =>
+ if (cameraView.PluginDecoder != null || cameraView.PluginDecoders?.Count > 0)
{
try
{
- UIImage image2;
- lock (lockCapture)
+ UIImage img = null;
+ await MainThread.InvokeOnMainThreadAsync(() =>
+ {
+ lock (lockCapture)
+ {
+ var ciContext = new CIContext();
+ CGImage cgImage = ciContext.CreateCGImage(lastCapture, lastCapture.Extent);
+ var image = UIImage.FromImage(cgImage, UIScreen.MainScreen.Scale, UIImageOrientation.Right);
+ img = CropImage(image);
+ }
+ });
+ if (img != null)
{
- var ciContext = new CIContext();
- CGImage cgImage = ciContext.CreateCGImage(lastCapture, lastCapture.Extent);
- var image = UIImage.FromImage(cgImage, UIScreen.MainScreen.Scale, UIImageOrientation.Right);
- image2 = CropImage(image);
+ cameraView.PluginDecoder?.Decode(img);
+ cameraView.PluginDecoders?
+ .Where(x => x != cameraView.PluginDecoder)
+ .ToList()
+ .ForEach(x => x.Decode(img));
}
- cameraView.DecodeBarcode(image2);
}
catch
{
}
- });
+ }
}
+
private void ProcessImage(CIImage capture)
- {
- new Task(() =>
+ {
+ new Task(async () =>
{
lock (lockCapture)
{
- lastCapture?.Dispose();
+ if (lastCapture != null)
+ {
+ lastCapture.Dispose();
+ GC.Collect();
+ }
lastCapture = capture;
}
if (!snapping && cameraView.AutoSnapShotSeconds > 0 && (DateTime.Now - cameraView.lastSnapshot).TotalSeconds >= cameraView.AutoSnapShotSeconds)
cameraView.RefreshSnapshot(GetSnapShot(cameraView.AutoSnapShotFormat, true));
- else if (cameraView.BarCodeDetectionEnabled && currentFrames >= cameraView.BarCodeDetectionFrameRate)
+ else if (cameraView.PluginProcessingEnabled && currentFrames >= cameraView.PluginProcessingSkipFrames)
{
- bool processQR = false;
+ bool processPlugin = false;
lock (cameraView.currentThreadsLocker)
{
- if (cameraView.currentThreads < cameraView.BarCodeDetectionMaxThreads)
+ if (cameraView.currentThreads < cameraView.PluginProcessingMaxThreads)
{
cameraView.currentThreads++;
- processQR = true;
+ processPlugin = true;
+ currentFrames = 0;
}
}
- if (processQR)
+ if (processPlugin)
{
- ProccessQR();
- currentFrames = 0;
+ await ProcessPluginAsync();
lock (cameraView.currentThreadsLocker) cameraView.currentThreads--;
}
}
@@ -544,11 +594,11 @@ private void ProcessImage(CIImage capture)
}
[Export("captureOutput:didOutputSampleBuffer:fromConnection:")]
- public void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
+ public virtual void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
{
frames++;
currentFrames++;
- if (frames >= 12 || (cameraView.BarCodeDetectionEnabled && currentFrames >= cameraView.BarCodeDetectionFrameRate))
+ if (frames >= 12 || cameraView.PluginProcessingEnabled && currentFrames >= cameraView.PluginProcessingSkipFrames)
{
var capture = CIImage.FromImageBuffer(sampleBuffer.GetImageBuffer());
ProcessImage(capture);
@@ -561,19 +611,20 @@ public void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer
sampleBuffer?.Dispose();
}
}
- [Export("captureOutput:didFinishProcessingPhotoSampleBuffer:previewPhotoSampleBuffer:resolvedSettings:bracketSettings:error:")]
- void DidFinishProcessingPhoto(AVCapturePhotoOutput captureOutput, CMSampleBuffer photoSampleBuffer, CMSampleBuffer previewPhotoSampleBuffer, AVCaptureResolvedPhotoSettings resolvedSettings, AVCaptureBracketedStillImageSettings bracketSettings, NSError error)
+
+ [Export("captureOutput:didFinishProcessingPhoto:error:")]
+ public virtual void DidFinishProcessingPhoto(AVCapturePhotoOutput captureOutput, AVCapturePhoto photo, NSError error)
{
- if (photoSampleBuffer == null)
+ if (error != null)
{
photoError = true;
- return;
}
-
- NSData imageData = AVCapturePhotoOutput.GetJpegPhotoDataRepresentation(photoSampleBuffer, previewPhotoSampleBuffer);
-
- photo = new UIImage(imageData);
- photoTaken = true;
+ else
+ {
+ var imageData = photo.FileDataRepresentation;
+ this.photo = new UIImage(imageData);
+ photoTaken = true;
+ }
}
public override void LayoutSubviews()
@@ -585,14 +636,19 @@ public override void LayoutSubviews()
case UIDeviceOrientation.Portrait:
transform = CATransform3D.MakeRotation(0, 0, 0, 1.0f);
break;
+
case UIDeviceOrientation.PortraitUpsideDown:
transform = CATransform3D.MakeRotation((nfloat)Math.PI, 0, 0, 1.0f);
break;
+
case UIDeviceOrientation.LandscapeLeft:
- transform = CATransform3D.MakeRotation((nfloat)(-Math.PI / 2), 0, 0, 1.0f);
+ var rotation = IsFlipOrientation() ? -Math.PI / 2 : Math.PI / 2;
+ transform = CATransform3D.MakeRotation((nfloat)rotation, 0, 0, 1.0f);
break;
+
case UIDeviceOrientation.LandscapeRight:
- transform = CATransform3D.MakeRotation((nfloat)Math.PI / 2, 0, 0, 1.0f);
+ var rotation2 = IsFlipOrientation() ? Math.PI / 2 : -Math.PI / 2;
+ transform = CATransform3D.MakeRotation((nfloat)rotation2, 0, 0, 1.0f);
break;
}
@@ -602,7 +658,5 @@ public override void LayoutSubviews()
public void FinishedRecording(AVCaptureFileOutput captureOutput, NSUrl outputFileUrl, NSObject[] connections, NSError error)
{
-
}
-}
-#endif
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Platforms/Android/MauiCameraView.cs b/Camera.MAUI/Platforms/Android/MauiCameraView.cs
index f8f2e8a..eae2477 100644
--- a/Camera.MAUI/Platforms/Android/MauiCameraView.cs
+++ b/Camera.MAUI/Platforms/Android/MauiCameraView.cs
@@ -1,26 +1,25 @@
using Android.Content;
-using Android.Widget;
-using Java.Util.Concurrent;
+using Android.Content.Res;
using Android.Graphics;
-using CameraCharacteristics = Android.Hardware.Camera2.CameraCharacteristics;
using Android.Hardware.Camera2;
+using Android.Hardware.Camera2.Params;
using Android.Media;
-using Android.Views;
+using Android.OS;
+using Android.Runtime;
using Android.Util;
-using Android.Hardware.Camera2.Params;
-using Size = Android.Util.Size;
+using Android.Views;
+using Android.Widget;
+using Java.Util.Concurrent;
+using CameraCharacteristics = Android.Hardware.Camera2.CameraCharacteristics;
using Class = Java.Lang.Class;
using Rect = Android.Graphics.Rect;
-using SizeF = Android.Util.SizeF;
-using Android.Runtime;
-using Android.OS;
-using Android.Renderscripts;
using RectF = Android.Graphics.RectF;
-using Android.Content.Res;
+using Size = Android.Util.Size;
+using SizeF = Android.Util.SizeF;
-namespace Camera.MAUI.Platforms.Android;
+namespace Camera.MAUI;
-internal class MauiCameraView: GridLayout
+internal class MauiCameraView : GridLayout
{
private readonly CameraView cameraView;
private IExecutorService executorService;
@@ -30,6 +29,7 @@ internal class MauiCameraView: GridLayout
private bool snapping = false;
private bool recording = false;
private readonly Context context;
+ private readonly object changeCameraStateLocker = new();
private readonly TextureView textureView;
public CameraCaptureSession previewSession;
@@ -51,7 +51,6 @@ internal class MauiCameraView: GridLayout
private Handler backgroundHandler;
private ImageReader imgReader;
-
public MauiCameraView(Context context, CameraView cameraView) : base(context)
{
this.context = context;
@@ -96,7 +95,26 @@ private void InitDevices()
cameraInfo.Name = "Camera " + id;
cameraInfo.Position = CameraPosition.Unknow;
}
- cameraInfo.MaxZoomFactor = (float)(chars.Get(CameraCharacteristics.ScalerAvailableMaxDigitalZoom) as Java.Lang.Number);
+#if ANDROID30_0_OR_GREATER
+ if (OperatingSystem.IsAndroidVersionAtLeast(30))
+ {
+ if (chars.Get(CameraCharacteristics.ControlZoomRatioRange) is Android.Util.Range zoomRatio)
+ {
+ cameraInfo.MinZoomFactor = (float)zoomRatio.Lower;
+ cameraInfo.MaxZoomFactor = (float)zoomRatio.Upper;
+ cameraInfo.UseZoomRatio = true;
+ }
+ else
+ {
+ // Fallback to pre API 30 method if hardware doesn't support.
+ cameraInfo.UseZoomRatio = false;
+ }
+ }
+ if (!cameraInfo.UseZoomRatio)
+#endif
+ {
+ cameraInfo.MaxZoomFactor = (float)(chars.Get(CameraCharacteristics.ScalerAvailableMaxDigitalZoom) as Java.Lang.Number);
+ }
cameraInfo.HasFlashUnit = (bool)(chars.Get(CameraCharacteristics.FlashInfoAvailable) as Java.Lang.Boolean);
cameraInfo.AvailableResolutions = new();
try
@@ -122,7 +140,7 @@ private void InitDevices()
cameraInfo.AvailableResolutions.Add(new(352, 288));
}
cameraView.Cameras.Add(cameraInfo);
- }
+ }
if (OperatingSystem.IsAndroidVersionAtLeast(30))
{
cameraView.Microphones.Clear();
@@ -211,58 +229,68 @@ private void StartPreview()
SurfaceTexture texture = textureView.SurfaceTexture;
texture.SetDefaultBufferSize(videoSize.Width, videoSize.Height);
- previewBuilder = cameraDevice.CreateCaptureRequest(recording ? CameraTemplate.Record : CameraTemplate.Preview);
- var surfaces = new List();
- var surfaces26 = new List();
- var previewSurface = new Surface(texture);
- surfaces.Add(new OutputConfiguration(previewSurface));
- surfaces26.Add(previewSurface);
- previewBuilder.AddTarget(previewSurface);
- if (imgReader != null)
- {
- surfaces.Add(new OutputConfiguration(imgReader.Surface));
- surfaces26.Add(imgReader.Surface);
- }
- if (mediaRecorder != null)
+ try
{
- surfaces.Add(new OutputConfiguration(mediaRecorder.Surface));
- surfaces26.Add(mediaRecorder.Surface);
- previewBuilder.AddTarget(mediaRecorder.Surface);
- }
+ previewBuilder = cameraDevice.CreateCaptureRequest(recording ? CameraTemplate.Record : CameraTemplate.Preview);
+ var surfaces = new List();
+ var surfaces26 = new List();
+ var previewSurface = new Surface(texture);
+ surfaces.Add(new OutputConfiguration(previewSurface));
+ surfaces26.Add(previewSurface);
+ previewBuilder.AddTarget(previewSurface);
+ if (imgReader != null)
+ {
+ surfaces.Add(new OutputConfiguration(imgReader.Surface));
+ surfaces26.Add(imgReader.Surface);
+ }
+ if (mediaRecorder != null)
+ {
+ surfaces.Add(new OutputConfiguration(mediaRecorder.Surface));
+ surfaces26.Add(mediaRecorder.Surface);
+ previewBuilder.AddTarget(mediaRecorder.Surface);
+ }
- sessionCallback = new PreviewCaptureStateCallback(this);
- if (OperatingSystem.IsAndroidVersionAtLeast(28))
- {
- SessionConfiguration config = new((int)SessionType.Regular, surfaces, executorService, sessionCallback);
- cameraDevice.CreateCaptureSession(config);
- }
- else
- {
+ sessionCallback = new PreviewCaptureStateCallback(this);
+ if (OperatingSystem.IsAndroidVersionAtLeast(28))
+ {
+ SessionConfiguration config = new((int)SessionType.Regular, surfaces, executorService, sessionCallback);
+ cameraDevice.CreateCaptureSession(config);
+ }
+ else
+ {
#pragma warning disable CS0618 // El tipo o el miembro están obsoletos
- cameraDevice.CreateCaptureSession(surfaces26, sessionCallback, null);
+ cameraDevice.CreateCaptureSession(surfaces26, sessionCallback, null);
#pragma warning restore CS0618 // El tipo o el miembro están obsoletos
+ }
+ }
+ catch (CameraAccessException)
+ {
}
}
+
private void UpdatePreview()
{
- if (null == cameraDevice)
- return;
-
- try
- {
- previewBuilder.Set(CaptureRequest.ControlMode, Java.Lang.Integer.ValueOf((int)ControlMode.Auto));
- //Rect m = (Rect)camChars.Get(CameraCharacteristics.SensorInfoActiveArraySize);
- //videoSize = new Size(m.Width(), m.Height());
- //AdjustAspectRatio(videoSize.Width, videoSize.Height);
- AdjustAspectRatio(videoSize.Width, videoSize.Height);
- SetZoomFactor(cameraView.ZoomFactor);
- //previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
- if (recording)
- mediaRecorder?.Start();
- }
- catch (CameraAccessException e)
+ lock (changeCameraStateLocker)
{
- e.PrintStackTrace();
+ if (null == cameraDevice || previewBuilder == null)
+ return;
+
+ try
+ {
+ previewBuilder.Set(CaptureRequest.ControlMode, Java.Lang.Integer.ValueOf((int)ControlMode.Auto));
+ //Rect m = (Rect)camChars.Get(CameraCharacteristics.SensorInfoActiveArraySize);
+ //videoSize = new Size(m.Width(), m.Height());
+ //AdjustAspectRatio(videoSize.Width, videoSize.Height);
+ AdjustAspectRatio(videoSize.Width, videoSize.Height);
+ SetZoomFactor(cameraView.ZoomFactor);
+ //previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ if (recording)
+ mediaRecorder?.Start();
+ }
+ catch (CameraAccessException e)
+ {
+ e.PrintStackTrace();
+ }
}
}
internal async Task StartCameraAsync(Microsoft.Maui.Graphics.Size PhotosResolution)
@@ -272,39 +300,42 @@ internal async Task StartCameraAsync(Microsoft.Maui.Graphics.Size
{
if (await CameraView.RequestPermissions())
{
- if (started) StopCamera();
- if (cameraView.Camera != null)
+ lock (changeCameraStateLocker)
{
- try
+ if (started) StopCamera();
+ if (cameraView.Camera != null)
{
- camChars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);
-
- StreamConfigurationMap map = (StreamConfigurationMap)camChars.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
- videoSize = ChooseVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
- var maxVideoSize = ChooseMaxVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
- if (PhotosResolution.Width != 0 && PhotosResolution.Height != 0)
- maxVideoSize = new((int)PhotosResolution.Width, (int)PhotosResolution.Height);
- imgReader = ImageReader.NewInstance(maxVideoSize.Width, maxVideoSize.Height, ImageFormatType.Jpeg, 1);
- backgroundThread = new HandlerThread("CameraBackground");
- backgroundThread.Start();
- backgroundHandler = new Handler(backgroundThread.Looper);
- imgReader.SetOnImageAvailableListener(photoListener, backgroundHandler);
-
- if (OperatingSystem.IsAndroidVersionAtLeast(28))
- cameraManager.OpenCamera(cameraView.Camera.DeviceId, executorService, stateListener);
- else
- cameraManager.OpenCamera(cameraView.Camera.DeviceId, stateListener, null);
- timer.Start();
-
- started = true;
- }
- catch
- {
- result = CameraResult.AccessError;
+ try
+ {
+ camChars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);
+
+ StreamConfigurationMap map = (StreamConfigurationMap)camChars.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
+ videoSize = ChooseVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
+ var maxVideoSize = ChooseMaxVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
+ if (PhotosResolution.Width != 0 && PhotosResolution.Height != 0)
+ maxVideoSize = new((int)PhotosResolution.Width, (int)PhotosResolution.Height);
+ imgReader = ImageReader.NewInstance(maxVideoSize.Width, maxVideoSize.Height, ImageFormatType.Jpeg, 1);
+ backgroundThread = new HandlerThread("CameraBackground");
+ backgroundThread.Start();
+ backgroundHandler = new Handler(backgroundThread.Looper);
+ imgReader.SetOnImageAvailableListener(photoListener, backgroundHandler);
+
+ if (OperatingSystem.IsAndroidVersionAtLeast(28))
+ cameraManager.OpenCamera(cameraView.Camera.DeviceId, executorService, stateListener);
+ else
+ cameraManager.OpenCamera(cameraView.Camera.DeviceId, stateListener, null);
+ timer.Start();
+
+ started = true;
+ }
+ catch
+ {
+ result = CameraResult.AccessError;
+ }
}
+ else
+ result = CameraResult.NoCameraSelected;
}
- else
- result = CameraResult.NoCameraSelected;
}
else
result = CameraResult.AccessDenied;
@@ -325,38 +356,44 @@ internal CameraResult StopCamera()
CameraResult result = CameraResult.Success;
if (initiated)
{
- timer.Stop();
- try
+ lock (changeCameraStateLocker)
{
- mediaRecorder?.Stop();
- mediaRecorder?.Dispose();
- } catch { }
- try
- {
- backgroundThread?.QuitSafely();
- backgroundThread?.Join();
- backgroundThread = null;
- backgroundHandler = null;
- imgReader?.Dispose();
- imgReader = null;
+ timer.Stop();
+ try
+ {
+ mediaRecorder?.Stop();
+ mediaRecorder?.Dispose();
+ }
+ catch { }
+ try
+ {
+ backgroundThread?.QuitSafely();
+ backgroundThread?.Join();
+ backgroundThread = null;
+ backgroundHandler = null;
+ imgReader?.Dispose();
+ imgReader = null;
+ }
+ catch { }
+ try
+ {
+ previewSession?.StopRepeating();
+ previewSession?.Dispose();
+ }
+ catch { }
+ try
+ {
+ cameraDevice?.Close();
+ cameraDevice?.Dispose();
+ }
+ catch { }
+ previewSession = null;
+ cameraDevice = null;
+ previewBuilder = null;
+ mediaRecorder = null;
+ started = false;
+ recording = false;
}
- catch { }
- try
- {
- previewSession?.StopRepeating();
- previewSession?.Dispose();
- } catch { }
- try
- {
- cameraDevice?.Close();
- cameraDevice?.Dispose();
- } catch { }
- previewSession = null;
- cameraDevice = null;
- previewBuilder = null;
- mediaRecorder = null;
- started = false;
- recording = false;
}
else
result = CameraResult.NotInitiated;
@@ -376,19 +413,27 @@ internal void DisposeControl()
}
catch { }
}
- private void ProccessQR()
+ private void ProcessPlugin()
{
Task.Run(() =>
{
- Bitmap bitmap = TakeSnap();
- if (bitmap != null)
+ if (cameraView.PluginDecoder != null || cameraView.PluginDecoders?.Count > 0)
{
- System.Diagnostics.Debug.WriteLine($"Processing QR ({bitmap.Width}x{bitmap.Height}) " + DateTime.Now.ToString("mm:ss:fff"));
- cameraView.DecodeBarcode(bitmap);
- bitmap.Dispose();
- System.Diagnostics.Debug.WriteLine("QR Processed " + DateTime.Now.ToString("mm:ss:fff"));
+ var bitmap = TakeSnap();
+ if (bitmap != null)
+ {
+ System.Diagnostics.Debug.WriteLine($"Processing Plugin ({bitmap.Width}x{bitmap.Height}) " + DateTime.Now.ToString("mm:ss:fff"));
+ cameraView.PluginDecoder?.Decode(bitmap);
+ cameraView.PluginDecoders?
+ .Where(x => x != cameraView.PluginDecoder)
+ .ToList()
+ .ForEach(x => x.Decode(bitmap));
+ bitmap.Dispose();
+ System.Diagnostics.Debug.WriteLine("Plugin Processed " + DateTime.Now.ToString("mm:ss:fff"));
+ GC.Collect();
+ }
+ lock (cameraView.currentThreadsLocker) cameraView.currentThreads--;
}
- lock (cameraView.currentThreadsLocker) cameraView.currentThreads--;
});
}
private void RefreshSnapShot()
@@ -402,28 +447,27 @@ private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Task.Run(() => RefreshSnapShot());
}
- else if (cameraView.BarCodeDetectionEnabled)
+ else if (cameraView.PluginProcessingEnabled)
{
frames++;
- if (frames >= cameraView.BarCodeDetectionFrameRate)
+ if (frames >= cameraView.PluginProcessingSkipFrames)
{
- bool processQR = false;
+ bool processPlugin = false;
lock (cameraView.currentThreadsLocker)
{
- if (cameraView.currentThreads < cameraView.BarCodeDetectionMaxThreads)
+ if (cameraView.currentThreads < cameraView.PluginProcessingMaxThreads)
{
cameraView.currentThreads++;
- processQR = true;
+ processPlugin = true;
+ frames = 0;
}
}
- if (processQR)
+ if (processPlugin)
{
- ProccessQR();
- frames = 0;
+ ProcessPlugin();
}
}
}
-
}
private Bitmap TakeSnap()
@@ -431,26 +475,36 @@ private Bitmap TakeSnap()
Bitmap bitmap = null;
try
{
- MainThread.InvokeOnMainThreadAsync(() => { bitmap = textureView.GetBitmap(null); bitmap = textureView.Bitmap; }).Wait();
+ MainThread.InvokeOnMainThreadAsync(() =>
+ {
+ //bitmap = textureView.GetBitmap(null);
+ bitmap = textureView.Bitmap;
+ }).Wait();
if (bitmap != null)
{
- int oriWidth = bitmap.Width;
- int oriHeight = bitmap.Height;
-
+ var oriWidth = bitmap.Width;
+ var oriHeight = bitmap.Height;
bitmap = Bitmap.CreateBitmap(bitmap, 0, 0, bitmap.Width, bitmap.Height, textureView.GetTransform(null), false);
- float xscale = (float)oriWidth / bitmap.Width;
- float yscale = (float)oriHeight / bitmap.Height;
- //bitmap = Bitmap.CreateBitmap(bitmap, Math.Abs(bitmap.Width - (int)((float)Width*xscale)) / 2, Math.Abs(bitmap.Height - (int)((float)Height * yscale)) / 2, Width, Height);
- bitmap = Bitmap.CreateBitmap(bitmap, 0, 0, Width, Height);
+
+ Matrix matrix;
if (textureView.ScaleX == -1)
{
- Matrix matrix = new();
+ matrix = new();
matrix.PreScale(-1, 1);
- bitmap = Bitmap.CreateBitmap(bitmap, 0, 0, bitmap.Width, bitmap.Height, matrix, false);
}
+ else
+ {
+ matrix = null;
+ }
+
+ var x = (float)((oriWidth - bitmap.Width) / 2.0);
+ var y = (float)((oriHeight - bitmap.Height) / 2.0);
+
+ bitmap = Bitmap.CreateBitmap(bitmap, (int)Math.Abs(x), (int)Math.Abs(y), oriWidth, oriHeight, matrix, false);
}
}
- catch { }
+ catch
+ { }
return bitmap;
}
internal async Task TakePhotoAsync(ImageFormat imageFormat)
@@ -468,9 +522,11 @@ private Bitmap TakeSnap()
case FlashMode.Auto:
singleRequest.Set(CaptureRequest.FlashMode, (int)ControlAEMode.OnAutoFlash);
break;
+
case FlashMode.Enabled:
singleRequest.Set(CaptureRequest.FlashMode, (int)ControlAEMode.On);
break;
+
case FlashMode.Disabled:
singleRequest.Set(CaptureRequest.FlashMode, (int)ControlAEMode.Off);
break;
@@ -525,7 +581,7 @@ private Bitmap TakeSnap()
}
}
}
- catch(Java.Lang.Exception ex)
+ catch (Java.Lang.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.StackTrace);
}
@@ -598,7 +654,7 @@ public void UpdateMirroredImage()
{
if (cameraView != null && textureView != null)
{
- if (cameraView.MirroredImage)
+ if (cameraView.MirroredImage)
textureView.ScaleX = -1;
else
textureView.ScaleX = 1;
@@ -608,11 +664,16 @@ internal void UpdateTorch()
{
if (cameraView.Camera != null && cameraView.Camera.HasFlashUnit)
{
- if (started)
+ if (started && previewBuilder != null && previewSession != null)
{
- previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On);
- previewBuilder.Set(CaptureRequest.FlashMode, cameraView.TorchEnabled ? (int)ControlAEMode.OnAutoFlash : (int)ControlAEMode.Off);
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ try
+ {
+ previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On);
+ previewBuilder.Set(CaptureRequest.FlashMode, cameraView.TorchEnabled ? (int)ControlAEMode.OnAutoFlash : (int)ControlAEMode.Off);
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ }
+ catch (CameraAccessException)
+ { }
}
else if (initiated)
cameraManager.SetTorchMode(cameraView.Camera.DeviceId, cameraView.TorchEnabled);
@@ -620,64 +681,84 @@ internal void UpdateTorch()
}
internal void UpdateFlashMode()
{
- if (previewSession != null && previewBuilder != null && cameraView.Camera != null && cameraView != null)
+ lock (changeCameraStateLocker)
{
- try
+ if (previewSession != null && previewBuilder != null && cameraView.Camera != null && cameraView != null)
{
- if (cameraView.Camera.HasFlashUnit)
+ try
{
- switch (cameraView.FlashMode)
+ if (cameraView.Camera.HasFlashUnit)
{
- case FlashMode.Auto:
- previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash);
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
- break;
- case FlashMode.Enabled:
- previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On);
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
- break;
- case FlashMode.Disabled:
- previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.Off);
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
- break;
+ switch (cameraView.FlashMode)
+ {
+ case FlashMode.Auto:
+ previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash);
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ break;
+
+ case FlashMode.Enabled:
+ previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.On);
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ break;
+
+ case FlashMode.Disabled:
+ previewBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.Off);
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ break;
+ }
}
}
- }
- catch (System.Exception)
- {
+ catch (System.Exception)
+ {
+ }
}
}
}
internal void SetZoomFactor(float zoom)
{
- if (previewSession != null && previewBuilder != null && cameraView.Camera != null)
+ lock (changeCameraStateLocker)
{
- //if (OperatingSystem.IsAndroidVersionAtLeast(30))
- //{
- //previewBuilder.Set(CaptureRequest.ControlZoomRatio, Math.Max(Camera.MinZoomFactor, Math.Min(zoom, Camera.MaxZoomFactor)));
- //}
- var destZoom = Math.Clamp(zoom, 1, Math.Min(6, cameraView.Camera.MaxZoomFactor)) - 1;
- Rect m = (Rect)camChars.Get(CameraCharacteristics.SensorInfoActiveArraySize);
- int minW = (int)(m.Width() / (cameraView.Camera.MaxZoomFactor));
- int minH = (int)(m.Height() / (cameraView.Camera.MaxZoomFactor));
- int newWidth = (int)(m.Width() - (minW * destZoom));
- int newHeight = (int)(m.Height() - (minH * destZoom));
- Rect zoomArea = new((m.Width()-newWidth)/2, (m.Height()-newHeight)/2, newWidth, newHeight);
- previewBuilder.Set(CaptureRequest.ScalerCropRegion, zoomArea);
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ if (previewSession != null && previewBuilder != null && cameraView.Camera != null)
+ {
+ try
+ {
+#if ANDROID30_0_OR_GREATER
+ if (OperatingSystem.IsAndroidVersionAtLeast(30) && cameraView.Camera.UseZoomRatio)
+ {
+ previewBuilder.Set(CaptureRequest.ControlZoomRatio, Math.Max(cameraView.Camera.MinZoomFactor, Math.Min(zoom, cameraView.Camera.MaxZoomFactor)));
+ }
+ else
+#endif
+ {
+ var newZoom = Math.Clamp(zoom, 1, cameraView.Camera.MaxZoomFactor);
+ var m = (Rect)camChars.Get(CameraCharacteristics.SensorInfoActiveArraySize);
+ var centerX = m.Width() / 2;
+ var centerY = m.Height() / 2;
+ var deltaX = (int)(0.5f * m.Width() / newZoom);
+ var deltaY = (int)(0.5f * m.Height() / newZoom);
+
+ var mCropRegion = new Rect(centerX - deltaX, centerY - deltaY, centerX + deltaX, centerY + deltaY);
+ previewBuilder.Set(CaptureRequest.ScalerCropRegion, mCropRegion);
+ }
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ }
+ catch (Java.Lang.IllegalStateException) { }
+ }
}
}
internal void ForceAutoFocus()
{
- if (previewSession != null && previewBuilder != null && cameraView.Camera != null)
+ lock (changeCameraStateLocker)
{
- previewBuilder.Set(CaptureRequest.ControlAfMode, Java.Lang.Integer.ValueOf((int)ControlAFMode.Off));
- previewBuilder.Set(CaptureRequest.ControlAfTrigger, Java.Lang.Integer.ValueOf((int)ControlAFTrigger.Cancel));
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
- previewBuilder.Set(CaptureRequest.ControlAfMode, Java.Lang.Integer.ValueOf((int)ControlAFMode.Auto));
- previewBuilder.Set(CaptureRequest.ControlAfTrigger, Java.Lang.Integer.ValueOf((int)ControlAFTrigger.Start));
- previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
-
+ if (previewSession != null && previewBuilder != null && cameraView.Camera != null)
+ {
+ previewBuilder.Set(CaptureRequest.ControlAfMode, Java.Lang.Integer.ValueOf((int)ControlAFMode.Off));
+ previewBuilder.Set(CaptureRequest.ControlAfTrigger, Java.Lang.Integer.ValueOf((int)ControlAFTrigger.Cancel));
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ previewBuilder.Set(CaptureRequest.ControlAfMode, Java.Lang.Integer.ValueOf((int)ControlAFMode.Auto));
+ previewBuilder.Set(CaptureRequest.ControlAfTrigger, Java.Lang.Integer.ValueOf((int)ControlAFTrigger.Start));
+ previewSession.SetRepeatingRequest(previewBuilder.Build(), null, null);
+ }
}
}
private static Size ChooseMaxVideoSize(Size[] choices)
@@ -777,7 +858,7 @@ private bool IsDimensionSwapped()
var chars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);
int sensorOrientation = (int)(chars.Get(CameraCharacteristics.SensorOrientation) as Java.Lang.Integer);
bool swappedDimensions = false;
- switch(displayRotation)
+ switch (displayRotation)
{
case SurfaceOrientation.Rotation0:
case SurfaceOrientation.Rotation180:
@@ -786,6 +867,7 @@ private bool IsDimensionSwapped()
swappedDimensions = true;
}
break;
+
case SurfaceOrientation.Rotation90:
case SurfaceOrientation.Rotation270:
if (sensorOrientation == 0 || sensorOrientation == 180)
@@ -830,10 +912,13 @@ public MyCameraStateCallback(MauiCameraView camView)
}
public override void OnOpened(CameraDevice camera)
{
- if (camera != null)
+ lock (cameraView.changeCameraStateLocker)
{
- cameraView.cameraDevice = camera;
- cameraView.StartPreview();
+ if (camera != null)
+ {
+ cameraView.cameraDevice = camera;
+ cameraView.StartPreview();
+ }
}
}
@@ -861,7 +946,10 @@ public override void OnConfigured(CameraCaptureSession session)
{
cameraView.previewSession = session;
cameraView.UpdatePreview();
-
+ if (cameraView.started)
+ {
+ cameraView.UpdateTorch();
+ }
}
public override void OnConfigureFailed(CameraCaptureSession session)
{
@@ -899,6 +987,4 @@ public void OnImageAvailable(ImageReader reader)
cameraView.captureDone = true;
}
}
-}
-
-
+}
\ No newline at end of file
diff --git a/Camera.MAUI/Platforms/Windows/MauiCameraView.cs b/Camera.MAUI/Platforms/Windows/MauiCameraView.cs
index 76979b7..a8dbcc4 100644
--- a/Camera.MAUI/Platforms/Windows/MauiCameraView.cs
+++ b/Camera.MAUI/Platforms/Windows/MauiCameraView.cs
@@ -1,14 +1,14 @@
using Microsoft.UI.Xaml.Controls;
-using Windows.Media.Capture.Frames;
-using Windows.Media.Capture;
using Windows.Devices.Enumeration;
-using Windows.Media.Core;
using Windows.Graphics.Imaging;
-using Panel = Windows.Devices.Enumeration.Panel;
-using Windows.Media.MediaProperties;
+using Windows.Media.Capture;
+using Windows.Media.Capture.Frames;
+using Windows.Media.Core;
using Windows.Media.Devices;
+using Windows.Media.MediaProperties;
+using Panel = Windows.Devices.Enumeration.Panel;
-namespace Camera.MAUI.Platforms.Windows;
+namespace Camera.MAUI;
public sealed partial class MauiCameraView : UserControl, IDisposable
{
@@ -50,7 +50,7 @@ internal void UpdateMirroredImage()
{
if (cameraView != null)
{
- if(cameraView.MirroredImage)
+ if (cameraView.MirroredImage)
flowDirection = Microsoft.UI.Xaml.FlowDirection.RightToLeft;
else
flowDirection = Microsoft.UI.Xaml.FlowDirection.LeftToRight;
@@ -83,10 +83,12 @@ internal void UpdateFlashMode()
case FlashMode.Auto:
frameSource.Controller.VideoDeviceController.FlashControl.Auto = true;
break;
+
case FlashMode.Enabled:
frameSource.Controller.VideoDeviceController.FlashControl.Auto = false;
frameSource.Controller.VideoDeviceController.FlashControl.Enabled = true;
break;
+
case FlashMode.Disabled:
frameSource.Controller.VideoDeviceController.FlashControl.Auto = false;
frameSource.Controller.VideoDeviceController.FlashControl.Enabled = false;
@@ -149,7 +151,7 @@ private void InitDevices()
MaxZoomFactor = frameSource.Controller.VideoDeviceController.ZoomControl.Supported ? frameSource.Controller.VideoDeviceController.ZoomControl.Max : 1f,
AvailableResolutions = new()
};
- foreach(var profile in MediaCapture.FindAllVideoProfiles(s.Id))
+ foreach (var profile in MediaCapture.FindAllVideoProfiles(s.Id))
{
foreach (var recordMediaP in profile.SupportedRecordMediaDescription)
{
@@ -301,7 +303,6 @@ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
result = CameraResult.AccessError;
}
}
-
}
else
result = CameraResult.NoVideoFormatsAvailable;
@@ -332,28 +333,40 @@ await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
mediaCapture.Dispose();
mediaCapture = null;
}
- }else
+ }
+ else
result = CameraResult.NotInitiated;
return result;
}
- private void ProcessQRImage(SoftwareBitmap simg)
+
+ private void ProcessPlugin(SoftwareBitmap simg)
{
if (simg != null)
{
Task.Run(() =>
{
- var img = SoftwareBitmap.Convert(simg, BitmapPixelFormat.Gray8, BitmapAlphaMode.Ignore);
- if (img != null)
+ if (cameraView.PluginDecoder != null || cameraView.PluginDecoders?.Count > 0)
{
- if (img.PixelWidth > 0 && img.PixelHeight > 0)
- cameraView.DecodeBarcode(img);
- img.Dispose();
+ var img = SoftwareBitmap.Convert(simg, BitmapPixelFormat.Gray8, BitmapAlphaMode.Ignore);
+ if (img != null)
+ {
+ if (img.PixelWidth > 0 && img.PixelHeight > 0)
+ {
+ cameraView.PluginDecoder?.Decode(img);
+ cameraView.PluginDecoders?
+ .Where(x => x != cameraView.PluginDecoder)
+ .ToList()
+ .ForEach(x => x.Decode(img));
+ }
+ img.Dispose();
+ }
+ lock (cameraView.currentThreadsLocker) cameraView.currentThreads--;
+ GC.Collect();
}
- lock (cameraView.currentThreadsLocker) cameraView.currentThreads--;
- GC.Collect();
});
}
}
+
private void RefreshSnapShot()
{
cameraView.RefreshSnapshot(GetSnapShot(cameraView.AutoSnapShotFormat, true));
@@ -365,25 +378,25 @@ private void FrameReader_FrameArrived(MediaFrameReader sender, MediaFrameArrived
{
Task.Run(() => RefreshSnapShot());
}
- else if (cameraView.BarCodeDetectionEnabled)
+ else if (cameraView.PluginProcessingEnabled)
{
frames++;
- if (frames >= cameraView.BarCodeDetectionFrameRate)
+ if (frames >= cameraView.PluginProcessingSkipFrames)
{
- bool processQR = false;
+ bool processPlugin = false;
lock (cameraView.currentThreadsLocker)
{
- if (cameraView.currentThreads < cameraView.BarCodeDetectionMaxThreads)
+ if (cameraView.currentThreads < cameraView.PluginProcessingMaxThreads)
{
cameraView.currentThreads++;
- processQR = true;
+ processPlugin = true;
+ frames = 0;
}
}
- if (processQR)
+ if (processPlugin)
{
var frame = sender.TryAcquireLatestFrame();
- ProcessQRImage(frame.VideoMediaFrame.SoftwareBitmap);
- frames = 0;
+ ProcessPlugin(frame.VideoMediaFrame.SoftwareBitmap);
}
}
}
@@ -421,7 +434,8 @@ internal async Task StopCameraAsync()
{
result = CameraResult.AccessError;
}
- }else
+ }
+ else
result = CameraResult.NotInitiated;
started = false;
@@ -540,7 +554,8 @@ internal ImageSource GetSnapShot(ImageFormat imageFormat, bool auto = false)
result = ImageSource.FromStream(() => stream);
cameraView.SnapShotStream?.Dispose();
cameraView.SnapShotStream = stream;
- }else
+ }
+ else
result = ImageSource.FromStream(() => stream);
img.Dispose();
snapshot.Dispose();
@@ -594,7 +609,8 @@ internal async Task SaveSnapShot(ImageFormat imageFormat, string SnapFileP
stream.Close();
}
snapping = false;
- }else
+ }
+ else
result = false;
return result;
}
diff --git a/Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs b/Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs
deleted file mode 100644
index 9be3d3f..0000000
--- a/Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using ZXing;
-
-namespace Camera.MAUI.ZXingHelper;
-
-public record BarcodeDecodeOptions
-{
- public bool AutoRotate { get; init; } = true;
- public string CharacterSet { get; init; } = string.Empty;
- public IList PossibleFormats { get; init; } = new List { BarcodeFormat.QR_CODE };
- public bool PureBarcode { get; init; } = false;
- public bool ReadMultipleCodes { get; init; } = false;
- public bool TryHarder { get; init; } = true;
- public bool TryInverted { get; init; } = true;
-}
diff --git a/Camera.MAUI/ZXingHelper/BarcodeEventArgs.cs b/Camera.MAUI/ZXingHelper/BarcodeEventArgs.cs
deleted file mode 100644
index 76181a6..0000000
--- a/Camera.MAUI/ZXingHelper/BarcodeEventArgs.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using ZXing;
-
-namespace Camera.MAUI.ZXingHelper;
-
-public record BarcodeEventArgs
-{
- public Result[] Result { get; init; }
-}
diff --git a/Readme.md b/Readme.md
index 0075756..295299d 100644
--- a/Readme.md
+++ b/Readme.md
@@ -51,6 +51,7 @@ In your `AndroidManifest.xml` file (Platforms\Android) add the following permiss
+
```
@@ -76,7 +77,9 @@ For more information on permissions, see the [Microsoft Docs](https://docs.micro
In XAML, make sure to add the right XML namespace:
-`xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"`
+`xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"`
+`xmlns:bc="clr-namespace:Camera.MAUI.Plugin.ZXing;assembly=Camera.MAUI.Plugin.ZXing"`, or
+`xmlns:bc="clr-namespace:Camera.MAUI.Plugin.MLKit;assembly=Camera.MAUI.Plugin.MLKit"`
Use the control:
```xaml
@@ -86,8 +89,32 @@ Use the control:
Configure the events:
```csharp
cameraView.CamerasLoaded += CameraView_CamerasLoaded;
- cameraView.BarcodeDetected += CameraView_BarcodeDetected;
```
+
+Configure plugin:
+```csharp
+ // ZXing
+ cameraView.PluginDecoder = new Camera.MAUI.Plugin.ZXing.ZXingDecoder();
+ cameraView.PluginDecoder.Decoded += CameraView_BarcodeDetected;
+```
+or
+```csharp
+ // MLKit
+ cameraView.PluginDecoder = new Camera.MAUI.Plugin.MLKit.MLKitBarcodeDecoder();
+ cameraView.PluginDecoder.Decoded += CameraView_BarcodeDetected;
+```
+
+Configure a collection of plugins:
+```csharp
+ var zxing = new Camera.MAUI.Plugin.ZXing.ZXingDecoder();
+ zxing.Decoded += CameraView_BarcodeDetected;
+
+ cameraView.PluginDecoders = new PluginDecoderCollection
+ {
+ zxing
+ };
+```
+
Configure the camera and microphone to use:
```csharp
private void CameraView_CamerasLoaded(object sender, EventArgs e)
@@ -219,37 +246,72 @@ The control has several binding properties for take an snapshot:
```
```xaml
- ```
+ Cameras="{Binding Cameras, Mode=OneWayToSource}" Camera="{Binding Camera}"
+ AutoStartPreview="{Binding AutoStartPreview}"
+ NumCamerasDetected="{Binding NumCameras, Mode=OneWayToSource}"
+ AutoSnapShotAsImageSource="True" AutoSnapShotFormat="PNG"
+ TakeAutoSnapShot="{Binding TakeSnapshot}" AutoSnapShotSeconds="{Binding SnapshotSeconds}"
+ Microphones="{Binding Microphones, Mode=OneWayToSource}" Microphone="{Binding Microphone}"
+ NumMicrophonesDetected="{Binding NumMicrophones, Mode=OneWayToSource}"
+ AutoRecordingFile="{Binding RecordingFile}"
+ AutoStartRecording="{Binding AutoStartRecording}">
+
+
+
+
+```
+
+To bind ```CameraView.PluginDecoders``` instead of ```CameraView.PluginDecoder```
+```xaml
+
+
+
+
+
+
+
+```
+
You have a complete example of MVVM in [MVVM Example](https://github.com/hjam40/Camera.MAUI/tree/master/Camera.MAUI.Test/MVVM)
Enable and Handle barcodes detection:
```csharp
- cameraView.BarcodeDetected += CameraView_BarcodeDetected;
- cameraView.BarCodeOptions = new ZXingHelper.BarcodeDecodeOptions
+ cameraView.PluginDecoder.Decoded += CameraView_BarcodeDetected;
+ if (cameraView.PluginDecoder is ZXingDecoder zxingDecoder)
+ {
+ zxingDecoder.Options = new ZXingDecoderOptions
+ {
+ AutoRotate = true,
+ PossibleFormats = { Plugin.BarcodeFormat.QR_CODE },
+ ReadMultipleCodes = false,
+ TryHarder = false,
+ TryInverted = true
+ };
+ zxingDecoder.ControlBarcodeResultDuplicate = true;
+ }
+ if (cameraView.PluginDecoder is MLKitBarcodeDecoder mlkitDecoder)
{
- AutoRotate = true,
- PossibleFormats = { ZXing.BarcodeFormat.QR_CODE },
- ReadMultipleCodes = false,
- TryHarder = true,
- TryInverted = true
- };
- cameraView.BarCodeDetectionFrameRate = 10;
- cameraView.BarCodeDetectionMaxThreads = 5;
- cameraView.ControlBarcodeResultDuplicate = true;
- cameraView.BarCodeDetectionEnabled = true;
+ mlkitDecoder.Options = new MLKitBarcodeDecoderOptions
+ {
+ PossibleFormats = { Plugin.BarcodeFormat.QR_CODE },
+ };
+ }
+ cameraView.PluginProcessingFrameRate = 10;
+ cameraView.PluginProcessingMaxThreads = 5;
+ cameraView.PluginProcessingEnabled = true;
private void CameraView_BarcodeDetected(object sender, ZXingHelper.BarcodeEventArgs args)
{
@@ -258,10 +320,10 @@ Enable and Handle barcodes detection:
```
Use the event or the bindable property BarCodeResults
```csharp
- /// Event launched every time a code is detected in the image if "BarCodeDetectionEnabled" is set to true.
- public event BarcodeResultHandler BarcodeDetected;
- /// It refresh each time a barcode is detected if BarCodeDetectionEnabled porperty is true
- public Result[] BarCodeResults
+ /// Event launched every time a successful decode occurs in the image if "Camera.MAUI.CameraView.PluginProcessingEnabled" is set to true.
+ public event PluginDecoderResultHandler Decoded;
+ /// It refresh each time a successful decode occurs, if "Camera.MAUI.CameraView.PluginProcessingEnabled" property is true.
+ public IPluginResult[] Results
```
## BarcodeImage
@@ -270,7 +332,10 @@ A ContentView control for generate codebars images.
In XAML, make sure to add the right XML namespace:
-`xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"`
+```xaml
+xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
+xmlns:bc="clr-namespace:Camera.MAUI.Plugin.ZXing;assembly=Camera.MAUI.Plugin.ZXing"
+```
Use the control and its bindable properties:
```xaml
@@ -278,7 +343,11 @@ Use the control and its bindable properties:
WidthRequest="400" HeightRequest="400"
BarcodeWidth="200" BarcodeHeight="200" BarcodeMargin="5"
BarcodeBackground="White" BarcodeForeground="Blue"
- BarcodeFormat="QR_CODE" />
+ BarcodeFormat="QR_CODE">
+
+
+
+