diff --git a/Samples/Forms/Core/CustomScanPage.cs b/Samples/Forms/Core/CustomScanPage.cs index d7f990cc6..2675d3363 100644 --- a/Samples/Forms/Core/CustomScanPage.cs +++ b/Samples/Forms/Core/CustomScanPage.cs @@ -21,17 +21,27 @@ public CustomScanPage () : base () VerticalOptions = LayoutOptions.FillAndExpand, AutomationId = "zxingScannerView", }; + + var formats = zxing.Options.PossibleFormats; + formats.Clear(); + formats.Add(ZXing.BarcodeFormat.CODE_128); + formats.Add(ZXing.BarcodeFormat.CODE_39); + formats.Add(ZXing.BarcodeFormat.QR_CODE); + + zxing.Options.DelayBetweenContinuousScans = 1000; + + zxing.IsTorchOn = true; + zxing.OnScanResult += (result) => Device.BeginInvokeOnMainThread (async () => { // Stop analysis until we navigate away so we don't keep reading barcodes - zxing.IsAnalyzing = false; + zxing.IsAnalyzing = true; // Show an alert await DisplayAlert ("Scanned Barcode", result.Text, "OK"); - // Navigate away - await Navigation.PopAsync (); + zxing.IsAnalyzing = true; }); overlay = new ZXingDefaultOverlay @@ -46,6 +56,10 @@ public CustomScanPage () : base () }; var grid = new Grid { + RowDefinitions = { + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) }, + new RowDefinition { Height = new GridLength(1, GridUnitType.Star) } + }, VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, }; diff --git a/Samples/Forms/Droid/FormsSample.Droid.csproj b/Samples/Forms/Droid/FormsSample.Droid.csproj index f57e8f665..811ad46d8 100644 --- a/Samples/Forms/Droid/FormsSample.Droid.csproj +++ b/Samples/Forms/Droid/FormsSample.Droid.csproj @@ -16,6 +16,7 @@ FormsSample.Droid Properties\AndroidManifest.xml v7.1 + v7.1 @@ -29,6 +30,7 @@ 4 None false + true full @@ -39,6 +41,7 @@ false false armeabi;armeabi-v7a;x86;arm64-v8a;x86_64 + true diff --git a/Samples/Forms/iOS/FormsSample.iOS.csproj b/Samples/Forms/iOS/FormsSample.iOS.csproj index fbe820f86..cb5306c9c 100644 --- a/Samples/Forms/iOS/FormsSample.iOS.csproj +++ b/Samples/Forms/iOS/FormsSample.iOS.csproj @@ -23,8 +23,6 @@ false i386 None - true - true true true iPhone Developer @@ -40,9 +38,7 @@ ARMv7, ARM64 Entitlements.plist true - true iPhone Developer - true full @@ -53,9 +49,7 @@ false i386 None - true iPhone Developer - true true @@ -72,8 +66,6 @@ iPhone Developer true true - true - true true diff --git a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraAnalyzer.cs b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraAnalyzer.cs index ba33d73e3..b78fdfa82 100644 --- a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraAnalyzer.cs +++ b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraAnalyzer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Android.Views; using ApxLabs.FastAndroidCamera; namespace ZXing.Mobile.CameraAccess @@ -10,18 +9,16 @@ public class CameraAnalyzer { private readonly CameraController _cameraController; private readonly MobileBarcodeScanningOptions _scanningOptions; - private readonly CameraEventsListener _cameraEventListener; - private Task _processingTask; private DateTime _lastPreviewAnalysis = DateTime.UtcNow; private bool _wasScanned; private BarcodeReaderGeneric _barcodeReader; - public CameraAnalyzer(SurfaceView surfaceView, MobileBarcodeScanningOptions scanningOptions) + public CameraAnalyzer(CameraController cameraController, MobileBarcodeScanningOptions scanningOptions) { _scanningOptions = scanningOptions; - _cameraEventListener = new CameraEventsListener(); - _cameraController = new CameraController(surfaceView, _cameraEventListener, scanningOptions); - Torch = new Torch(_cameraController, surfaceView.Context); + _cameraController = cameraController; + + Torch = new Torch(_cameraController); } public event EventHandler BarcodeFound; @@ -43,13 +40,13 @@ public void ResumeAnalysis() public void ShutdownCamera() { IsAnalyzing = false; - _cameraEventListener.OnPreviewFrameReady -= HandleOnPreviewFrameReady; + _cameraController.OnPreviewFrameReady -= HandleOnPreviewFrameReady; _cameraController.ShutdownCamera(); } public void SetupCamera() { - _cameraEventListener.OnPreviewFrameReady += HandleOnPreviewFrameReady; + _cameraController.OnPreviewFrameReady += HandleOnPreviewFrameReady; _cameraController.SetupCamera(); } @@ -75,11 +72,6 @@ private bool CanAnalyzeFrame if (!IsAnalyzing) return false; - //Check and see if we're still processing a previous frame - // todo: check if we can run as many as possible or mby run two analyzers at once (Vision + ZXing) - if (_processingTask != null && !_processingTask.IsCompleted) - return false; - var elapsedTimeMs = (DateTime.UtcNow - _lastPreviewAnalysis).TotalMilliseconds; if (elapsedTimeMs < _scanningOptions.DelayBetweenAnalyzingFrames) return false; @@ -100,24 +92,17 @@ private void HandleOnPreviewFrameReady(object sender, FastJavaByteArray fastArra _wasScanned = false; _lastPreviewAnalysis = DateTime.UtcNow; - _processingTask = Task.Run(() => - { - try - { - DecodeFrame(fastArray); - } catch (Exception ex) { - Console.WriteLine(ex); - } - }).ContinueWith(task => + try + { + DecodeFrame(fastArray); + } + catch (Exception ex) { - if (task.IsFaulted) - Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "DecodeFrame exception occurs"); - }, TaskContinuationOptions.OnlyOnFaulted); + Android.Util.Log.Debug(MobileBarcodeScanner.TAG, $"DecodeFrame exception occured: {ex.Message}"); + } } - byte[] _matrix; - byte[] _rotatedMatrix; - + private byte[] buffer; private void DecodeFrame(FastJavaByteArray fastArray) { var cameraParameters = _cameraController.Camera.GetParameters(); @@ -126,52 +111,36 @@ private void DecodeFrame(FastJavaByteArray fastArray) InitBarcodeReaderIfNeeded(); - var rotate = false; - var newWidth = width; - var newHeight = height; - // use last value for performance gain var cDegrees = _cameraController.LastCameraDisplayOrientationDegree; + var rotate = (cDegrees == 90 || cDegrees == 270); - if (cDegrees == 90 || cDegrees == 270) - { - rotate = true; - newWidth = height; - newHeight = width; - } - - ZXing.Result result = null; + Result result = null; var start = PerformanceCounter.Start(); - LuminanceSource luminanceSource; - - var fast = new FastJavaByteArrayYUVLuminanceSource(fastArray, width, height, 0, 0, width, height); // _area.Left, _area.Top, _area.Width, _area.Height); - if (rotate) + if (rotate) { - fast.CopyMatrix(ref _matrix); - RotateCounterClockwise(_matrix, ref _rotatedMatrix, width, height); // _area.Width, _area.Height); - luminanceSource = new PlanarYUVLuminanceSource(_rotatedMatrix, height, width, 0, 0, height, width, false); // _area.Height, _area.Width, 0, 0, _area.Height, _area.Width, false); + fastArray.Transpose(ref buffer, width, height); + var tmp = width; + width = height; + height = tmp; } - else - luminanceSource = fast; + + var luminanceSource = new FastJavaByteArrayYUVLuminanceSource(fastArray, width, height, 0, 0, width, height); // _area.Left, _area.Top, _area.Width, _area.Height); result = _barcodeReader.Decode(luminanceSource); - fastArray.Dispose(); - fastArray = null; + PerformanceCounter.Stop(start, "Decode Time: {0} ms (width: " + width + ", height: " + height + ", degrees: " + cDegrees + ", rotate: " + rotate + ")"); - PerformanceCounter.Stop(start, - "Decode Time: {0} ms (width: " + width + ", height: " + height + ", degrees: " + cDegrees + ", rotate: " + - rotate + ")"); - - if (result != null) - { - Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Barcode Found: " + result.Text); + if (result != null) + { + Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Barcode Found: " + result.Text); - _wasScanned = true; - BarcodeFound?.Invoke(this, result); - return; - } + _wasScanned = true; + BarcodeFound?.Invoke(this, result); + } + else + AutoFocus(); } private void InitBarcodeReaderIfNeeded() @@ -200,24 +169,5 @@ private void InitBarcodeReaderIfNeeded() _barcodeReader.Options.PossibleFormats.Add(pf); } } - - private static byte[] RotateCounterClockwise(byte[] data, int width, int height) - { - var rotatedData = new byte[data.Length]; - for (var y = 0; y < height; y++) - for (var x = 0; x < width; x++) - rotatedData[x*height + height - y - 1] = data[x + y*width]; - return rotatedData; - } - - private void RotateCounterClockwise(byte[] source, ref byte[] target, int width, int height) - { - if (source.Length != (target?.Length ?? -1)) - target = new byte[source.Length]; - - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) - target[x * height + height - y - 1] = source[x + y * width]; - } } } \ No newline at end of file diff --git a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraController.cs b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraController.cs index 543d00833..1520864d0 100644 --- a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraController.cs +++ b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraController.cs @@ -17,22 +17,35 @@ public class CameraController private readonly Context _context; private readonly MobileBarcodeScanningOptions _scanningOptions; private readonly ISurfaceHolder _holder; - private readonly SurfaceView _surfaceView; private readonly CameraEventsListener _cameraEventListener; private int _cameraId; + private bool _autoFocusCycleDone = true; + private bool _useContinousFocus; public CameraController(SurfaceView surfaceView, CameraEventsListener cameraEventListener, MobileBarcodeScanningOptions scanningOptions) { + SurfaceView = surfaceView; + _context = surfaceView.Context; + _scanningOptions = scanningOptions; _holder = surfaceView.Holder; - _surfaceView = surfaceView; + _cameraEventListener = cameraEventListener; - _scanningOptions = scanningOptions; + _cameraEventListener.AutoFocus += (s, e) => + _autoFocusCycleDone = true; } + public SurfaceView SurfaceView { get; } + public Camera Camera { get; private set; } + public event EventHandler OnPreviewFrameReady + { + add { _cameraEventListener.OnPreviewFrameReady += value; } + remove { _cameraEventListener.OnPreviewFrameReady -= value; } + } + public int LastCameraDisplayOrientationDegree { get; private set; } public void RefreshCamera() @@ -78,13 +91,8 @@ public void SetupCamera() int bufferSize = (previewSize.Width * previewSize.Height * bitsPerPixel) / 8; - const int NUM_PREVIEW_BUFFERS = 5; - for (uint i = 0; i < NUM_PREVIEW_BUFFERS; ++i) - { - using (var buffer = new FastJavaByteArray(bufferSize)) - Camera.AddCallbackBuffer(buffer); - } - + using (var buffer = new FastJavaByteArray(bufferSize)) + Camera.AddCallbackBuffer(buffer); Camera.StartPreview(); @@ -117,8 +125,8 @@ public void AutoFocus(int x, int y) { // The bounds for focus areas are actually -1000 to 1000 // So we need to translate the touch coordinates to this scale - var focusX = x / _surfaceView.Width * 2000 - 1000; - var focusY = y / _surfaceView.Height * 2000 - 1000; + var focusX = x / SurfaceView.Width * 2000 - 1000; + var focusY = y / SurfaceView.Height * 2000 - 1000; // Call the autofocus with our coords AutoFocus(focusX, focusY, true); @@ -134,10 +142,9 @@ public void ShutdownCamera() { try { - //Camera.SetPreviewCallback(null); Camera.SetPreviewDisplay(null); Camera.StopPreview(); - Camera.SetNonMarshalingPreviewCallback(null); + Camera.SetNonMarshalingPreviewCallback(null); // replaces Camera.SetPreviewCallback(null); } catch (Exception ex) { @@ -201,11 +208,6 @@ private void OpenCamera() { Camera = Camera.Open(); } - - //if (Camera != null) - // Camera.SetPreviewCallback(_cameraEventListener); - //else - // MobileBarcodeScanner.LogWarn(MobileBarcodeScanner.TAG, "Camera is null :("); } catch (Exception ex) { @@ -217,8 +219,13 @@ private void OpenCamera() private void ApplyCameraSettings() { var parameters = Camera.GetParameters(); - parameters.PreviewFormat = ImageFormatType.Nv21; + parameters.PreviewFormat = ImageFormatType.Nv21; // YCrCb format (all Android devices must support this) + + // Android actually defines a barcode scene mode .. + if (parameters.SupportedSceneModes.Contains(Camera.Parameters.SceneModeBarcode)) // .. we might be lucky :-) + parameters.SceneMode = Camera.Parameters.SceneModeBarcode; + // First try continuous video, then auto focus, then fixed var supportedFocusModes = parameters.SupportedFocusModes; if (Build.VERSION.SdkInt >= BuildVersionCodes.IceCreamSandwich && supportedFocusModes.Contains(Camera.Parameters.FocusModeContinuousPicture)) @@ -230,20 +237,6 @@ private void ApplyCameraSettings() else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeFixed)) parameters.FocusMode = Camera.Parameters.FocusModeFixed; - var selectedFps = parameters.SupportedPreviewFpsRange.FirstOrDefault(); - if (selectedFps != null) - { - // This will make sure we select a range with the lowest minimum FPS - // and maximum FPS which still has the lowest minimum - // This should help maximize performance / support for hardware - foreach (var fpsRange in parameters.SupportedPreviewFpsRange) - { - if (fpsRange[0] <= selectedFps[0] && fpsRange[1] > selectedFps[1]) - selectedFps = fpsRange; - } - parameters.SetPreviewFpsRange(selectedFps[0], selectedFps[1]); - } - var availableResolutions = parameters.SupportedPreviewSizes.Select(sps => new CameraResolution { Width = sps.Width, @@ -292,12 +285,18 @@ private void ApplyCameraSettings() Camera.SetParameters(parameters); + parameters = Camera.GetParameters(); // refresh to see what is actually set! + + _useContinousFocus = parameters.FocusMode == Camera.Parameters.FocusModeContinuousPicture || parameters.FocusMode == Camera.Parameters.FocusModeContinuousVideo; + SetCameraDisplayOrientation(); } private void AutoFocus(int x, int y, bool useCoordinates) { - if (Camera == null) return; + if (_useContinousFocus || !_autoFocusCycleDone || Camera == null) + return; + var cameraParams = Camera.GetParameters(); Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus Requested"); @@ -317,7 +316,7 @@ private void AutoFocus(int x, int y, bool useCoordinates) // So we'll offset -10 from the center of the touch and then // make a rect of 20 to give an area to focus on based on the center of the touch x = x - 10; - y = y - 10; + y = y - 10; // todo: ensure positive! // Ensure we don't go over the -1000 to 1000 limit of focus area if (x >= 1000) @@ -340,6 +339,7 @@ private void AutoFocus(int x, int y, bool useCoordinates) } // Finally autofocus (weather we used focus areas or not) + _autoFocusCycleDone = false; Camera.AutoFocus(_cameraEventListener); } catch (Exception ex) diff --git a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraEventsListener.cs b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraEventsListener.cs index fcf0c224f..9b8ceaea0 100644 --- a/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraEventsListener.cs +++ b/Source/ZXing.Net.Mobile.Android/CameraAccess/CameraEventsListener.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Android.Hardware; using ApxLabs.FastAndroidCamera; @@ -6,26 +7,30 @@ namespace ZXing.Mobile.CameraAccess { public class CameraEventsListener : Java.Lang.Object, INonMarshalingPreviewCallback, Camera.IAutoFocusCallback { - public event EventHandler OnPreviewFrameReady; + public event EventHandler OnPreviewFrameReady; + public event EventHandler AutoFocus; - //public void OnPreviewFrame(byte[] data, Camera camera) - //{ - // OnPreviewFrameReady?.Invoke(this, data); - //} - - public void OnPreviewFrame(IntPtr data, Camera camera) +#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void + public async void OnPreviewFrame(IntPtr data, Camera camera) { - using (var fastArray = new FastJavaByteArray(data)) - { - OnPreviewFrameReady?.Invoke(this, fastArray); - - camera.AddCallbackBuffer(fastArray); - } + try + { + using (var fastArray = new FastJavaByteArray(data)) + { + await Task.Run(() => OnPreviewFrameReady?.Invoke(this, fastArray)); + camera.AddCallbackBuffer(fastArray); + } + } + catch (Exception ex) + { + Android.Util.Log.Warn(MobileBarcodeScanner.TAG, $"Exception squashed! {ex.Message}"); + } } +#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void public void OnAutoFocus(bool success, Camera camera) { - Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "AutoFocus {0}", success ? "Succeeded" : "Failed"); + AutoFocus?.Invoke(this, success); } } } \ No newline at end of file diff --git a/Source/ZXing.Net.Mobile.Android/CameraAccess/Torch.cs b/Source/ZXing.Net.Mobile.Android/CameraAccess/Torch.cs index 2ec8c9c5d..eeda39c02 100644 --- a/Source/ZXing.Net.Mobile.Android/CameraAccess/Torch.cs +++ b/Source/ZXing.Net.Mobile.Android/CameraAccess/Torch.cs @@ -10,10 +10,10 @@ public class Torch private readonly Context _context; private bool? _hasTorch; - public Torch(CameraController cameraController, Context context) + public Torch(CameraController cameraController) { _cameraController = cameraController; - _context = context; + _context = cameraController.SurfaceView.Context; } public bool IsSupported diff --git a/Source/ZXing.Net.Mobile.Android/FastJavaArrayEx.cs b/Source/ZXing.Net.Mobile.Android/FastJavaArrayEx.cs index 504663da2..ed877fa08 100644 --- a/Source/ZXing.Net.Mobile.Android/FastJavaArrayEx.cs +++ b/Source/ZXing.Net.Mobile.Android/FastJavaArrayEx.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Runtime.InteropServices; +using System.Threading; using ApxLabs.FastAndroidCamera; namespace ZXing.Mobile @@ -13,5 +14,40 @@ public static void BlockCopyTo(this FastJavaByteArray self, int sourceIndex, byt Marshal.Copy(new IntPtr(self.Raw + sourceIndex), array, arrayIndex, Math.Min(length, Math.Min(self.Count, array.Length - arrayIndex))); } } + + static readonly ThreadLocal _buffer = new ThreadLocal(); + public static void Transpose(this FastJavaByteArray self, int width, int height) + { + var data = _buffer.Value; + self.Transpose(ref data, width, height); + _buffer.Value = data; + } + + public static void Transpose(this FastJavaByteArray self, ref byte[] buffer, int width, int height) + { + var length = self.Count; + + if (length < width * height) + throw new ArgumentException($"(this.Count) {length} < {width * height} = {width} * {height} (width * height)"); + + // todo: Make transpose in-place, but this is not trivial for a non-square matrix, encoded in a 1d array. + // Currently we spend a bit of time + + if (buffer == null || buffer.Length < length) + buffer = new byte[length]; // ensure we have enough buffer space for the operation + + self.BlockCopyTo(0, buffer, 0, length); // this is fairly quick (~1ms per MiB) + + unsafe + { + // This loop is kind of slow (~20ms per MiB) + fixed (byte* src = &buffer[0]) + { + for (var y = 0; y < height; y++) + for (var x = 0; x < width; x++) + self.Raw[y + x * height] = src[x + y * width]; + } + } + } } -} \ No newline at end of file +} diff --git a/Source/ZXing.Net.Mobile.Android/FastJavaByteArrayYUVLuminanceSource.cs b/Source/ZXing.Net.Mobile.Android/FastJavaByteArrayYUVLuminanceSource.cs index 523a447df..e04817122 100644 --- a/Source/ZXing.Net.Mobile.Android/FastJavaByteArrayYUVLuminanceSource.cs +++ b/Source/ZXing.Net.Mobile.Android/FastJavaByteArrayYUVLuminanceSource.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2009 ZXing authors * Modifications copyright 2016 kasper@byolimit.com * @@ -42,16 +42,15 @@ public sealed class FastJavaByteArrayYUVLuminanceSource : BaseLuminanceSource private readonly int _top; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The yuv data. + /// The yuv data. /// Width of the data. /// Height of the data. /// The left. /// The top. /// The width. /// The height. - /// if set to true [reverse horiz]. public FastJavaByteArrayYUVLuminanceSource(FastJavaByteArray yuv, int dataWidth, int dataHeight, @@ -116,27 +115,8 @@ override public byte[] Matrix { get { - int width = Width; - int height = Height; - - int area = width * height; - byte[] matrix = new byte[area]; - int inputOffset = _top * _dataWidth + _left; - - // If the width matches the full width of the underlying data, perform a single copy. - if (width == _dataWidth) - { - _yuv.BlockCopyTo(inputOffset, matrix, 0, area); - return matrix; - } - - // Otherwise copy one cropped row at a time. - for (int y = 0; y < height; y++) - { - int outputOffset = y * width; - _yuv.BlockCopyTo(inputOffset, matrix, outputOffset, width); - inputOffset += _dataWidth; - } + byte[] matrix = null; + CopyMatrix(ref matrix); return matrix; } } @@ -160,7 +140,8 @@ public void CopyMatrix(ref byte[] matrix) // If the width matches the full width of the underlying data, perform a single copy. _yuv.BlockCopyTo(inputOffset, matrix, 0, area); } - else { + else + { // Otherwise copy one cropped row at a time. for (int y = 0; y < height; y++) { @@ -201,9 +182,7 @@ override public LuminanceSource crop(int left, int top, int width, int height) protected override LuminanceSource CreateLuminanceSource(byte[] newLuminances, int width, int height) { - // Called when rotating. - // todo: This partially defeats the purpose as we traffic in byte[] luminances return new PlanarYUVLuminanceSource(newLuminances, width, height, 0, 0, width, height, false); } } -} \ No newline at end of file +} diff --git a/Source/ZXing.Net.Mobile.Android/ZXing.Net.Mobile.Android.csproj b/Source/ZXing.Net.Mobile.Android/ZXing.Net.Mobile.Android.csproj index 631c78468..eb1d6374d 100644 --- a/Source/ZXing.Net.Mobile.Android/ZXing.Net.Mobile.Android.csproj +++ b/Source/ZXing.Net.Mobile.Android/ZXing.Net.Mobile.Android.csproj @@ -13,6 +13,7 @@ ZXing.Mobile ZXingNetMobile v4.0.3 + False @@ -44,6 +45,8 @@ ..\..\packages\FastAndroidCamera.2.0.0\lib\MonoAndroid403\FastAndroidCamera.dll + + @@ -62,8 +65,9 @@ - + + @@ -88,4 +92,4 @@ - \ No newline at end of file + diff --git a/Source/ZXing.Net.Mobile.Android/ZXingSurfaceView.cs b/Source/ZXing.Net.Mobile.Android/ZXingSurfaceView.cs index 777d6c976..9b410451d 100644 --- a/Source/ZXing.Net.Mobile.Android/ZXingSurfaceView.cs +++ b/Source/ZXing.Net.Mobile.Android/ZXingSurfaceView.cs @@ -4,6 +4,7 @@ using Android.Views; using Android.Graphics; using ZXing.Mobile.CameraAccess; +using Android.OS; namespace ZXing.Mobile { @@ -24,9 +25,8 @@ protected ZXingSurfaceView(IntPtr javaReference, JniHandleOwnership transfer) private void Init() { - _cameraAnalyzer = new CameraAnalyzer(this, ScanningOptions); + _cameraAnalyzer = new CameraAnalyzer(new CameraController(this, new CameraEventsListener(), ScanningOptions), ScanningOptions); Holder.AddCallback(this); - Holder.SetType(SurfaceType.PushBuffers); } public async void SurfaceCreated(ISurfaceHolder holder) diff --git a/Source/ZXing.Net.Mobile.Android/ZXingTextureView.cs b/Source/ZXing.Net.Mobile.Android/ZXingTextureView.cs new file mode 100644 index 000000000..67f7c9a39 --- /dev/null +++ b/Source/ZXing.Net.Mobile.Android/ZXingTextureView.cs @@ -0,0 +1,764 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Threading.Tasks; + +using Android.Content; +using Android.Content.PM; +using Android.Graphics; +using Android.Hardware; +using Android.OS; +using Android.Runtime; +using Android.Util; +using Android.Views; +using Android.Widget; +using ApxLabs.FastAndroidCamera; +using ZXing.Net.Mobile.Android; +using Camera = Android.Hardware.Camera; +using Matrix = Android.Graphics.Matrix; + +namespace ZXing.Mobile +{ + public static class IntEx + { + public static bool Between(this int i, int lower, int upper) + { + return lower <= i && i <= upper; + } + } + + public static class HandlerEx + { + public static void PostSafe(this Handler self, Action action) + { + self.Post(() => + { + try + { + action(); + } + catch (Exception ex) + { + // certain death, unless we squash + Log.Debug(MobileBarcodeScanner.TAG, $"Squashing: {ex} to avoid certain death! Handler is: {self.GetHashCode()}"); + } + }); + } + + public static void PostSafe(this Handler self, Func action) + { + self.Post(async () => + { + try + { + await action(); + } + catch (Exception ex) + { + // certain death, unless we squash + Log.Debug(MobileBarcodeScanner.TAG, $"Squashing: {ex} to avoid certain death! Handler is: {self.GetHashCode()}"); + } + }); + } + + } + + public static class RectFEx + { + public static void Flip(this RectF s) + { + var tmp = s.Left; + s.Left = s.Top; + s.Top = tmp; + tmp = s.Right; + s.Right = s.Bottom; + s.Bottom = tmp; + } + } + + class MyOrientationEventListener : OrientationEventListener + { + public MyOrientationEventListener(Context context, SensorDelay delay) : base(context, delay) { } + + public event Action OrientationChanged; + + public override void OnOrientationChanged(int orientation) + { + try + { + OrientationChanged?.Invoke(orientation); + } + catch (Exception ex) + { + Log.Warn(MobileBarcodeScanner.TAG, $"Squashing {ex} in OnOrientationChanged!"); + } + } + } + + public class ZXingTextureView : TextureView, IScannerView, Camera.IAutoFocusCallback, INonMarshalingPreviewCallback + { + Camera.CameraInfo _cameraInfo; + Camera _camera; + + static ZXingTextureView() + { + } + + public ZXingTextureView(IntPtr javaRef, JniHandleOwnership transfer) : base(javaRef, transfer) + { + Init(); + } + + public ZXingTextureView(Context ctx) : base(ctx) + { + Init(); + } + + public ZXingTextureView(Context ctx, MobileBarcodeScanningOptions options) : base(ctx) + { + Init(); + ScanningOptions = options; + } + + public ZXingTextureView(Context ctx, IAttributeSet attr) : base(ctx, attr) + { + Init(); + } + + public ZXingTextureView(Context ctx, IAttributeSet attr, int defStyle) : base(ctx, attr, defStyle) + { + Init(); + } + + Toast _toast; + Handler _handler; + MyOrientationEventListener _orientationEventListener; + TaskCompletionSource _surfaceAvailable = new TaskCompletionSource(); + void Init() + { + _toast = Toast.MakeText(Context, string.Empty, ToastLength.Short); + + var handlerThread = new HandlerThread("ZXingTextureView"); + handlerThread.Start(); + _handler = new Handler(handlerThread.Looper); + + // We have to handle changes to screen orientation explicitly, as we cannot rely on OnConfigurationChanges + _orientationEventListener = new MyOrientationEventListener(Context, SensorDelay.Normal); + _orientationEventListener.OrientationChanged += OnOrientationChanged; + if (_orientationEventListener.CanDetectOrientation()) + _orientationEventListener.Enable(); + + SurfaceTextureAvailable += (sender, e) => + _surfaceAvailable.SetResult(e); + + SurfaceTextureSizeChanged += (sender, e) => + SetSurfaceTransform(e.Surface, e.Width, e.Height); + + SurfaceTextureDestroyed += (sender, e) => + { + ShutdownCamera(); + _surfaceAvailable = new TaskCompletionSource(); + }; + } + + Camera.Size PreviewSize { get; set; } + + int _lastOrientation; + SurfaceOrientation _lastSurfaceOrientation; + void OnOrientationChanged(int orientation) + { + // + // This code should only run when UI snaps into either portrait or landscape mode. + // At first glance we could just override OnConfigurationChanged, but unfortunately + // a rotation from landscape directly to reverse landscape won't fire an event + // (which is easily done by rotating via upside-down on many devices), because Android + // can just reuse the existing config and handle the rotation automatically .. + // + // .. except of course for camera orientation, which must handled explicitly *sigh*. + // Hurray Google, you sure suck at API design! + // + // Instead we waste some CPU by tracking orientation down to the last degree, every 200ms. + // I have yet to come up with a better way. + // + if (_camera == null) + return; + + var o = (((orientation + 45) % 360) / 90) * 90; // snap to 0, 90, 180, or 270. + if (o == _lastOrientation) + return; // fast path, no change .. + + // Actual snap is delayed, so check if we are actually rotated + var rotation = WindowManager.DefaultDisplay.Rotation; + if (rotation == _lastSurfaceOrientation) + return; // .. still no change + + _lastOrientation = o; + _lastSurfaceOrientation = rotation; + + _handler.PostSafe(() => + { + _camera?.SetDisplayOrientation(CameraOrientation(WindowManager.DefaultDisplay.Rotation)); // and finally, the interesting part *sigh* + }); + } + + bool IsPortrait + { + get + { + var rotation = WindowManager.DefaultDisplay.Rotation; + return rotation == SurfaceOrientation.Rotation0 || rotation == SurfaceOrientation.Rotation180; + } + } + + Rectangle _area; + void SetSurfaceTransform(SurfaceTexture st, int width, int height) + { + var p = PreviewSize; + if (p == null) + return; // camera no ready yet, we will be called again later from SetupCamera. + + using (var metrics = new DisplayMetrics()) + { + #region transform + // Compensate for non-square pixels + WindowManager.DefaultDisplay.GetMetrics(metrics); + var aspectRatio = metrics.Xdpi / metrics.Ydpi; // close to 1, but rarely perfect 1 + + // Compensate for preview streams aspect ratio + aspectRatio *= (float)p.Height / p.Width; + + // Compensate for portrait mode + if (IsPortrait) + aspectRatio = 1f / aspectRatio; + + // OpenGL coordinate system goes form 0 to 1 + var transform = new Matrix(); + transform.SetScale(1f, aspectRatio * width / height); // lock on to width + + Post(() => + { + try + { + SetTransform(transform); + } + catch (ObjectDisposedException) { } // todo: What to do here?! For now we squash :-/ + }); // ensure we use the right thread when updating transform + + Log.Debug(MobileBarcodeScanner.TAG, $"Aspect ratio: {aspectRatio}, Transform: {transform}"); + + #endregion + + #region area + using (var max = new RectF(0, 0, p.Width, p.Height)) + using (var r = new RectF(max)) + { + // Calculate area of interest within preview + var inverse = new Matrix(); + transform.Invert(inverse); + + Log.Debug(MobileBarcodeScanner.TAG, $"Inverse: {inverse}"); + + var flip = IsPortrait; + if (flip) r.Flip(); + inverse.MapRect(r); + if (flip) r.Flip(); + + r.Intersect(max); // stream doesn't always fill the view! + + // Compensate for reverse mounted camera, like on the Nexus 5X. + var reverse = _cameraInfo.Orientation == 270; + if (reverse) + { + if (flip) + r.OffsetTo(p.Width - r.Right, 0); // shift area right + else + r.Offset(0, p.Height - r.Bottom); // shift are down + } + + _area = new Rectangle((int)r.Left, (int)r.Top, (int)r.Width(), (int)r.Height()); + + Log.Debug(MobileBarcodeScanner.TAG, $"Area: {_area}"); + } + #endregion + } + } + + IWindowManager _wm; + IWindowManager WindowManager + { + get + { + _wm = _wm ?? Context.GetSystemService(Context.WindowService).JavaCast(); + return _wm; + } + } + + bool? _hasTorch; + public bool HasTorch + { + get + { + if (_hasTorch.HasValue) + return _hasTorch.Value; + + var p = _camera.GetParameters(); + var supportedFlashModes = p.SupportedFlashModes; + + if (supportedFlashModes != null + && (supportedFlashModes.Contains(Camera.Parameters.FlashModeTorch) + || supportedFlashModes.Contains(Camera.Parameters.FlashModeOn))) + _hasTorch = CheckTorchPermissions(false); + + return _hasTorch.HasValue && _hasTorch.Value; + } + } + + bool _isAnalyzing; + public bool IsAnalyzing + { + get { return _isAnalyzing; } + } + + bool _isTorchOn; + public bool IsTorchOn + { + get { return _isTorchOn; } + } + + MobileBarcodeScanningOptions _scanningOptions; + IBarcodeReaderGeneric _barcodeReader; + public MobileBarcodeScanningOptions ScanningOptions + { + get { return _scanningOptions; } + set + { + _scanningOptions = value; + _barcodeReader = CreateBarcodeReader(value); + } + } + + bool _useContinuousFocus; + bool _autoFocusRunning; + public void AutoFocus() + { + _handler.PostSafe(() => + { + var camera = _camera; + if (camera == null || _autoFocusRunning || _useContinuousFocus) + return; // Allow camera to complete autofocus cycle, before trying again! + + _autoFocusRunning = true; + camera.CancelAutoFocus(); + camera.AutoFocus(this); + }); + } + + public void AutoFocus(int x, int y) + { + // todo: Needs some slightly serious math to map back to camera coordinates. + // The method used in ZXingSurfaceView is simply wrong. + AutoFocus(); + } + + public void OnAutoFocus(bool focus, Camera camera) + { + Log.Debug(MobileBarcodeScanner.TAG, $"OnAutoFocus: {focus}"); + _autoFocusRunning = false; + if (!(focus || _useContinuousFocus)) + AutoFocus(); + } + + public void PauseAnalysis() + { + _isAnalyzing = false; + } + + public void ResumeAnalysis() + { + _isAnalyzing = true; + } + + Action _callback; + public void StartScanning(Action scanResultCallback, MobileBarcodeScanningOptions options = null) + { + _callback = scanResultCallback; + ScanningOptions = options ?? MobileBarcodeScanningOptions.Default; + + _handler.PostSafe(SetupCamera); + + ResumeAnalysis(); + } + + void OpenCamera() + { + if (_camera != null) + return; + + CheckCameraPermissions(); + + if (Build.VERSION.SdkInt >= BuildVersionCodes.Gingerbread) // Choose among multiple cameras from Gingerbread forward + { + int max = Camera.NumberOfCameras; + Log.Debug(MobileBarcodeScanner.TAG, $"Found {max} cameras"); + var requestedFacing = CameraFacing.Back; // default to back facing camera, .. + if (ScanningOptions.UseFrontCameraIfAvailable.HasValue && ScanningOptions.UseFrontCameraIfAvailable.Value) + requestedFacing = CameraFacing.Front; // .. but use front facing if available and requested + + var info = new Camera.CameraInfo(); + int idx = 0; + do + { + Camera.GetCameraInfo(idx++, info); // once again Android sucks! + } + while (info.Facing != requestedFacing && idx < max); + --idx; + + Log.Debug(MobileBarcodeScanner.TAG, $"Opening {info.Facing} facing camera: {idx}..."); + _cameraInfo = info; + _camera = Camera.Open(idx); + } + else + { + _camera = Camera.Open(); + } + + _camera.Lock(); + } + + async Task SetupCamera() + { + OpenCamera(); + + var p = _camera.GetParameters(); + p.PreviewFormat = ImageFormatType.Nv21; // YCrCb format (all Android devices must support this) + + // Android actually defines a barcode scene mode + if (p.SupportedSceneModes.Contains(Camera.Parameters.SceneModeBarcode)) // we might be lucky :-) + p.SceneMode = Camera.Parameters.SceneModeBarcode; + + // First try continuous video, then auto focus, then fixed + var supportedFocusModes = p.SupportedFocusModes; + if (supportedFocusModes.Contains(Camera.Parameters.FocusModeContinuousVideo)) + p.FocusMode = Camera.Parameters.FocusModeContinuousVideo; + else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeAuto)) + p.FocusMode = Camera.Parameters.FocusModeAuto; + else if (supportedFocusModes.Contains(Camera.Parameters.FocusModeFixed)) + p.FocusMode = Camera.Parameters.FocusModeFixed; + + // Set automatic white balance if possible + if (p.SupportedWhiteBalance.Contains(Camera.Parameters.WhiteBalanceAuto)) + p.WhiteBalance = Camera.Parameters.WhiteBalanceAuto; + + // Check if we can support requested resolution .. + var availableResolutions = p.SupportedPreviewSizes.Select(s => new CameraResolution { Width = s.Width, Height = s.Height }).ToList(); + var resolution = ScanningOptions.GetResolution(availableResolutions); + + // .. If not, let's try and find a suitable one + resolution = resolution ?? availableResolutions.OrderBy(r => r.Width).FirstOrDefault(r => r.Width.Between(640, 1280) && r.Height.Between(640, 960)); + + // Hopefully a resolution was selected at some point + if (resolution != null) + p.SetPreviewSize(resolution.Width, resolution.Height); + + _camera.SetParameters(p); + + SetupTorch(_isTorchOn); + + p = _camera.GetParameters(); // refresh! + + _useContinuousFocus = p.FocusMode == Camera.Parameters.FocusModeContinuousVideo; + PreviewSize = p.PreviewSize; // get actual preview size (may differ from requested size) + var bitsPerPixel = ImageFormat.GetBitsPerPixel(p.PreviewFormat); + + Log.Debug(MobileBarcodeScanner.TAG, $"Preview size {PreviewSize.Width}x{PreviewSize.Height} with {bitsPerPixel} bits per pixel"); + + var surfaceInfo = await _surfaceAvailable.Task; + + SetSurfaceTransform(surfaceInfo.Surface, surfaceInfo.Width, surfaceInfo.Height); + + _camera.SetDisplayOrientation(CameraOrientation(WindowManager.DefaultDisplay.Rotation)); + _camera.SetPreviewTexture(surfaceInfo.Surface); + _camera.StartPreview(); + + int bufferSize = (PreviewSize.Width * PreviewSize.Height * bitsPerPixel) / 8; + using (var buffer = new FastJavaByteArray(bufferSize)) + _camera.AddCallbackBuffer(buffer); + + _camera.SetNonMarshalingPreviewCallback(this); + + // Docs suggest if Auto or Macro modes, we should invoke AutoFocus at least once + _autoFocusRunning = false; + if (!_useContinuousFocus) + AutoFocus(); + } + + public int CameraOrientation(SurfaceOrientation rotation) + { + int degrees = 0; + switch (rotation) + { + case SurfaceOrientation.Rotation0: + degrees = 0; + break; + case SurfaceOrientation.Rotation90: + degrees = 90; + break; + case SurfaceOrientation.Rotation180: + degrees = 180; + break; + case SurfaceOrientation.Rotation270: + degrees = 270; + break; + } + + // Handle front facing camera + if (_cameraInfo.Facing == CameraFacing.Front) + return (360 - ((_cameraInfo.Orientation + degrees) % 360)) % 360; // compensate for mirror + + return (_cameraInfo.Orientation - degrees + 360) % 360; + } + + void ShutdownCamera() + { + _handler.Post(() => + { + if (_camera == null) + return; + + var camera = _camera; + _camera = null; + + try + { + camera.StopPreview(); + camera.SetNonMarshalingPreviewCallback(null); + } + catch (Exception e) + { + Log.Error(MobileBarcodeScanner.TAG, e.ToString()); + } + finally + { + camera.Release(); + } + }); + } + + public void StopScanning() + { + PauseAnalysis(); + ShutdownCamera(); + } + + public void Torch(bool on) + { + if (!Context.PackageManager.HasSystemFeature(PackageManager.FeatureCameraFlash)) + { + Log.Info(MobileBarcodeScanner.TAG, "Flash not supported on this device"); + return; + } + + CheckTorchPermissions(); + + _isTorchOn = on; + if (_camera != null) // already running + SetupTorch(on); + } + + public void ToggleTorch() + { + Torch(!_isTorchOn); + } + + void SetupTorch(bool on) + { + var p = _camera.GetParameters(); + var supportedFlashModes = p.SupportedFlashModes ?? Enumerable.Empty(); + + string flashMode = null; + + if (on) + { + if (supportedFlashModes.Contains(Camera.Parameters.FlashModeTorch)) + flashMode = Camera.Parameters.FlashModeTorch; + else if (supportedFlashModes.Contains(Camera.Parameters.FlashModeOn)) + flashMode = Camera.Parameters.FlashModeOn; + } + else + { + if (supportedFlashModes.Contains(Camera.Parameters.FlashModeOff)) + flashMode = Camera.Parameters.FlashModeOff; + } + + if (!string.IsNullOrEmpty(flashMode)) + { + p.FlashMode = flashMode; + _camera.SetParameters(p); + } + } + + bool CheckCameraPermissions(bool throwOnError = true) + { + return CheckPermissions(Android.Manifest.Permission.Camera, throwOnError); + } + + bool CheckTorchPermissions(bool throwOnError = true) + { + return CheckPermissions(Android.Manifest.Permission.Flashlight, throwOnError); + } + + bool CheckPermissions(string permission, bool throwOnError = true) + { + Log.Debug(MobileBarcodeScanner.TAG, $"Checking {permission}..."); + + if (!PermissionsHandler.IsPermissionInManifest(Context, permission) + || !PermissionsHandler.IsPermissionGranted(Context, permission)) + { + var msg = $"Requires: {permission}, but was not found in your AndroidManifest.xml file."; + Log.Error(MobileBarcodeScanner.TAG, msg); + + if (throwOnError) + throw new UnauthorizedAccessException(msg); + + return false; + } + + return true; + } + + IBarcodeReaderGeneric CreateBarcodeReader(MobileBarcodeScanningOptions options) + { + var barcodeReader = new BarcodeReaderGeneric(); + + if (options == null) + return barcodeReader; + + if (options.TryHarder.HasValue) + barcodeReader.Options.TryHarder = options.TryHarder.Value; + + if (options.PureBarcode.HasValue) + barcodeReader.Options.PureBarcode = options.PureBarcode.Value; + + if (!string.IsNullOrEmpty(options.CharacterSet)) + barcodeReader.Options.CharacterSet = options.CharacterSet; + + if (options.TryInverted.HasValue) + barcodeReader.TryInverted = options.TryInverted.Value; + + if (options.AutoRotate.HasValue) + barcodeReader.AutoRotate = options.AutoRotate.Value; + + if (options.PossibleFormats?.Any() ?? false) + { + barcodeReader.Options.PossibleFormats = new List(); + + foreach (var pf in options.PossibleFormats) + barcodeReader.Options.PossibleFormats.Add(pf); + } + + return barcodeReader; + } + + protected override void Dispose(bool disposing) + { + _orientationEventListener?.Disable(); + _orientationEventListener?.Dispose(); + _orientationEventListener = null; + base.Dispose(disposing); + } + + private bool _wasScanned; + private DateTime _lastPreviewAnalysis; + private bool CanAnalyzeFrame + { + get + { + if (!IsAnalyzing) + return false; + + var elapsedTimeMs = (DateTime.UtcNow - _lastPreviewAnalysis).TotalMilliseconds; + if (elapsedTimeMs < _scanningOptions.DelayBetweenAnalyzingFrames) + return false; + + // Delay a minimum between scans + if (_wasScanned && elapsedTimeMs < _scanningOptions.DelayBetweenContinuousScans) + return false; + + // reset! + _wasScanned = false; + _lastPreviewAnalysis = DateTime.UtcNow; + + return true; + } + } + + byte[] _buffer; + async public void OnPreviewFrame(IntPtr data, Camera camera) + { + System.Diagnostics.Stopwatch sw = null; + using (var fastArray = new FastJavaByteArray(data)) // avoids marshalling + { + try + { +#if DEBUG + sw = new Stopwatch(); + sw.Start(); +#endif + if (!CanAnalyzeFrame) + return; + + var isPortrait = IsPortrait; // this is checked asynchronously, so make sure to copy. + + var result = await Task.Run(() => + { + var dataWidth = PreviewSize.Width; + var dataHeight = PreviewSize.Height; + + LuminanceSource luminanceSource; + if (isPortrait) + { + fastArray.Transpose(ref _buffer, dataWidth, dataHeight); + luminanceSource = new FastJavaByteArrayYUVLuminanceSource(fastArray, dataHeight, dataWidth, _area.Top, _area.Left, _area.Height, _area.Width); + } + else + luminanceSource = new FastJavaByteArrayYUVLuminanceSource(fastArray, dataWidth, dataHeight, _area.Left, _area.Top, _area.Width, _area.Height); + + return _barcodeReader.Decode(luminanceSource); + }); + + if (result != null) + { + _wasScanned = true; + _callback(result); + } + else if (!_useContinuousFocus) + AutoFocus(); + } + catch (Exception ex) + { + // It is better to just skip a frame :-) .. + Log.Warn(MobileBarcodeScanner.TAG, ex.ToString()); + } + finally + { + camera.AddCallbackBuffer(fastArray); // IMPORTANT! + +#if DEBUG + sw.Stop(); + try + { + Post(() => + { + _toast.SetText(string.Format("{0}ms", sw.ElapsedMilliseconds)); + _toast.Show(); + }); + } + catch { } // squash +#endif + } + } + } + } +} diff --git a/Source/ZXing.Net.Mobile.Core/MobileBarcodeScanningOptions.cs b/Source/ZXing.Net.Mobile.Core/MobileBarcodeScanningOptions.cs index ca13e9684..82ef96954 100644 --- a/Source/ZXing.Net.Mobile.Core/MobileBarcodeScanningOptions.cs +++ b/Source/ZXing.Net.Mobile.Core/MobileBarcodeScanningOptions.cs @@ -14,11 +14,10 @@ public class MobileBarcodeScanningOptions public MobileBarcodeScanningOptions () { - this.PossibleFormats = new List(); - //this.AutoRotate = true; - this.DelayBetweenAnalyzingFrames = 150; - this.InitialDelayBeforeAnalyzingFrames = 300; - this.DelayBetweenContinuousScans = 1000; + PossibleFormats = new List(); + DelayBetweenAnalyzingFrames = 150; + InitialDelayBeforeAnalyzingFrames = 300; + DelayBetweenContinuousScans = 1000; UseNativeScanning = false; } diff --git a/Source/ZXing.Net.Mobile.Core/Performance.cs b/Source/ZXing.Net.Mobile.Core/Performance.cs index 2cefdbdd4..62ac98389 100644 --- a/Source/ZXing.Net.Mobile.Core/Performance.cs +++ b/Source/ZXing.Net.Mobile.Core/Performance.cs @@ -43,8 +43,7 @@ public static void Stop(string guid, string msg) msg += " {0}"; if (Debugger.IsAttached) - System.Diagnostics.Debug.WriteLine (msg, elapsed.TotalMilliseconds); + Debug.WriteLine (msg, elapsed.TotalMilliseconds); } } - } \ No newline at end of file diff --git a/Source/ZXing.Net.Mobile.Forms.Android/ZXing.Net.Mobile.Forms.Android.csproj b/Source/ZXing.Net.Mobile.Forms.Android/ZXing.Net.Mobile.Forms.Android.csproj index d2ba91aa9..cb8f353e8 100644 --- a/Source/ZXing.Net.Mobile.Forms.Android/ZXing.Net.Mobile.Forms.Android.csproj +++ b/Source/ZXing.Net.Mobile.Forms.Android/ZXing.Net.Mobile.Forms.Android.csproj @@ -14,8 +14,10 @@ True ZXing.Net.Mobile.Forms.Android v7.1 + v7.1 + true @@ -38,9 +40,6 @@ false - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\MonoAndroid10\FormsViewGroup.dll - @@ -77,20 +76,23 @@ ..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\lib\MonoAndroid403\Xamarin.Android.Support.Vector.Drawable.dll True - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\MonoAndroid10\Xamarin.Forms.Core.dll + + ..\..\packages\FastAndroidCamera.2.0.0\lib\MonoAndroid403\FastAndroidCamera.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\MonoAndroid10\Xamarin.Forms.Platform.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\FormsViewGroup.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Core.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll - - ..\..\Samples\Forms\packages\FastAndroidCamera.2.0.0\lib\MonoAndroid403\FastAndroidCamera.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Platform.dll + + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll @@ -133,7 +135,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file + + + diff --git a/Source/ZXing.Net.Mobile.Forms.Android/ZXingScannerViewRenderer.cs b/Source/ZXing.Net.Mobile.Forms.Android/ZXingScannerViewRenderer.cs index f88201000..ba30130e5 100644 --- a/Source/ZXing.Net.Mobile.Forms.Android/ZXingScannerViewRenderer.cs +++ b/Source/ZXing.Net.Mobile.Forms.Android/ZXingScannerViewRenderer.cs @@ -1,28 +1,25 @@ -using System; +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +using Android.App; +using Android.Runtime; +using Android.Views; + using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + using ZXing.Net.Mobile.Forms; using ZXing.Net.Mobile.Forms.Android; -using Android.Runtime; -using Android.App; -using Xamarin.Forms.Platform.Android; -using Android.Views; -using System.ComponentModel; -using System.Reflection; -using Android.Widget; -using ZXing.Mobile; -using System.Threading.Tasks; -using System.Linq.Expressions; -[assembly:ExportRenderer(typeof(ZXingScannerView), typeof(ZXingScannerViewRenderer))] +using MyView = ZXing.Mobile.ZXingTextureView; + +[assembly: ExportRenderer(typeof(ZXingScannerView), typeof(ZXingScannerViewRenderer))] namespace ZXing.Net.Mobile.Forms.Android { [Preserve(AllMembers = true)] - public class ZXingScannerViewRenderer : ViewRenderer + public class ZXingScannerViewRenderer : ViewRenderer { - public ZXingScannerViewRenderer () : base () - { - } - public static void Init () { // Keep linker from stripping empty method @@ -31,7 +28,7 @@ public static void Init () protected ZXingScannerView formsView; - protected ZXingSurfaceView zxingSurface; + protected MyView view; internal Task requestPermissionsTask; protected override async void OnElementChanged(ElementChangedEventArgs e) @@ -40,15 +37,15 @@ protected override async void OnElementChanged(ElementChangedEventArgs { - if (zxingSurface != null) { + if (view != null) { if (x < 0 && y < 0) - zxingSurface.AutoFocus (); + view.AutoFocus (); else - zxingSurface.AutoFocus (x, y); + view.AutoFocus (x, y); } }; @@ -57,19 +54,19 @@ protected override async void OnElementChanged(ElementChangedEventArgs - + \ No newline at end of file diff --git a/Source/ZXing.Net.Mobile.Forms.iOS/ZXing.Net.Mobile.Forms.iOS.csproj b/Source/ZXing.Net.Mobile.Forms.iOS/ZXing.Net.Mobile.Forms.iOS.csproj index 47ada2301..5cbcfa890 100644 --- a/Source/ZXing.Net.Mobile.Forms.iOS/ZXing.Net.Mobile.Forms.iOS.csproj +++ b/Source/ZXing.Net.Mobile.Forms.iOS/ZXing.Net.Mobile.Forms.iOS.csproj @@ -34,19 +34,19 @@ - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll + + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll - @@ -83,7 +83,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file + + + diff --git a/Source/ZXing.Net.Mobile.Forms.iOS/packages.config b/Source/ZXing.Net.Mobile.Forms.iOS/packages.config index 894c7ac89..5f8f73cbd 100644 --- a/Source/ZXing.Net.Mobile.Forms.iOS/packages.config +++ b/Source/ZXing.Net.Mobile.Forms.iOS/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Source/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj b/Source/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj index 6d29c648f..04dd131c9 100644 --- a/Source/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj +++ b/Source/ZXing.Net.Mobile.Forms/ZXing.Net.Mobile.Forms.csproj @@ -51,14 +51,14 @@ - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll - - ..\..\packages\Xamarin.Forms.2.3.3.193\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll + + ..\..\packages\Xamarin.Forms.2.3.4.247\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll @@ -68,7 +68,7 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file + + + diff --git a/Source/ZXing.Net.Mobile.Forms/ZXingScannerView.cs b/Source/ZXing.Net.Mobile.Forms/ZXingScannerView.cs index 2cb6d5814..752c52b77 100644 --- a/Source/ZXing.Net.Mobile.Forms/ZXingScannerView.cs +++ b/Source/ZXing.Net.Mobile.Forms/ZXingScannerView.cs @@ -72,7 +72,7 @@ public bool HasTorch { } public static readonly BindableProperty IsAnalyzingProperty = - BindableProperty.Create( nameof( IsAnalyzing ), typeof( bool ), typeof( ZXingScannerView ), false ); + BindableProperty.Create( nameof( IsAnalyzing ), typeof( bool ), typeof( ZXingScannerView ), true ); public bool IsAnalyzing { get { return (bool)GetValue (IsAnalyzingProperty); } diff --git a/Source/ZXing.Net.Mobile.Forms/packages.config b/Source/ZXing.Net.Mobile.Forms/packages.config index fd571b304..e18960ffb 100644 --- a/Source/ZXing.Net.Mobile.Forms/packages.config +++ b/Source/ZXing.Net.Mobile.Forms/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/ZXing.Net.Mobile.Forms.nuspec b/ZXing.Net.Mobile.Forms.nuspec index 8445fbd79..ef0b0bff6 100644 --- a/ZXing.Net.Mobile.Forms.nuspec +++ b/ZXing.Net.Mobile.Forms.nuspec @@ -1,4 +1,4 @@ - + ZXing.Net.Mobile.Forms @@ -23,6 +23,7 @@ + diff --git a/ZXing.Net.Mobile.nuspec b/ZXing.Net.Mobile.nuspec index 42376c294..4e772ef13 100644 --- a/ZXing.Net.Mobile.nuspec +++ b/ZXing.Net.Mobile.nuspec @@ -1,4 +1,4 @@ - + ZXing.Net.Mobile