Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inverted QR codes (#94) #766

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 94 additions & 10 deletions src/html5-qrcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ export class Html5Qrcode {
private readonly qrcode: RobustQrcodeDecoderAsync;

private shouldScan: boolean;
private flippedScan: boolean;
private invertedScan: boolean;

// Nullable elements
// TODO(mebjas): Reduce the state-fulness of this mammoth class, by splitting
Expand Down Expand Up @@ -316,7 +318,7 @@ export class Html5Qrcode {

this.elementId = elementId;
this.verbose = false;

let experimentalFeatureConfig : ExperimentalFeaturesConfig | undefined;
let configObject: Html5QrcodeFullConfig | undefined;
if (typeof configOrVerbosityFlag == "boolean") {
Expand All @@ -336,6 +338,8 @@ export class Html5Qrcode {

this.foreverScanTimeout;
this.shouldScan = true;
this.flippedScan = false;
this.invertedScan = false;
this.stateManagerProxy = StateManagerFactory.create();
}

Expand Down Expand Up @@ -1211,20 +1215,37 @@ export class Html5Qrcode {
internalConfig, qrCodeSuccessCallback, qrCodeErrorCallback);
}, this.getTimeoutFps(internalConfig.fps));
};

if(this.context) {
// apply invert function that replace this.context.filter = 'invert(X)'
this.context = this.invert(this.context, (this.invertedScan) ? '1' : '0');
}
// Try scanning normal frame and in case of failure, scan
// the inverted context if not explictly disabled.
// TODO(mebjas): Move this logic to decoding library.
this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback)
.then((isSuccessfull) => {
// Previous scan failed and disableFlip is off.
if (!isSuccessfull && internalConfig.disableFlip !== true) {
this.context!.translate(this.context!.canvas.width, 0);
this.context!.scale(-1, 1);
this.scanContext(qrCodeSuccessCallback, qrCodeErrorCallback)
.finally(() => {
triggerNextScan();
});
// Previous scan failed
if (!isSuccessfull) {
// disableFlip is off and is not flipped
if (internalConfig.disableFlip !== true && this.flippedScan === false) {
this.context!.translate(this.context!.canvas.width, 0);
this.context!.scale(-1, 1);
this.flippedScan = true;
} else {
// disableFlip is off
if (internalConfig.disableFlip !== true) {
this.context!.translate(this.context!.canvas.width, 0);
this.context!.scale(-1, 1);
}
this.flippedScan = false;
}
// previous scan failed, check if next time I shouldapply invert function with amount parameter set to 1 or 0
if (this.invertedScan === false && this.flippedScan === false) {
this.invertedScan = true;
} else if (this.invertedScan === true && this.flippedScan === false) {
this.invertedScan = false;
}
triggerNextScan();
} else {
triggerNextScan();
}
Expand Down Expand Up @@ -1330,6 +1351,69 @@ export class Html5Qrcode {
const type = (typeof cameraIdOrConfig);
throw `Invalid type of 'cameraIdOrConfig' = ${type}`;
}

/**
* Method that replace CanvasRenderingContext2D.filter that is not supported from Safari
* https://github.com/davidenke/context-filter-polyfill/blob/main/src/filters/invert.filter.ts
* @param context
* @param amount from 0 to 1
*/
private invert(
context: CanvasRenderingContext2D,
amount: any = '0'
) {
amount = this.normalizeNumberPercentage(amount);
// do not manipulate without proper amount
if (amount <= 0) {
return context;
}
// a maximum of 100%
if (amount > 1) {
amount = 1;
}
const { height, width } = context.canvas;
const imageData = context.getImageData(0, 0, width, height);
const { data } = imageData;
const { length } = data;

// in rgba world, every
// n * 4 + 0 is red,
// n * 4 + 1 green and
// n * 4 + 2 is blue
// the fourth can be skipped as it's the alpha channel
for (let i = 0; i < length; i += 4) {
data[i + 0] = Math.abs(data[i + 0] - 255 * amount);
data[i + 1] = Math.abs(data[i + 1] - 255 * amount);
data[i + 2] = Math.abs(data[i + 2] - 255 * amount);
}

// set back image data to context
context.putImageData(imageData, 0, 0);

// return the context itself
return context;
}

/**
* filter options are often represented as number-percentage,
* means that they'll be percentages like `50%` or floating
* in-between 0 and 1 like `.5`, so we normalize them.
* https://developer.mozilla.org/en-US/docs/Web/CSS/filter#number-percentage
* https://github.com/davidenke/context-filter-polyfill/blob/main/src/utils/filter.utils.ts
* @param percentage
* @returns
*/
private normalizeNumberPercentage(
percentage: string
) {
let normalized = parseFloat(percentage);
// check for percentages and divide by a hundred
if (/%\s*?$/i.test(percentage)) {
normalized /= 100;
}
return normalized;
}

//#endregion

//#region Documented private methods for file based scanner.
Expand Down