From 19768a07f254d9d4c8bd4ca2da2f5bc444d0e0cb Mon Sep 17 00:00:00 2001 From: programatix Date: Wed, 8 Nov 2023 20:51:13 +0800 Subject: [PATCH 01/20] The idea of this commit is to make barcode capability as a plugin. Removed ZXing from Camera.MAUI and move it into a new project as plugin. Added Google MLKit as a new project as plugin. --- .../Camera.MAUI.Barcode.MLKit.csproj | 65 ++++ Camera.MAUI.Barcode.MLKit/Extensions.cs | 12 + .../MLKitBarcodeDecoder.cs | 114 +++++++ .../Platforms/Android/Methods.cs | 90 ++++++ .../Platforms/Android/TaskCompleteListener.cs | 35 ++ .../Platforms/iOS/Methods.cs | 63 ++++ .../BarcodeDecodeOptions.cs | 4 +- .../Camera.MAUI.Barcode.ZXing.csproj | 42 +++ Camera.MAUI.Barcode.ZXing/Extensions.cs | 77 +++++ .../MaciOS}/BitmapRenderer.cs | 9 +- .../MaciOS}/RGBLuminanceSource.cs | 4 +- .../Android/BitmapLuminanceSource.cs | 4 +- .../Platforms/Android/BitmapRenderer.cs | 8 +- .../Windows/SoftwareBitmapLuminanceSource.cs | 2 +- .../Windows/SoftwareBitmapRenderer.cs | 58 ++-- .../RGBLuminanceSource.cs | 51 +-- .../ZXingBarcodeDecoder.cs | 164 ++++++++++ .../ZXingBarcodeRenderer.cs | 47 ++- Camera.MAUI.Barcode/BarcodeEventArgs.cs | 7 + Camera.MAUI.Barcode/BarcodeFormat.cs | 127 ++++++++ Camera.MAUI.Barcode/BarcodeResult.cs | 64 ++++ .../Camera.MAUI.Barcode.csproj | 30 ++ Camera.MAUI.Barcode/IBarcodeDecoder.cs | 37 +++ Camera.MAUI.Barcode/IBarcodeRenderer.cs | 18 ++ Camera.MAUI.Test/BarcodeGenerationPage.xaml | 11 +- Camera.MAUI.Test/Camera.MAUI.Test.csproj | 2 + Camera.MAUI.Test/FullScreenPage.xaml | 4 +- Camera.MAUI.Test/MVVM/CameraViewModel.cs | 38 ++- Camera.MAUI.Test/MVVM/MVVMPage.xaml | 45 +-- Camera.MAUI.Test/MainPage.xaml | 10 +- Camera.MAUI.Test/SizedPage.xaml.cs | 39 ++- Camera.MAUI.sln | 18 ++ Camera.MAUI/Apple/MauiCameraView.cs | 52 +-- Camera.MAUI/BarcodeImage.xaml.cs | 36 ++- Camera.MAUI/Camera.MAUI.csproj | 35 +- Camera.MAUI/CameraView.cs | 298 ++++++++++++++---- .../Platforms/Android/MauiCameraView.cs | 54 ++-- .../Platforms/Windows/MauiCameraView.cs | 36 ++- Camera.MAUI/ZXingHelper/BarcodeEventArgs.cs | 8 - 39 files changed, 1550 insertions(+), 268 deletions(-) create mode 100644 Camera.MAUI.Barcode.MLKit/Camera.MAUI.Barcode.MLKit.csproj create mode 100644 Camera.MAUI.Barcode.MLKit/Extensions.cs create mode 100644 Camera.MAUI.Barcode.MLKit/MLKitBarcodeDecoder.cs create mode 100644 Camera.MAUI.Barcode.MLKit/Platforms/Android/Methods.cs create mode 100644 Camera.MAUI.Barcode.MLKit/Platforms/Android/TaskCompleteListener.cs create mode 100644 Camera.MAUI.Barcode.MLKit/Platforms/iOS/Methods.cs rename {Camera.MAUI/ZXingHelper => Camera.MAUI.Barcode.ZXing}/BarcodeDecodeOptions.cs (92%) create mode 100644 Camera.MAUI.Barcode.ZXing/Camera.MAUI.Barcode.ZXing.csproj create mode 100644 Camera.MAUI.Barcode.ZXing/Extensions.cs rename {Camera.MAUI/Apple => Camera.MAUI.Barcode.ZXing/MaciOS}/BitmapRenderer.cs (78%) rename {Camera.MAUI/Apple => Camera.MAUI.Barcode.ZXing/MaciOS}/RGBLuminanceSource.cs (90%) rename {Camera.MAUI => Camera.MAUI.Barcode.ZXing}/Platforms/Android/BitmapLuminanceSource.cs (94%) rename {Camera.MAUI => Camera.MAUI.Barcode.ZXing}/Platforms/Android/BitmapRenderer.cs (80%) rename {Camera.MAUI => Camera.MAUI.Barcode.ZXing}/Platforms/Windows/SoftwareBitmapLuminanceSource.cs (94%) rename {Camera.MAUI => Camera.MAUI.Barcode.ZXing}/Platforms/Windows/SoftwareBitmapRenderer.cs (63%) rename {Camera.MAUI/ZXingHelper => Camera.MAUI.Barcode.ZXing}/RGBLuminanceSource.cs (82%) create mode 100644 Camera.MAUI.Barcode.ZXing/ZXingBarcodeDecoder.cs rename Camera.MAUI/BarcodeRenderer.cs => Camera.MAUI.Barcode.ZXing/ZXingBarcodeRenderer.cs (77%) create mode 100644 Camera.MAUI.Barcode/BarcodeEventArgs.cs create mode 100644 Camera.MAUI.Barcode/BarcodeFormat.cs create mode 100644 Camera.MAUI.Barcode/BarcodeResult.cs create mode 100644 Camera.MAUI.Barcode/Camera.MAUI.Barcode.csproj create mode 100644 Camera.MAUI.Barcode/IBarcodeDecoder.cs create mode 100644 Camera.MAUI.Barcode/IBarcodeRenderer.cs delete mode 100644 Camera.MAUI/ZXingHelper/BarcodeEventArgs.cs diff --git a/Camera.MAUI.Barcode.MLKit/Camera.MAUI.Barcode.MLKit.csproj b/Camera.MAUI.Barcode.MLKit/Camera.MAUI.Barcode.MLKit.csproj new file mode 100644 index 0000000..620a0d9 --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/Camera.MAUI.Barcode.MLKit.csproj @@ -0,0 +1,65 @@ + + + + net7.0-android;net7.0-ios; + $(TargetFrameworks);net7.0-windows10.0.19041.0 + + + true + true + enable + + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + + programatix + + + + + + + + + + + + + + + 1.8.0.1 + + + 1.8.0.1 + + + 1.3.0.1 + + + 1.3.0.1 + + + 2.6.2.2 + + + 117.2.0.2 + + + 117.0.0.7 + + + 118.3.0.2 + + + + + + + + + + diff --git a/Camera.MAUI.Barcode.MLKit/Extensions.cs b/Camera.MAUI.Barcode.MLKit/Extensions.cs new file mode 100644 index 0000000..bac4294 --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/Extensions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Camera.MAUI.Barcode.MLKit +{ + internal static class Extensions + { + } +} \ No newline at end of file diff --git a/Camera.MAUI.Barcode.MLKit/MLKitBarcodeDecoder.cs b/Camera.MAUI.Barcode.MLKit/MLKitBarcodeDecoder.cs new file mode 100644 index 0000000..aa1d526 --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/MLKitBarcodeDecoder.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +#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 DecodeDataType = Android.Graphics.Bitmap; +using static Xamarin.Google.MLKit.Vision.Barcode.Common.Barcode; + +#elif WINDOWS +using DecodeDataType = Windows.Graphics.Imaging.SoftwareBitmap; +#else + +using DecodeDataType = System.Object; + +#endif + +namespace Camera.MAUI.Barcode.MLKit +{ + public class MLKitBarcodeDecoder : BindableObject, IBarcodeDecoder + { +#if ANDROID + private IBarcodeScanner barcodeScanner; +#elif IOS + private BarcodeScanner barcodeDetector; +#endif + + public event IBarcodeDecoder.BarcodeResultHandler BarcodeDetected; + + public static readonly BindableProperty BarCodeFormatsProperty = BindableProperty.Create(nameof(BarCodeFormats), typeof(IList), typeof(MLKitBarcodeDecoder), new List { BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX }, propertyChanged: BarCodeFormatsChanged); + + public IList BarCodeFormats + { + get { return (IList)GetValue(BarCodeFormatsProperty); } + set { SetValue(BarCodeFormatsProperty, value); } + } + + private static void BarCodeFormatsChanged(BindableObject bindable, object oldValue, object newValue) + { + if (newValue != null && oldValue != newValue && bindable is MLKitBarcodeDecoder decoder && newValue is IList formats) + { +#if ANDROID + decoder.barcodeScanner = BarcodeScanning.GetClient(new BarcodeScannerOptions.Builder() + .SetBarcodeFormats(formats?.Count > 0 ? formats.Select(x => x.ToPlatform()).Aggregate((r, x) => r |= x) : FormatAllFormats) + .Build() + ); +#endif + } + } + + public MLKitBarcodeDecoder() + { +#if ANDROID || IOS + var platformFormats = BarCodeFormats?.Count > 0 + ? BarCodeFormats.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 options = new BarcodeScannerOptions(platformFormats == default ? global::MLKit.BarcodeScanning.BarcodeFormat.Unknown : platformFormats); + barcodeDetector = BarcodeScanner.BarcodeScannerWithOptions(options); +#endif +#endif + } + + public void ClearResults() + { + } + + public +#if ANDROID + async +#endif + void Decode(DecodeDataType data) + { +#if ANDROID + var image = InputImage.FromBitmap(data, 0); + var result = await barcodeScanner.Process(image).ToAwaitableTask(); + var results = Methods.ProcessBarcodeResult(result); + if (results.Count > 0) + { + BarcodeDetected?.Invoke(this, new BarcodeEventArgs { Result = results.ToArray() }); + } +#elif IOS + var visionImage = new MLImage(data) { Orientation = UIKit.UIImageOrientation.Up }; + barcodeDetector.ProcessImage(visionImage, (barcodes, error) => + { + var results = new List(); + foreach (var barcode in barcodes) + results.Add(Methods.ProcessBarcodeResult(barcode)); + + if (results.Count > 0) + { + BarcodeDetected?.Invoke(this, new BarcodeEventArgs { Result = results.ToArray() }); + } + }); +#endif + } + } +} \ No newline at end of file diff --git a/Camera.MAUI.Barcode.MLKit/Platforms/Android/Methods.cs b/Camera.MAUI.Barcode.MLKit/Platforms/Android/Methods.cs new file mode 100644 index 0000000..8072499 --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/Platforms/Android/Methods.cs @@ -0,0 +1,90 @@ +using Android.Gms.Extensions; +using Android.Graphics; +using Android.Runtime; +using Java.Nio; +using Java.Util; +using Xamarin.Google.MLKit.Vision.Barcode.Common; +using Xamarin.Google.MLKit.Vision.BarCode; +using Xamarin.Google.MLKit.Vision.Common; + +using static Xamarin.Google.MLKit.Vision.Barcode.Common.Barcode; + +namespace Camera.MAUI.Barcode.MLKit +{ + internal static class Methods + { + internal static int ToPlatform(this BarcodeFormat format) + { + return format switch + { + BarcodeFormat.AZTEC => FormatAztec, + BarcodeFormat.CODABAR => FormatCodabar, + BarcodeFormat.CODE_128 => FormatCode128, + BarcodeFormat.CODE_39 => FormatCode39, + BarcodeFormat.CODE_93 => FormatCode93, + BarcodeFormat.DATA_MATRIX => FormatDataMatrix, + BarcodeFormat.EAN_13 => FormatEan13, + BarcodeFormat.EAN_8 => FormatEan8, + BarcodeFormat.ITF => FormatItf, + BarcodeFormat.PDF_417 => FormatPdf417, + BarcodeFormat.QR_CODE => FormatQrCode, + BarcodeFormat.UPC_A => FormatUpcA, + BarcodeFormat.UPC_E => FormatUpcE, + _ => FormatUnknown, + }; + } + + internal static BarcodeFormat BarcodeFormatToNative(this int format) + { + return format switch + { + FormatAztec => BarcodeFormat.AZTEC, + FormatCodabar => BarcodeFormat.CODABAR, + FormatCode128 => BarcodeFormat.CODE_128, + FormatCode39 => BarcodeFormat.CODE_39, + FormatCode93 => BarcodeFormat.CODE_93, + FormatDataMatrix => BarcodeFormat.DATA_MATRIX, + FormatEan13 => BarcodeFormat.EAN_13, + FormatEan8 => BarcodeFormat.EAN_8, + FormatItf => BarcodeFormat.ITF, + FormatPdf417 => BarcodeFormat.PDF_417, + FormatQrCode => BarcodeFormat.QR_CODE, + FormatUpcA => BarcodeFormat.UPC_A, + 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 Microsoft.Maui.Graphics.Point(cornerPoint.X, cornerPoint.Y)); + + resultList.Add(new BarcodeResult(mapped.DisplayValue, mapped.GetRawBytes(), cornerPoints.ToArray(), mapped.Format.BarcodeFormatToNative(), null, 0, 0)); + } + + 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.Barcode.MLKit/Platforms/Android/TaskCompleteListener.cs b/Camera.MAUI.Barcode.MLKit/Platforms/Android/TaskCompleteListener.cs new file mode 100644 index 0000000..f73c434 --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/Platforms/Android/TaskCompleteListener.cs @@ -0,0 +1,35 @@ +using Android.Gms.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Camera.MAUI.Barcode.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.Barcode.MLKit/Platforms/iOS/Methods.cs b/Camera.MAUI.Barcode.MLKit/Platforms/iOS/Methods.cs new file mode 100644 index 0000000..a3442aa --- /dev/null +++ b/Camera.MAUI.Barcode.MLKit/Platforms/iOS/Methods.cs @@ -0,0 +1,63 @@ +using CoreGraphics; +using Foundation; +using global::MLKit.BarcodeScanning; +using global::MLKit.Core; +using UIKit; + +namespace Camera.MAUI.Barcode.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(), null, 0, 0); + } + } +} \ No newline at end of file diff --git a/Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs b/Camera.MAUI.Barcode.ZXing/BarcodeDecodeOptions.cs similarity index 92% rename from Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs rename to Camera.MAUI.Barcode.ZXing/BarcodeDecodeOptions.cs index 9be3d3f..d6bb92f 100644 --- a/Camera.MAUI/ZXingHelper/BarcodeDecodeOptions.cs +++ b/Camera.MAUI.Barcode.ZXing/BarcodeDecodeOptions.cs @@ -1,6 +1,6 @@ using ZXing; -namespace Camera.MAUI.ZXingHelper; +namespace Camera.MAUI.Barcode.ZXing; public record BarcodeDecodeOptions { @@ -11,4 +11,4 @@ public record BarcodeDecodeOptions 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.Barcode.ZXing/Camera.MAUI.Barcode.ZXing.csproj b/Camera.MAUI.Barcode.ZXing/Camera.MAUI.Barcode.ZXing.csproj new file mode 100644 index 0000000..fe54f0c --- /dev/null +++ b/Camera.MAUI.Barcode.ZXing/Camera.MAUI.Barcode.ZXing.csproj @@ -0,0 +1,42 @@ + + + + net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst + $(TargetFrameworks);net7.0-windows10.0.19041.0 + + + true + true + enable + + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + + programatix + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Camera.MAUI.Barcode.ZXing/Extensions.cs b/Camera.MAUI.Barcode.ZXing/Extensions.cs new file mode 100644 index 0000000..c66e69d --- /dev/null +++ b/Camera.MAUI.Barcode.ZXing/Extensions.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ZXing; + +namespace Camera.MAUI.Barcode.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 BarcodeResult ToNative(this Result result) + { + return new BarcodeResult(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/Apple/BitmapRenderer.cs b/Camera.MAUI.Barcode.ZXing/MaciOS/BitmapRenderer.cs similarity index 78% rename from Camera.MAUI/Apple/BitmapRenderer.cs rename to Camera.MAUI.Barcode.ZXing/MaciOS/BitmapRenderer.cs index 266cc0d..acd12e1 100644 --- a/Camera.MAUI/Apple/BitmapRenderer.cs +++ b/Camera.MAUI.Barcode.ZXing/MaciOS/BitmapRenderer.cs @@ -5,7 +5,7 @@ using ZXing.Rendering; using ZXing; -namespace Camera.MAUI.Platforms.Apple; +namespace Camera.MAUI.Barcode.ZXing.Platforms.MaciOS; public class BitmapRenderer : IBarcodeRenderer { @@ -17,12 +17,12 @@ 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) + public UIImage Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content) { return Render(matrix, format, content, new EncodingOptions()); } - public UIImage Render(BitMatrix matrix, BarcodeFormat format, string content, EncodingOptions options) + public UIImage Render(BitMatrix matrix, global::ZXing.BarcodeFormat format, string content, EncodingOptions options) { UIGraphics.BeginImageContext(new CGSize(matrix.Width, matrix.Height)); var context = UIGraphics.GetCurrentContext(); @@ -36,7 +36,6 @@ public UIImage Render(BitMatrix matrix, BarcodeFormat format, string content, En } } - var img = UIGraphics.GetImageFromCurrentImageContext(); UIGraphics.EndImageContext(); @@ -44,4 +43,4 @@ public UIImage Render(BitMatrix matrix, BarcodeFormat format, string content, En return img; } } -#endif +#endif \ No newline at end of file diff --git a/Camera.MAUI/Apple/RGBLuminanceSource.cs b/Camera.MAUI.Barcode.ZXing/MaciOS/RGBLuminanceSource.cs similarity index 90% rename from Camera.MAUI/Apple/RGBLuminanceSource.cs rename to Camera.MAUI.Barcode.ZXing/MaciOS/RGBLuminanceSource.cs index ad77bf3..695c0d8 100644 --- a/Camera.MAUI/Apple/RGBLuminanceSource.cs +++ b/Camera.MAUI.Barcode.ZXing/MaciOS/RGBLuminanceSource.cs @@ -3,9 +3,9 @@ using System.Runtime.InteropServices; using UIKit; -namespace Camera.MAUI.ZXingHelper; +namespace Camera.MAUI.Barcode.ZXing.Platforms.MaciOS; -internal partial class RGBLuminanceSource +public class RGBLuminanceSource : Barcode.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.Barcode.ZXing/Platforms/Android/BitmapLuminanceSource.cs similarity index 94% rename from Camera.MAUI/Platforms/Android/BitmapLuminanceSource.cs rename to Camera.MAUI.Barcode.ZXing/Platforms/Android/BitmapLuminanceSource.cs index 3a17599..fa08ec2 100644 --- a/Camera.MAUI/Platforms/Android/BitmapLuminanceSource.cs +++ b/Camera.MAUI.Barcode.ZXing/Platforms/Android/BitmapLuminanceSource.cs @@ -1,7 +1,7 @@ using Android.Graphics; using ZXing; -namespace Camera.MAUI.Platforms.Android; +namespace Camera.MAUI.Barcode.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.Barcode.ZXing/Platforms/Android/BitmapRenderer.cs similarity index 80% rename from Camera.MAUI/Platforms/Android/BitmapRenderer.cs rename to Camera.MAUI.Barcode.ZXing/Platforms/Android/BitmapRenderer.cs index 662696b..d277b56 100644 --- a/Camera.MAUI/Platforms/Android/BitmapRenderer.cs +++ b/Camera.MAUI.Barcode.ZXing/Platforms/Android/BitmapRenderer.cs @@ -4,7 +4,7 @@ using ZXing.Rendering; using Color = Android.Graphics.Color; -namespace Camera.MAUI.Platforms.Android; +namespace Camera.MAUI.Barcode.ZXing.Platforms.Android; public class BitmapRenderer : IBarcodeRenderer { @@ -18,12 +18,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 +45,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.Barcode.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs similarity index 94% rename from Camera.MAUI/Platforms/Windows/SoftwareBitmapLuminanceSource.cs rename to Camera.MAUI.Barcode.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs index fc34d3f..ad7a94b 100644 --- a/Camera.MAUI/Platforms/Windows/SoftwareBitmapLuminanceSource.cs +++ b/Camera.MAUI.Barcode.ZXing/Platforms/Windows/SoftwareBitmapLuminanceSource.cs @@ -2,7 +2,7 @@ using Windows.Graphics.Imaging; using ZXing; -namespace Camera.MAUI.Platforms.Windows; +namespace Camera.MAUI.Barcode.ZXing.Platforms.Windows; internal class SoftwareBitmapLuminanceSource : BaseLuminanceSource { diff --git a/Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs b/Camera.MAUI.Barcode.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs similarity index 63% rename from Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs rename to Camera.MAUI.Barcode.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs index 1b89a54..564695d 100644 --- a/Camera.MAUI/Platforms/Windows/SoftwareBitmapRenderer.cs +++ b/Camera.MAUI.Barcode.ZXing/Platforms/Windows/SoftwareBitmapRenderer.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices.WindowsRuntime; using Color = Windows.UI.Color; -namespace Camera.MAUI.Platforms.Windows +namespace Camera.MAUI.Barcode.ZXing.Platforms.Windows { public class SoftwareBitmapRenderer : IBarcodeRenderer { @@ -18,29 +18,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 +67,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.Barcode.ZXing/RGBLuminanceSource.cs similarity index 82% rename from Camera.MAUI/ZXingHelper/RGBLuminanceSource.cs rename to Camera.MAUI.Barcode.ZXing/RGBLuminanceSource.cs index fef317c..81a3b5b 100644 --- a/Camera.MAUI/ZXingHelper/RGBLuminanceSource.cs +++ b/Camera.MAUI.Barcode.ZXing/RGBLuminanceSource.cs @@ -1,8 +1,8 @@ using ZXing; -namespace Camera.MAUI.ZXingHelper; +namespace Camera.MAUI.Barcode.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.Barcode.ZXing/ZXingBarcodeDecoder.cs b/Camera.MAUI.Barcode.ZXing/ZXingBarcodeDecoder.cs new file mode 100644 index 0000000..e9fb728 --- /dev/null +++ b/Camera.MAUI.Barcode.ZXing/ZXingBarcodeDecoder.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Camera.MAUI.Barcode; +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.Barcode.ZXing +{ + public class ZXingBarcodeDecoder : BindableObject, IBarcodeDecoder + { + #region Public Fields + + public static readonly BindableProperty BarCodeOptionsProperty = BindableProperty.Create(nameof(BarCodeOptions), typeof(BarcodeDecodeOptions), typeof(ZXingBarcodeDecoder), new BarcodeDecodeOptions(), propertyChanged: BarCodeOptionsChanged); + public static readonly BindableProperty BarCodeResultsProperty = BindableProperty.Create(nameof(BarCodeResults), typeof(BarcodeResult[]), typeof(ZXingBarcodeDecoder), null, BindingMode.OneWayToSource); + + #endregion Public Fields + + #region Private Fields + + private readonly BarcodeReaderGeneric BarcodeReader; + + #endregion Private Fields + + #region Public Constructors + + public ZXingBarcodeDecoder() + { + BarcodeReader = new BarcodeReaderGeneric(); + BarCodeOptions = new() + { + PossibleFormats = { BarcodeFormat.QR_CODE, BarcodeFormat.DATA_MATRIX }, + AutoRotate = true, + }; + } + + #endregion Public Constructors + + #region Public Events + + /// + /// Event launched every time a code is detected in the image if "BarCodeDetectionEnabled" is set to true. + /// + public event IBarcodeDecoder.BarcodeResultHandler BarcodeDetected; + + #endregion Public Events + + #region Public Properties + + /// + /// 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 BarcodeResult[] BarCodeResults + { + get { return (BarcodeResult[])GetValue(BarCodeResultsProperty); } + set { SetValue(BarCodeResultsProperty, value); } + } + + /// + /// If true BarcodeDetected 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 void ClearResults() + { + BarCodeResults = null; + } + + public 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 (BarCodeOptions.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 (BarCodeResults != null) + { + foreach (var result in nativeResults) + { + refresh = BarCodeResults.FirstOrDefault(b => b.Text == result.Text && b.BarcodeFormat == result.BarcodeFormat) == null; + if (refresh) break; + } + } + } + if (refresh) + { + BarCodeResults = nativeResults; + BarcodeDetected?.Invoke(this, new BarcodeEventArgs { Result = results.Select(x => x.ToNative()).ToArray() }); + } + } + } + catch { } + } + + #endregion Public Methods + + #region Private Methods + + private static void BarCodeOptionsChanged(BindableObject bindable, object oldValue, object newValue) + { + if (newValue != null && oldValue != newValue && bindable is ZXingBarcodeDecoder xingDecoder && newValue is BarcodeDecodeOptions options) + { + xingDecoder.BarcodeReader.AutoRotate = options.AutoRotate; + if (options.CharacterSet != string.Empty) + xingDecoder.BarcodeReader.Options.CharacterSet = options.CharacterSet; + xingDecoder.BarcodeReader.Options.PossibleFormats = options.PossibleFormats.Select(x => x.ToPlatform()).ToList(); + xingDecoder.BarcodeReader.Options.TryHarder = options.TryHarder; + xingDecoder.BarcodeReader.Options.TryInverted = options.TryInverted; + xingDecoder.BarcodeReader.Options.PureBarcode = options.PureBarcode; + } + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/Camera.MAUI/BarcodeRenderer.cs b/Camera.MAUI.Barcode.ZXing/ZXingBarcodeRenderer.cs similarity index 77% rename from Camera.MAUI/BarcodeRenderer.cs rename to Camera.MAUI.Barcode.ZXing/ZXingBarcodeRenderer.cs index ede671f..4031188 100644 --- a/Camera.MAUI/BarcodeRenderer.cs +++ b/Camera.MAUI.Barcode.ZXing/ZXingBarcodeRenderer.cs @@ -1,43 +1,60 @@ using ZXing.Common; using ZXing; +using Camera.MAUI.Barcode; +using BarcodeFormat = Camera.MAUI.Barcode.BarcodeFormat; +using Camera.MAUI.Barcode.ZXing; + #if WINDOWS using Windows.Graphics.Imaging; -using CustomRenderer = Camera.MAUI.Platforms.Windows.SoftwareBitmapRenderer; +using CustomRenderer = Camera.MAUI.Barcode.ZXing.Platforms.Windows.SoftwareBitmapRenderer; #elif IOS || MACCATALYST -using CustomRenderer = Camera.MAUI.Platforms.Apple.BitmapRenderer; +using CustomRenderer = Camera.MAUI.Barcode.ZXing.Platforms.MaciOS.BitmapRenderer; #elif ANDROID -using CustomRenderer = Camera.MAUI.Platforms.Android.BitmapRenderer; +using CustomRenderer = Camera.MAUI.Barcode.ZXing.Platforms.Android.BitmapRenderer; #else + using CustomRenderer = System.Object; + #endif -namespace Camera.MAUI; +namespace Camera.MAUI.Barcode.ZXing; -public class BarcodeRenderer +public class ZXingBarcodeRenderer : IBarcodeRenderer { - public Color Foreground { get; set; } = Colors.Black; - public Color Background { get; set; } = Colors.White; + #region Private Fields - private BarcodeWriterPixelData writer = new(); private CustomRenderer customRenderer = new(); + private 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, 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; + writer.Format = format.ToPlatform(); try { var bitMatrix = writer.Encode(code); if (bitMatrix != null) { - MemoryStream stream = new MemoryStream(); + var 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); + var bitmap = customRenderer.Render(bitMatrix, format.ToPlatform(), code); BitmapEncoder encoder = BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream.AsRandomAccessStream()).GetAwaiter().GetResult(); encoder.SetSoftwareBitmap(bitmap); encoder.FlushAsync().GetAwaiter().GetResult(); @@ -46,7 +63,7 @@ public ImageSource EncodeBarcode(string code, BarcodeFormat format = BarcodeForm #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); + var bitmap = customRenderer.Render(bitMatrix, format.ToPlatform(), code); bitmap.AsPNG().AsStream().CopyTo(stream); stream.Position = 0; imageSource = ImageSource.FromStream(() => stream); @@ -56,7 +73,7 @@ public ImageSource EncodeBarcode(string code, BarcodeFormat format = BarcodeForm 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); + var bitmap = customRenderer.Render(bitMatrix, format.ToPlatform(), code); bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Png, 100, stream); stream.Position = 0; imageSource = ImageSource.FromStream(() => stream); @@ -68,4 +85,6 @@ public ImageSource EncodeBarcode(string code, BarcodeFormat format = BarcodeForm } return imageSource; } -} + + #endregion Public Methods +} \ No newline at end of file diff --git a/Camera.MAUI.Barcode/BarcodeEventArgs.cs b/Camera.MAUI.Barcode/BarcodeEventArgs.cs new file mode 100644 index 0000000..4c6522b --- /dev/null +++ b/Camera.MAUI.Barcode/BarcodeEventArgs.cs @@ -0,0 +1,7 @@ +namespace Camera.MAUI.Barcode; + +public record BarcodeEventArgs +{ + public BarcodeResult[] Result { get; init; } + public byte[][] Images { get; init; } +} \ No newline at end of file diff --git a/Camera.MAUI.Barcode/BarcodeFormat.cs b/Camera.MAUI.Barcode/BarcodeFormat.cs new file mode 100644 index 0000000..6bc780b --- /dev/null +++ b/Camera.MAUI.Barcode/BarcodeFormat.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Camera.MAUI.Barcode +{ + // + // 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.Barcode/BarcodeResult.cs b/Camera.MAUI.Barcode/BarcodeResult.cs new file mode 100644 index 0000000..b322a65 --- /dev/null +++ b/Camera.MAUI.Barcode/BarcodeResult.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Camera.MAUI.Barcode +{ + public class BarcodeResult + { + public BarcodeResult(string text, byte[] rawBytes, Point[] resultPoints, BarcodeFormat barcodeFormat, IDictionary resultMetadata, int numBits, long timestamp) + { + Text = text; + RawBytes = rawBytes; + ResultPoints = resultPoints; + BarcodeFormat = barcodeFormat; + ResultMetadata = resultMetadata; + NumBits = numBits; + Timestamp = timestamp; + } + + // + // 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; } + + // + // 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.Barcode/Camera.MAUI.Barcode.csproj b/Camera.MAUI.Barcode/Camera.MAUI.Barcode.csproj new file mode 100644 index 0000000..5c37e61 --- /dev/null +++ b/Camera.MAUI.Barcode/Camera.MAUI.Barcode.csproj @@ -0,0 +1,30 @@ + + + + net7.0;net7.0-android;net7.0-ios;net7.0-maccatalyst + $(TargetFrameworks);net7.0-windows10.0.19041.0 + + + true + true + enable + + 14.2 + 14.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + 6.5 + + programatix + + + + + + + + + + + diff --git a/Camera.MAUI.Barcode/IBarcodeDecoder.cs b/Camera.MAUI.Barcode/IBarcodeDecoder.cs new file mode 100644 index 0000000..400ace0 --- /dev/null +++ b/Camera.MAUI.Barcode/IBarcodeDecoder.cs @@ -0,0 +1,37 @@ +#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.Barcode +{ + public interface IBarcodeDecoder + { + #region Public Delegates + + public delegate void BarcodeResultHandler(object sender, BarcodeEventArgs args); + + #endregion Public Delegates + + #region Public Events + + event BarcodeResultHandler BarcodeDetected; + + #endregion Public Events + + #region Public Methods + + void ClearResults(); + + void Decode(DecodeDataType data); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/Camera.MAUI.Barcode/IBarcodeRenderer.cs b/Camera.MAUI.Barcode/IBarcodeRenderer.cs new file mode 100644 index 0000000..23189a7 --- /dev/null +++ b/Camera.MAUI.Barcode/IBarcodeRenderer.cs @@ -0,0 +1,18 @@ +namespace Camera.MAUI.Barcode +{ + public interface IBarcodeRenderer + { + #region Public Properties + + Color Background { get; set; } + Color Foreground { get; set; } + + #endregion Public Properties + + #region Public Methods + + ImageSource EncodeBarcode(string code, BarcodeFormat format = BarcodeFormat.QR_CODE, int width = 400, int height = 400, int margin = 5); + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/Camera.MAUI.Test/BarcodeGenerationPage.xaml b/Camera.MAUI.Test/BarcodeGenerationPage.xaml index dce0e24..492d9ed 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.Barcode.ZXing;assembly=Camera.MAUI.Barcode.ZXing" Title="BarcodeGenerationPage"> -