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"> -