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

V2.3.4 #632

Merged
merged 8 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ interface Html5QrcodeConfigs {
* enable faster native code scanning experience.
*
* Set this flag to true, to enable using {@class BarcodeDetector} if
* supported. This is false by default.
* supported. This is true by default.
*
* Documentations:
* - https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector
Expand Down
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### Version 2.3.4
- `useBarCodeDetectorIfSupported` defaults to `true` and if supported the library will internally alternate between `BarcodeDetector` and `zxing-js`. Same robustness added for file based scan as well if more than one decoder is supported.
- Fixed the UI issue - [Issue#613](https://github.com/mebjas/html5-qrcode/issues/613).
- Fix for torch issue - [Issue#634](https://github.com/mebjas/html5-qrcode/issues/634).
- In case of `scanFile(..)` APIs, scan at image resolution. Show `Loading image...` while the image is being loaded for rendering. More info at [Issue#612](https://github.com/mebjas/html5-qrcode/issues/612)

### Version 2.3.3
Quick fix for - [issue#621](https://github.com/mebjas/html5-qrcode/issues/621). With this zoom & torch is not supported in firefox for now.

Expand Down
45 changes: 1 addition & 44 deletions experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,7 @@ these features will get upgraded to general feature list.

## Using experimental native BarcodeDetector API

> **Note** This config has now been graduated to `Html5QrcodeConfigs` and deprecated from experimental config.

Turning on this flag allows using native [BarcodeDetector](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector)
api now being introduced in web browsers for code scanning instead of `ZXing`
library we use officially.

### How to turn this on
Setting `useBarCodeDetectorIfSupported` to `true` in `Html5QrcodeConfigs` will
enable this option.

#### Html5Qrcode class

```js
function onScanSuccess(decodedText, decodedResult) {
/** Handle success condition. */
}

let html5qrcode = new Html5Qrcode("reader", {
// Use this flag to turn on the feature.
useBarCodeDetectorIfSupported: false
});

const scanConfig = { fps: 10, qrbox: 250 };
// If you want to prefer front camera
html5qrcode.start({ facingMode: "user" }, scanConfig, onScanSuccess);
```

#### Html5QrcodeScanner class

```js
function onScanSuccess(decodedText, decodedResult) {
/** Handle success condition. */
}

let html5QrcodeScanner = new Html5QrcodeScanner(
"reader",
{
fps: 10,
qrbox: 250,
// Use this flag to turn on the feature.
useBarCodeDetectorIfSupported: false
});
html5QrcodeScanner.render(onScanSuccess);
```
> **Update** This config has now been graduated to `Html5QrcodeConfigs` and deprecated from experimental config. This feature is now enabled by default.

### performance

Expand Down
2 changes: 1 addition & 1 deletion minified/html5-qrcode.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "html5-qrcode",
"version": "2.3.3",
"version": "2.3.4",
"description": "A cross platform HTML5 QR Code & bar code scanner",
"main": "./cjs/index.js",
"module": "./esm/index.js",
Expand Down
66 changes: 54 additions & 12 deletions src/code-decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
QrcodeResult,
Html5QrcodeSupportedFormats,
Logger,
QrcodeDecoderAsync
QrcodeDecoderAsync,
RobustQrcodeDecoderAsync,
} from "./core";

import { ZXingHtml5QrcodeDecoder } from "./zxing-html5-qrcode-decoder";
Expand All @@ -23,14 +24,16 @@ import { BarcodeDetectorDelegate } from "./native-bar-code-detector";
*
* Currently uses {@class ZXingHtml5QrcodeDecoder}, can be replace with another library.
*/
export class Html5QrcodeShim implements QrcodeDecoderAsync {
export class Html5QrcodeShim implements RobustQrcodeDecoderAsync {

private verbose: boolean;
private decoder: QrcodeDecoderAsync;
private primaryDecoder: QrcodeDecoderAsync;
private secondaryDecoder: QrcodeDecoderAsync | undefined;

private readonly EXECUTIONS_TO_REPORT_PERFORMANCE = 100;
private executions: number = 0;
private executionResults: Array<number> = [];
private wasPrimaryDecoderUsedInLastDecode = false;

public constructor(
requestedFormats: Array<Html5QrcodeSupportedFormats>,
Expand All @@ -42,26 +45,65 @@ export class Html5QrcodeShim implements QrcodeDecoderAsync {
// Use BarcodeDetector library if enabled by config and is supported.
if (useBarCodeDetectorIfSupported
&& BarcodeDetectorDelegate.isSupported()) {
this.decoder = new BarcodeDetectorDelegate(
this.primaryDecoder = new BarcodeDetectorDelegate(
requestedFormats, verbose, logger);
// If 'BarcodeDetector' is supported, the library will alternate
// between 'BarcodeDetector' and 'zxing-js' to compensate for
// quality gaps between the two.
this.secondaryDecoder = new ZXingHtml5QrcodeDecoder(
requestedFormats, verbose, logger);
} else {
this.decoder = new ZXingHtml5QrcodeDecoder(
this.primaryDecoder = new ZXingHtml5QrcodeDecoder(
requestedFormats, verbose, logger);
}
}

async decodeAsync(canvas: HTMLCanvasElement): Promise<QrcodeResult> {
let start = performance.now();
let startTime = performance.now();
try {
return await this.decoder.decodeAsync(canvas);
return await this.getDecoder().decodeAsync(canvas);
} finally {
if (this.verbose) {
let executionTime = performance.now() - start;
this.executionResults.push(executionTime);
this.executions++;
this.possiblyFlushPerformanceReport();
this.possiblyLogPerformance(startTime);
}
}

async decodeRobustlyAsync(canvas: HTMLCanvasElement)
: Promise<QrcodeResult> {
let startTime = performance.now();
try {
return await this.primaryDecoder.decodeAsync(canvas);
} catch(error) {
if (this.secondaryDecoder) {
// Try fallback.
return this.secondaryDecoder.decodeAsync(canvas);
}
throw error;
} finally {
this.possiblyLogPerformance(startTime);
}
}

private getDecoder(): QrcodeDecoderAsync {
if (!this.secondaryDecoder) {
return this.primaryDecoder;
}

if (this.wasPrimaryDecoderUsedInLastDecode === false) {
this.wasPrimaryDecoderUsedInLastDecode = true;
return this.primaryDecoder;
}
this.wasPrimaryDecoderUsedInLastDecode = false;
return this.secondaryDecoder;
}

private possiblyLogPerformance(startTime: number) {
if (!this.verbose) {
return;
}
let executionTime = performance.now() - startTime;
this.executionResults.push(executionTime);
this.executions++;
this.possiblyFlushPerformanceReport();
}

// Dumps mean decoding latency to console for last
Expand Down
18 changes: 18 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,24 @@ export interface QrcodeDecoderAsync {
decodeAsync(canvas: HTMLCanvasElement): Promise<QrcodeResult>;
}

/**
* Code robust decoder interface.
*
* <p> A robust decoder may sacrifice latency of scanning for scanning quality.
* Ideal for file scan kind of operation.
*/
export interface RobustQrcodeDecoderAsync extends QrcodeDecoderAsync {
/**
* Decodes content of the canvas to find a valid QR code or bar code.
*
* <p>The method implementation will run the decoder more robustly at the
* expense of latency.
*
* @param canvas a valid html5 canvas element.
*/
decodeRobustlyAsync(canvas: HTMLCanvasElement): Promise<QrcodeResult>;
}

/** Interface for logger. */
export interface Logger {
log(message: string): void;
Expand Down
9 changes: 8 additions & 1 deletion src/html5-qrcode-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ function toHtml5QrcodeFullConfig(
};
}

// End to end scanner library.
export class Html5QrcodeScanner {

//#region private fields
Expand Down Expand Up @@ -533,6 +534,7 @@ export class Html5QrcodeScanner {
requestPermissionContainer: HTMLDivElement,
requestPermissionButton?: HTMLButtonElement) {
const $this = this;
$this.showHideScanTypeSwapLink(false);
$this.setHeaderMessage(
Html5QrcodeScannerStrings.cameraPermissionRequesting());

Expand All @@ -547,7 +549,7 @@ export class Html5QrcodeScanner {
// By this point the user has granted camera permissions.
$this.persistedDataManager.setHasPermission(
/* hasPermission */ true);

$this.showHideScanTypeSwapLink(true);
$this.resetHeaderMessage();
if (cameras && cameras.length > 0) {
scpCameraScanRegion.removeChild(requestPermissionContainer);
Expand Down Expand Up @@ -575,6 +577,7 @@ export class Html5QrcodeScanner {
}
$this.setHeaderMessage(
error, Html5QrcodeScannerStatus.STATUS_WARNING);
$this.showHideScanTypeSwapLink(true);
});
}

Expand Down Expand Up @@ -676,6 +679,7 @@ export class Html5QrcodeScanner {
return;
}

$this.setHeaderMessage(Html5QrcodeScannerStrings.loadingImage());
$this.html5Qrcode.scanFileV2(file, /* showImage= */ true)
.then((html5qrcodeResult: Html5QrcodeResult) => {
$this.resetHeaderMessage();
Expand Down Expand Up @@ -775,6 +779,9 @@ export class Html5QrcodeScanner {
Html5QrcodeScannerStatus.STATUS_WARNING);
}
);
} else {
torchButton.updateTorchCapability(
cameraCapabilities.torchFeature());
}
torchButton.show();
};
Expand Down
48 changes: 32 additions & 16 deletions src/html5-qrcode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Html5QrcodeResultFactory,
Html5QrcodeErrorFactory,
Html5QrcodeSupportedFormats,
QrcodeDecoderAsync,
RobustQrcodeDecoderAsync,
isValidHtml5QrcodeSupportedFormats,
Html5QrcodeConstants,
Html5QrcodeResult,
Expand Down Expand Up @@ -55,6 +55,7 @@ class Constants extends Html5QrcodeConstants {
static DEFAULT_WIDTH = 300;
static DEFAULT_WIDTH_OFFSET = 2;
static FILE_SCAN_MIN_HEIGHT = 300;
static FILE_SCAN_HIDDEN_CANVAS_PADDING = 100;
static MIN_QR_BOX_SIZE = 50;
static SHADED_LEFT = 1;
static SHADED_RIGHT = 2;
Expand Down Expand Up @@ -87,7 +88,7 @@ export interface Html5QrcodeConfigs {
* enable faster native code scanning experience.
*
* Set this flag to true, to enable using {@class BarcodeDetector} if
* supported. This is false by default.
* supported. This is true by default.
*
* Documentations:
* - https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector
Expand Down Expand Up @@ -255,7 +256,7 @@ export class Html5Qrcode {
private readonly logger: Logger;
private readonly elementId: string;
private readonly verbose: boolean;
private readonly qrcode: QrcodeDecoderAsync;
private readonly qrcode: RobustQrcodeDecoderAsync;

private shouldScan: boolean;

Expand Down Expand Up @@ -681,27 +682,41 @@ export class Html5Qrcode {
/* dHeight= */ config.height);
}

// Hidden canvas should be at-least as big as the image.
// This could get really troublesome for large images like 12MP
// images or 48MP images captured on phone.
let padding = Constants.FILE_SCAN_HIDDEN_CANVAS_PADDING;
let hiddenImageWidth = Math.max(inputImage.width, config.width);
let hiddenImageHeight = Math.max(inputImage.height, config.height);

let hiddenCanvasWidth = hiddenImageWidth + 2 * padding;
let hiddenCanvasHeight = hiddenImageHeight + 2 * padding;

// Try harder for file scan.
// TODO(minhazav): Fallback to mirroring, 90 degree rotation and
// color inversion.
const hiddenCanvas = this.createCanvasElement(
config.width, config.height);
hiddenCanvasWidth, hiddenCanvasHeight);
element.appendChild(hiddenCanvas);
const context = hiddenCanvas.getContext("2d");
if (!context) {
throw "Unable to get 2d context from canvas";
}
context.canvas.width = config.width;
context.canvas.height = config.height;

context.canvas.width = hiddenCanvasWidth;
context.canvas.height = hiddenCanvasHeight;
context.drawImage(
inputImage,
/* sx= */ 0,
/* sy= */ 0,
/* sWidth= */ imageWidth,
/* sHeight= */ imageHeight,
/* dx= */ 0,
/* dy= */ 0,
/* dWidth= */ config.width,
/* dHeight= */ config.height);
/* dx= */ padding,
/* dy= */ padding,
/* dWidth= */ hiddenImageWidth,
/* dHeight= */ hiddenImageHeight);
try {
this.qrcode.decodeAsync(hiddenCanvas)
this.qrcode.decodeRobustlyAsync(hiddenCanvas)
.then((result) => {
resolve(
Html5QrcodeResultFactory.createFromQrcodeResult(
Expand Down Expand Up @@ -891,26 +906,27 @@ export class Html5Qrcode {
/*eslint complexity: ["error", 10]*/
private getUseBarCodeDetectorIfSupported(
config: Html5QrcodeConfigs | undefined) : boolean {
// Default value is true.
if (isNullOrUndefined(config)) {
return false;
return true;
}

if (!isNullOrUndefined(config!.useBarCodeDetectorIfSupported)) {
// Default value is false.
return config!.useBarCodeDetectorIfSupported === true;
return config!.useBarCodeDetectorIfSupported !== false;
}

if (isNullOrUndefined(config!.experimentalFeatures)) {
return false;
return true;
}

let experimentalFeatures = config!.experimentalFeatures!;
if (isNullOrUndefined(
experimentalFeatures.useBarCodeDetectorIfSupported)) {
return false;
return true;
}

return experimentalFeatures.useBarCodeDetectorIfSupported === true;
return experimentalFeatures.useBarCodeDetectorIfSupported !== false;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ export class Html5QrcodeScannerStrings {
public static zoom(): string {
return "zoom";
}

public static loadingImage(): string {
return "Loading image...";
}
}

/** Strings used in {@class LibraryInfoDiv} */
Expand Down
Loading