tapirscan 1.2.1 → 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 -226
- 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 +53 -46
- package/dist/index.js +168 -209
- package/dist/layout.d.ts +19 -0
- package/dist/layout.js +27 -0
- package/dist/multiformat/format-registry.d.ts +26 -0
- package/dist/multiformat/format-registry.js +63 -0
- package/dist/multiformat/formats.d.ts +4 -24
- package/dist/multiformat/formats.js +15 -46
- package/dist/rust-session.d.ts +19 -0
- package/dist/rust-session.js +102 -0
- 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 +28 -11
- 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/dist/completion-host.d.mts +0 -40
- package/dist/completion-host.mjs +0 -188
- package/dist/detail-20260914/continuity.d.mts +0 -2
- package/dist/detail-20260914/continuity.mjs +0 -62
- package/dist/detail-20260914/detail-proposals-rich.d.mts +0 -13
- package/dist/detail-20260914/detail-proposals-rich.mjs +0 -53
- package/dist/detail-20260914/direct-recovery.d.mts +0 -28
- package/dist/detail-20260914/direct-recovery.mjs +0 -139
- package/dist/detail-20260914/host.d.mts +0 -40
- package/dist/detail-20260914/host.mjs +0 -280
- package/dist/detail-20260914/scanner.d.mts +0 -12
- package/dist/detail-20260914/scanner.mjs +0 -64
- package/dist/detail-20260914/source-evidence.d.mts +0 -10
- package/dist/detail-20260914/source-evidence.mjs +0 -94
- package/dist/detail-canvas.d.ts +0 -35
- package/dist/detail-canvas.js +0 -73
- package/dist/detail-runtime/direct-recovery.d.mts +0 -28
- package/dist/detail-runtime/direct-recovery.mjs +0 -142
- package/dist/detail-runtime/host.d.mts +0 -40
- package/dist/detail-runtime/host.mjs +0 -284
- package/dist/detail-runtime/scanner.d.mts +0 -12
- package/dist/detail-runtime/scanner.mjs +0 -70
- package/dist/detail.d.ts +0 -20
- package/dist/detail.js +0 -84
- package/dist/host.d.ts +0 -112
- package/dist/host.js +0 -184
- package/dist/host64.d.ts +0 -112
- package/dist/host64.js +0 -184
- package/dist/multiformat/coverage.d.ts +0 -7
- package/dist/multiformat/coverage.js +0 -26
- package/dist/multiformat/geometry.d.ts +0 -37
- package/dist/multiformat/geometry.js +0 -132
- package/dist/multiformat/linear-duplicates.d.ts +0 -16
- package/dist/multiformat/linear-duplicates.js +0 -163
- package/dist/multiformat/pixels.d.ts +0 -3
- package/dist/multiformat/pixels.js +0 -36
- package/dist/multiformat/scanner.d.ts +0 -60
- package/dist/multiformat/scanner.js +0 -382
- package/dist/multiformat-host.d.ts +0 -121
- package/dist/multiformat-host.js +0 -207
- package/dist/policy.d.ts +0 -12
- package/dist/policy.js +0 -12
- package/examples/scan-worker.mjs +0 -20
- package/examples/worker-client.mjs +0 -63
- package/wasm/high-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/low-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/medium-shared-retail-portable-20260919.wasm +0 -0
- package/wasm/multiformat.json +0 -7
- package/wasm/multiformat.wasm +0 -0
- package/wasm/very-high-shared-retail-portable-20260919.wasm +0 -0
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/** Cheap alternating-run evidence. It prioritizes geometry, never accepts digits. */
|
|
2
|
-
export function sourceEvidence(image, polygon, local = false, target = Infinity) {
|
|
3
|
-
const { data, width, height } = image;
|
|
4
|
-
const gray = (x, y) => {
|
|
5
|
-
x = Math.max(0, Math.min(width - 1, x));
|
|
6
|
-
y = Math.max(0, Math.min(height - 1, y));
|
|
7
|
-
const x0 = Math.floor(x), y0 = Math.floor(y), x1 = Math.min(width - 1, x0 + 1), y1 = Math.min(height - 1, y0 + 1), fx = x - x0, fy = y - y0;
|
|
8
|
-
const v = (a, b) => {
|
|
9
|
-
const i = (b * width + a) * 4;
|
|
10
|
-
return (77 * data[i] + 150 * data[i + 1] + 29 * data[i + 2]) / 256;
|
|
11
|
-
};
|
|
12
|
-
return ((v(x0, y0) * (1 - fx) + v(x1, y0) * fx) * (1 - fy) +
|
|
13
|
-
(v(x0, y1) * (1 - fx) + v(x1, y1) * fx) * fy);
|
|
14
|
-
};
|
|
15
|
-
let best = 0, localBest = 0, rows = 0, contrast = 0;
|
|
16
|
-
for (let axis = 0; axis < 2; axis++)
|
|
17
|
-
for (const f of [0.2, 0.4, 0.6, 0.8]) {
|
|
18
|
-
const a = polygon[axis], b = polygon[(axis + 1) % 4], c = polygon[(axis + 2) % 4], d = polygon[(axis + 3) % 4];
|
|
19
|
-
const p = [a[0] * (1 - f) + d[0] * f, a[1] * (1 - f) + d[1] * f], q = [b[0] * (1 - f) + c[0] * f, b[1] * (1 - f) + c[1] * f];
|
|
20
|
-
const n = Math.min(2048, Math.max(64, Math.ceil(Math.hypot(q[0] - p[0], q[1] - p[1]) * 1.25)));
|
|
21
|
-
const samples = new Float32Array(n);
|
|
22
|
-
let lo = 255, hi = 0;
|
|
23
|
-
for (let i = 0; i < n; i++) {
|
|
24
|
-
const t = -0.125 + (1.25 * i) / (n - 1), v = gray(p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t);
|
|
25
|
-
samples[i] = v;
|
|
26
|
-
lo = Math.min(lo, v);
|
|
27
|
-
hi = Math.max(hi, v);
|
|
28
|
-
}
|
|
29
|
-
contrast = Math.max(contrast, hi - lo);
|
|
30
|
-
if (hi - lo < (local ? 12 : 20))
|
|
31
|
-
continue;
|
|
32
|
-
const threshold = (lo + hi) / 2, hysteresis = (hi - lo) * 0.06;
|
|
33
|
-
let state = samples[0] > threshold, runs = 0;
|
|
34
|
-
for (const v of samples) {
|
|
35
|
-
if (state ? v < threshold - hysteresis : v > threshold + hysteresis) {
|
|
36
|
-
state = !state;
|
|
37
|
-
runs++;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
best = Math.max(best, runs);
|
|
41
|
-
if (runs >= 20)
|
|
42
|
-
rows++;
|
|
43
|
-
if (best >= target)
|
|
44
|
-
return { transitions: best, localTransitions: localBest, rows, contrast };
|
|
45
|
-
if (local) {
|
|
46
|
-
const sum = new Float64Array(n + 1);
|
|
47
|
-
for (let i = 0; i < n; i++)
|
|
48
|
-
sum[i + 1] = sum[i] + samples[i];
|
|
49
|
-
let previous, localRuns = 0;
|
|
50
|
-
for (let i = 0; i < n; i++) {
|
|
51
|
-
const left = Math.max(0, i - 8), right = Math.min(n, i + 9), mean = (sum[right] - sum[left]) / (right - left);
|
|
52
|
-
const next = samples[i] > mean + 2 ? true : samples[i] < mean - 2 ? false : undefined;
|
|
53
|
-
if (next !== undefined) {
|
|
54
|
-
if (previous !== undefined && previous !== next)
|
|
55
|
-
localRuns++;
|
|
56
|
-
previous = next;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
localBest = Math.max(localBest, localRuns);
|
|
60
|
-
if (localBest >= target)
|
|
61
|
-
return { transitions: best, localTransitions: localBest, rows, contrast };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return { transitions: best, localTransitions: localBest, rows, contrast };
|
|
65
|
-
}
|
|
66
|
-
export function evidenceFilter(image, localization, minimum = 20) {
|
|
67
|
-
const ranked = localization.proposals.map((p) => ({
|
|
68
|
-
...p,
|
|
69
|
-
evidence: sourceEvidence(image, p.polygon),
|
|
70
|
-
}));
|
|
71
|
-
const proposals = ranked.filter((p) => p.evidence.transitions >= minimum), deferred = ranked.filter((p) => p.evidence.transitions < minimum);
|
|
72
|
-
return {
|
|
73
|
-
...localization,
|
|
74
|
-
proposals,
|
|
75
|
-
deferred,
|
|
76
|
-
workLimited: localization.workLimited || deferred.length > 0,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/** Retain all candidates. Evidence stops once eligibility is established; counts are lower bounds. */
|
|
80
|
-
export function evidencePlan(image, localization, minimum = 20) {
|
|
81
|
-
const proposals = localization.proposals.map((p) => ({
|
|
82
|
-
...p,
|
|
83
|
-
evidence: sourceEvidence(image, p.polygon, false, minimum),
|
|
84
|
-
}));
|
|
85
|
-
const masks = [0, 0];
|
|
86
|
-
proposals.forEach((p, i) => {
|
|
87
|
-
if (p.evidence.transitions >= minimum)
|
|
88
|
-
masks[i >>> 5] |= 1 << (i & 31);
|
|
89
|
-
});
|
|
90
|
-
const fullFrame = proposals.length;
|
|
91
|
-
if (fullFrame < 64)
|
|
92
|
-
masks[fullFrame >>> 5] |= 1 << (fullFrame & 31);
|
|
93
|
-
return { ...localization, proposals, retryMask: masks.map((x) => x >>> 0) };
|
|
94
|
-
}
|
package/dist/detail-canvas.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/** Canvas interpolation in browsers; deterministic bilinear RGB interpolation in Node. */
|
|
2
|
-
interface Pixels {
|
|
3
|
-
data: Uint8ClampedArray;
|
|
4
|
-
width: number;
|
|
5
|
-
height: number;
|
|
6
|
-
}
|
|
7
|
-
export declare function makeCanvas(width: number, height: number): {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
getContext(): {
|
|
11
|
-
imageSmoothingEnabled: boolean;
|
|
12
|
-
putImageData(value: Pixels): void;
|
|
13
|
-
getImageData(): Pixels;
|
|
14
|
-
drawImage(source: {
|
|
15
|
-
getContext(): {
|
|
16
|
-
getImageData(): Pixels;
|
|
17
|
-
};
|
|
18
|
-
}, _x: number, _y: number, w: number, h: number): void;
|
|
19
|
-
};
|
|
20
|
-
} | {
|
|
21
|
-
width: number;
|
|
22
|
-
height: number;
|
|
23
|
-
native: OffscreenCanvas;
|
|
24
|
-
getContext(_kind: string, options?: {
|
|
25
|
-
willReadFrequently?: boolean;
|
|
26
|
-
}): {
|
|
27
|
-
imageSmoothingEnabled: boolean;
|
|
28
|
-
putImageData(pixels: Pixels, x: number, y: number): void;
|
|
29
|
-
drawImage(source: {
|
|
30
|
-
native: OffscreenCanvas;
|
|
31
|
-
}, x: number, y: number, w: number, h: number): void;
|
|
32
|
-
getImageData(x: number, y: number, w: number, h: number): ImageData;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
export {};
|
package/dist/detail-canvas.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
export function makeCanvas(width, height) {
|
|
2
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
3
|
-
const canvas = new OffscreenCanvas(width, height);
|
|
4
|
-
return {
|
|
5
|
-
get width() {
|
|
6
|
-
return canvas.width;
|
|
7
|
-
},
|
|
8
|
-
set width(value) {
|
|
9
|
-
canvas.width = value;
|
|
10
|
-
},
|
|
11
|
-
get height() {
|
|
12
|
-
return canvas.height;
|
|
13
|
-
},
|
|
14
|
-
set height(value) {
|
|
15
|
-
canvas.height = value;
|
|
16
|
-
},
|
|
17
|
-
native: canvas,
|
|
18
|
-
getContext(_kind, options) {
|
|
19
|
-
const context = canvas.getContext("2d", options);
|
|
20
|
-
if (!context)
|
|
21
|
-
throw Error("Could not create recovery canvas");
|
|
22
|
-
return {
|
|
23
|
-
imageSmoothingEnabled: true,
|
|
24
|
-
putImageData(pixels, x, y) {
|
|
25
|
-
context.putImageData(new ImageData(new Uint8ClampedArray(pixels.data), pixels.width, pixels.height), x, y);
|
|
26
|
-
},
|
|
27
|
-
drawImage(source, x, y, w, h) {
|
|
28
|
-
context.imageSmoothingEnabled = true;
|
|
29
|
-
context.drawImage(source.native, x, y, w, h);
|
|
30
|
-
},
|
|
31
|
-
getImageData(x, y, w, h) {
|
|
32
|
-
return context.getImageData(x, y, w, h);
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
let pixels = { data: new Uint8ClampedArray(), width, height };
|
|
39
|
-
const canvas = {
|
|
40
|
-
width,
|
|
41
|
-
height,
|
|
42
|
-
getContext() {
|
|
43
|
-
return {
|
|
44
|
-
imageSmoothingEnabled: true,
|
|
45
|
-
putImageData(value) {
|
|
46
|
-
pixels = value;
|
|
47
|
-
},
|
|
48
|
-
getImageData() {
|
|
49
|
-
return pixels;
|
|
50
|
-
},
|
|
51
|
-
drawImage(source, _x, _y, w, h) {
|
|
52
|
-
const input = source.getContext().getImageData();
|
|
53
|
-
const data = new Uint8ClampedArray(w * h * 4);
|
|
54
|
-
for (let y = 0; y < h; y++) {
|
|
55
|
-
const sy = Math.max(0, Math.min(input.height - 1, ((y + 0.5) * input.height) / h - 0.5));
|
|
56
|
-
const y0 = Math.floor(sy), y1 = Math.min(input.height - 1, y0 + 1), fy = sy - y0;
|
|
57
|
-
for (let x = 0; x < w; x++) {
|
|
58
|
-
const sx = Math.max(0, Math.min(input.width - 1, ((x + 0.5) * input.width) / w - 0.5));
|
|
59
|
-
const x0 = Math.floor(sx), x1 = Math.min(input.width - 1, x0 + 1), fx = sx - x0;
|
|
60
|
-
for (let c = 0; c < 4; c++) {
|
|
61
|
-
const a = input.data[(y0 * input.width + x0) * 4 + c], b = input.data[(y0 * input.width + x1) * 4 + c];
|
|
62
|
-
const d = input.data[(y1 * input.width + x0) * 4 + c], e = input.data[(y1 * input.width + x1) * 4 + c];
|
|
63
|
-
data[(y * w + x) * 4 + c] = Math.round((a * (1 - fx) + b * fx) * (1 - fy) + (d * (1 - fx) + e * fx) * fy);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
pixels = { data, width: w, height: h };
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
return canvas;
|
|
73
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/** Bounded source-guided hypotheses, without another localizer/shear search per crop. */
|
|
2
|
-
export function recoverDirectSeed(image: any, scanner: any, policy: any, baseline: any, budget?: number, factor?: number, sequential?: boolean, scharr?: boolean, minimumSpan?: number, maxDirections?: number, coverage?: any[]): {
|
|
3
|
-
barcodes: any[];
|
|
4
|
-
additions: any[];
|
|
5
|
-
attempts: {
|
|
6
|
-
x: number;
|
|
7
|
-
y: number;
|
|
8
|
-
w: number;
|
|
9
|
-
h: number;
|
|
10
|
-
factor: number;
|
|
11
|
-
frame: any;
|
|
12
|
-
reads: any;
|
|
13
|
-
deferredReads: any;
|
|
14
|
-
proposals: {
|
|
15
|
-
polygon: number[][];
|
|
16
|
-
score: number;
|
|
17
|
-
text: string;
|
|
18
|
-
}[];
|
|
19
|
-
unfinished: any;
|
|
20
|
-
}[];
|
|
21
|
-
proposals: {
|
|
22
|
-
polygon: number[][];
|
|
23
|
-
score: number;
|
|
24
|
-
text: string;
|
|
25
|
-
}[];
|
|
26
|
-
extraMs: number;
|
|
27
|
-
searchLimited: boolean;
|
|
28
|
-
};
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { makeCanvas } from "../detail-canvas.js";
|
|
2
|
-
import { detailProposals } from "../detail-20260914/detail-proposals-rich.mjs";
|
|
3
|
-
import { sourceEvidence } from "../detail-20260914/source-evidence.mjs";
|
|
4
|
-
import { coveredByContinuousBars } from "../detail-20260914/continuity.mjs";
|
|
5
|
-
function refinedAngle(image, seed) {
|
|
6
|
-
const gray = (x, y) => {
|
|
7
|
-
const i = (y * image.width + x) * 4;
|
|
8
|
-
return (77 * image.data[i] + 150 * image.data[i + 1] + 29 * image.data[i + 2]) / 256;
|
|
9
|
-
};
|
|
10
|
-
let xx = 0, yy = 0, xy = 0, sx = 0, sy = 0, n = 0;
|
|
11
|
-
for (let y = Math.max(1, Math.round(seed.y) - 12); y < Math.min(image.height - 1, seed.y + 12); y++)
|
|
12
|
-
for (let x = Math.max(1, Math.round(seed.x) - 12); x < Math.min(image.width - 1, seed.x + 12); x++) {
|
|
13
|
-
const a = gray(x - 1, y - 1), b = gray(x, y - 1), c = gray(x + 1, y - 1), d = gray(x - 1, y), e = gray(x + 1, y), f = gray(x - 1, y + 1), g = gray(x, y + 1), h = gray(x + 1, y + 1);
|
|
14
|
-
const gx = (3 * (c - a) + 10 * (e - d) + 3 * (h - f)) / 16, gy = (3 * (f - a) + 10 * (g - b) + 3 * (h - c)) / 16;
|
|
15
|
-
xx += gx * gx;
|
|
16
|
-
yy += gy * gy;
|
|
17
|
-
xy += gx * gy;
|
|
18
|
-
sx += gx;
|
|
19
|
-
sy += gy;
|
|
20
|
-
n++;
|
|
21
|
-
}
|
|
22
|
-
return 0.5 * Math.atan2(2 * (xy - (sx * sy) / n), xx - (sx * sx) / n - yy + (sy * sy) / n);
|
|
23
|
-
}
|
|
24
|
-
function contains(point, q) {
|
|
25
|
-
let hit = false;
|
|
26
|
-
for (let i = 0, j = q.length - 1; i < q.length; j = i++) {
|
|
27
|
-
if (q[i][1] > point[1] !== q[j][1] > point[1] &&
|
|
28
|
-
point[0] < ((q[j][0] - q[i][0]) * (point[1] - q[i][1])) / (q[j][1] - q[i][1]) + q[i][0])
|
|
29
|
-
hit = !hit;
|
|
30
|
-
}
|
|
31
|
-
return hit;
|
|
32
|
-
}
|
|
33
|
-
function covered(image, point, barcode) {
|
|
34
|
-
return contains(point, barcode.polygon) || coveredByContinuousBars(image, point, barcode);
|
|
35
|
-
}
|
|
36
|
-
function sourceSpan(p) {
|
|
37
|
-
const dx = p[1][0] - p[0][0], dy = p[1][1] - p[0][1];
|
|
38
|
-
return (Math.abs(dx * (p[3][1] - p[0][1]) - dy * (p[3][0] - p[0][0])) /
|
|
39
|
-
Math.max(1e-9, Math.hypot(dx, dy)));
|
|
40
|
-
}
|
|
41
|
-
/** Bounded source-guided hypotheses, without another localizer/shear search per crop. */
|
|
42
|
-
export function recoverDirectSeed(image, scanner, policy, baseline, budget = 64, factor = 3, sequential = false, scharr = false, minimumSpan = 0, maxDirections = Infinity, coverage = []) {
|
|
43
|
-
const start = performance.now();
|
|
44
|
-
const seeds = detailProposals(image, 2, 256, 4);
|
|
45
|
-
const additions = [], attempts = [], proposals = [];
|
|
46
|
-
const tile = makeCanvas(1, 1), up = makeCanvas(1, 1);
|
|
47
|
-
const tc = tile.getContext("2d"), uc = up.getContext("2d", { willReadFrequently: true });
|
|
48
|
-
for (const seed of seeds) {
|
|
49
|
-
if (seed.score < seeds[0].score * 0.6)
|
|
50
|
-
continue;
|
|
51
|
-
if (coverage.some((quad) => containsPoint([seed.x, seed.y], quad)))
|
|
52
|
-
continue;
|
|
53
|
-
if ([...baseline.scan.barcodes, ...additions].some((b) => covered(image, [seed.x, seed.y], b)))
|
|
54
|
-
continue;
|
|
55
|
-
const quads = [];
|
|
56
|
-
const initialAngle = scharr ? refinedAngle(image, seed) : seed.angle;
|
|
57
|
-
const directions = (scharr ? [0, -3, 3] : [0, -5, 5, -10, 10, -15, 15, -20, 20]).map((offset) => initialAngle + (offset * Math.PI) / 180);
|
|
58
|
-
for (const angle of directions) {
|
|
59
|
-
const c = Math.cos(angle), s = Math.sin(angle);
|
|
60
|
-
const polygon = [
|
|
61
|
-
[-96, -24],
|
|
62
|
-
[96, -24],
|
|
63
|
-
[96, 24],
|
|
64
|
-
[-96, 24],
|
|
65
|
-
].map(([u, v]) => [seed.x + u * c - v * s, seed.y + u * s + v * c]);
|
|
66
|
-
const evidence = sourceEvidence(image, polygon, true, 32);
|
|
67
|
-
if (Math.max(evidence.transitions, evidence.localTransitions) >= 32)
|
|
68
|
-
quads.push(polygon);
|
|
69
|
-
if (quads.length >= maxDirections)
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
if (!quads.length)
|
|
73
|
-
continue;
|
|
74
|
-
const size = 256, w = Math.min(size, image.width), h = Math.min(size, image.height);
|
|
75
|
-
const x = Math.max(0, Math.min(image.width - w, Math.round(seed.x - w / 2)));
|
|
76
|
-
const y = Math.max(0, Math.min(image.height - h, Math.round(seed.y - h / 2)));
|
|
77
|
-
const bytes = new Uint8ClampedArray(w * h * 4);
|
|
78
|
-
for (let row = 0; row < h; row++) {
|
|
79
|
-
const from = ((y + row) * image.width + x) * 4;
|
|
80
|
-
bytes.set(image.data.subarray(from, from + w * 4), row * w * 4);
|
|
81
|
-
}
|
|
82
|
-
tile.width = w;
|
|
83
|
-
tile.height = h;
|
|
84
|
-
tc.putImageData({ data: bytes, width: w, height: h }, 0, 0);
|
|
85
|
-
up.width = w * factor;
|
|
86
|
-
up.height = h * factor;
|
|
87
|
-
uc.imageSmoothingEnabled = true;
|
|
88
|
-
uc.drawImage(tile, 0, 0, up.width, up.height);
|
|
89
|
-
const pixels = uc.getImageData(0, 0, up.width, up.height);
|
|
90
|
-
const batches = sequential ? quads.map((q) => [q]) : [quads];
|
|
91
|
-
for (const batch of batches) {
|
|
92
|
-
const result = scanner.scan({
|
|
93
|
-
data: new Uint8Array(pixels.data.buffer),
|
|
94
|
-
width: up.width,
|
|
95
|
-
height: up.height,
|
|
96
|
-
channels: 4,
|
|
97
|
-
stride: up.width * 4,
|
|
98
|
-
}, batch.map((q) => q.map(([a, b]) => [(a - x) * factor, (b - y) * factor])), {
|
|
99
|
-
...policy,
|
|
100
|
-
maxRetryPathsPerCandidate: budget,
|
|
101
|
-
maxRetryPathsPerFrame: budget * batch.length,
|
|
102
|
-
});
|
|
103
|
-
const allReads = result.barcodes.map((b) => ({
|
|
104
|
-
...b,
|
|
105
|
-
polygon: b.polygon.map(([a, b]) => [x + a / factor, y + b / factor]),
|
|
106
|
-
}));
|
|
107
|
-
const reads = allReads.filter((b) => sourceSpan(b.polygon) >= minimumSpan);
|
|
108
|
-
const deferredReads = allReads.filter((b) => sourceSpan(b.polygon) < minimumSpan);
|
|
109
|
-
for (const read of reads) {
|
|
110
|
-
const center = read.polygon.reduce((a, p) => [a[0] + p[0] / 4, a[1] + p[1] / 4], [0, 0]);
|
|
111
|
-
if (![...baseline.scan.barcodes, ...additions].some((old) => old.text === read.text && covered(image, center, old)))
|
|
112
|
-
additions.push(read);
|
|
113
|
-
}
|
|
114
|
-
const localized = batch.map((polygon) => ({ polygon, score: seed.score, text: "" }));
|
|
115
|
-
proposals.push(...localized);
|
|
116
|
-
attempts.push({
|
|
117
|
-
x,
|
|
118
|
-
y,
|
|
119
|
-
w,
|
|
120
|
-
h,
|
|
121
|
-
factor,
|
|
122
|
-
// Raw evidence remains in crop coordinates with this explicit transform.
|
|
123
|
-
frame: result,
|
|
124
|
-
reads,
|
|
125
|
-
deferredReads,
|
|
126
|
-
proposals: localized,
|
|
127
|
-
unfinished: result.unfinished || deferredReads.length > 0,
|
|
128
|
-
});
|
|
129
|
-
if (sequential && reads.some((b) => covered(image, [seed.x, seed.y], b)))
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
barcodes: [...baseline.scan.barcodes, ...additions],
|
|
135
|
-
additions,
|
|
136
|
-
attempts,
|
|
137
|
-
proposals,
|
|
138
|
-
extraMs: performance.now() - start,
|
|
139
|
-
searchLimited: true,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
import { containsPoint } from "../multiformat/coverage.js";
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/** Explicit indices reject sparse arrays; nonfinite numeric geometry reaches Rust. */
|
|
2
|
-
export function isQuadShape(value: any): boolean;
|
|
3
|
-
/** Validate before asynchronous work; returned bytes are owned by the caller. */
|
|
4
|
-
export function snapshotImage(image: any): {
|
|
5
|
-
data: Uint8Array<any>;
|
|
6
|
-
width: any;
|
|
7
|
-
height: any;
|
|
8
|
-
channels: any;
|
|
9
|
-
stride: any;
|
|
10
|
-
};
|
|
11
|
-
export function snapshotPolicy(policy: any): any;
|
|
12
|
-
/** Uncalibrated support ordering; ties preserve spatial output order. */
|
|
13
|
-
export function rankBarcodes(barcodes: any): any[];
|
|
14
|
-
export class ScannerError extends Error {
|
|
15
|
-
constructor(code: any, message: any);
|
|
16
|
-
code: any;
|
|
17
|
-
}
|
|
18
|
-
export class IndependentScanner {
|
|
19
|
-
static create(bytes: any): Promise<IndependentScanner>;
|
|
20
|
-
constructor(exports: any);
|
|
21
|
-
/** All supplied regions receive the cheap pass; successful reads do not end scanning. */
|
|
22
|
-
scan(image: any, quads: any, policy?: {}): any;
|
|
23
|
-
/** Synchronous transaction: upload once, localize, decode the same owned pixels. */
|
|
24
|
-
scanLocalized(image: any, policy?: {}, fitLimit?: number, fullFrame?: boolean, transform?: undefined): {
|
|
25
|
-
localization: any;
|
|
26
|
-
searchWindows: {
|
|
27
|
-
kind: string;
|
|
28
|
-
polygon: number[][];
|
|
29
|
-
candidateIndex: any;
|
|
30
|
-
}[];
|
|
31
|
-
scan: any;
|
|
32
|
-
localizationMs: number;
|
|
33
|
-
decodingMs: number;
|
|
34
|
-
scanMs: number;
|
|
35
|
-
};
|
|
36
|
-
/** Separate convenience; it never changes find-all work or suppresses frame evidence. */
|
|
37
|
-
best(result: any): any;
|
|
38
|
-
dispose(): void;
|
|
39
|
-
#private;
|
|
40
|
-
}
|