Skip to content

Commit 7a7a94f

Browse files
committed
Fix: NRE in SetSurfaceTransform, if called before camera is ready. Keep a ringbuffer of recent barcodes, and honor DelayBetweenContinuousScans (of same barcode)
1 parent 4a49525 commit 7a7a94f

2 files changed

Lines changed: 103 additions & 8 deletions

File tree

Samples/Forms/Core/CustomScanPage.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ public CustomScanPage () : base ()
2828
formats.Add(ZXing.BarcodeFormat.CODE_39);
2929
formats.Add(ZXing.BarcodeFormat.QR_CODE);
3030

31+
zxing.Options.DelayBetweenContinuousScans = 1000; // same barcode can only be scanned once a second. Different barcodes is a different matter
32+
3133
zxing.IsTorchOn = true;
3234

3335
zxing.OnScanResult += (result) =>
3436
Device.BeginInvokeOnMainThread (async () => {
3537

3638
// Stop analysis until we navigate away so we don't keep reading barcodes
37-
zxing.IsAnalyzing = false;
39+
zxing.IsAnalyzing = true;
3840

3941
// Show an alert
4042
await DisplayAlert ("Scanned Barcode", result.Text, "OK");

Source/ZXing.Net.Mobile.Android/ZXingTextureView.cs

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Diagnostics;
34
using System.Drawing;
45
using System.Linq;
56
using System.Threading.Tasks;
@@ -93,6 +94,75 @@ public override void OnOrientationChanged(int orientation)
9394
}
9495
}
9596

97+
public class RingBuffer<T>
98+
{
99+
readonly T[] _buffer;
100+
int _tail;
101+
int _length;
102+
103+
public RingBuffer(int capacity)
104+
{
105+
_buffer = new T[capacity];
106+
}
107+
108+
public void Add(T item)
109+
{
110+
_buffer[_tail] = item; // will overwrite existing entry, if any
111+
_tail = (_tail + 1) % _buffer.Length; // roll over
112+
_length++;
113+
}
114+
115+
public T this[int index]
116+
{
117+
get { return _buffer[WrapIndex(index)]; }
118+
set { _buffer[WrapIndex(index)] = value; }
119+
}
120+
121+
public int Length
122+
{
123+
get { return _length; }
124+
}
125+
126+
public int FindIndex(ref T toFind, IComparer<T> comparer = null)
127+
{
128+
comparer = comparer ?? Comparer<T>.Default;
129+
int idx = -1;
130+
for (int i = 0; i < Length; ++i)
131+
{
132+
var candidate = this[i];
133+
if (comparer.Compare(candidate, toFind) == 0)
134+
{
135+
idx = i;
136+
toFind = candidate;
137+
break; // item found in history ring
138+
}
139+
}
140+
return idx;
141+
}
142+
143+
public void AddOrUpdate(ref T item, IComparer<T> comparer = null)
144+
{
145+
var idx = FindIndex(ref item);
146+
if (idx < 0)
147+
Add(item);
148+
else
149+
this[idx] = item;
150+
}
151+
152+
int Head
153+
{
154+
get { return (_tail - _length) % _buffer.Length; }
155+
}
156+
157+
int WrapIndex(int index)
158+
{
159+
if (index < 0 || index >= _length)
160+
throw new IndexOutOfRangeException($"{nameof(index)} = {index}");
161+
162+
return (Head + index) % _buffer.Length;
163+
}
164+
}
165+
96166
public class ZXingTextureView : TextureView, IScannerView, Camera.IAutoFocusCallback, INonMarshalingPreviewCallback
97167
{
98168
Camera.CameraInfo _cameraInfo;
@@ -204,6 +274,10 @@ bool IsPortrait {
204274
Rectangle _area;
205275
void SetSurfaceTransform(SurfaceTexture st, int width, int height)
206276
{
277+
var p = PreviewSize;
278+
if (p == null)
279+
return; // camera no ready yet, we will be called again later from SetupCamera.
280+
207281
using (var metrics = new DisplayMetrics())
208282
{
209283
#region transform
@@ -212,7 +286,6 @@ void SetSurfaceTransform(SurfaceTexture st, int width, int height)
212286
var aspectRatio = metrics.Xdpi / metrics.Ydpi; // close to 1, but rarely perfect 1
213287

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

218291
// Compensate for portrait mode
@@ -314,6 +387,7 @@ public MobileBarcodeScanningOptions ScanningOptions
314387
set
315388
{
316389
_scanningOptions = value;
390+
_delay = TimeSpan.FromMilliseconds(value.DelayBetweenContinuousScans).Ticks;
317391
_barcodeReader = CreateBarcodeReader(value);
318392
}
319393
}
@@ -647,9 +721,20 @@ public void RotateCounterClockwise(byte[] source, ref byte[] target, int width,
647721
target[x * height + height - y - 1] = source[x + y * width];
648722
}
649723

724+
const int maxHistory = 10; // a bit arbitrary :-/
725+
struct LastResult
726+
{
727+
public long Timestamp;
728+
public Result Result;
729+
};
730+
731+
readonly RingBuffer<LastResult> _ring = new RingBuffer<LastResult>(maxHistory);
732+
readonly IComparer<LastResult> _resultComparer = Comparer<LastResult>.Create((x, y) => x.Result.Text.CompareTo(y.Result.Text));
733+
long _delay;
734+
650735
byte[] _matrix;
651736
byte[] _rotatedMatrix;
652-
Result _lastResult;
737+
653738
async public void OnPreviewFrame(IntPtr data, Camera camera)
654739
{
655740
System.Diagnostics.Stopwatch sw = null;
@@ -658,7 +743,7 @@ async public void OnPreviewFrame(IntPtr data, Camera camera)
658743
try
659744
{
660745
#if DEBUG
661-
sw = new System.Diagnostics.Stopwatch();
746+
sw = new Stopwatch();
662747
sw.Start();
663748
#endif
664749
if (!_isAnalyzing)
@@ -684,14 +769,22 @@ async public void OnPreviewFrame(IntPtr data, Camera camera)
684769

685770
if (result != null)
686771
{
687-
// don't raise the same barcode multiple times, unless we have seen atleast one other barcode or an empty frame
688-
if (result.Text != _lastResult?.Text)
772+
var now = Stopwatch.GetTimestamp();
773+
var lastResult = new LastResult { Result = result };
774+
int idx = _ring.FindIndex(ref lastResult, _resultComparer);
775+
if (idx < 0 || lastResult.Timestamp + _delay < now)
776+
{
689777
_callback(result);
778+
779+
lastResult.Timestamp = now; // update timestamp
780+
if (idx < 0)
781+
_ring.Add(lastResult);
782+
else
783+
_ring[idx] = lastResult;
784+
}
690785
}
691786
else if (!_useContinuousFocus)
692787
AutoFocus();
693-
694-
_lastResult = result;
695788
}
696789
catch (Exception ex)
697790
{

0 commit comments

Comments
 (0)