@sythos/js_barcode_universal 1.5.8 → 1.5.9
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 +5 -4
- package/NOTICE.md +2 -2
- package/README.md +17 -13
- package/bundle/sythos-barcode.esm.js +11658 -11244
- package/bundle/sythos-barcode.js +11658 -11244
- package/package.json +10 -3
- package/src/index.d.ts +1 -1
- package/src/index.js +508 -516
- package/src/js/aztec/decoder.js +208 -199
- package/src/js/aztec/detector.js +153 -126
- package/src/js/aztec/encoder.js +172 -153
- package/src/js/aztec/high-level.js +106 -101
- package/src/js/aztec/index.js +1 -12
- package/src/js/aztec/tables.js +106 -120
- package/src/js/aztecrune/decoder.js +76 -85
- package/src/js/aztecrune/detector.js +107 -93
- package/src/js/aztecrune/encoder.js +50 -68
- package/src/js/aztecrune/index.js +1 -16
- package/src/js/aztecrune/tables.js +59 -54
- package/src/js/compactpdf417/decoder.js +69 -74
- package/src/js/compactpdf417/detector.js +74 -75
- package/src/js/compactpdf417/encoder.js +68 -81
- package/src/js/compactpdf417/index.js +1 -13
- package/src/js/compactpdf417/tables.js +71 -80
- package/src/js/core/bit-buffer.js +106 -122
- package/src/js/core/bit-matrix.js +196 -194
- package/src/js/core/errors.js +13 -15
- package/src/js/core/galois-field.js +119 -133
- package/src/js/core/index.js +2 -19
- package/src/js/core/reed-solomon.js +195 -204
- package/src/js/databar/codec.js +93 -108
- package/src/js/databar/decoder.js +149 -140
- package/src/js/databar/encoder.js +47 -55
- package/src/js/databar/gs1.js +122 -123
- package/src/js/databar/index.js +4 -36
- package/src/js/databar/patterns.js +81 -85
- package/src/js/databar/tables.js +51 -52
- package/src/js/datamatrix/decoder.js +216 -182
- package/src/js/datamatrix/detector.js +161 -132
- package/src/js/datamatrix/encoder.js +192 -133
- package/src/js/datamatrix/index.js +1 -9
- package/src/js/datamatrix/tables.js +68 -69
- package/src/js/frameqr/decoder.js +102 -125
- package/src/js/frameqr/detector.js +101 -94
- package/src/js/frameqr/encoder.js +67 -80
- package/src/js/frameqr/index.js +1 -10
- package/src/js/frameqr/tables.js +145 -163
- package/src/js/image/binarizer.js +146 -162
- package/src/js/image/grid-sampler.js +63 -79
- package/src/js/image/index.js +0 -2
- package/src/js/image/luminance.js +129 -140
- package/src/js/image/perspective.js +112 -149
- package/src/js/micropdf417/compaction.js +64 -65
- package/src/js/micropdf417/decoder.js +106 -109
- package/src/js/micropdf417/detector.js +73 -70
- package/src/js/micropdf417/encoder.js +129 -154
- package/src/js/micropdf417/error-correction.js +6 -11
- package/src/js/micropdf417/index.js +2 -16
- package/src/js/micropdf417/tables.js +112 -99
- package/src/js/microqr/decoder.js +177 -182
- package/src/js/microqr/detector.js +240 -256
- package/src/js/microqr/encoder.js +239 -194
- package/src/js/microqr/index.js +0 -2
- package/src/js/microqr/tables.js +208 -200
- package/src/js/oned/addons.js +167 -191
- package/src/js/oned/index.js +27 -58
- package/src/js/oned/patterns.js +194 -213
- package/src/js/oned/reader.js +1003 -984
- package/src/js/oned/writers.js +411 -466
- package/src/js/pdf417/compaction.js +315 -222
- package/src/js/pdf417/decoder.js +71 -36
- package/src/js/pdf417/detector.js +377 -361
- package/src/js/pdf417/encoder.js +57 -46
- package/src/js/pdf417/error-correction.js +5 -8
- package/src/js/pdf417/tables.js +248 -250
- package/src/js/qr/decoder.js +311 -347
- package/src/js/qr/detector.js +378 -395
- package/src/js/qr/encoder.js +514 -599
- package/src/js/qr/index.js +0 -2
- package/src/js/qr/tables.js +375 -422
- package/src/js/render/image-data.js +57 -62
- package/src/js/render/index.js +21 -23
- package/src/js/render/options.js +82 -90
- package/src/js/render/png.js +144 -169
- package/src/js/render/svg.js +41 -44
- package/src/js/render/webgl.js +103 -109
- package/src/js/render/webgpu.js +205 -217
- package/src/js/rmqr/decoder.js +161 -53
- package/src/js/rmqr/detector.js +73 -41
- package/src/js/rmqr/encoder.js +253 -104
- package/src/js/rmqr/index.js +1 -5
- package/src/js/rmqr/tables.js +108 -85
- package/src/ts/aztec/decoder.ts +317 -0
- package/src/ts/aztec/detector.ts +224 -0
- package/src/ts/aztec/encoder.ts +257 -0
- package/src/ts/aztec/high-level.ts +211 -0
- package/src/ts/aztec/index.ts +45 -0
- package/src/ts/aztec/tables.ts +210 -0
- package/src/ts/aztecrune/decoder.ts +155 -0
- package/src/ts/aztecrune/detector.ts +166 -0
- package/src/ts/aztecrune/encoder.ts +121 -0
- package/src/ts/aztecrune/index.ts +49 -0
- package/src/ts/aztecrune/tables.ts +137 -0
- package/src/ts/compactpdf417/decoder.ts +128 -0
- package/src/ts/compactpdf417/detector.ts +139 -0
- package/src/ts/compactpdf417/encoder.ts +140 -0
- package/src/ts/compactpdf417/index.ts +17 -0
- package/src/ts/compactpdf417/tables.ts +173 -0
- package/src/ts/core/bit-buffer.ts +174 -0
- package/src/ts/core/bit-matrix.ts +241 -0
- package/src/ts/core/errors.ts +61 -0
- package/src/ts/core/galois-field.ts +207 -0
- package/src/ts/core/index.ts +56 -0
- package/src/ts/core/reed-solomon.ts +327 -0
- package/src/ts/databar/codec.ts +181 -0
- package/src/ts/databar/decoder.ts +215 -0
- package/src/ts/databar/encoder.ts +96 -0
- package/src/ts/databar/gs1.ts +177 -0
- package/src/ts/databar/index.ts +68 -0
- package/src/ts/databar/patterns.ts +143 -0
- package/src/ts/databar/tables.ts +124 -0
- package/src/ts/datamatrix/decoder.ts +262 -0
- package/src/ts/datamatrix/detector.ts +225 -0
- package/src/ts/datamatrix/encoder.ts +191 -0
- package/src/ts/datamatrix/index.ts +42 -0
- package/src/ts/datamatrix/tables.ts +123 -0
- package/src/ts/frameqr/decoder.ts +239 -0
- package/src/ts/frameqr/detector.ts +192 -0
- package/src/ts/frameqr/encoder.ts +156 -0
- package/src/ts/frameqr/index.ts +42 -0
- package/src/ts/frameqr/tables.ts +270 -0
- package/src/ts/image/binarizer.ts +270 -0
- package/src/ts/image/grid-sampler.ts +164 -0
- package/src/ts/image/index.ts +40 -0
- package/src/ts/image/luminance.ts +196 -0
- package/src/ts/image/perspective.ts +195 -0
- package/src/ts/index.d.ts +1 -1
- package/src/ts/index.ts +790 -0
- package/src/ts/micropdf417/compaction.ts +116 -0
- package/src/ts/micropdf417/decoder.ts +183 -0
- package/src/ts/micropdf417/detector.ts +149 -0
- package/src/ts/micropdf417/encoder.ts +209 -0
- package/src/ts/micropdf417/error-correction.ts +55 -0
- package/src/ts/micropdf417/index.ts +49 -0
- package/src/ts/micropdf417/tables.ts +184 -0
- package/src/ts/microqr/decoder.ts +245 -0
- package/src/ts/microqr/detector.ts +355 -0
- package/src/ts/microqr/encoder.ts +269 -0
- package/src/ts/microqr/index.ts +36 -0
- package/src/ts/microqr/tables.ts +316 -0
- package/src/ts/oned/addons.ts +420 -0
- package/src/ts/oned/index.ts +106 -0
- package/src/ts/oned/patterns.ts +384 -0
- package/src/ts/oned/reader.ts +1364 -0
- package/src/ts/oned/writers.ts +746 -0
- package/src/ts/pdf417/compaction.ts +298 -0
- package/src/ts/pdf417/decoder.ts +75 -0
- package/src/ts/pdf417/detector.ts +468 -0
- package/src/ts/pdf417/encoder.ts +91 -0
- package/src/ts/pdf417/error-correction.ts +47 -0
- package/src/ts/pdf417/index.ts +6 -0
- package/src/ts/pdf417/tables.ts +317 -0
- package/src/ts/qr/decoder.ts +575 -0
- package/src/ts/qr/detector.ts +630 -0
- package/src/ts/qr/encoder.ts +958 -0
- package/src/ts/qr/index.ts +44 -0
- package/src/ts/qr/tables.ts +737 -0
- package/src/ts/render/image-data.ts +125 -0
- package/src/ts/render/index.ts +130 -0
- package/src/ts/render/options.ts +160 -0
- package/src/ts/render/png.ts +295 -0
- package/src/ts/render/svg.ts +120 -0
- package/src/ts/render/webgl.ts +206 -0
- package/src/ts/render/webgpu.ts +369 -0
- package/src/ts/rmqr/decoder.ts +101 -0
- package/src/ts/rmqr/detector.ts +90 -0
- package/src/ts/rmqr/encoder.ts +172 -0
- package/src/ts/rmqr/index.ts +37 -0
- package/src/ts/rmqr/tables.ts +154 -0
|
@@ -27,442 +27,458 @@
|
|
|
27
27
|
*
|
|
28
28
|
* Original work. No code from any other barcode implementation.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
30
|
import { BitMatrix } from '../core/bit-matrix.js';
|
|
32
31
|
import { sampleGrid, sampleGridVoting } from '../image/grid-sampler.js';
|
|
33
32
|
import { PerspectiveTransform } from '../image/perspective.js';
|
|
34
33
|
import { decodePDF417 } from './decoder.js';
|
|
35
|
-
|
|
36
34
|
const START_PATTERN = [8, 1, 1, 1, 1, 1, 1, 3];
|
|
37
35
|
const STOP_PATTERN = [7, 1, 1, 3, 1, 1, 1, 2, 1];
|
|
38
36
|
const SCAN_ANGLES = [0, -4, 4, -8, 8, -14, 14, -22, 22, -32, 32]
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
.map((degrees) => degrees * Math.PI / 180);
|
|
41
38
|
function rotateClockwise(source) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
const rotated = new BitMatrix(source.height, source.width);
|
|
40
|
+
for (let y = 0; y < source.height; y++)
|
|
41
|
+
for (let x = 0; x < source.width; x++) {
|
|
42
|
+
if (source.get(x, y))
|
|
43
|
+
rotated.set(source.height - 1 - y, x);
|
|
44
|
+
}
|
|
45
|
+
return rotated;
|
|
47
46
|
}
|
|
48
|
-
|
|
49
47
|
function scanGeometry(angle) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
return {
|
|
49
|
+
along: { x: Math.cos(angle), y: Math.sin(angle) },
|
|
50
|
+
across: { x: -Math.sin(angle), y: Math.cos(angle) },
|
|
51
|
+
};
|
|
54
52
|
}
|
|
55
|
-
|
|
56
53
|
function projectionRange(image, vector) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
const points = [
|
|
55
|
+
{ x: 0, y: 0 }, { x: image.width - 1, y: 0 },
|
|
56
|
+
{ x: image.width - 1, y: image.height - 1 }, { x: 0, y: image.height - 1 },
|
|
57
|
+
];
|
|
58
|
+
const values = points.map((point) => point.x * vector.x + point.y * vector.y);
|
|
59
|
+
return { min: Math.min(...values), max: Math.max(...values) };
|
|
63
60
|
}
|
|
64
|
-
|
|
65
61
|
function lineRange(image, geometry, across) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
62
|
+
const { along, across: normal } = geometry;
|
|
63
|
+
let min = -Infinity, max = Infinity;
|
|
64
|
+
const constrain = (low, high, step, offset) => {
|
|
65
|
+
if (Math.abs(step) < 1e-9)
|
|
66
|
+
return offset >= low && offset <= high;
|
|
67
|
+
const first = (low - offset) / step, second = (high - offset) / step;
|
|
68
|
+
min = Math.max(min, Math.min(first, second));
|
|
69
|
+
max = Math.min(max, Math.max(first, second));
|
|
70
|
+
return min <= max;
|
|
71
|
+
};
|
|
72
|
+
if (!constrain(0, image.width - 1, along.x, normal.x * across) ||
|
|
73
|
+
!constrain(0, image.height - 1, along.y, normal.y * across))
|
|
74
|
+
return null;
|
|
75
|
+
return { min: Math.ceil(min), max: Math.floor(max) };
|
|
78
76
|
}
|
|
79
|
-
|
|
80
77
|
function runsInLine(image, geometry, across) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Math.round(along.x * position + normal.x * across),
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
78
|
+
const range = lineRange(image, geometry, across);
|
|
79
|
+
if (!range || range.max < range.min)
|
|
80
|
+
return [];
|
|
81
|
+
const { along, across: normal } = geometry;
|
|
82
|
+
const runs = [];
|
|
83
|
+
const valueAt = (position) => image.get(Math.round(along.x * position + normal.x * across), Math.round(along.y * position + normal.y * across));
|
|
84
|
+
let dark = valueAt(range.min), start = range.min;
|
|
85
|
+
for (let position = range.min + 1; position <= range.max + 1; position++) {
|
|
86
|
+
const value = position <= range.max ? valueAt(position) : !dark;
|
|
87
|
+
if (value !== dark) {
|
|
88
|
+
runs.push({ dark, start, end: position, length: position - start });
|
|
89
|
+
start = position;
|
|
90
|
+
dark = value;
|
|
91
|
+
}
|
|
95
92
|
}
|
|
96
|
-
|
|
97
|
-
return runs;
|
|
93
|
+
return runs;
|
|
98
94
|
}
|
|
99
|
-
|
|
100
95
|
function removeSinglePixelSpecks(runs) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
96
|
+
const clean = runs.map((run) => ({ ...run }));
|
|
97
|
+
for (let index = 1; index < clean.length - 1;) {
|
|
98
|
+
if (clean[index].length > 1) {
|
|
99
|
+
index++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const merged = {
|
|
103
|
+
dark: clean[index - 1].dark,
|
|
104
|
+
start: clean[index - 1].start,
|
|
105
|
+
end: clean[index + 1].end,
|
|
106
|
+
length: clean[index - 1].length + clean[index].length + clean[index + 1].length,
|
|
107
|
+
};
|
|
108
|
+
clean.splice(index - 1, 3, merged);
|
|
109
|
+
index = Math.max(1, index - 2);
|
|
110
|
+
}
|
|
111
|
+
return clean;
|
|
114
112
|
}
|
|
115
|
-
|
|
116
113
|
function matchPattern(runs, at, expected) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
if (at + expected.length > runs.length || !runs[at].dark)
|
|
115
|
+
return null;
|
|
116
|
+
let observed = 0, modules = 0;
|
|
117
|
+
for (let i = 0; i < expected.length; i++) {
|
|
118
|
+
observed += runs[at + i].length;
|
|
119
|
+
modules += expected[i];
|
|
120
|
+
}
|
|
121
|
+
const scale = observed / modules;
|
|
122
|
+
if (scale < 0.65)
|
|
123
|
+
return null;
|
|
124
|
+
let error = 0;
|
|
125
|
+
for (let i = 0; i < expected.length; i++) {
|
|
126
|
+
const delta = Math.abs(runs[at + i].length - expected[i] * scale);
|
|
127
|
+
if (delta > Math.max(1.15, scale * 0.62))
|
|
128
|
+
return null;
|
|
129
|
+
error += delta / scale;
|
|
130
|
+
}
|
|
131
|
+
if (error / expected.length > 0.48)
|
|
132
|
+
return null;
|
|
133
|
+
return { start: runs[at].start, end: runs[at + expected.length - 1].end, scale, error, at };
|
|
130
134
|
}
|
|
131
|
-
|
|
132
135
|
function quietPenalty(runs, start, stop) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
const before = start.at > 0 ? runs[start.at - 1].length / start.scale : 0;
|
|
137
|
+
const afterIndex = stop.at + STOP_PATTERN.length;
|
|
138
|
+
const after = afterIndex < runs.length ? runs[afterIndex].length / stop.scale : 0;
|
|
139
|
+
return Math.max(0, 2 - before) + Math.max(0, 2 - after);
|
|
137
140
|
}
|
|
138
|
-
|
|
139
141
|
function patternPairs(runs) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const measured = stop.end - start.start;
|
|
149
|
-
const localScale = Math.sqrt(start.scale * stop.scale);
|
|
150
|
-
const roughColumns = Math.round((measured / localScale - 69) / 17);
|
|
151
|
-
for (let columns = Math.max(1, roughColumns - 2); columns <= Math.min(30, roughColumns + 2); columns++) {
|
|
152
|
-
const width = 69 + columns * 17;
|
|
153
|
-
const globalScale = measured / width;
|
|
154
|
-
const ratio = Math.max(start.scale, stop.scale, globalScale) /
|
|
155
|
-
Math.min(start.scale, stop.scale, globalScale);
|
|
156
|
-
if (ratio > 1.85) continue;
|
|
157
|
-
const scaleError = Math.abs(Math.log(start.scale / globalScale)) +
|
|
158
|
-
Math.abs(Math.log(stop.scale / globalScale));
|
|
159
|
-
pairs.push({ start, stop, columns, width, scale: globalScale,
|
|
160
|
-
startScale: start.scale, stopScale: stop.scale,
|
|
161
|
-
error: start.error + stop.error + scaleError * 4 + quietPenalty(runs, start, stop) * 0.15 });
|
|
142
|
+
const starts = [], stops = [];
|
|
143
|
+
for (let at = 0; at < runs.length; at++) {
|
|
144
|
+
const start = matchPattern(runs, at, START_PATTERN);
|
|
145
|
+
if (start)
|
|
146
|
+
starts.push(start);
|
|
147
|
+
const stop = matchPattern(runs, at, STOP_PATTERN);
|
|
148
|
+
if (stop)
|
|
149
|
+
stops.push(stop);
|
|
162
150
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
151
|
+
const pairs = [];
|
|
152
|
+
for (const start of starts)
|
|
153
|
+
for (const stop of stops) {
|
|
154
|
+
if (stop.start <= start.end)
|
|
155
|
+
continue;
|
|
156
|
+
const measured = stop.end - start.start;
|
|
157
|
+
const localScale = Math.sqrt(start.scale * stop.scale);
|
|
158
|
+
const roughColumns = Math.round((measured / localScale - 69) / 17);
|
|
159
|
+
for (let columns = Math.max(1, roughColumns - 2); columns <= Math.min(30, roughColumns + 2); columns++) {
|
|
160
|
+
const width = 69 + columns * 17;
|
|
161
|
+
const globalScale = measured / width;
|
|
162
|
+
const ratio = Math.max(start.scale, stop.scale, globalScale) /
|
|
163
|
+
Math.min(start.scale, stop.scale, globalScale);
|
|
164
|
+
if (ratio > 1.85)
|
|
165
|
+
continue;
|
|
166
|
+
const scaleError = Math.abs(Math.log(start.scale / globalScale)) +
|
|
167
|
+
Math.abs(Math.log(stop.scale / globalScale));
|
|
168
|
+
pairs.push({ start, stop, columns, width, scale: globalScale,
|
|
169
|
+
startScale: start.scale, stopScale: stop.scale,
|
|
170
|
+
error: start.error + stop.error + scaleError * 4 + quietPenalty(runs, start, stop) * 0.15 });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
pairs.sort((a, b) => a.error - b.error);
|
|
174
|
+
const used = new Set();
|
|
175
|
+
return pairs.filter((pair) => {
|
|
176
|
+
if (used.has(pair.columns))
|
|
177
|
+
return false;
|
|
178
|
+
used.add(pair.columns);
|
|
179
|
+
return used.size <= 3;
|
|
180
|
+
});
|
|
171
181
|
}
|
|
172
|
-
|
|
173
182
|
function pointAt(geometry, along, across) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
183
|
+
return {
|
|
184
|
+
x: geometry.along.x * along + geometry.across.x * across,
|
|
185
|
+
y: geometry.along.y * along + geometry.across.y * across,
|
|
186
|
+
};
|
|
178
187
|
}
|
|
179
|
-
|
|
180
188
|
function scanHits(image, angle) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
189
|
+
const geometry = scanGeometry(angle);
|
|
190
|
+
const range = projectionRange(image, geometry.across);
|
|
191
|
+
const hits = [];
|
|
192
|
+
for (let across = Math.ceil(range.min); across <= Math.floor(range.max); across++) {
|
|
193
|
+
const raw = runsInLine(image, geometry, across);
|
|
194
|
+
let pairs = patternPairs(raw);
|
|
195
|
+
if (!pairs.length)
|
|
196
|
+
pairs = patternPairs(removeSinglePixelSpecks(raw));
|
|
197
|
+
for (const pair of pairs) {
|
|
198
|
+
hits.push({ across, left: pair.start.start, right: pair.stop.end,
|
|
199
|
+
leftPoint: pointAt(geometry, pair.start.start, across),
|
|
200
|
+
rightPoint: pointAt(geometry, pair.stop.end, across),
|
|
201
|
+
scale: pair.scale, startScale: pair.startScale, stopScale: pair.stopScale,
|
|
202
|
+
columns: pair.columns, width: pair.width, error: pair.error,
|
|
203
|
+
geometry });
|
|
204
|
+
}
|
|
195
205
|
}
|
|
196
|
-
|
|
197
|
-
return hits;
|
|
206
|
+
return hits;
|
|
198
207
|
}
|
|
199
|
-
|
|
200
208
|
function fittedLine(hits, key) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
const meanX = hits.reduce((sum, hit) => sum + hit.across, 0) / hits.length;
|
|
210
|
+
const meanY = hits.reduce((sum, hit) => sum + hit[key], 0) / hits.length;
|
|
211
|
+
let covariance = 0, variance = 0;
|
|
212
|
+
for (const hit of hits) {
|
|
213
|
+
const delta = hit.across - meanX;
|
|
214
|
+
covariance += delta * (hit[key] - meanY);
|
|
215
|
+
variance += delta * delta;
|
|
216
|
+
}
|
|
217
|
+
const slope = variance ? covariance / variance : 0;
|
|
218
|
+
return { slope, intercept: meanY - slope * meanX };
|
|
211
219
|
}
|
|
212
|
-
|
|
213
220
|
function lineValue(line, at) {
|
|
214
|
-
|
|
221
|
+
return line.intercept + line.slope * at;
|
|
215
222
|
}
|
|
216
|
-
|
|
217
223
|
function finderExtent(image, hits, edgeKey, scaleKey, centreModules, fallback) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
224
|
+
const geometry = hits[0].geometry;
|
|
225
|
+
const edge = fittedLine(hits, edgeKey), scale = fittedLine(hits, scaleKey);
|
|
226
|
+
const span = fallback.bottom - fallback.top;
|
|
227
|
+
const projection = projectionRange(image, geometry.across);
|
|
228
|
+
const margin = Math.max(6, span * 0.35, hits[0].scale * 4);
|
|
229
|
+
const start = Math.max(Math.ceil(projection.min), Math.floor(fallback.top - margin));
|
|
230
|
+
const end = Math.min(Math.floor(projection.max), Math.ceil(fallback.bottom + margin));
|
|
231
|
+
const values = [];
|
|
232
|
+
for (let across = start; across <= end; across++) {
|
|
233
|
+
const localScale = Math.max(0.5, lineValue(scale, across));
|
|
234
|
+
const centre = lineValue(edge, across) + localScale * centreModules;
|
|
235
|
+
let dark = 0;
|
|
236
|
+
for (const offset of [-0.75, 0, 0.75]) {
|
|
237
|
+
const point = pointAt(geometry, centre + localScale * offset, across);
|
|
238
|
+
if (image.get(Math.round(point.x), Math.round(point.y)))
|
|
239
|
+
dark++;
|
|
240
|
+
}
|
|
241
|
+
values.push({ across, dark: dark >= 2 });
|
|
233
242
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
const segments = [];
|
|
244
|
+
let first = null, lastDark = null, gap = 0;
|
|
245
|
+
for (const value of values) {
|
|
246
|
+
if (value.dark) {
|
|
247
|
+
if (first === null)
|
|
248
|
+
first = value.across;
|
|
249
|
+
lastDark = value.across;
|
|
250
|
+
gap = 0;
|
|
251
|
+
}
|
|
252
|
+
else if (first !== null && ++gap > 2) {
|
|
253
|
+
segments.push({ top: first, bottom: lastDark + 1 });
|
|
254
|
+
first = null;
|
|
255
|
+
lastDark = null;
|
|
256
|
+
gap = 0;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (first !== null)
|
|
260
|
+
segments.push({ top: first, bottom: lastDark + 1 });
|
|
261
|
+
let best = null, bestScore = -Infinity;
|
|
262
|
+
for (const segment of segments) {
|
|
263
|
+
const overlap = Math.max(0, Math.min(segment.bottom, fallback.bottom) - Math.max(segment.top, fallback.top));
|
|
264
|
+
const score = overlap * 4 + (segment.bottom - segment.top) -
|
|
265
|
+
Math.abs((segment.top + segment.bottom) / 2 - (fallback.top + fallback.bottom) / 2);
|
|
266
|
+
if (overlap >= span * 0.55 && score > bestScore) {
|
|
267
|
+
best = segment;
|
|
268
|
+
bestScore = score;
|
|
269
|
+
}
|
|
245
270
|
}
|
|
246
|
-
|
|
247
|
-
if (first !== null) segments.push({ top: first, bottom: lastDark + 1 });
|
|
248
|
-
let best = null, bestScore = -Infinity;
|
|
249
|
-
for (const segment of segments) {
|
|
250
|
-
const overlap = Math.max(0, Math.min(segment.bottom, fallback.bottom) - Math.max(segment.top, fallback.top));
|
|
251
|
-
const score = overlap * 4 + (segment.bottom - segment.top) -
|
|
252
|
-
Math.abs((segment.top + segment.bottom) / 2 - (fallback.top + fallback.bottom) / 2);
|
|
253
|
-
if (overlap >= span * 0.55 && score > bestScore) { best = segment; bestScore = score; }
|
|
254
|
-
}
|
|
255
|
-
return best ?? fallback;
|
|
271
|
+
return best ?? fallback;
|
|
256
272
|
}
|
|
257
|
-
|
|
258
273
|
function intersectBoundary(leftAcross, rightAcross, leftCentre, rightCentre, leftEdge, rightEdge) {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
274
|
+
const delta = rightCentre - leftCentre;
|
|
275
|
+
if (Math.abs(delta) < 1e-6)
|
|
276
|
+
return { left: leftAcross, right: rightAcross };
|
|
277
|
+
const slope = (rightAcross - leftAcross) / delta;
|
|
278
|
+
const intercept = leftAcross - slope * leftCentre;
|
|
279
|
+
const atEdge = (edge, fallback) => {
|
|
280
|
+
const denominator = 1 - slope * edge.slope;
|
|
281
|
+
return Math.abs(denominator) < 1e-6 ? fallback :
|
|
282
|
+
(slope * edge.intercept + intercept) / denominator;
|
|
283
|
+
};
|
|
284
|
+
return { left: atEdge(leftEdge, leftAcross), right: atEdge(rightEdge, rightAcross) };
|
|
269
285
|
}
|
|
270
|
-
|
|
271
286
|
function groupHits(hits, image) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
287
|
+
const groups = [];
|
|
288
|
+
for (const hit of hits.sort((a, b) => a.across - b.across || a.error - b.error)) {
|
|
289
|
+
let best = null, bestDistance = Infinity;
|
|
290
|
+
for (const group of groups) {
|
|
291
|
+
const last = group.hits[group.hits.length - 1];
|
|
292
|
+
const gap = hit.across - last.across;
|
|
293
|
+
if (hit.columns !== last.columns || gap <= 0 || gap > Math.max(5, hit.scale * 3))
|
|
294
|
+
continue;
|
|
295
|
+
const scaleRatio = Math.max(hit.scale, last.scale) / Math.min(hit.scale, last.scale);
|
|
296
|
+
if (scaleRatio > 1.45)
|
|
297
|
+
continue;
|
|
298
|
+
const tolerance = Math.max(5, hit.scale * 4 + gap);
|
|
299
|
+
const distance = Math.abs(hit.left - last.left) + Math.abs(hit.right - last.right);
|
|
300
|
+
if (distance > tolerance * 2 || distance >= bestDistance)
|
|
301
|
+
continue;
|
|
302
|
+
best = group;
|
|
303
|
+
bestDistance = distance;
|
|
304
|
+
}
|
|
305
|
+
if (best)
|
|
306
|
+
best.hits.push(hit);
|
|
307
|
+
else
|
|
308
|
+
groups.push({ hits: [hit] });
|
|
309
|
+
}
|
|
310
|
+
const candidates = [];
|
|
275
311
|
for (const group of groups) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
312
|
+
const rows = group.hits;
|
|
313
|
+
const scale = rows.reduce((sum, hit) => sum + hit.scale, 0) / rows.length;
|
|
314
|
+
const top = rows[0].across, bottom = rows[rows.length - 1].across + 1;
|
|
315
|
+
const span = bottom - top;
|
|
316
|
+
if (rows.length < 4 || span < Math.max(6, scale * 4) || rows.length / span < 0.28)
|
|
317
|
+
continue;
|
|
318
|
+
const geometry = rows[0].geometry;
|
|
319
|
+
const left = fittedLine(rows, 'left'), right = fittedLine(rows, 'right');
|
|
320
|
+
const startScale = fittedLine(rows, 'startScale'), stopScale = fittedLine(rows, 'stopScale');
|
|
321
|
+
const fallback = { top, bottom };
|
|
322
|
+
const leftExtent = image ? finderExtent(image, rows, 'left', 'startScale', 4, fallback) : fallback;
|
|
323
|
+
const rightExtent = image ? finderExtent(image, rows, 'right', 'stopScale', -14.5, fallback) : fallback;
|
|
324
|
+
const boundary = (leftAcross, rightAcross) => intersectBoundary(leftAcross, rightAcross, lineValue(left, leftAcross) + lineValue(startScale, leftAcross) * 4, lineValue(right, rightAcross) - lineValue(stopScale, rightAcross) * 14.5, left, right);
|
|
325
|
+
const topBoundary = boundary(leftExtent.top, rightExtent.top);
|
|
326
|
+
const bottomBoundary = boundary(leftExtent.bottom, rightExtent.bottom);
|
|
327
|
+
candidates.push({
|
|
328
|
+
width: rows[0].width, scale,
|
|
329
|
+
corners: [
|
|
330
|
+
pointAt(geometry, lineValue(left, topBoundary.left), topBoundary.left),
|
|
331
|
+
pointAt(geometry, lineValue(right, topBoundary.right), topBoundary.right),
|
|
332
|
+
pointAt(geometry, lineValue(right, bottomBoundary.right), bottomBoundary.right),
|
|
333
|
+
pointAt(geometry, lineValue(left, bottomBoundary.left), bottomBoundary.left),
|
|
334
|
+
],
|
|
335
|
+
score: rows.length / span * 8 + Math.log2(rows.length + 1) * 2 -
|
|
336
|
+
rows.reduce((sum, hit) => sum + hit.error, 0) / rows.length,
|
|
337
|
+
});
|
|
285
338
|
}
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
const candidates = [];
|
|
289
|
-
for (const group of groups) {
|
|
290
|
-
const rows = group.hits;
|
|
291
|
-
const scale = rows.reduce((sum, hit) => sum + hit.scale, 0) / rows.length;
|
|
292
|
-
const top = rows[0].across, bottom = rows[rows.length - 1].across + 1;
|
|
293
|
-
const span = bottom - top;
|
|
294
|
-
if (rows.length < 4 || span < Math.max(6, scale * 4) || rows.length / span < 0.28) continue;
|
|
295
|
-
const geometry = rows[0].geometry;
|
|
296
|
-
const left = fittedLine(rows, 'left'), right = fittedLine(rows, 'right');
|
|
297
|
-
const startScale = fittedLine(rows, 'startScale'), stopScale = fittedLine(rows, 'stopScale');
|
|
298
|
-
const fallback = { top, bottom };
|
|
299
|
-
const leftExtent = image ? finderExtent(image, rows, 'left', 'startScale', 4, fallback) : fallback;
|
|
300
|
-
const rightExtent = image ? finderExtent(image, rows, 'right', 'stopScale', -14.5, fallback) : fallback;
|
|
301
|
-
const boundary = (leftAcross, rightAcross) => intersectBoundary(
|
|
302
|
-
leftAcross,
|
|
303
|
-
rightAcross,
|
|
304
|
-
lineValue(left, leftAcross) + lineValue(startScale, leftAcross) * 4,
|
|
305
|
-
lineValue(right, rightAcross) - lineValue(stopScale, rightAcross) * 14.5,
|
|
306
|
-
left,
|
|
307
|
-
right,
|
|
308
|
-
);
|
|
309
|
-
const topBoundary = boundary(leftExtent.top, rightExtent.top);
|
|
310
|
-
const bottomBoundary = boundary(leftExtent.bottom, rightExtent.bottom);
|
|
311
|
-
candidates.push({
|
|
312
|
-
width: rows[0].width, scale,
|
|
313
|
-
corners: [
|
|
314
|
-
pointAt(geometry, lineValue(left, topBoundary.left), topBoundary.left),
|
|
315
|
-
pointAt(geometry, lineValue(right, topBoundary.right), topBoundary.right),
|
|
316
|
-
pointAt(geometry, lineValue(right, bottomBoundary.right), bottomBoundary.right),
|
|
317
|
-
pointAt(geometry, lineValue(left, bottomBoundary.left), bottomBoundary.left),
|
|
318
|
-
],
|
|
319
|
-
score: rows.length / span * 8 + Math.log2(rows.length + 1) * 2 -
|
|
320
|
-
rows.reduce((sum, hit) => sum + hit.error, 0) / rows.length,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
return candidates.sort((a, b) => b.score - a.score);
|
|
339
|
+
return candidates.sort((a, b) => b.score - a.score);
|
|
324
340
|
}
|
|
325
|
-
|
|
326
341
|
function validQuadrilateral(value) {
|
|
327
|
-
|
|
328
|
-
|
|
342
|
+
return Array.isArray(value) && value.length === 4 &&
|
|
343
|
+
value.every((point) => Number.isFinite(point?.x) && Number.isFinite(point?.y));
|
|
329
344
|
}
|
|
330
|
-
|
|
331
345
|
function manualCandidate(corners) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
const horizontal = (Math.hypot(corners[1].x - corners[0].x, corners[1].y - corners[0].y) +
|
|
347
|
+
Math.hypot(corners[2].x - corners[3].x, corners[2].y - corners[3].y)) / 2;
|
|
348
|
+
const vertical = (Math.hypot(corners[3].x - corners[0].x, corners[3].y - corners[0].y) +
|
|
349
|
+
Math.hypot(corners[2].x - corners[1].x, corners[2].y - corners[1].y)) / 2;
|
|
350
|
+
const out = [];
|
|
351
|
+
for (let columns = 1; columns <= 30; columns++) {
|
|
352
|
+
const width = 69 + columns * 17, scale = horizontal / width;
|
|
353
|
+
if (scale < 0.65)
|
|
354
|
+
continue;
|
|
355
|
+
const moduleHeight = vertical / scale;
|
|
356
|
+
let plausibility = Infinity;
|
|
357
|
+
for (let rowHeight = 3; rowHeight <= 12; rowHeight++) {
|
|
358
|
+
const rows = Math.round(moduleHeight / rowHeight);
|
|
359
|
+
if (rows >= 3 && rows <= 90)
|
|
360
|
+
plausibility = Math.min(plausibility, Math.abs(moduleHeight - rows * rowHeight) / rowHeight);
|
|
361
|
+
}
|
|
362
|
+
out.push({ width, scale, corners, score: Number.isFinite(plausibility) ? 2 - plausibility : 0 });
|
|
346
363
|
}
|
|
347
|
-
out.
|
|
348
|
-
}
|
|
349
|
-
return out.sort((a, b) => b.score - a.score);
|
|
364
|
+
return out.sort((a, b) => b.score - a.score);
|
|
350
365
|
}
|
|
351
|
-
|
|
352
366
|
function edgeLength(first, second) {
|
|
353
|
-
|
|
367
|
+
return Math.hypot(second.x - first.x, second.y - first.y);
|
|
354
368
|
}
|
|
355
|
-
|
|
356
369
|
function rowCandidates(candidate, options) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
370
|
+
const vertical = (edgeLength(candidate.corners[0], candidate.corners[3]) +
|
|
371
|
+
edgeLength(candidate.corners[1], candidate.corners[2])) / 2;
|
|
372
|
+
const allowedHeights = Number.isInteger(options.rowHeight) ? [options.rowHeight] :
|
|
373
|
+
Array.from({ length: 10 }, (_, index) => index + 3);
|
|
374
|
+
const rows = [];
|
|
375
|
+
for (let value = 3; value <= 90; value++) {
|
|
376
|
+
let score = Infinity;
|
|
377
|
+
for (const rowHeight of allowedHeights) {
|
|
378
|
+
score = Math.min(score, Math.abs(Math.log(vertical / (candidate.scale * value * rowHeight))));
|
|
379
|
+
}
|
|
380
|
+
rows.push({ value, score });
|
|
366
381
|
}
|
|
367
|
-
rows.
|
|
368
|
-
}
|
|
369
|
-
return rows.sort((a, b) => a.score - b.score).map((entry) => entry.value);
|
|
382
|
+
return rows.sort((a, b) => a.score - b.score).map((entry) => entry.value);
|
|
370
383
|
}
|
|
371
|
-
|
|
372
384
|
function shiftedCandidate(candidate, amount) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
{
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
if (!amount)
|
|
386
|
+
return candidate;
|
|
387
|
+
const topDx = candidate.corners[3].x - candidate.corners[0].x;
|
|
388
|
+
const topDy = candidate.corners[3].y - candidate.corners[0].y;
|
|
389
|
+
const bottomDx = candidate.corners[2].x - candidate.corners[1].x;
|
|
390
|
+
const bottomDy = candidate.corners[2].y - candidate.corners[1].y;
|
|
391
|
+
const topLength = Math.hypot(topDx, topDy) || 1;
|
|
392
|
+
const bottomLength = Math.hypot(bottomDx, bottomDy) || 1;
|
|
393
|
+
return { ...candidate, corners: [
|
|
394
|
+
{ x: candidate.corners[0].x - topDx / topLength * amount, y: candidate.corners[0].y - topDy / topLength * amount },
|
|
395
|
+
{ x: candidate.corners[1].x - bottomDx / bottomLength * amount, y: candidate.corners[1].y - bottomDy / bottomLength * amount },
|
|
396
|
+
{ x: candidate.corners[2].x + bottomDx / bottomLength * amount, y: candidate.corners[2].y + bottomDy / bottomLength * amount },
|
|
397
|
+
{ x: candidate.corners[3].x + topDx / topLength * amount, y: candidate.corners[3].y + topDy / topLength * amount },
|
|
398
|
+
] };
|
|
386
399
|
}
|
|
387
|
-
|
|
388
400
|
function canonicalRows(matrix) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
401
|
+
const rowHeight = 3;
|
|
402
|
+
const out = new BitMatrix(matrix.width, matrix.height * rowHeight);
|
|
403
|
+
for (let y = 0; y < matrix.height; y++)
|
|
404
|
+
for (let x = 0; x < matrix.width; x++) {
|
|
405
|
+
if (matrix.get(x, y))
|
|
406
|
+
out.setRegion(x, y * rowHeight, 1, rowHeight);
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
395
409
|
}
|
|
396
|
-
|
|
397
410
|
function sampleCandidate(image, candidate, options) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
+
for (const edgeShift of [0, candidate.scale * 0.35, -candidate.scale * 0.25]) {
|
|
412
|
+
const geometry = shiftedCandidate(candidate, edgeShift);
|
|
413
|
+
for (const rows of rowCandidates(geometry, options)) {
|
|
414
|
+
const transform = PerspectiveTransform.quadToQuad(0, 0, geometry.width, 0, geometry.width, rows, 0, rows, geometry.corners[0].x, geometry.corners[0].y, geometry.corners[1].x, geometry.corners[1].y, geometry.corners[2].x, geometry.corners[2].y, geometry.corners[3].x, geometry.corners[3].y);
|
|
415
|
+
for (const voting of [false, true]) {
|
|
416
|
+
let matrix;
|
|
417
|
+
try {
|
|
418
|
+
matrix = voting ? sampleGridVoting(image, geometry.width, rows, transform) : sampleGrid(image, geometry.width, rows, transform);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
const result = decodePDF417(matrix, { ...options, rowHeight: 1 });
|
|
425
|
+
return { matrix: canonicalRows(matrix), result, corners: geometry.corners };
|
|
426
|
+
}
|
|
427
|
+
catch { /* Try the next geometry. */ }
|
|
428
|
+
}
|
|
411
429
|
}
|
|
412
|
-
catch { /* Try the next geometry. */ }
|
|
413
|
-
}
|
|
414
430
|
}
|
|
415
|
-
|
|
416
|
-
return null;
|
|
431
|
+
return null;
|
|
417
432
|
}
|
|
418
|
-
|
|
419
433
|
function automaticCandidates(image) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
const out = [];
|
|
435
|
+
for (const angle of SCAN_ANGLES) {
|
|
436
|
+
out.push(...groupHits(scanHits(image, angle), image));
|
|
437
|
+
if (out.length)
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
426
441
|
}
|
|
427
|
-
|
|
428
442
|
function detectInOrientation(image, options, supplied) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (supplied.length) return null;
|
|
434
|
-
for (const angle of SCAN_ANGLES.slice(1)) {
|
|
435
|
-
const candidates = groupHits(scanHits(image, angle), image);
|
|
436
|
-
for (const candidate of candidates) {
|
|
437
|
-
const decoded = sampleCandidate(image, candidate, options);
|
|
438
|
-
if (decoded) return { candidate, decoded };
|
|
443
|
+
for (const candidate of [...supplied, ...automaticCandidates(image)]) {
|
|
444
|
+
const decoded = sampleCandidate(image, candidate, options);
|
|
445
|
+
if (decoded)
|
|
446
|
+
return { candidate, decoded };
|
|
439
447
|
}
|
|
440
|
-
|
|
441
|
-
|
|
448
|
+
if (supplied.length)
|
|
449
|
+
return null;
|
|
450
|
+
for (const angle of SCAN_ANGLES.slice(1)) {
|
|
451
|
+
const candidates = groupHits(scanHits(image, angle), image);
|
|
452
|
+
for (const candidate of candidates) {
|
|
453
|
+
const decoded = sampleCandidate(image, candidate, options);
|
|
454
|
+
if (decoded)
|
|
455
|
+
return { candidate, decoded };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return null;
|
|
442
459
|
}
|
|
443
|
-
|
|
444
460
|
/*
|
|
445
461
|
* Locate a binarized raster symbol from its repeated start and stop patterns.
|
|
446
462
|
* The detector estimates a projective quadrilateral; grayscale binarization
|
|
447
463
|
* remains the caller's responsibility.
|
|
448
464
|
*/
|
|
449
465
|
export function detectPDF417(binaryImage, options = {}) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
466
|
+
if (!binaryImage?.width || !binaryImage?.height || typeof binaryImage.get !== 'function')
|
|
467
|
+
return null;
|
|
468
|
+
let oriented = binaryImage;
|
|
469
|
+
let toOriginal = (point) => ({ x: point.x, y: point.y });
|
|
470
|
+
for (let turns = 0; turns < 4; turns++) {
|
|
471
|
+
const supplied = turns === 0 && validQuadrilateral(options.quadrilateral)
|
|
472
|
+
? manualCandidate(options.quadrilateral.map(({ x, y }) => ({ x, y }))) : [];
|
|
473
|
+
const found = detectInOrientation(oriented, options, supplied);
|
|
474
|
+
if (found) {
|
|
475
|
+
return { matrix: found.decoded.matrix, rotation: turns * 90,
|
|
476
|
+
corners: found.decoded.corners.map(toOriginal), ...found.decoded.result };
|
|
477
|
+
}
|
|
478
|
+
const previous = oriented, previousToOriginal = toOriginal;
|
|
479
|
+
oriented = rotateClockwise(previous);
|
|
480
|
+
toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
|
|
460
481
|
}
|
|
461
|
-
|
|
462
|
-
oriented = rotateClockwise(previous);
|
|
463
|
-
toOriginal = (point) => previousToOriginal({ x: point.y, y: previous.height - point.x });
|
|
464
|
-
}
|
|
465
|
-
return null;
|
|
482
|
+
return null;
|
|
466
483
|
}
|
|
467
|
-
|
|
468
484
|
export function detectAndDecodePDF417(binaryImage, options = {}) { return detectPDF417(binaryImage, options); }
|