Skip to content

Commit

Permalink
Fix: NRE in SetSurfaceTransform, if called before camera is ready. Ke…
Browse files Browse the repository at this point in the history
…ep a ringbuffer of recent barcodes, and honor DelayBetweenContinuousScans (of same barcode)
  • Loading branch information
nielsenko committed Jan 17, 2017
1 parent 4a49525 commit 7a7a94f
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 8 deletions.
4 changes: 3 additions & 1 deletion Samples/Forms/Core/CustomScanPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public CustomScanPage () : base ()
formats.Add(ZXing.BarcodeFormat.CODE_39);
formats.Add(ZXing.BarcodeFormat.QR_CODE);

zxing.Options.DelayBetweenContinuousScans = 1000; // same barcode can only be scanned once a second. Different barcodes is a different matter

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");
Expand Down
107 changes: 100 additions & 7 deletions Source/ZXing.Net.Mobile.Android/ZXingTextureView.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -93,6 +94,75 @@ public override void OnOrientationChanged(int orientation)
}
}

public class RingBuffer<T>
{
readonly T[] _buffer;
int _tail;
int _length;

public RingBuffer(int capacity)
{
_buffer = new T[capacity];
}

public void Add(T item)
{
_buffer[_tail] = item; // will overwrite existing entry, if any
_tail = (_tail + 1) % _buffer.Length; // roll over
_length++;
}

public T this[int index]
{
get { return _buffer[WrapIndex(index)]; }
set { _buffer[WrapIndex(index)] = value; }
}

public int Length
{
get { return _length; }
}

public int FindIndex(ref T toFind, IComparer<T> comparer = null)
{
comparer = comparer ?? Comparer<T>.Default;
int idx = -1;
for (int i = 0; i < Length; ++i)
{
var candidate = this[i];
if (comparer.Compare(candidate, toFind) == 0)
{
idx = i;
toFind = candidate;
break; // item found in history ring
}
}
return idx;
}

public void AddOrUpdate(ref T item, IComparer<T> comparer = null)
{
var idx = FindIndex(ref item);
if (idx < 0)
Add(item);
else
this[idx] = item;
}

int Head
{
get { return (_tail - _length) % _buffer.Length; }
}

int WrapIndex(int index)
{
if (index < 0 || index >= _length)
throw new IndexOutOfRangeException($"{nameof(index)} = {index}");

return (Head + index) % _buffer.Length;
}
}

public class ZXingTextureView : TextureView, IScannerView, Camera.IAutoFocusCallback, INonMarshalingPreviewCallback
{
Camera.CameraInfo _cameraInfo;
Expand Down Expand Up @@ -204,6 +274,10 @@ bool IsPortrait {
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
Expand All @@ -212,7 +286,6 @@ void SetSurfaceTransform(SurfaceTexture st, int width, int height)
var aspectRatio = metrics.Xdpi / metrics.Ydpi; // close to 1, but rarely perfect 1

// Compensate for preview streams aspect ratio
var p = PreviewSize;
aspectRatio *= (float)p.Height / p.Width;

// Compensate for portrait mode
Expand Down Expand Up @@ -314,6 +387,7 @@ public MobileBarcodeScanningOptions ScanningOptions
set
{
_scanningOptions = value;
_delay = TimeSpan.FromMilliseconds(value.DelayBetweenContinuousScans).Ticks;
_barcodeReader = CreateBarcodeReader(value);
}
}
Expand Down Expand Up @@ -647,9 +721,20 @@ public void RotateCounterClockwise(byte[] source, ref byte[] target, int width,
target[x * height + height - y - 1] = source[x + y * width];
}

const int maxHistory = 10; // a bit arbitrary :-/
struct LastResult
{
public long Timestamp;
public Result Result;
};

readonly RingBuffer<LastResult> _ring = new RingBuffer<LastResult>(maxHistory);
readonly IComparer<LastResult> _resultComparer = Comparer<LastResult>.Create((x, y) => x.Result.Text.CompareTo(y.Result.Text));
long _delay;

byte[] _matrix;
byte[] _rotatedMatrix;
Result _lastResult;

async public void OnPreviewFrame(IntPtr data, Camera camera)
{
System.Diagnostics.Stopwatch sw = null;
Expand All @@ -658,7 +743,7 @@ async public void OnPreviewFrame(IntPtr data, Camera camera)
try
{
#if DEBUG
sw = new System.Diagnostics.Stopwatch();
sw = new Stopwatch();
sw.Start();
#endif
if (!_isAnalyzing)
Expand All @@ -684,14 +769,22 @@ async public void OnPreviewFrame(IntPtr data, Camera camera)

if (result != null)
{
// don't raise the same barcode multiple times, unless we have seen atleast one other barcode or an empty frame
if (result.Text != _lastResult?.Text)
var now = Stopwatch.GetTimestamp();
var lastResult = new LastResult { Result = result };
int idx = _ring.FindIndex(ref lastResult, _resultComparer);
if (idx < 0 || lastResult.Timestamp + _delay < now)
{
_callback(result);

lastResult.Timestamp = now; // update timestamp
if (idx < 0)
_ring.Add(lastResult);
else
_ring[idx] = lastResult;
}
}
else if (!_useContinuousFocus)
AutoFocus();

_lastResult = result;
}
catch (Exception ex)
{
Expand Down

0 comments on commit 7a7a94f

Please sign in to comment.