diff --git a/BarcodeScanning.Native.Maui/BarcodeScanning.Native.Maui.csproj b/BarcodeScanning.Native.Maui/BarcodeScanning.Native.Maui.csproj
index cd22414..115769a 100644
--- a/BarcodeScanning.Native.Maui/BarcodeScanning.Native.Maui.csproj
+++ b/BarcodeScanning.Native.Maui/BarcodeScanning.Native.Maui.csproj
@@ -8,7 +8,7 @@
true
enable
true
- 1.5.7
+ 1.5.8
Alen Friščić
MIT
README.md
@@ -49,17 +49,12 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
diff --git a/BarcodeScanning.Native.Maui/CameraView.cs b/BarcodeScanning.Native.Maui/CameraView.cs
index 3313d21..38e2571 100644
--- a/BarcodeScanning.Native.Maui/CameraView.cs
+++ b/BarcodeScanning.Native.Maui/CameraView.cs
@@ -32,7 +32,7 @@ public ICommand OnImageCapturedCommand
public static readonly BindableProperty VibrationOnDetectedProperty = BindableProperty.Create(nameof(VibrationOnDetected)
, typeof(bool)
, typeof(CameraView)
- , true
+ , false
, BindingMode.TwoWay
, propertyChanged: (bindable, value, newValue) => ((CameraView)bindable).VibrationOnDetected = (bool)newValue);
///
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/BarcodeAnalyzer.cs b/BarcodeScanning.Native.Maui/Platform/Android/BarcodeAnalyzer.cs
index abb372a..0103b75 100644
--- a/BarcodeScanning.Native.Maui/Platform/Android/BarcodeAnalyzer.cs
+++ b/BarcodeScanning.Native.Maui/Platform/Android/BarcodeAnalyzer.cs
@@ -1,43 +1,139 @@
-using AndroidX.Camera.Core;
+using Android.Gms.Tasks;
+using AndroidX.Camera.Core;
+using AndroidX.Camera.View.Transform;
+using Microsoft.Maui.Graphics.Platform;
using System.Diagnostics;
+using Xamarin.Google.MLKit.Vision.Common;
using Size = Android.Util.Size;
namespace BarcodeScanning;
-internal class BarcodeAnalyzer : Java.Lang.Object, ImageAnalysis.IAnalyzer
+internal class BarcodeAnalyzer : Java.Lang.Object, ImageAnalysis.IAnalyzer, IOnSuccessListener, IOnCompleteListener
{
public Size DefaultTargetResolution => Methods.TargetResolution(CaptureQuality.Medium);
public int TargetCoordinateSystem => ImageAnalysis.CoordinateSystemOriginal;
+ private CoordinateTransform _coordinateTransform;
+ private bool _processInverted;
+ private IImageProxy _proxy;
+
+ private readonly HashSet _barcodeResults;
private readonly CameraManager _cameraManager;
internal BarcodeAnalyzer(CameraManager cameraManager)
{
+ _barcodeResults = [];
_cameraManager = cameraManager;
+ _processInverted = false;
}
public void Analyze(IImageProxy proxy)
{
try
{
- _cameraManager?.AnalyzeFrame(proxy);
+ _proxy = proxy;
+ _barcodeResults.Clear();
+
+ if (_cameraManager.CameraView.CaptureNextFrame)
+ {
+ _cameraManager.CameraView.CaptureNextFrame = false;
+ var image = new PlatformImage(_proxy.ToBitmap());
+ _cameraManager.CameraView.TriggerOnImageCaptured(image);
+ }
+
+ if (_cameraManager.RecalculateCoordinateTransform || _coordinateTransform is null)
+ _coordinateTransform = _cameraManager.GetCoordinateTransform(_proxy);
+
+ _processInverted = _cameraManager.CameraView.ForceInverted;
+ using var inputImage = InputImage.FromMediaImage(_proxy.Image, _proxy.ImageInfo.RotationDegrees);
+ _cameraManager.BarcodeScanner.Process(inputImage).AddOnSuccessListener(this).AddOnCompleteListener(this);
}
- catch (Exception ex)
+ catch (Exception)
{
- Debug.WriteLine(ex);
+ CloseProxy();
+ }
+ }
+
+ public void OnSuccess(Java.Lang.Object result)
+ {
+ try
+ {
+ Methods.ProcessBarcodeResult(result, _barcodeResults, _coordinateTransform);
+
+ if (!_processInverted)
+ {
+ if (_cameraManager.CameraView.AimMode)
+ {
+ var previewCenter = new Point(_cameraManager.PreviewView.Width / 2, _cameraManager.PreviewView.Height / 2);
+
+ foreach (var barcode in _barcodeResults)
+ {
+ if (!barcode.PreviewBoundingBox.Contains(previewCenter))
+ _barcodeResults.Remove(barcode);
+ }
+ }
+
+ if (_cameraManager.CameraView.ViewfinderMode)
+ {
+ var previewRect = new RectF(0, 0, _cameraManager.PreviewView.Width, _cameraManager.PreviewView.Height);
+
+ foreach (var barcode in _barcodeResults)
+ {
+ if (!previewRect.Contains(barcode.PreviewBoundingBox))
+ _barcodeResults.Remove(barcode);
+ }
+ }
+
+ _cameraManager?.CameraView?.DetectionFinished(_barcodeResults);
+ }
}
- finally
+ catch (Exception)
+ {
+ }
+ }
+
+ public void OnComplete(Android.Gms.Tasks.Task task)
+ {
+ if (_processInverted)
{
try
{
- proxy?.Close();
+ Methods.InvertLuminance(_proxy.Image);
+
+ _processInverted = false;
+ using var inputImage = InputImage.FromMediaImage(_proxy.Image, _proxy.ImageInfo.RotationDegrees);
+ _cameraManager.BarcodeScanner.Process(inputImage).AddOnSuccessListener(this).AddOnCompleteListener(this);
}
- catch (Exception ex)
+ catch (Exception)
{
- Debug.WriteLine(ex);
- MainThread.BeginInvokeOnMainThread(() => _cameraManager?.Start());
+ CloseProxy();
}
}
+ else
+ {
+ CloseProxy();
+ }
+
+ }
+
+ private void CloseProxy()
+ {
+ try
+ {
+ _proxy?.Close();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine(ex);
+ MainThread.BeginInvokeOnMainThread(() => _cameraManager?.Start());
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _coordinateTransform?.Dispose();
+
+ base.Dispose(disposing);
}
}
\ No newline at end of file
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/CameraManager.cs b/BarcodeScanning.Native.Maui/Platform/Android/CameraManager.cs
index faebe46..93dc2d3 100644
--- a/BarcodeScanning.Native.Maui/Platform/Android/CameraManager.cs
+++ b/BarcodeScanning.Native.Maui/Platform/Android/CameraManager.cs
@@ -1,63 +1,84 @@
using Android.Content;
-using Android.Gms.Extensions;
using Android.Graphics;
using Android.Widget;
using AndroidX.Camera.Core;
using AndroidX.Camera.View;
using AndroidX.Camera.View.Transform;
+using AndroidX.Core.Content;
using AndroidX.Lifecycle;
using Java.Util.Concurrent;
-using Microsoft.Maui.Graphics.Platform;
using Microsoft.Maui.Platform;
using Xamarin.Google.MLKit.Vision.BarCode;
-using Xamarin.Google.MLKit.Vision.Common;
using static Android.Views.ViewGroup;
using Color = Android.Graphics.Color;
using MLKitBarcodeScanning = Xamarin.Google.MLKit.Vision.BarCode.BarcodeScanning;
using Paint = Android.Graphics.Paint;
-using Point = Microsoft.Maui.Graphics.Point;
-using RectF = Microsoft.Maui.Graphics.RectF;
namespace BarcodeScanning;
internal class CameraManager : IDisposable
{
internal BarcodeView BarcodeView { get => _barcodeView; }
+ internal IBarcodeScanner BarcodeScanner { get => _barcodeScanner; }
+ internal CameraView CameraView { get => _cameraView; }
+ internal PreviewView PreviewView { get => _previewView; }
+
+ internal CameraState OpenedCameraState { get; set; }
+ internal bool RecalculateCoordinateTransform { get; set; }
- private BarcodeAnalyzer _barcodeAnalyzer;
private IBarcodeScanner _barcodeScanner;
+ private ICameraInfo _currentCameraInfo;
+ private readonly BarcodeAnalyzer _barcodeAnalyzer;
private readonly BarcodeView _barcodeView;
private readonly CameraView _cameraView;
private readonly Context _context;
private readonly IExecutorService _cameraExecutor;
private readonly ImageView _imageView;
private readonly LifecycleCameraController _cameraController;
+ private readonly ILifecycleOwner _lifecycleOwner;
private readonly PreviewView _previewView;
+ private readonly PreviewViewOnLayoutChangeListener _previewViewOnLayoutChangeListener;
private readonly RelativeLayout _relativeLayout;
- private readonly ZoomStateObserver _zoomStateObserver;
+ private readonly CameraStateObserver _cameraStateObserver;
- private readonly HashSet _barcodeResults = [];
private const int aimRadius = 25;
- private bool _cameraRunning = false;
internal CameraManager(CameraView cameraView, Context context)
{
_context = context;
_cameraView = cameraView;
+ if (_context is ILifecycleOwner)
+ _lifecycleOwner = _context as ILifecycleOwner;
+ else if ((_context as ContextWrapper)?.BaseContext is ILifecycleOwner)
+ _lifecycleOwner = (_context as ContextWrapper)?.BaseContext as ILifecycleOwner;
+ else if (Platform.CurrentActivity is ILifecycleOwner)
+ _lifecycleOwner = Platform.CurrentActivity as ILifecycleOwner;
+ else
+ _lifecycleOwner = null;
+
_cameraExecutor = Executors.NewSingleThreadExecutor();
- _zoomStateObserver = new ZoomStateObserver(this, _cameraView);
+ _barcodeAnalyzer = new BarcodeAnalyzer(this);
+
+ _cameraStateObserver = new CameraStateObserver(this, _cameraView);
_cameraController = new LifecycleCameraController(_context)
{
TapToFocusEnabled = _cameraView?.TapToFocusEnabled ?? false,
ImageAnalysisBackpressureStrategy = ImageAnalysis.StrategyKeepOnlyLatest
};
_cameraController.SetEnabledUseCases(CameraController.ImageAnalysis);
- _cameraController.ZoomState.ObserveForever(_zoomStateObserver);
-
+ _cameraController.ZoomState.ObserveForever(_cameraStateObserver);
+ _cameraController.InitializationFuture.AddListener(new Java.Lang.Runnable(() =>
+ {
+ _currentCameraInfo?.CameraState.RemoveObserver(_cameraStateObserver);
+ _currentCameraInfo = _cameraController.CameraInfo;
+ _currentCameraInfo?.CameraState.ObserveForever(_cameraStateObserver);
+ }), ContextCompat.GetMainExecutor(_context));
+
+ _previewViewOnLayoutChangeListener = new PreviewViewOnLayoutChangeListener(this);
_previewView = new PreviewView(_context)
{
Controller = _cameraController,
@@ -66,6 +87,7 @@ internal CameraManager(CameraView cameraView, Context context)
_previewView.SetBackgroundColor(_cameraView?.BackgroundColor?.ToPlatform() ?? Color.Transparent);
_previewView.SetImplementationMode(PreviewView.ImplementationMode.Compatible);
_previewView.SetScaleType(PreviewView.ScaleType.FillCenter);
+ _previewView.AddOnLayoutChangeListener(_previewViewOnLayoutChangeListener);
var layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
layoutParams.AddRule(LayoutRules.CenterInParent);
@@ -97,46 +119,26 @@ internal CameraManager(CameraView cameraView, Context context)
//TODO Implement camera-mlkit-vision
//https://developer.android.com/reference/androidx/camera/mlkit/vision/MlKitAnalyzer
- internal void Start()
+ internal void Start(bool skipResolution = false)
{
if (_cameraController is not null)
{
- if (_cameraRunning)
- {
+ if (OpenedCameraState?.GetType() != CameraState.Type.Closed)
_cameraController.Unbind();
- _cameraRunning = false;
- }
-
- ILifecycleOwner lifecycleOwner = null;
- if (_context is ILifecycleOwner)
- lifecycleOwner = _context as ILifecycleOwner;
- else if ((_context as ContextWrapper)?.BaseContext is ILifecycleOwner)
- lifecycleOwner = (_context as ContextWrapper)?.BaseContext as ILifecycleOwner;
- else if (Platform.CurrentActivity is ILifecycleOwner)
- lifecycleOwner = Platform.CurrentActivity as ILifecycleOwner;
-
- if (lifecycleOwner is null)
- return;
if (_cameraController.CameraSelector is null)
UpdateCamera();
- if (_cameraController.ImageAnalysisTargetSize is null)
+ if (_cameraController.ImageAnalysisTargetSize is null && !skipResolution)
UpdateResolution();
-
- if (_cameraController is not null && _cameraExecutor is not null)
- {
- _cameraController.ClearImageAnalysisAnalyzer();
- _barcodeAnalyzer?.Dispose();
- _barcodeAnalyzer = new BarcodeAnalyzer(this);
+ if (_barcodeAnalyzer is not null && _cameraExecutor is not null)
_cameraController.SetImageAnalysisAnalyzer(_cameraExecutor, _barcodeAnalyzer);
- }
UpdateSymbologies();
UpdateTorch();
- _cameraController.BindToLifecycle(lifecycleOwner);
- _cameraRunning = true;
- }
+ if (_lifecycleOwner is not null)
+ _cameraController.BindToLifecycle(_lifecycleOwner);
+ }
}
internal void Stop()
@@ -151,10 +153,7 @@ internal void Stop()
_cameraView.TorchOn = false;
}
- if (_cameraRunning)
- _cameraController.Unbind();
-
- _cameraRunning = false;
+ _cameraController.Unbind();
}
}
@@ -197,8 +196,8 @@ internal void UpdateResolution()
if (_cameraController is not null)
_cameraController.ImageAnalysisTargetSize = new CameraController.OutputSize(Methods.TargetResolution(_cameraView?.CaptureQuality));
- if (_cameraRunning)
- Start();
+ if (OpenedCameraState?.GetType() == CameraState.Type.Open || OpenedCameraState?.GetType() == CameraState.Type.Opening || OpenedCameraState?.GetType() == CameraState.Type.PendingOpen)
+ Start(true);
}
internal void UpdateSymbologies()
@@ -239,72 +238,19 @@ internal void UpdateZoomFactor()
}
}
- internal void AnalyzeFrame(IImageProxy proxy)
- {
- if (proxy is not null && _cameraView is not null && _previewView is not null && _barcodeResults is not null && _barcodeScanner is not null && !_cameraView.PauseScanning)
- {
- DetectBarcode(proxy).Wait(2000);
-
- if (_cameraView.CaptureNextFrame)
- CaptureImage(proxy);
- }
- }
-
- private void CaptureImage(IImageProxy proxy)
- {
- _cameraView.CaptureNextFrame = false;
- var image = new PlatformImage(proxy.ToBitmap());
- _cameraView.TriggerOnImageCaptured(image);
- }
-
- private async Task DetectBarcode(IImageProxy proxy)
+ internal CoordinateTransform GetCoordinateTransform(IImageProxy proxy)
{
- _barcodeResults.Clear();
- using var target = await MainThread.InvokeOnMainThreadAsync(() => _previewView.OutputTransform).ConfigureAwait(false);
- using var source = new ImageProxyTransformFactory
+ var imageOutputTransform = new ImageProxyTransformFactory
{
UsingRotationDegrees = true
}
.GetOutputTransform(proxy);
- using var coordinateTransform = new CoordinateTransform(source, target);
-
- using var image = InputImage.FromMediaImage(proxy.Image, proxy.ImageInfo.RotationDegrees);
- using var results = await _barcodeScanner.Process(image).AsAsync().ConfigureAwait(false);
-
- Methods.ProcessBarcodeResult(results, _barcodeResults, coordinateTransform);
-
- if (_cameraView.ForceInverted)
- {
- Methods.InvertLuminance(proxy.Image);
- using var invertedimage = InputImage.FromMediaImage(proxy.Image, proxy.ImageInfo.RotationDegrees);
- using var invertedresults = await _barcodeScanner.Process(invertedimage).AsAsync().ConfigureAwait(false);
-
- Methods.ProcessBarcodeResult(invertedresults, _barcodeResults, coordinateTransform);
- }
-
- if (_cameraView.AimMode)
- {
- var previewCenter = new Point(_previewView.Width / 2, _previewView.Height / 2);
-
- foreach (var barcode in _barcodeResults)
- {
- if (!barcode.PreviewBoundingBox.Contains(previewCenter))
- _barcodeResults.Remove(barcode);
- }
- }
-
- if (_cameraView.ViewfinderMode)
- {
- var previewRect = new RectF(0, 0, _previewView.Width, _previewView.Height);
-
- foreach (var barcode in _barcodeResults)
- {
- if (!previewRect.Contains(barcode.PreviewBoundingBox))
- _barcodeResults.Remove(barcode);
- }
- }
+ var previewOutputTransform = MainThread.InvokeOnMainThreadAsync(() => _previewView?.OutputTransform).Result;
- _cameraView.DetectionFinished(_barcodeResults);
+ if (imageOutputTransform is not null && previewOutputTransform is not null)
+ return new CoordinateTransform(imageOutputTransform, previewOutputTransform);
+ else
+ return null;
}
private void MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e)
@@ -316,7 +262,6 @@ private void MainDisplayInfoChanged(object sender, DisplayInfoChangedEventArgs e
{
try
{
- if (_cameraRunning && _cameraView.CameraEnabled)
UpdateResolution();
}
catch (Exception)
@@ -337,17 +282,13 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
- try
- {
- DeviceDisplay.Current.MainDisplayInfoChanged -= MainDisplayInfoChanged;
- }
- catch (Exception)
- {
- }
+ DeviceDisplay.Current.MainDisplayInfoChanged -= MainDisplayInfoChanged;
Stop();
- _cameraController?.ZoomState.RemoveObserver(_zoomStateObserver);
+ _cameraController?.ZoomState.RemoveObserver(_cameraStateObserver);
+ _currentCameraInfo?.CameraState.RemoveObserver(_cameraStateObserver);
+ _previewView?.RemoveOnLayoutChangeListener(_previewViewOnLayoutChangeListener);
_barcodeView?.RemoveAllViews();
_relativeLayout?.RemoveAllViews();
@@ -355,8 +296,10 @@ protected virtual void Dispose(bool disposing)
_relativeLayout?.Dispose();
_imageView?.Dispose();
_previewView?.Dispose();
+ _previewViewOnLayoutChangeListener?.Dispose();
_cameraController?.Dispose();
- _zoomStateObserver?.Dispose();
+ _currentCameraInfo?.Dispose();
+ _cameraStateObserver?.Dispose();
_barcodeAnalyzer?.Dispose();
_barcodeScanner?.Dispose();
_cameraExecutor?.Dispose();
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/CameraStateObserver.cs b/BarcodeScanning.Native.Maui/Platform/Android/CameraStateObserver.cs
new file mode 100644
index 0000000..5bcc920
--- /dev/null
+++ b/BarcodeScanning.Native.Maui/Platform/Android/CameraStateObserver.cs
@@ -0,0 +1,38 @@
+using AndroidX.Camera.Core;
+using AndroidX.Lifecycle;
+
+namespace BarcodeScanning;
+
+internal class CameraStateObserver : Java.Lang.Object, IObserver
+{
+ private readonly CameraManager _cameraManager;
+ private readonly CameraView _cameraView;
+
+ internal CameraStateObserver(CameraManager cameraManager, CameraView cameraView)
+ {
+ _cameraManager = cameraManager;
+ _cameraView = cameraView;
+ }
+
+ public void OnChanged(Java.Lang.Object value)
+ {
+ if (value is not null && _cameraView is not null && _cameraManager is not null)
+ {
+ if (value is IZoomState zoomState)
+ {
+ _cameraView.CurrentZoomFactor = zoomState.ZoomRatio;
+ _cameraView.MinZoomFactor = zoomState.MinZoomRatio;
+ _cameraView.MaxZoomFactor = zoomState.MaxZoomRatio;
+
+ _cameraManager.UpdateZoomFactor();
+ }
+
+ if (value is CameraState cameraState)
+ {
+ _cameraManager.OpenedCameraState = cameraState;
+ }
+
+ _cameraManager.RecalculateCoordinateTransform = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/Methods.cs b/BarcodeScanning.Native.Maui/Platform/Android/Methods.cs
index 3c36c7f..da54264 100644
--- a/BarcodeScanning.Native.Maui/Platform/Android/Methods.cs
+++ b/BarcodeScanning.Native.Maui/Platform/Android/Methods.cs
@@ -35,16 +35,17 @@ public static async Task> ScanFromImageAsync(Stream strea
=> await ProcessBitmapAsync(await BitmapFactory.DecodeStreamAsync(stream));
private static async Task> ProcessBitmapAsync(Bitmap bitmap)
{
+ var barcodeResults = new HashSet();
+
if (bitmap is null)
- return null;
+ return barcodeResults;
- var barcodeResults = new HashSet();
using var scanner = Xamarin.Google.MLKit.Vision.BarCode.BarcodeScanning.GetClient(new BarcodeScannerOptions.Builder()
.SetBarcodeFormats(Barcode.FormatAllFormats)
.Build());
using var image = InputImage.FromBitmap(bitmap, 0);
- using var results = await scanner.Process(image).AsAsync();
+ using var results = await scanner.Process(image);
ProcessBarcodeResult(results, barcodeResults);
using var invertedBitmap = Bitmap.CreateBitmap(bitmap.Height, bitmap.Width, Bitmap.Config.Argb8888);
@@ -65,7 +66,7 @@ private static async Task> ProcessBitmapAsync(Bitmap bitm
canvas.DrawBitmap(bitmap, 0, 0, paint);
using var invertedImage = InputImage.FromBitmap(invertedBitmap, 0);
- using var invertedResults = await scanner.Process(invertedImage).AsAsync();
+ using var invertedResults = await scanner.Process(invertedImage);
ProcessBarcodeResult(invertedResults, barcodeResults);
return barcodeResults;
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/PreviewViewOnLayoutChangeListener.cs b/BarcodeScanning.Native.Maui/Platform/Android/PreviewViewOnLayoutChangeListener.cs
new file mode 100644
index 0000000..7be8cea
--- /dev/null
+++ b/BarcodeScanning.Native.Maui/Platform/Android/PreviewViewOnLayoutChangeListener.cs
@@ -0,0 +1,19 @@
+using static Android.Views.View;
+
+namespace BarcodeScanning;
+
+internal class PreviewViewOnLayoutChangeListener : Java.Lang.Object, IOnLayoutChangeListener
+{
+ private readonly CameraManager _cameraManager;
+
+ internal PreviewViewOnLayoutChangeListener(CameraManager cameraManager)
+ {
+ _cameraManager = cameraManager;
+ }
+
+ public void OnLayoutChange(Android.Views.View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)
+ {
+ if (_cameraManager is not null)
+ _cameraManager.RecalculateCoordinateTransform = true;
+ }
+}
\ No newline at end of file
diff --git a/BarcodeScanning.Native.Maui/Platform/Android/ZoomStateObserver.cs b/BarcodeScanning.Native.Maui/Platform/Android/ZoomStateObserver.cs
deleted file mode 100644
index e26803f..0000000
--- a/BarcodeScanning.Native.Maui/Platform/Android/ZoomStateObserver.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using AndroidX.Camera.Core;
-using AndroidX.Lifecycle;
-
-namespace BarcodeScanning;
-
-internal class ZoomStateObserver : Java.Lang.Object, IObserver
-{
- private readonly CameraManager _cameraManager;
- private readonly CameraView _cameraView;
-
- internal ZoomStateObserver(CameraManager cameraManager, CameraView cameraView)
- {
- _cameraManager = cameraManager;
- _cameraView = cameraView;
- }
-
- public void OnChanged(Java.Lang.Object value)
- {
- if (value is not null && _cameraView is not null && value is IZoomState state)
- {
- _cameraView.CurrentZoomFactor = state.ZoomRatio;
- _cameraView.MinZoomFactor = state.MinZoomRatio;
- _cameraView.MaxZoomFactor = state.MaxZoomRatio;
-
- _cameraManager?.UpdateZoomFactor();
- }
- }
-}
\ No newline at end of file
diff --git a/BarcodeScanning.Native.Maui/Platform/MaciOS/BarcodeAnalyzer.cs b/BarcodeScanning.Native.Maui/Platform/MaciOS/BarcodeAnalyzer.cs
index 087eec0..23fd8a9 100644
--- a/BarcodeScanning.Native.Maui/Platform/MaciOS/BarcodeAnalyzer.cs
+++ b/BarcodeScanning.Native.Maui/Platform/MaciOS/BarcodeAnalyzer.cs
@@ -1,23 +1,78 @@
using AVFoundation;
+using CoreImage;
using CoreMedia;
+using Microsoft.Maui.Graphics.Platform;
using System.Diagnostics;
+using UIKit;
+using Vision;
namespace BarcodeScanning;
internal class BarcodeAnalyzer : AVCaptureVideoDataOutputSampleBufferDelegate
{
+ private readonly HashSet _barcodeResults;
private readonly CameraManager _cameraManager;
+ private readonly VNDetectBarcodesRequest _detectBarcodesRequest;
+ private readonly VNSequenceRequestHandler _sequenceRequestHandler;
internal BarcodeAnalyzer(CameraManager cameraManager)
{
+ _barcodeResults = [];
_cameraManager = cameraManager;
+ _detectBarcodesRequest = new VNDetectBarcodesRequest((request, error) =>
+ {
+ if (error is null)
+ Methods.ProcessBarcodeResult(request.GetResults(), _barcodeResults, _cameraManager.PreviewLayer);
+ });
+ _sequenceRequestHandler = new VNSequenceRequestHandler();
+ }
+
+ internal void UpdateSymbologies(VNBarcodeSymbology[] symbologies)
+ {
+ _detectBarcodesRequest.Symbologies = symbologies;
}
public override void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
{
try
{
- _cameraManager?.AnalyzeFrame(sampleBuffer);
+ if (_cameraManager.CameraView.CaptureNextFrame)
+ {
+ _cameraManager.CameraView.CaptureNextFrame = false;
+ using var imageBuffer = sampleBuffer.GetImageBuffer();
+ using var cIImage = new CIImage(imageBuffer);
+ using var cIContext = new CIContext();
+ using var cGImage = cIContext.CreateCGImage(cIImage, cIImage.Extent);
+ var image = new PlatformImage(new UIImage(cGImage));
+ _cameraManager.CameraView.TriggerOnImageCaptured(image);
+ }
+
+ _barcodeResults.Clear();
+ _sequenceRequestHandler?.Perform([_detectBarcodesRequest], sampleBuffer, out _);
+
+ if (_cameraManager.CameraView.AimMode)
+ {
+ var previewCenter = new Point(_cameraManager.PreviewLayer.Bounds.Width / 2, _cameraManager.PreviewLayer.Bounds.Height / 2);
+
+ foreach (var barcode in _barcodeResults)
+ {
+ if (!barcode.PreviewBoundingBox.Contains(previewCenter))
+ _barcodeResults.Remove(barcode);
+ }
+ }
+
+ if (_cameraManager.CameraView.ViewfinderMode)
+ {
+ var previewRect = new RectF(0, 0, (float)_cameraManager.PreviewLayer.Bounds.Width, (float)_cameraManager.PreviewLayer.Bounds.Height);
+
+ foreach (var barcode in _barcodeResults)
+ {
+ if (!previewRect.Contains(barcode.PreviewBoundingBox))
+ _barcodeResults.Remove(barcode);
+ }
+ }
+
+ _cameraManager.CameraView.DetectionFinished(_barcodeResults);
}
catch (Exception ex)
{
@@ -36,4 +91,12 @@ public override void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSamp
}
}
}
+
+ protected override void Dispose(bool disposing)
+ {
+ _sequenceRequestHandler?.Dispose();
+ _detectBarcodesRequest?.Dispose();
+
+ base.Dispose(disposing);
+ }
}
\ No newline at end of file
diff --git a/BarcodeScanning.Native.Maui/Platform/MaciOS/CameraManager.cs b/BarcodeScanning.Native.Maui/Platform/MaciOS/CameraManager.cs
index 340747f..469be65 100644
--- a/BarcodeScanning.Native.Maui/Platform/MaciOS/CameraManager.cs
+++ b/BarcodeScanning.Native.Maui/Platform/MaciOS/CameraManager.cs
@@ -2,38 +2,33 @@
using CoreAnimation;
using CoreFoundation;
using CoreGraphics;
-using CoreImage;
-using CoreMedia;
using Foundation;
-using Microsoft.Maui.Graphics.Platform;
using Microsoft.Maui.Platform;
using System.Diagnostics;
using UIKit;
-using Vision;
namespace BarcodeScanning;
internal class CameraManager : IDisposable
{
+ internal AVCaptureVideoPreviewLayer PreviewLayer { get => _previewLayer; }
internal BarcodeView BarcodeView { get => _barcodeView; }
+ internal CameraView CameraView { get => _cameraView; }
private AVCaptureDevice _captureDevice;
private AVCaptureInput _captureInput;
- private BarcodeAnalyzer _barcodeAnalyzer;
private readonly AVCaptureVideoDataOutput _videoDataOutput;
private readonly AVCaptureVideoPreviewLayer _previewLayer;
private readonly AVCaptureSession _captureSession;
+ private readonly BarcodeAnalyzer _barcodeAnalyzer;
private readonly BarcodeView _barcodeView;
private readonly CameraView _cameraView;
private readonly CAShapeLayer _shapeLayer;
private readonly DispatchQueue _dispatchQueue;
private readonly NSObject _subjectAreaChangedNotificaion;
- private readonly VNDetectBarcodesRequest _detectBarcodesRequest;
- private readonly VNSequenceRequestHandler _sequenceRequestHandler;
private readonly UITapGestureRecognizer _uITapGestureRecognizer;
- private readonly HashSet _barcodeResults = [];
private const int aimRadius = 8;
internal CameraManager(CameraView cameraView)
@@ -41,7 +36,7 @@ internal CameraManager(CameraView cameraView)
_cameraView = cameraView;
_captureSession = new AVCaptureSession();
- _sequenceRequestHandler = new VNSequenceRequestHandler();
+ _barcodeAnalyzer = new BarcodeAnalyzer(this);
_dispatchQueue = new DispatchQueue("com.barcodescanning.maui.sessionQueue", new DispatchQueue.Attributes()
{
QualityOfService = DispatchQualityOfService.UserInitiated
@@ -50,11 +45,6 @@ internal CameraManager(CameraView cameraView)
{
AlwaysDiscardsLateVideoFrames = true
};
- _detectBarcodesRequest = new VNDetectBarcodesRequest((request, error) =>
- {
- if (error is null)
- Methods.ProcessBarcodeResult(request.GetResults(), _barcodeResults, _previewLayer);
- });
_uITapGestureRecognizer = new UITapGestureRecognizer(FocusOnTap);
_subjectAreaChangedNotificaion = NSNotificationCenter.DefaultCenter.AddObserver(AVCaptureDevice.SubjectAreaDidChangeNotification, (n) =>
@@ -109,8 +99,6 @@ internal void Start()
if (_videoDataOutput is not null)
{
_videoDataOutput.SetSampleBufferDelegate(null, null);
- _barcodeAnalyzer?.Dispose();
- _barcodeAnalyzer = new BarcodeAnalyzer(this);
_videoDataOutput.SetSampleBufferDelegate(_barcodeAnalyzer, DispatchQueue.DefaultGlobalQueue);
}
@@ -222,8 +210,8 @@ internal void UpdateResolution()
internal void UpdateSymbologies()
{
- if (_detectBarcodesRequest is not null && _cameraView is not null)
- _detectBarcodesRequest.Symbologies = Methods.SelectedSymbologies(_cameraView.BarcodeSymbologies);
+ if (_barcodeAnalyzer is not null && _cameraView is not null)
+ _barcodeAnalyzer.UpdateSymbologies(Methods.SelectedSymbologies(_cameraView.BarcodeSymbologies));
}
internal void UpdateTapToFocus() {}
@@ -271,58 +259,6 @@ internal void UpdateZoomFactor()
}
}
- internal void AnalyzeFrame(CMSampleBuffer sampleBuffer)
- {
- if (sampleBuffer is not null && _cameraView is not null && _previewLayer is not null && _barcodeResults is not null && _detectBarcodesRequest is not null && !_cameraView.PauseScanning)
- {
- DetectBarcode(sampleBuffer);
-
- if (_cameraView.CaptureNextFrame)
- CaptureImage(sampleBuffer);
- }
- }
-
- private void CaptureImage(CMSampleBuffer sampleBuffer)
- {
- _cameraView.CaptureNextFrame = false;
- using var imageBuffer = sampleBuffer.GetImageBuffer();
- using var cIImage = new CIImage(imageBuffer);
- using var cIContext = new CIContext();
- using var cGImage = cIContext.CreateCGImage(cIImage, cIImage.Extent);
- var image = new PlatformImage(new UIImage(cGImage));
- _cameraView.TriggerOnImageCaptured(image);
- }
-
- private void DetectBarcode(CMSampleBuffer sampleBuffer)
- {
- _barcodeResults.Clear();
- _sequenceRequestHandler?.Perform([_detectBarcodesRequest], sampleBuffer, out _);
-
- if (_cameraView.AimMode)
- {
- var previewCenter = new Point(_previewLayer.Bounds.Width / 2, _previewLayer.Bounds.Height / 2);
-
- foreach (var barcode in _barcodeResults)
- {
- if (!barcode.PreviewBoundingBox.Contains(previewCenter))
- _barcodeResults.Remove(barcode);
- }
- }
-
- if (_cameraView.ViewfinderMode)
- {
- var previewRect = new RectF(0, 0, (float)_previewLayer.Bounds.Width, (float)_previewLayer.Bounds.Height);
-
- foreach (var barcode in _barcodeResults)
- {
- if (!previewRect.Contains(barcode.PreviewBoundingBox))
- _barcodeResults.Remove(barcode);
- }
- }
-
- _cameraView.DetectionFinished(_barcodeResults);
- }
-
private void DeviceLock(Action action)
{
DispatchQueue.MainQueue.DispatchAsync(() =>
@@ -406,8 +342,6 @@ protected virtual void Dispose(bool disposing)
_barcodeAnalyzer?.Dispose();
_captureDevice?.Dispose();
- _sequenceRequestHandler?.Dispose();
- _detectBarcodesRequest?.Dispose();
_uITapGestureRecognizer?.Dispose();
_subjectAreaChangedNotificaion?.Dispose();
_dispatchQueue?.Dispose();
diff --git a/BarcodeScanning.Native.Maui/Platform/MaciOS/Methods.cs b/BarcodeScanning.Native.Maui/Platform/MaciOS/Methods.cs
index 9094f7a..bc8ec42 100644
--- a/BarcodeScanning.Native.Maui/Platform/MaciOS/Methods.cs
+++ b/BarcodeScanning.Native.Maui/Platform/MaciOS/Methods.cs
@@ -1,6 +1,5 @@
using AVFoundation;
using CoreGraphics;
-using CoreImage;
using Foundation;
using Microsoft.Maui.Graphics.Platform;
using System.Text;
@@ -21,8 +20,10 @@ public static async Task> ScanFromImageAsync(Stream strea
=> await ProcessBitmapAsync(UIImage.LoadFromData(NSData.FromStream(stream)));
private static async Task> ProcessBitmapAsync(UIImage image)
{
+ var barcodeResults = new HashSet();
+
if (image is null)
- return null;
+ return barcodeResults;
VNBarcodeObservation[] observations = null;
using var barcodeRequest = new VNDetectBarcodesRequest((request, error) => {
@@ -31,7 +32,6 @@ private static async Task> ProcessBitmapAsync(UIImage ima
});
using var handler = new VNImageRequestHandler(image.CGImage, new NSDictionary());
await Task.Run(() => handler.Perform([barcodeRequest], out _));
- var barcodeResults = new HashSet();
ProcessBarcodeResult(observations, barcodeResults);
return barcodeResults;
}
@@ -43,7 +43,6 @@ internal static void ProcessBarcodeResult(VNBarcodeObservation[] inputResults, H
lock (outputResults)
{
- //TODO NSProcessInfo.ProcessInfo.OperatingSystemVersion.Major > 17 and add payloadData
foreach (var barcode in inputResults)
{
outputResults.Add(new BarcodeResult()
@@ -52,7 +51,7 @@ internal static void ProcessBarcodeResult(VNBarcodeObservation[] inputResults, H
BarcodeFormat = ConvertFromIOSFormats(barcode.Symbology),
DisplayValue = barcode.PayloadStringValue,
RawValue = barcode.PayloadStringValue,
- RawBytes = GetRawBytes(barcode) ?? Encoding.ASCII.GetBytes(barcode.PayloadStringValue),
+ RawBytes = OperatingSystem.IsIOSVersionAtLeast(17) ? [.. barcode.PayloadData] : Encoding.ASCII.GetBytes(barcode.PayloadStringValue),
PreviewBoundingBox = previewLayer?.MapToLayerCoordinates(InvertY(barcode.BoundingBox)).AsRectangleF() ?? RectF.Zero,
ImageBoundingBox = barcode.BoundingBox.AsRectangleF()
});
@@ -168,18 +167,6 @@ private static CGRect InvertY(CGRect rect)
return new CGRect(rect.X, 1 - rect.Y - rect.Height, rect.Width, rect.Height);
}
- private static byte[] GetRawBytes(VNBarcodeObservation barcodeObservation)
- {
- return barcodeObservation.Symbology switch
- {
- VNBarcodeSymbology.QR => ((CIQRCodeDescriptor)barcodeObservation.BarcodeDescriptor)?.ErrorCorrectedPayload?.ToArray(),
- VNBarcodeSymbology.Aztec => ((CIAztecCodeDescriptor)barcodeObservation.BarcodeDescriptor)?.ErrorCorrectedPayload?.ToArray(),
- VNBarcodeSymbology.Pdf417 => ((CIPdf417CodeDescriptor)barcodeObservation.BarcodeDescriptor)?.ErrorCorrectedPayload?.ToArray(),
- VNBarcodeSymbology.DataMatrix => ((CIDataMatrixCodeDescriptor)barcodeObservation.BarcodeDescriptor)?.ErrorCorrectedPayload?.ToArray(),
- _ => null
- };
- }
-
private static NSString SessionPresetTranslator(CaptureQuality quality)
{
return quality switch
diff --git a/BarcodeScanning.Native.Maui/Shared/BarcodeResult.cs b/BarcodeScanning.Native.Maui/Shared/BarcodeResult.cs
index b59450a..1b0aa4b 100644
--- a/BarcodeScanning.Native.Maui/Shared/BarcodeResult.cs
+++ b/BarcodeScanning.Native.Maui/Shared/BarcodeResult.cs
@@ -15,7 +15,7 @@ public bool Equals(BarcodeResult other)
if (other is null)
return false;
- if (this.RawValue == other.RawValue && this.ImageBoundingBox.IntersectsWith(other.ImageBoundingBox))
+ if (this.DisplayValue == other.DisplayValue && this.ImageBoundingBox.IntersectsWith(other.ImageBoundingBox))
return true;
else
return false;
@@ -31,6 +31,6 @@ public override bool Equals(object obj)
}
public override int GetHashCode()
{
- return this.RawValue.GetHashCode();
+ return this.DisplayValue.GetHashCode();
}
}
\ No newline at end of file