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