Skip to content

Commit fdefa7f

Browse files
Merge PR mebjas#766 from upstream
2 parents 0d42e6b + 1c19869 commit fdefa7f

File tree

1 file changed

+93
-9
lines changed

1 file changed

+93
-9
lines changed

src/html5-qrcode.ts

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)