Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | 33x 34x 34x 7x 27x 27x 6x 1x 5x 21x 3x 3x 2x 1x 20x 6x 2x 2x | import { QRUnsupportedError } from "./errors";
import { decodeImageData, type DecodeImageDataOptions } from "./decodeImage";
import { toImageData } from "./image/source";
import { decodeWithNative, isNativeSupported } from "./native";
import type { ImageSource, QRScanOptions, QRScanResult } from "./types";
/**
* Choosing between the platform's decoder and the one in this package.
*
* The rule is "use the platform where it exists, and be complete everywhere
* else" — but the details are what make it work in practice rather than in a
* README, so they are all here in one place.
*/
/**
* Options that force the internal engine regardless of preference, because the
* native API structurally cannot answer them.
*/
function requiresInternal(options: QRScanOptions): boolean {
return (
options.binary === true ||
options.invert === "always" ||
typeof options.binarizer === "function" ||
options.sampling === "majority5" ||
// `region` and `maxDimension` describe *how* to look at the image, and the
// native detector takes no such instruction — it scans the whole frame at
// full resolution and reports coordinates accordingly. Honouring them on
// one engine and ignoring them on the other would make an option's meaning
// depend on the browser, which is precisely the trap this package refuses
// to ship (SPEC §2.2). So asking for either picks the engine that can obey.
options.region !== undefined ||
(options.maxDimension !== undefined && options.maxDimension !== 0)
);
}
export interface DecodeOptions extends DecodeImageDataOptions {
/** Skip the native path even in `auto`, for this call only. */
forceInternal?: boolean;
/**
* In `auto`, whether a native pass that found nothing should be followed by
* the internal engine on the *same* image.
*
* `true` (the default) is right for a still image: the caller asked about one
* picture and recall matters more than a few milliseconds. The live scanner
* passes `false` — running both engines on every empty frame doubles the cost
* of the most common frame there is, at twelve frames a second.
*
* @default true
*/
fallbackToInternal?: boolean;
}
/**
* Decode one image, choosing an engine.
*
* Order of business for `engine: "auto"`:
*
* 1. If any option needs detail the native API cannot supply, go internal.
* 2. Otherwise try native. If it returns results, done.
* 3. If native returned nothing, run the internal engine anyway. A still image
* is a question the caller asked once, and recall matters more there than a
* few milliseconds — the live scanner suppresses this second pass itself
* when it is deciding frame by frame.
*/
export async function decode(
source: ImageSource,
options: DecodeOptions = {},
): Promise<QRScanResult[]> {
const preference = options.engine ?? "auto";
if (preference === "internal" || options.forceInternal || requiresInternal(options)) {
return decodeImageData(await toImageData(source), options);
}
const native = await isNativeSupported();
if (preference === "native") {
if (!native) {
throw new QRUnsupportedError(
'engine: "native" was requested but this browser has no BarcodeDetector ' +
'for QR codes. Use engine: "auto" to fall back to the built-in decoder.',
);
}
return decodeWithNative(source, options);
}
if (native) {
// A native detector that throws (a format it advertised but cannot really
// do, a source it dislikes) must not take the whole decode down: the
// internal engine is right there, and a throw is a *failure* rather than a
// miss, so it falls through even when `fallbackToInternal` is off.
try {
const results = await decodeWithNative(source, options);
if (results.length > 0) return results;
Iif (options.fallbackToInternal === false) return [];
} catch {
// fall through to the internal engine
}
}
return decodeImageData(await toImageData(source), options);
}
/**
* Decode a file, blob, or anything else a drop/paste/`<input>` hands over.
*
* A thin alias for {@link decode} that exists because "scan this file" reads
* better at a call site than "decode this ImageSource", and because it is the
* name a consumer looks for.
*/
export async function decodeFile(
file: Blob,
options: DecodeOptions = {},
): Promise<QRScanResult[]> {
return decode(file, options);
}
/**
* Decode and return the first result, or `null`.
*
* The common shape at a call site: most callers want one code, and `[0] ?? null`
* at every one of them is noise.
*/
export async function decodeFirst(
source: ImageSource,
options: DecodeOptions = {},
): Promise<QRScanResult | null> {
const results = await decode(source, options);
return results[0] ?? null;
}
|