11using System ;
22using System . Collections . Generic ;
3+ using System . Diagnostics ;
34using System . Drawing ;
45using System . Linq ;
56using 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