tapirscan 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +220 -0
- package/README.md +384 -230
- package/dist/browser-worker.d.ts +33 -0
- package/dist/browser-worker.js +83 -0
- package/dist/browser.d.ts +44 -0
- package/dist/browser.js +211 -0
- package/dist/freeze.d.ts +6 -0
- package/dist/freeze.js +7 -0
- package/dist/index.d.ts +30 -18
- package/dist/index.js +138 -87
- package/dist/layout.d.ts +19 -0
- package/dist/layout.js +27 -0
- package/dist/multiformat/format-registry.d.ts +2 -2
- package/dist/multiformat/format-registry.js +12 -11
- package/dist/multiformat/formats.d.ts +2 -0
- package/dist/multiformat/formats.js +13 -6
- package/dist/rust-session.d.ts +2 -2
- package/dist/rust-session.js +16 -6
- package/examples/camera.html +28 -29
- package/examples/vite/README.md +31 -0
- package/examples/vite/index.html +14 -0
- package/examples/vite/main.js +33 -0
- package/examples/vite/package.json +16 -0
- package/examples/vite/vite.config.js +6 -0
- package/package.json +27 -8
- package/wasm/build.json +295 -0
- package/wasm/experimental-turbo16.wasm +0 -0
- package/wasm/experimental-turbo2.wasm +0 -0
- package/wasm/experimental-turbo4.wasm +0 -0
- package/wasm/experimental-turbo8.wasm +0 -0
- package/wasm/high.wasm +0 -0
- package/wasm/low.wasm +0 -0
- package/wasm/medium.wasm +0 -0
- package/wasm/very-high.wasm +0 -0
- package/examples/scan-worker.mjs +0 -20
- package/examples/worker-client.mjs +0 -63
- package/wasm/high-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/low-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/medium-release-1.2.2-public-low-r2.wasm +0 -0
- package/wasm/very-high-release-1.2.2-public-low-r2.wasm +0 -0
package/README.md
CHANGED
|
@@ -1,35 +1,210 @@
|
|
|
1
1
|
# Tapirscan for JavaScript and TypeScript
|
|
2
2
|
|
|
3
|
-
Scan
|
|
4
|
-
|
|
3
|
+
Scan barcodes in the browser or Node with a Rust/WASM core. Each scan returns
|
|
4
|
+
every decoded barcode with its `text`, `format` and source-image `polygon` / `rect`.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
- **`tapirscan/browser`** for web apps: scan files, `<img>`, `<video>`, canvases
|
|
7
|
+
or bitmaps in a bundled worker, with no WASM setup.
|
|
8
|
+
- **`tapirscan`** (the core) for Node and decoded pixels, scanning synchronously
|
|
9
|
+
on the calling thread.
|
|
10
|
+
|
|
11
|
+
[Try the live demo](https://tapirscan.f-kleinicke.de) · [Browser apps](#browser-apps-react-and-svelte) · [Core API](#core-api) · [WASM loading](#wasm-loading) · [All options](#all-options) · [Results](#results)
|
|
7
12
|
|
|
8
13
|
```sh
|
|
9
14
|
npm install tapirscan
|
|
10
15
|
```
|
|
11
16
|
|
|
12
|
-
TypeScript declarations and WASM binaries are included
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
TypeScript declarations and WASM binaries are included. Your runtime must support
|
|
18
|
+
WebAssembly SIMD; Node 20 or later is required.
|
|
19
|
+
|
|
20
|
+
## Browser apps, React and Svelte
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
import { scan } from "tapirscan/browser";
|
|
24
|
+
|
|
25
|
+
const result = await scan(file); // a File from <input type="file">
|
|
26
|
+
console.log(result.values); // e.g. ["4006381333931"]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Reuse a scanner for several images or camera frames. Construction is synchronous:
|
|
30
|
+
the worker and WASM load in the background, and the first scan waits for them.
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { Scanner } from "tapirscan/browser";
|
|
34
|
+
|
|
35
|
+
const scanner = new Scanner({ formats: ["EAN13", "QRCode"] });
|
|
36
|
+
const result = await scanner.scan(image); // a file, <img>, canvas, video frame, ...
|
|
37
|
+
scanner.dispose(); // stops the worker
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Camera:** scan a playing `<video>` in a loop. Waiting for the next frame scans
|
|
41
|
+
each frame at most once, and awaiting each scan means slow devices skip frames
|
|
42
|
+
instead of falling behind:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
while (running) {
|
|
46
|
+
await new Promise((resolve) => video.requestVideoFrameCallback(resolve));
|
|
47
|
+
if (!running) break;
|
|
48
|
+
const result = await scanner.scan(video);
|
|
49
|
+
// ...show result.values and result.barcodes
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`dispose()` rejects the scan in progress with "Scanner was disposed". In React
|
|
54
|
+
(add `"use client";` at the top in Next.js):
|
|
55
|
+
|
|
56
|
+
```jsx
|
|
57
|
+
import { useEffect, useRef, useState } from "react";
|
|
58
|
+
import { Scanner } from "tapirscan/browser";
|
|
59
|
+
|
|
60
|
+
export default function BarcodeScanner() {
|
|
61
|
+
const video = useRef(null);
|
|
62
|
+
const [barcodes, setBarcodes] = useState([]);
|
|
63
|
+
const [error, setError] = useState(null);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const element = video.current;
|
|
67
|
+
const scanner = new Scanner();
|
|
68
|
+
const camera = navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } });
|
|
69
|
+
let running = true; // React StrictMode mounts twice in development
|
|
70
|
+
// One cleanup for unmounting and for failures: stop scanning and the camera.
|
|
71
|
+
const stop = () => {
|
|
72
|
+
running = false;
|
|
73
|
+
scanner.dispose();
|
|
74
|
+
camera.then(
|
|
75
|
+
(stream) => stream.getTracks().forEach((track) => track.stop()),
|
|
76
|
+
() => {}, // No stream to stop.
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
camera
|
|
80
|
+
.then(async (stream) => {
|
|
81
|
+
if (!running) return;
|
|
82
|
+
element.srcObject = stream;
|
|
83
|
+
while (running) {
|
|
84
|
+
await new Promise((resolve) => element.requestVideoFrameCallback(resolve));
|
|
85
|
+
if (!running) break;
|
|
86
|
+
const found = await scanner.scan(element);
|
|
87
|
+
if (running) setBarcodes(found.barcodes);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.catch((cause) => {
|
|
91
|
+
if (running) setError(cause.message); // camera denied, loading or scan failure
|
|
92
|
+
stop();
|
|
93
|
+
});
|
|
94
|
+
return stop;
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
<video ref={video} autoPlay muted playsInline />
|
|
100
|
+
{error && <p role="alert">{error}</p>}
|
|
101
|
+
{barcodes.map((barcode, i) => (
|
|
102
|
+
<p key={i}>
|
|
103
|
+
{barcode.format}: {barcode.text}
|
|
104
|
+
</p>
|
|
105
|
+
))}
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
In Svelte 5 and SvelteKit:
|
|
112
|
+
|
|
113
|
+
```svelte
|
|
114
|
+
<script>
|
|
115
|
+
import { Scanner } from "tapirscan/browser";
|
|
116
|
+
import { onDestroy } from "svelte";
|
|
117
|
+
|
|
118
|
+
const scanner = new Scanner();
|
|
119
|
+
onDestroy(() => scanner.dispose());
|
|
120
|
+
|
|
121
|
+
let barcodes = $state.raw([]);
|
|
122
|
+
let error = $state(null);
|
|
123
|
+
let latest = 0; // scans can finish out of order; keep only the newest
|
|
124
|
+
async function onchange(event) {
|
|
125
|
+
const file = event.currentTarget.files?.[0];
|
|
126
|
+
if (!file) return;
|
|
127
|
+
const request = ++latest;
|
|
128
|
+
try {
|
|
129
|
+
const found = await scanner.scan(file);
|
|
130
|
+
if (request === latest) [barcodes, error] = [found.barcodes, null];
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
if (request === latest) [barcodes, error] = [[], cause.message];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<input type="file" accept="image/*" {onchange} />
|
|
138
|
+
{#if error}<p role="alert">{error}</p>{/if}
|
|
139
|
+
{#each barcodes as barcode}<p>{barcode.format}: {barcode.text}</p>{/each}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Bundlers:** Next.js (Turbopack or webpack), Vite 8 and production builds bundle
|
|
143
|
+
the worker and WASM files with no configuration. The Vite 6 and 7 development
|
|
144
|
+
servers (including SvelteKit on them) need one line, or the scanner reports that
|
|
145
|
+
its worker failed to start:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
// vite.config.js
|
|
149
|
+
export default defineConfig({
|
|
150
|
+
// ...your plugins
|
|
151
|
+
optimizeDeps: { exclude: ["tapirscan"] },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
15
154
|
|
|
16
|
-
|
|
155
|
+
CI builds the [Vite example](examples/vite/) from the packed package and scans an
|
|
156
|
+
uploaded photo with the production build in Chrome. The Next.js and Vite 7–8 setups
|
|
157
|
+
were verified by hand.
|
|
158
|
+
|
|
159
|
+
- **Sources:** a `File` or `Blob` (any image the browser decodes), `<img>`,
|
|
160
|
+
`<video>` (its current frame), `<canvas>`, `OffscreenCanvas`, `ImageBitmap`,
|
|
161
|
+
`VideoFrame`, `ImageData`, or decoded pixels as in the core API. Inputs are not
|
|
162
|
+
modified or transferred. Images and canvases are composited onto white, so
|
|
163
|
+
transparent areas count as background; `ImageData` and pixel buffers are scanned
|
|
164
|
+
as given, with alpha ignored.
|
|
165
|
+
- **Methods:** `scan` and `inspect` work as in the core API but return promises.
|
|
166
|
+
Each call captures its image and options when it starts, so buffers and option
|
|
167
|
+
objects can be reused immediately. Calls reach the worker in call order.
|
|
168
|
+
- **Options:** `mode`, `formats`, `eanAddOnPolicy` and
|
|
169
|
+
[`experimentalTurbo`](#experimental-turbo-presets) work as in the core, and
|
|
170
|
+
`scan(image, { formats })` overrides the formats for one call. `wasmBaseUrl`
|
|
171
|
+
serves the WASM files from another directory. `loadWasm` needs the core.
|
|
172
|
+
- **Lifecycle:** `scanner.ready` resolves once loaded; awaiting it is optional,
|
|
173
|
+
because loading errors also reject every scan. During server rendering the
|
|
174
|
+
constructor does nothing and scans reject. `dispose()` stops the worker and
|
|
175
|
+
immediately rejects every unfinished scan; returned results stay valid.
|
|
176
|
+
- **Errors:** unknown option names and invalid `wasmBaseUrl` values throw in the constructor; invalid option values
|
|
177
|
+
and scan arguments reject with `TypeError`; engine failures reject with
|
|
178
|
+
`ScannerError`. Browser image decoding and loading can also fail.
|
|
179
|
+
- **Requirements:** module workers, `OffscreenCanvas` and WebAssembly SIMD:
|
|
180
|
+
Chrome 91, Firefox 114, Safari 16.4 or later. The camera loop also needs
|
|
181
|
+
`requestVideoFrameCallback`. Images may have at most 32 megapixels.
|
|
182
|
+
- **Deployment size:** bundlers emit all eight WASM files (four modes and four
|
|
183
|
+
Turbo presets, 1.8–2.5 MB each, about 16 MB together), so any can be selected
|
|
184
|
+
without configuration. A page downloads only the one its scanner uses.
|
|
185
|
+
|
|
186
|
+
The [camera example](examples/camera.html) is a complete page: from this
|
|
187
|
+
directory (or the installed package), run `python3 -m http.server` and open
|
|
188
|
+
`/examples/camera.html` on localhost. Camera capture requires HTTPS; localhost
|
|
189
|
+
works for development.
|
|
190
|
+
|
|
191
|
+
## Core API
|
|
192
|
+
|
|
193
|
+
The core scans decoded pixels, for example a canvas's `ImageData`:
|
|
17
194
|
|
|
18
195
|
```js
|
|
19
196
|
import { scan } from "tapirscan";
|
|
20
197
|
|
|
21
|
-
// Using an existing canvas and its 2D context:
|
|
22
198
|
const image = context.getImageData(0, 0, canvas.width, canvas.height);
|
|
23
199
|
const result = await scan(image);
|
|
24
200
|
console.log(result.values); // e.g. ["4006381333931"]
|
|
25
201
|
```
|
|
26
202
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
serve its [WASM assets](#wasm-loading); Node loads the packaged files automatically.
|
|
203
|
+
The one-shot helper creates and disposes a scanner. Defaults are Medium effort
|
|
204
|
+
and retail formats. In browsers the core needs its [WASM assets](#wasm-loading)
|
|
205
|
+
served; Node loads the packaged files automatically.
|
|
31
206
|
|
|
32
|
-
|
|
207
|
+
Reuse a scanner for repeated images. Its scans are synchronous:
|
|
33
208
|
|
|
34
209
|
```js
|
|
35
210
|
import { Scanner } from "tapirscan";
|
|
@@ -45,14 +220,59 @@ try {
|
|
|
45
220
|
}
|
|
46
221
|
```
|
|
47
222
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
223
|
+
Settings also work with the helper: `await scan(image, { mode: "high", formats: "1D" })`.
|
|
224
|
+
For unread regions, timing and diagnostics, call `inspect` instead of `scan`.
|
|
225
|
+
|
|
226
|
+
The core scans synchronously, so a long scan on the main thread blocks the page.
|
|
227
|
+
Use `tapirscan/browser`, or create one core scanner inside your own worker and
|
|
228
|
+
transfer frame buffers to it; configure `loadWasm` inside that worker.
|
|
229
|
+
|
|
230
|
+
| Function | Returns | Behavior |
|
|
231
|
+
| ------------------------------------- | --------------------------- | ---------------------------------------------------------------------------- |
|
|
232
|
+
| `scan(image, options = {})` | `Promise<ScanResult>` | One image; creates and disposes a scanner. |
|
|
233
|
+
| `inspect(image, options = {})` | `Promise<InspectionResult>` | Like `scan`, with timing, unread regions and diagnostics. |
|
|
234
|
+
| `Scanner.create(options = {})` | `Promise<Scanner>` | A reusable scanner with fixed mode, default formats and supplement policy. |
|
|
235
|
+
| `scanner.scan(image, { formats })` | `ScanResult` | Synchronous scan; `formats` optionally overrides the defaults for this call. |
|
|
236
|
+
| `scanner.inspect(image, { formats })` | `InspectionResult` | Synchronous inspection. |
|
|
237
|
+
| `scanner.dispose()` | `void` | Releases WASM memory. Repeated disposal is safe; do not scan after disposal. |
|
|
238
|
+
|
|
239
|
+
Create another scanner to change effort or the supplement policy. Per-call
|
|
240
|
+
formats such as `scanner.scan(image, { formats: "QRCode" })` apply only to that
|
|
241
|
+
call; `scanner.formats` exposes the defaults. Results stay valid after disposal.
|
|
242
|
+
|
|
243
|
+
## Experimental Turbo presets
|
|
244
|
+
|
|
245
|
+
For faster **1D barcode scanning**, opt into a Turbo preset instead of a mode.
|
|
246
|
+
Both entries accept it; in the browser it suits camera loops:
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
import { Scanner } from "tapirscan/browser";
|
|
250
|
+
|
|
251
|
+
const scanner = new Scanner({ experimentalTurbo: 2, formats: "retail" });
|
|
252
|
+
const result = await scanner.scan(video);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Accepted values are **2, 4, 8 and 16**. Each roughly indicates the speedup over Low
|
|
256
|
+
it targets; the actual speedup varies with images and devices and is not guaranteed.
|
|
257
|
+
Higher presets do less work and can miss more barcodes, including clean symbols
|
|
258
|
+
placed close together. Start with 2 and check detection on your own inputs.
|
|
259
|
+
The presets speed up EAN-13, UPC-A, EAN-8, UPC-E, Code 128, Code 39 and ITF.
|
|
260
|
+
Other formats stay readable when selected, but do not get faster, and mixed-format
|
|
261
|
+
scans still pay for the enabled 2D readers. See
|
|
262
|
+
[Turbo behavior and limitations](https://github.com/kleinicke/tapirscan/blob/main/docs/EXPERIMENTAL_TURBO.md).
|
|
263
|
+
|
|
264
|
+
`experimentalTurbo` and `mode` are mutually exclusive, and Turbo requires
|
|
265
|
+
`eanAddOnPolicy: "ignore"`. A Turbo scanner has no effort mode: `inspect` results
|
|
266
|
+
report `experimentalTurbo` and omit `mode`. The core scanner also exposes
|
|
267
|
+
`scanner.experimentalTurbo` and leaves `scanner.mode` `undefined`; the browser
|
|
268
|
+
`Scanner` has no configuration properties.
|
|
51
269
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
270
|
+
**Stability:** this option, its presets and their asset imports may change or be
|
|
271
|
+
removed in a minor release. Pin the exact package version if you rely on them.
|
|
272
|
+
|
|
273
|
+
`tapirscan/browser` loads the preset's WASM by itself. With the core in Vite or
|
|
274
|
+
SvelteKit, load the matching asset as in [WASM loading](#vite-and-sveltekit):
|
|
275
|
+
`tapirscan/wasm/experimental-turbo2.wasm`, `-turbo4`, `-turbo8` or `-turbo16`.
|
|
56
276
|
|
|
57
277
|
## WASM loading
|
|
58
278
|
|
|
@@ -60,14 +280,41 @@ In Node, the default loader reads assets from the installed package. Decode your
|
|
|
60
280
|
image with an image library first, then pass grayscale, RGB or RGBA bytes:
|
|
61
281
|
|
|
62
282
|
```js
|
|
283
|
+
import { scan } from "tapirscan";
|
|
284
|
+
|
|
63
285
|
const result = await scan({ data: pixels, width, height, channels: 1, stride: width });
|
|
64
286
|
```
|
|
65
287
|
|
|
66
|
-
Here `pixels` is a Uint8Array of decoded grayscale pixels.
|
|
67
|
-
|
|
288
|
+
Here `pixels` is a `Uint8Array` of decoded grayscale pixels. Filenames, URLs and
|
|
289
|
+
encoded JPEG/PNG bytes are not scan inputs.
|
|
290
|
+
|
|
291
|
+
### Vite and SvelteKit
|
|
292
|
+
|
|
293
|
+
[`tapirscan/browser`](#browser-apps-react-and-svelte) needs no WASM setup. To use the
|
|
294
|
+
core directly, import the WASM asset URL so Vite includes it in development and
|
|
295
|
+
production builds, including apps deployed under a base path:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
import { Scanner } from "tapirscan";
|
|
299
|
+
import mediumWasmUrl from "tapirscan/wasm/medium.wasm?url";
|
|
300
|
+
|
|
301
|
+
// Load once; reuse these bytes if you create more than one scanner.
|
|
302
|
+
const response = await fetch(mediumWasmUrl);
|
|
303
|
+
if (!response.ok) throw new Error(`WASM load failed: ${response.status}`);
|
|
304
|
+
const bytes = await response.arrayBuffer();
|
|
305
|
+
const scanner = await Scanner.create({ loadWasm: async () => bytes });
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
The imports are `tapirscan/wasm/low.wasm`, `medium.wasm`, `high.wasm` and
|
|
309
|
+
`very-high.wasm`; match the asset to `mode` (the example uses the default
|
|
310
|
+
Medium). A mismatched asset is an error. `?url` is Vite syntax. In SvelteKit,
|
|
311
|
+
create core scanners in browser code such as `onMount`, not during server rendering.
|
|
68
312
|
|
|
69
|
-
|
|
70
|
-
|
|
313
|
+
### Other browser setups
|
|
314
|
+
|
|
315
|
+
The default loader fetches assets relative to the module. If your bundler
|
|
316
|
+
relocates modules, copy the WASM files to your public directory as part of your
|
|
317
|
+
build, so package upgrades cannot leave stale assets:
|
|
71
318
|
|
|
72
319
|
```sh
|
|
73
320
|
mkdir -p public/tapirscan
|
|
@@ -78,242 +325,149 @@ Then point the scanner at that directory:
|
|
|
78
325
|
|
|
79
326
|
```js
|
|
80
327
|
const scanner = await Scanner.create({ wasmBaseUrl: "/tapirscan/" });
|
|
81
|
-
try {
|
|
82
|
-
console.log(scanner.scan(image).values);
|
|
83
|
-
} finally {
|
|
84
|
-
scanner.dispose();
|
|
85
|
-
}
|
|
86
328
|
```
|
|
87
329
|
|
|
88
|
-
The one-shot helper accepts the same option:
|
|
89
|
-
`await scan(image, { wasmBaseUrl: "/tapirscan/" })`.
|
|
90
330
|
`wasmBaseUrl` accepts a string or URL, with or without a trailing slash. Relative
|
|
91
|
-
URLs resolve against the page
|
|
92
|
-
Node
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Use the deployed base path if your app is hosted below a subpath. Copy all current
|
|
98
|
-
WASMs so every effort mode remains available. The demo and its comparison engines
|
|
99
|
-
are not needed in your app.
|
|
100
|
-
|
|
101
|
-
## Camera and worker use
|
|
102
|
-
|
|
103
|
-
A standalone [worker client](examples/worker-client.mjs), [worker](examples/scan-worker.mjs)
|
|
104
|
-
and [camera page](examples/camera.html) are included in the package. From this
|
|
105
|
-
binding directory (or the installed package directory), run `python3 -m http.server`
|
|
106
|
-
and open `/examples/camera.html` on localhost. The example handles initialization,
|
|
107
|
-
frame ownership transfer, one frame in flight, errors and shutdown. It transfers
|
|
108
|
-
pixel buffers; callers must not reuse the transferred buffer. Worker messages
|
|
109
|
-
produce independent mutable result copies through structured cloning. Custom
|
|
110
|
-
`loadWasm` functions must be configured inside the worker; functions cannot be sent
|
|
111
|
-
in a message. Adjust the worker import and WASM asset path for your bundler.
|
|
112
|
-
|
|
113
|
-
Initialization is asynchronous; scanning is synchronous. For a responsive browser
|
|
114
|
-
UI, initialize one scanner inside a Web Worker and transfer an owned frame buffer.
|
|
115
|
-
Capture the next frame after the previous result arrives. Avoid racing scanner
|
|
116
|
-
initialization or modifying pixels during scanning. The [demo worker](../../demo/src/lib/scan.worker.ts)
|
|
117
|
-
shows a complete integration. Camera capture belongs to your app and requires
|
|
118
|
-
HTTPS (localhost works for development).
|
|
119
|
-
|
|
120
|
-
## Functions
|
|
121
|
-
|
|
122
|
-
| Function | Return type | Behavior |
|
|
123
|
-
| ----------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
-
| `scan(image, options = {})` | `Promise<ScanResult>` | One image with automatic scanner creation and disposal, including on failure. Accepts creation and scan options together. |
|
|
125
|
-
| `Scanner.create(options = {})` | `Promise<Scanner>` | Initialize a reusable scanner. Mode is fixed; formats define defaults and allowed per-call subsets. |
|
|
126
|
-
| `scanner.scan(image, options = {})` | `ScanResult` | Synchronously scan pixels. Accepts scan options only. |
|
|
127
|
-
| `scanner.dispose()` | `void` | Release WASM sessions. Repeated disposal is safe; do not scan after disposal. |
|
|
128
|
-
|
|
129
|
-
`image` is required for either scan function. All options are optional. Reuse a
|
|
130
|
-
scanner for successive frames to avoid repeated initialization; create another
|
|
131
|
-
to change effort or enable formats outside its configured selection. A per-call
|
|
132
|
-
subset such as `scanner.scan(image, { formats: "EAN13" })` applies only to that
|
|
133
|
-
call and does not change the default formats. Previously returned results survive disposal. `scanner.formats` exposes the frozen creation selection.
|
|
331
|
+
URLs resolve against the page or worker in browsers and the package module in
|
|
332
|
+
Node. For authenticated requests or custom storage, use
|
|
333
|
+
`loadWasm: async (url) => arrayBuffer`; it receives URLs resolved against
|
|
334
|
+
`wasmBaseUrl`. The default loader caches loaded assets within a module instance;
|
|
335
|
+
custom loaders manage their own caching. Each scanner owns its own WASM instance.
|
|
134
336
|
|
|
135
337
|
## All options
|
|
136
338
|
|
|
137
|
-
| Option
|
|
138
|
-
|
|
|
139
|
-
| `mode`
|
|
140
|
-
| `formats`
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
Resolution, camera capture, preprocessing rotation, ROI, confidence thresholds,
|
|
153
|
-
timeouts and exact work budgets are not public scan options. Demo capture and
|
|
154
|
-
resize settings belong to the application.
|
|
339
|
+
| Option | Where | Default | Meaning |
|
|
340
|
+
| ------------------- | --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
341
|
+
| `mode` | Creation | `"medium"` | `"low"`, `"medium"`, `"high"`, `"very-high"`. |
|
|
342
|
+
| `formats` | Creation / scan | `"retail"` | An identifier, `"retail"`, `"common1D"`, `"common"`, `"1D"`, `"2D"`, `"all"`, or a nonempty array. Per-call formats override the defaults. |
|
|
343
|
+
| `eanAddOnPolicy` | Creation | `"ignore"` | `"ignore"`, `"read"`, `"require"`; see [supplements](#eanupc-supplements). |
|
|
344
|
+
| `wasmBaseUrl` | Creation | Module-relative assets | Directory URL for the packaged WASM files. |
|
|
345
|
+
| `loadWasm` | Creation | Module-relative loader | `(url: URL) => Promise<ArrayBuffer>`. |
|
|
346
|
+
| `experimentalTurbo` | Creation | Unset | **Experimental:** `2`, `4`, `8` or `16`. Mutually exclusive with `mode`. |
|
|
347
|
+
|
|
348
|
+
`"retail"` selects EAN13, UPCA, EAN8 and UPCE. `"common1D"` adds Code128,
|
|
349
|
+
Code39 and ITF; `"common"` adds QRCode and DataMatrix. `"1D"` and `"2D"`
|
|
350
|
+
enable all linear or all 2D formats.
|
|
351
|
+
The exports `retailFormats`, `commonLinearFormats`, `commonFormats`,
|
|
352
|
+
`linearFormats` and `matrixFormats` help compose custom selections. See
|
|
353
|
+
[format coverage](https://github.com/kleinicke/tapirscan/blob/main/docs/FORMATS.md) for identifiers and variants.
|
|
155
354
|
|
|
156
355
|
## Image input
|
|
157
356
|
|
|
158
|
-
|
|
159
|
-
|
|
357
|
+
The core accepts `ImageData` (or an object with its `data`, `width` and `height`)
|
|
358
|
+
or an explicit pixel buffer:
|
|
160
359
|
|
|
161
|
-
| Field | Type | Meaning
|
|
162
|
-
| ----------------- | ------------- |
|
|
163
|
-
| `data` | `Uint8Array` | Decoded pixels. ImageData
|
|
164
|
-
| `width`, `height` | `number` | Integer
|
|
165
|
-
| `channels` | `1 \| 3 \| 4` | Grayscale, RGB or RGBA. Alpha is ignored. Required for explicit buffers.
|
|
166
|
-
| `stride` | `number` | Bytes between row starts
|
|
360
|
+
| Field | Type | Meaning |
|
|
361
|
+
| ----------------- | ------------- | ------------------------------------------------------------------------------------ |
|
|
362
|
+
| `data` | `Uint8Array` | Decoded pixels. `ImageData` uses `Uint8ClampedArray` with tightly packed RGBA. |
|
|
363
|
+
| `width`, `height` | `number` | Integer dimensions, at least 3 pixels each. |
|
|
364
|
+
| `channels` | `1 \| 3 \| 4` | Grayscale, RGB or RGBA. Alpha is ignored. Required for explicit buffers. |
|
|
365
|
+
| `stride` | `number` | Bytes between row starts; defaults to width × channels. Larger values allow padding. |
|
|
167
366
|
|
|
168
|
-
Input is limited to 32 megapixels and 128 MiB of addressed pixels. Keep the buffer
|
|
169
|
-
call.
|
|
367
|
+
Input is limited to 32 megapixels and 128 MiB of addressed pixels. Keep the buffer
|
|
368
|
+
unchanged during the call. Coordinates start at the top left, x rightward and y
|
|
369
|
+
downward. If you resize or rotate before scanning, map coordinates back yourself.
|
|
170
370
|
|
|
171
371
|
## Results
|
|
172
372
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| `result.
|
|
179
|
-
| `result.
|
|
180
|
-
| `result.
|
|
181
|
-
| `result.
|
|
182
|
-
| `result.
|
|
183
|
-
| `result.
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `barcode.
|
|
187
|
-
| `barcode.
|
|
188
|
-
| `barcode.
|
|
189
|
-
| `barcode.
|
|
190
|
-
| `barcode.
|
|
191
|
-
| `barcode.
|
|
192
|
-
| `barcode.
|
|
193
|
-
| `barcode.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
373
|
+
`scan` returns a `ScanResult`; `inspect` returns an `InspectionResult` with the
|
|
374
|
+
same fields plus the ones marked _inspect_.
|
|
375
|
+
|
|
376
|
+
| Field | Type | Meaning |
|
|
377
|
+
| ------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
378
|
+
| `result.values` | `readonly string[]` | Decoded strings, one per barcode. |
|
|
379
|
+
| `result.barcodes` | `readonly Barcode[]` | Decoded barcodes with format and geometry. |
|
|
380
|
+
| `result.best` | `Barcode \| undefined` | Highest-support barcode, first on ties. |
|
|
381
|
+
| `result.image` | `{ width, height }` | _inspect_: dimensions of the supplied pixels. |
|
|
382
|
+
| `result.mode` | `Mode \| undefined` | _inspect_: effort mode used; absent for Turbo presets. |
|
|
383
|
+
| `result.elapsedMs` | `number` | _inspect_: scan time, excluding image loading and scanner creation. |
|
|
384
|
+
| `result.undecoded` | `readonly UndecodedRegion[]` | _inspect_: [regions without a decode](#undecoded-regions). |
|
|
385
|
+
| `result.diagnostics` | `Diagnostics` | _inspect_: [engine evidence](#diagnostics). |
|
|
386
|
+
| `barcode.text` | `string` | Decoded text. |
|
|
387
|
+
| `barcode.format` | `Format` | Symbology identifier. |
|
|
388
|
+
| `barcode.polygon` | `Quad` | Four `[x, y]` corners in input-image coordinates. |
|
|
389
|
+
| `barcode.rect` | `{ left: number, top: number, width: number, height: number }` | Enclosing integer rectangle. |
|
|
390
|
+
| `barcode.support` | `number` | Reader-specific ranking evidence used by `best`. |
|
|
391
|
+
| `barcode.payloadBytes` | `readonly number[] \| undefined` | Decoded matrix payload bytes, when available. |
|
|
392
|
+
| `barcode.gs1` | `boolean \| undefined` | GS1 indicator, when supplied by the reader. |
|
|
393
|
+
| `barcode.readerInitialization` | `boolean \| undefined` | Data Matrix, PDF417, Aztec and MaxiCode reader-initialization flag. |
|
|
394
|
+
| `barcode.structuredAppend` | `StructuredAppend \| undefined` | Multipart metadata: one-based `index`, `count`, optional `id` and `parity`. |
|
|
395
|
+
| `barcode.eanAddOn` | `string \| undefined` | EAN/UPC supplement, with `eanAddOnPolicy` `"read"` or `"require"`. |
|
|
396
|
+
|
|
397
|
+
Separate labels with the same value stay separate entries. Results are deeply
|
|
398
|
+
frozen; use `structuredClone(result)` for a mutable copy. `JSON.stringify(result)`
|
|
399
|
+
gives plain JSON.
|
|
400
|
+
|
|
401
|
+
`support` is an uncalibrated, reader-specific ranking heuristic, not a
|
|
402
|
+
probability, and is not comparable across formats. In a mixed-format image,
|
|
403
|
+
`best` is therefore not necessarily the most reliable read; select by format or
|
|
404
|
+
payload when your application knows what it expects. Checksums reduce wrong
|
|
405
|
+
reads but cannot rule them out.
|
|
406
|
+
|
|
407
|
+
`payloadBytes` is supplied by QR Code, Data Matrix, Aztec, PDF417 and MaxiCode.
|
|
408
|
+
It holds decoded data bytes before character-set interpretation, not raw
|
|
409
|
+
codewords; Aztec Rune gives its value as decimal ASCII. Encoding `text` as UTF-8
|
|
410
|
+
does not reconstruct these bytes. Use `Uint8Array.from(barcode.payloadBytes)`
|
|
411
|
+
for a byte buffer.
|
|
412
|
+
|
|
413
|
+
All types (`ScanResult`, `InspectionResult`, `Barcode`, `ScannerOptions`,
|
|
414
|
+
`ScanOptions`, `Format` and others) are exported from both entries.
|
|
212
415
|
|
|
213
416
|
## EAN/UPC supplements
|
|
214
417
|
|
|
215
|
-
Set `eanAddOnPolicy
|
|
216
|
-
The policy is fixed for that scanner; its default is `"Ignore"`.
|
|
418
|
+
Set `eanAddOnPolicy` when creating a scanner or calling one-shot `scan` or `inspect`.
|
|
217
419
|
|
|
218
420
|
| Policy | Behavior |
|
|
219
421
|
| ----------- | ------------------------------------------------------------------------------------------------ |
|
|
220
|
-
| `"
|
|
221
|
-
| `"
|
|
222
|
-
| `"
|
|
223
|
-
|
|
224
|
-
`barcode.polygon` and `barcode.rect` describe the main barcode, excluding the
|
|
225
|
-
supplement. Supplement geometry is not exposed separately.
|
|
422
|
+
| `"ignore"` | Decode the main barcode without reading its supplement. |
|
|
423
|
+
| `"read"` | Try reading the two- or five-digit supplement; keep the main barcode if none is readable. |
|
|
424
|
+
| `"require"` | Return an EAN/UPC barcode only when its supplement is readable. Other formats remain unaffected. |
|
|
226
425
|
|
|
227
|
-
The supplement appears
|
|
228
|
-
the main
|
|
229
|
-
work
|
|
230
|
-
by `"Require"` remain available in `result.undecoded`.
|
|
426
|
+
The supplement appears in `barcode.eanAddOn`; `barcode.text` is the main payload,
|
|
427
|
+
and `polygon` and `rect` describe the main barcode. Reading supplements adds
|
|
428
|
+
decoding work. Retail reads rejected by `"require"` appear in `result.undecoded`.
|
|
231
429
|
|
|
232
|
-
##
|
|
233
|
-
|
|
234
|
-
Most applications need `barcode.text`, `.format`, `.polygon` and `.rect`.
|
|
235
|
-
`barcode.support` exposes the evidence used by `.best`. It is an uncalibrated,
|
|
236
|
-
reader-specific ranking heuristic, not a certainty percentage; values are not
|
|
237
|
-
comparable confidence across formats or effort modes. Consequently, `.best` means the largest support value,
|
|
238
|
-
not the most reliable barcode in a mixed-format image. Select by the format or
|
|
239
|
-
payload your application needs when that distinction matters. Checksums and consistency
|
|
240
|
-
checks reduce wrong reads but cannot guarantee that every returned decode is correct.
|
|
241
|
-
|
|
242
|
-
Select EAN13/UPCA, Common1D and QR Code search effort with `mode: "low"` through `"very-high"` at creation.
|
|
243
|
-
Other matrix readers use fixed effort. `result.unfinished` is available without debug and
|
|
244
|
-
combines reported decoding and localization limits. Returned reads are still usable.
|
|
245
|
-
Candidate, retry and parsing caps are reported, including bounded searches that
|
|
246
|
-
also returned reads. False does not promise exhaustive scanning. Exact budgets and interruptible timeouts are not public options.
|
|
430
|
+
## Undecoded regions
|
|
247
431
|
|
|
248
|
-
`
|
|
249
|
-
|
|
432
|
+
`inspect` results list `undecoded` regions: localized proposals without an
|
|
433
|
+
accepted decode, each with a source-image `polygon` and a `format` hint. They
|
|
434
|
+
can overlap or be false candidates, and an empty list does not prove that every
|
|
435
|
+
barcode was found.
|
|
250
436
|
|
|
251
|
-
|
|
437
|
+
## Diagnostics
|
|
252
438
|
|
|
253
439
|
```js
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const retail = scanner.scan(image, { formats: "retail" });
|
|
258
|
-
const qr = scanner.scan(image, { formats: "QRCode" });
|
|
259
|
-
} finally {
|
|
260
|
-
scanner.dispose();
|
|
261
|
-
}
|
|
440
|
+
const report = scanner.inspect(image);
|
|
441
|
+
console.log(report.diagnostics.regions.proposals, report.diagnostics.regions.searchWindows);
|
|
442
|
+
console.log(report.diagnostics.scan.barcodes);
|
|
262
443
|
```
|
|
263
444
|
|
|
264
|
-
`
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
unchanged. The frozen number array is directly JSON-compatible.
|
|
445
|
+
`diagnostics.regions` has a stable shape: `proposals` and `searchWindows` are
|
|
446
|
+
`null` when a reader does not expose them, and empty arrays when it found nothing.
|
|
447
|
+
The other fields are the engine's own evidence; they vary by reader and may
|
|
448
|
+
change between releases. Candidate indices inside recovery crops are local to
|
|
449
|
+
the crop, not identifiers for tracking between frames.
|
|
270
450
|
|
|
271
|
-
##
|
|
451
|
+
## Errors
|
|
272
452
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
if (result.debug) {
|
|
276
|
-
console.log(result.debug.regions.proposals, result.debug.regions.searchWindows);
|
|
277
|
-
console.log(result.debug.regions.undecoded);
|
|
278
|
-
console.log(result.debug.scan.barcodes);
|
|
279
|
-
}
|
|
280
|
-
```
|
|
453
|
+
Invalid options throw `TypeError`. Engine and validation failures throw the
|
|
454
|
+
exported `ScannerError` with `.message` and a `.code`:
|
|
281
455
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
present. GS1, reader initialization and structured append are available directly on
|
|
292
|
-
barcodes without debug; raw metadata also retains these fields where supported.
|
|
293
|
-
Candidate indices inside recovery crops are local to the crop and are not
|
|
294
|
-
identifiers for tracking between frames.
|
|
295
|
-
|
|
296
|
-
Invalid options can raise TypeError. Scanner validation and engine failures can
|
|
297
|
-
raise the exported `ScannerError` with a `.code` and `.message`. Loader/fetch
|
|
298
|
-
errors propagate to the caller; creation and the one-shot helper reject their
|
|
299
|
-
promises on failure. Always dispose reusable scanners with `finally`.
|
|
300
|
-
|
|
301
|
-
## Extended work budget
|
|
302
|
-
|
|
303
|
-
Use `scanner.scan(image, { extendedBudget: true })` to allow additional reader work. The default
|
|
304
|
-
is false. This option is valid for every format; the exact budgets and stages are
|
|
305
|
-
implementation details that may evolve. Effort mode remains a separate setting.
|
|
306
|
-
|
|
307
|
-
Today this relaxes shared EAN-13/UPC-A retry and association limits. Other readers
|
|
308
|
-
currently retain their existing budgets. Per-candidate limits and intentional
|
|
309
|
-
deferrals remain; `unfinished` can still be true. This is not unlimited search,
|
|
310
|
-
an exhaustiveness guarantee or a wall-clock deadline. Custom primary-reader
|
|
311
|
-
engines must support the extended-work capability or report an error.
|
|
456
|
+
| Code | Meaning |
|
|
457
|
+
| ----------------------------------------------------------- | --------------------------------------------------------- |
|
|
458
|
+
| `"invalid_input"` | The engine rejected the image or options. |
|
|
459
|
+
| `"disposed"` | The scanner was disposed. |
|
|
460
|
+
| `"engine"` | Internal scanner failure. |
|
|
461
|
+
| `"capacity"` | Too many live scanners in this WASM instance. |
|
|
462
|
+
| `"abi_shape"`, `"abi_version"`, `"abi_mode"`, `"abi_turbo"` | The loaded WASM file does not match this package or mode. |
|
|
463
|
+
| `"invalid_output"` | The engine returned unreadable output. |
|
|
464
|
+
| `"core_<n>"` | An unexpected engine status `n`. |
|
|
312
465
|
|
|
313
|
-
|
|
466
|
+
Loader and fetch errors reject `Scanner.create` and the one-shot helpers. Dispose
|
|
467
|
+
reusable scanners in `finally`.
|
|
468
|
+
|
|
469
|
+
## License
|
|
314
470
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
can overlap or describe false candidates. An empty collection does not prove
|
|
319
|
-
that every barcode was found. Raw candidate attempts remain in debug diagnostics.
|
|
471
|
+
Tapirscan is dual-licensed under **MIT OR Apache-2.0**, at your option.
|
|
472
|
+
See the [full license texts](https://tapirscan.f-kleinicke.de/license/).
|
|
473
|
+
Third-party components retain their own licenses and notices.
|