@@ -268,6 +268,8 @@ export class Html5Qrcode {
268268 private readonly qrcode : RobustQrcodeDecoderAsync ;
269269
270270 private shouldScan : boolean ;
271+ private flippedScan : boolean ;
272+ private invertedScan : boolean ;
271273
272274 // Nullable elements
273275 // TODO(mebjas): Reduce the state-fulness of this mammoth class, by splitting
@@ -336,6 +338,8 @@ export class Html5Qrcode {
336338
337339 this . foreverScanTimeout ;
338340 this . shouldScan = true ;
341+ this . flippedScan = false ;
342+ this . invertedScan = false ;
339343 this . stateManagerProxy = StateManagerFactory . create ( ) ;
340344 }
341345
@@ -1211,20 +1215,37 @@ export class Html5Qrcode {
12111215 internalConfig , qrCodeSuccessCallback , qrCodeErrorCallback ) ;
12121216 } , this . getTimeoutFps ( internalConfig . fps ) ) ;
12131217 } ;
1214-
1218+ if ( this . context ) {
1219+ // apply invert function that replace this.context.filter = 'invert(X)'
1220+ this . context = this . invert ( this . context , ( this . invertedScan ) ? '1' : '0' ) ;
1221+ }
12151222 // Try scanning normal frame and in case of failure, scan
12161223 // the inverted context if not explictly disabled.
12171224 // TODO(mebjas): Move this logic to decoding library.
12181225 this . scanContext ( qrCodeSuccessCallback , qrCodeErrorCallback )
12191226 . then ( ( isSuccessfull ) => {
1220- // Previous scan failed and disableFlip is off.
1221- if ( ! isSuccessfull && internalConfig . disableFlip !== true ) {
1222- this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1223- this . context ! . scale ( - 1 , 1 ) ;
1224- this . scanContext ( qrCodeSuccessCallback , qrCodeErrorCallback )
1225- . finally ( ( ) => {
1226- triggerNextScan ( ) ;
1227- } ) ;
1227+ // Previous scan failed
1228+ if ( ! isSuccessfull ) {
1229+ // disableFlip is off and is not flipped
1230+ if ( internalConfig . disableFlip !== true && this . flippedScan === false ) {
1231+ this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1232+ this . context ! . scale ( - 1 , 1 ) ;
1233+ this . flippedScan = true ;
1234+ } else {
1235+ // disableFlip is off
1236+ if ( internalConfig . disableFlip !== true ) {
1237+ this . context ! . translate ( this . context ! . canvas . width , 0 ) ;
1238+ this . context ! . scale ( - 1 , 1 ) ;
1239+ }
1240+ this . flippedScan = false ;
1241+ }
1242+ // previous scan failed, check if next time I shouldapply invert function with amount parameter set to 1 or 0
1243+ if ( this . invertedScan === false && this . flippedScan === false ) {
1244+ this . invertedScan = true ;
1245+ } else if ( this . invertedScan === true && this . flippedScan === false ) {
1246+ this . invertedScan = false ;
1247+ }
1248+ triggerNextScan ( ) ;
12281249 } else {
12291250 triggerNextScan ( ) ;
12301251 }
@@ -1330,6 +1351,69 @@ export class Html5Qrcode {
13301351 const type = ( typeof cameraIdOrConfig ) ;
13311352 throw `Invalid type of 'cameraIdOrConfig' = ${ type } ` ;
13321353 }
1354+
1355+ /**
1356+ * Method that replace CanvasRenderingContext2D.filter that is not supported from Safari
1357+ * https://github.com/davidenke/context-filter-polyfill/blob/main/src/filters/invert.filter.ts
1358+ * @param context
1359+ * @param amount from 0 to 1
1360+ */
1361+ private invert (
1362+ context : CanvasRenderingContext2D ,
1363+ amount : any = '0'
1364+ ) {
1365+ amount = this . normalizeNumberPercentage ( amount ) ;
1366+ // do not manipulate without proper amount
1367+ if ( amount <= 0 ) {
1368+ return context ;
1369+ }
1370+ // a maximum of 100%
1371+ if ( amount > 1 ) {
1372+ amount = 1 ;
1373+ }
1374+ const { height, width } = context . canvas ;
1375+ const imageData = context . getImageData ( 0 , 0 , width , height ) ;
1376+ const { data } = imageData ;
1377+ const { length } = data ;
1378+
1379+ // in rgba world, every
1380+ // n * 4 + 0 is red,
1381+ // n * 4 + 1 green and
1382+ // n * 4 + 2 is blue
1383+ // the fourth can be skipped as it's the alpha channel
1384+ for ( let i = 0 ; i < length ; i += 4 ) {
1385+ data [ i + 0 ] = Math . abs ( data [ i + 0 ] - 255 * amount ) ;
1386+ data [ i + 1 ] = Math . abs ( data [ i + 1 ] - 255 * amount ) ;
1387+ data [ i + 2 ] = Math . abs ( data [ i + 2 ] - 255 * amount ) ;
1388+ }
1389+
1390+ // set back image data to context
1391+ context . putImageData ( imageData , 0 , 0 ) ;
1392+
1393+ // return the context itself
1394+ return context ;
1395+ }
1396+
1397+ /**
1398+ * filter options are often represented as number-percentage,
1399+ * means that they'll be percentages like `50%` or floating
1400+ * in-between 0 and 1 like `.5`, so we normalize them.
1401+ * https://developer.mozilla.org/en-US/docs/Web/CSS/filter#number-percentage
1402+ * https://github.com/davidenke/context-filter-polyfill/blob/main/src/utils/filter.utils.ts
1403+ * @param percentage
1404+ * @returns
1405+ */
1406+ private normalizeNumberPercentage (
1407+ percentage : string
1408+ ) {
1409+ let normalized = parseFloat ( percentage ) ;
1410+ // check for percentages and divide by a hundred
1411+ if ( / % \s * ?$ / i. test ( percentage ) ) {
1412+ normalized /= 100 ;
1413+ }
1414+ return normalized ;
1415+ }
1416+
13331417 //#endregion
13341418
13351419 //#region Documented private methods for file based scanner.
0 commit comments