@sythos/js_barcode_universal 1.5.8 → 1.5.10
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 +61 -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,34 +27,12 @@
|
|
|
27
27
|
*
|
|
28
28
|
* Original work. No code from any other barcode implementation.
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Micro QR detection in binarized rasters.
|
|
33
|
-
*
|
|
34
|
-
* A Micro QR symbol has one 7x7 finder in its top-left corner. That alone is
|
|
35
|
-
* not enough to distinguish it from one corner of a normal QR Code, so every
|
|
36
|
-
* candidate is also required to have the Micro QR timing arms and a format
|
|
37
|
-
* word which the decoder accepts. The decoder is deliberately the final
|
|
38
|
-
* geometric arbiter; BCH and Reed--Solomon verification make accidental
|
|
39
|
-
* acceptance of ordinary square artwork very unlikely.
|
|
40
|
-
*
|
|
41
|
-
* Finder geometry supplies two local axes. Timing arms refine their lengths,
|
|
42
|
-
* while a small fourth-corner search lets projective sampling absorb mild
|
|
43
|
-
* perspective despite the format having no remote alignment pattern.
|
|
44
|
-
*
|
|
45
|
-
* @module microqr/detector
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
import { BitMatrix } from '../core/bit-matrix.js';
|
|
49
30
|
import { NotFoundError } from '../core/errors.js';
|
|
50
31
|
import { sampleQuad } from '../image/grid-sampler.js';
|
|
51
32
|
import { decodeMicroQR } from './decoder.js';
|
|
52
|
-
|
|
53
33
|
/** Legal Micro QR side lengths (M1 through M4). */
|
|
54
34
|
const DIMENSIONS = [11, 13, 15, 17];
|
|
55
|
-
|
|
56
35
|
/** @typedef {{x:number, y:number}} Point */
|
|
57
|
-
|
|
58
36
|
/**
|
|
59
37
|
* @typedef {object} Detection
|
|
60
38
|
* @property {Point[]} corners Outer corners in reading order.
|
|
@@ -65,161 +43,160 @@ const DIMENSIONS = [11, 13, 15, 17];
|
|
|
65
43
|
* @property {boolean} inverted Whether the detected symbol used inverted polarity.
|
|
66
44
|
* @property {BitMatrix} matrix Rectified, normally polarised module matrix.
|
|
67
45
|
*/
|
|
68
|
-
|
|
69
46
|
function rotateVector(vector) {
|
|
70
|
-
|
|
47
|
+
return { x: -vector.y, y: vector.x };
|
|
71
48
|
}
|
|
72
|
-
|
|
73
49
|
function add(point, a, av, b, bv) {
|
|
74
|
-
|
|
50
|
+
return { x: point.x + a.x * av + b.x * bv, y: point.y + a.y * av + b.y * bv };
|
|
75
51
|
}
|
|
76
|
-
|
|
77
52
|
function sample(image, point) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
const x = Math.round(point.x);
|
|
54
|
+
const y = Math.round(point.y);
|
|
55
|
+
if (x < 0 || y < 0 || x >= image.width || y >= image.height)
|
|
56
|
+
return null;
|
|
57
|
+
return image.get(x, y);
|
|
82
58
|
}
|
|
83
|
-
|
|
84
59
|
function expectedFinder(x, y) {
|
|
85
|
-
|
|
86
|
-
|
|
60
|
+
return x === 0 || y === 0 || x === 6 || y === 6 ||
|
|
61
|
+
(x >= 2 && x <= 4 && y >= 2 && y <= 4);
|
|
87
62
|
}
|
|
88
|
-
|
|
89
63
|
/** Connected components matching one polarity, capped to plausible centre blocks. */
|
|
90
64
|
function components(image, value) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
65
|
+
const seen = new Uint8Array(image.width * image.height);
|
|
66
|
+
const result = [];
|
|
67
|
+
const maximumArea = Math.max(16, Math.floor(image.width * image.height * 0.08));
|
|
68
|
+
for (let y = 0; y < image.height; y++)
|
|
69
|
+
for (let x = 0; x < image.width; x++) {
|
|
70
|
+
const start = y * image.width + x;
|
|
71
|
+
if (seen[start] || image.get(x, y) !== value)
|
|
72
|
+
continue;
|
|
73
|
+
const queueX = [x];
|
|
74
|
+
const queueY = [y];
|
|
75
|
+
seen[start] = 1;
|
|
76
|
+
let head = 0;
|
|
77
|
+
let minX = x;
|
|
78
|
+
let maxX = x;
|
|
79
|
+
let minY = y;
|
|
80
|
+
let maxY = y;
|
|
81
|
+
while (head < queueX.length) {
|
|
82
|
+
const px = queueX[head];
|
|
83
|
+
const py = queueY[head++];
|
|
84
|
+
if (px < minX)
|
|
85
|
+
minX = px;
|
|
86
|
+
if (px > maxX)
|
|
87
|
+
maxX = px;
|
|
88
|
+
if (py < minY)
|
|
89
|
+
minY = py;
|
|
90
|
+
if (py > maxY)
|
|
91
|
+
maxY = py;
|
|
92
|
+
for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
|
|
93
|
+
if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height)
|
|
94
|
+
continue;
|
|
95
|
+
const index = ny * image.width + nx;
|
|
96
|
+
if (!seen[index] && image.get(nx, ny) === value) {
|
|
97
|
+
seen[index] = 1;
|
|
98
|
+
queueX.push(nx);
|
|
99
|
+
queueY.push(ny);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const width = maxX - minX + 1;
|
|
104
|
+
const height = maxY - minY + 1;
|
|
105
|
+
const area = width * height;
|
|
106
|
+
if (queueX.length > maximumArea || Math.min(width, height) < 2)
|
|
107
|
+
continue;
|
|
108
|
+
if (Math.max(width, height) > Math.min(width, height) * 1.7)
|
|
109
|
+
continue;
|
|
110
|
+
if (queueX.length < area * 0.42)
|
|
111
|
+
continue;
|
|
112
|
+
result.push({
|
|
113
|
+
x: (minX + maxX) / 2,
|
|
114
|
+
y: (minY + maxY) / 2,
|
|
115
|
+
width,
|
|
116
|
+
height,
|
|
117
|
+
pixels: queueX.length,
|
|
118
|
+
});
|
|
119
119
|
}
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const width = maxX - minX + 1;
|
|
124
|
-
const height = maxY - minY + 1;
|
|
125
|
-
const area = width * height;
|
|
126
|
-
if (queueX.length > maximumArea || Math.min(width, height) < 2) continue;
|
|
127
|
-
if (Math.max(width, height) > Math.min(width, height) * 1.7) continue;
|
|
128
|
-
if (queueX.length < area * 0.42) continue;
|
|
129
|
-
result.push({
|
|
130
|
-
x: (minX + maxX) / 2,
|
|
131
|
-
y: (minY + maxY) / 2,
|
|
132
|
-
width,
|
|
133
|
-
height,
|
|
134
|
-
pixels: queueX.length,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return result.sort((a, b) => b.pixels - a.pixels).slice(0, 256);
|
|
120
|
+
return result.sort((a, b) => b.pixels - a.pixels).slice(0, 256);
|
|
139
121
|
}
|
|
140
|
-
|
|
141
122
|
/** Score the complete 7x7 finder at module centres. */
|
|
142
123
|
function finderScore(image, centre, u, v, pitch, inverted) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
124
|
+
let correct = 0;
|
|
125
|
+
let total = 0;
|
|
126
|
+
for (let y = 0; y < 7; y++)
|
|
127
|
+
for (let x = 0; x < 7; x++) {
|
|
128
|
+
const actual = sample(image, add(centre, u, (x - 3) * pitch, v, (y - 3) * pitch));
|
|
129
|
+
if (actual === null)
|
|
130
|
+
continue;
|
|
131
|
+
const wanted = inverted ? !expectedFinder(x, y) : expectedFinder(x, y);
|
|
132
|
+
if (actual === wanted)
|
|
133
|
+
correct++;
|
|
134
|
+
total++;
|
|
135
|
+
}
|
|
136
|
+
return total === 49 ? correct / total : 0;
|
|
153
137
|
}
|
|
154
|
-
|
|
155
138
|
/** Validate separator, timing arms and a sparse quiet-zone outline. */
|
|
156
139
|
function structureScore(image, centre, u, v, pitch, dimension, sx, sy, inverted) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
140
|
+
let correct = 0;
|
|
141
|
+
let total = 0;
|
|
142
|
+
const check = (x, y, dark) => {
|
|
143
|
+
const point = add(centre, u, (x - 3) * pitch * sx, v, (y - 3) * pitch * sy);
|
|
144
|
+
const actual = sample(image, point);
|
|
145
|
+
if (actual !== null && actual === (inverted ? !dark : dark))
|
|
146
|
+
correct++;
|
|
147
|
+
total++;
|
|
148
|
+
};
|
|
149
|
+
// The light separator lies between the finder and encoding region.
|
|
150
|
+
for (let i = 0; i <= 7; i++) {
|
|
151
|
+
check(7, i, false);
|
|
152
|
+
check(i, 7, false);
|
|
153
|
+
}
|
|
154
|
+
// Both timing arms start dark at coordinate 8 and alternate to the edge.
|
|
155
|
+
for (let i = 8; i < dimension; i++) {
|
|
156
|
+
check(i, 0, (i & 1) === 0);
|
|
157
|
+
check(0, i, (i & 1) === 0);
|
|
158
|
+
}
|
|
159
|
+
// A quiet-zone sample just beyond each edge rejects an isolated normal-QR
|
|
160
|
+
// finder and most decorative squares without requiring a perfect crop.
|
|
161
|
+
for (let i = 0; i < dimension; i += 2) {
|
|
162
|
+
check(i, -1.25, false);
|
|
163
|
+
check(-1.25, i, false);
|
|
164
|
+
check(i, dimension + 0.75, false);
|
|
165
|
+
check(dimension + 0.75, i, false);
|
|
166
|
+
}
|
|
167
|
+
return correct / total;
|
|
185
168
|
}
|
|
186
|
-
|
|
187
169
|
function invert(matrix) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
170
|
+
const out = matrix.clone();
|
|
171
|
+
for (let y = 0; y < out.height; y++)
|
|
172
|
+
for (let x = 0; x < out.width; x++)
|
|
173
|
+
out.flip(x, y);
|
|
174
|
+
return out;
|
|
191
175
|
}
|
|
192
|
-
|
|
193
176
|
function orientationDegrees(u) {
|
|
194
|
-
|
|
195
|
-
|
|
177
|
+
const degrees = Math.atan2(u.y, u.x) * 180 / Math.PI;
|
|
178
|
+
return ((Math.round(degrees / 90) * 90) % 360 + 360) % 360;
|
|
196
179
|
}
|
|
197
|
-
|
|
198
180
|
function cornersFor(centre, u, v, pitch, dimension, sx, sy, dx = 0, dy = 0) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
181
|
+
const tl = add(centre, u, -3.5 * pitch, v, -3.5 * pitch);
|
|
182
|
+
const tr = add(tl, u, dimension * pitch * sx, v, 0);
|
|
183
|
+
const bl = add(tl, u, 0, v, dimension * pitch * sy);
|
|
184
|
+
const br = add(add(tr, v, dimension * pitch * sy, u, 0), u, dx * pitch, v, dy * pitch);
|
|
185
|
+
return [tl, tr, br, bl];
|
|
204
186
|
}
|
|
205
|
-
|
|
206
187
|
function candidateKey(detection) {
|
|
207
|
-
|
|
208
|
-
|
|
188
|
+
const centre = detection.finderCentre;
|
|
189
|
+
return `${Math.round(centre.x)},${Math.round(centre.y)},${detection.dimension}`;
|
|
209
190
|
}
|
|
210
|
-
|
|
211
191
|
function sameCandidate(left, right) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
(sum, point) => ({ x: sum.x + point.x / 4, y: sum.y + point.y / 4 }),
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const tolerance = Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
220
|
-
return Math.hypot(a.x - b.x, a.y - b.y) < tolerance;
|
|
192
|
+
if (left.dimension !== right.dimension)
|
|
193
|
+
return false;
|
|
194
|
+
const centre = (detection) => detection.corners.reduce((sum, point) => ({ x: sum.x + point.x / 4, y: sum.y + point.y / 4 }), { x: 0, y: 0 });
|
|
195
|
+
const a = centre(left);
|
|
196
|
+
const b = centre(right);
|
|
197
|
+
const tolerance = Math.max(left.moduleSize, right.moduleSize) * 2;
|
|
198
|
+
return Math.hypot(a.x - b.x, a.y - b.y) < tolerance;
|
|
221
199
|
}
|
|
222
|
-
|
|
223
200
|
/**
|
|
224
201
|
* Find Micro QR symbols in a binarized raster.
|
|
225
202
|
*
|
|
@@ -231,98 +208,100 @@ function sameCandidate(left, right) {
|
|
|
231
208
|
* @returns {Detection[]} Best candidate first; empty when no symbol is found.
|
|
232
209
|
*/
|
|
233
210
|
export function detectMicroQR(binaryImage) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
211
|
+
if (!binaryImage || !binaryImage.width || !binaryImage.height) {
|
|
212
|
+
throw new NotFoundError('detectMicroQR: no image supplied');
|
|
213
|
+
}
|
|
214
|
+
const detections = [];
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
for (const inverted of [false, true]) {
|
|
217
|
+
for (const centre of components(binaryImage, !inverted)) {
|
|
218
|
+
for (let degrees = 0; degrees < 180; degrees += 3) {
|
|
219
|
+
const angle = degrees * Math.PI / 180;
|
|
220
|
+
const axis = { x: Math.cos(angle), y: Math.sin(angle) };
|
|
221
|
+
const perpendicular = rotateVector(axis);
|
|
222
|
+
const footprint = Math.abs(axis.x) + Math.abs(axis.y);
|
|
223
|
+
const pitch = ((centre.width + centre.height) / 2) / (3 * footprint);
|
|
224
|
+
if (pitch < 0.75)
|
|
225
|
+
continue;
|
|
226
|
+
// The finder is rotationally symmetric; four turns decide which pair
|
|
227
|
+
// of arms points into the encoding region.
|
|
228
|
+
for (let turn = 0, u = axis, v = perpendicular; turn < 4; turn++) {
|
|
229
|
+
if (turn > 0) {
|
|
230
|
+
u = v;
|
|
231
|
+
v = { x: -u.y, y: u.x };
|
|
232
|
+
}
|
|
233
|
+
const fScore = finderScore(binaryImage, centre, u, v, pitch, inverted);
|
|
234
|
+
if (fScore < 0.9)
|
|
235
|
+
continue;
|
|
236
|
+
for (const dimension of DIMENSIONS) {
|
|
237
|
+
const scales = [0.84, 0.92, 1, 1.08, 1.16];
|
|
238
|
+
const rankedX = scales.map((scale) => ({
|
|
239
|
+
scale,
|
|
240
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, scale, 1, inverted),
|
|
241
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
242
|
+
const rankedY = scales.map((scale) => ({
|
|
243
|
+
scale,
|
|
244
|
+
score: structureScore(binaryImage, centre, u, v, pitch, dimension, 1, scale, inverted),
|
|
245
|
+
})).sort((a, b) => b.score - a.score).slice(0, 2);
|
|
246
|
+
for (const xs of rankedX)
|
|
247
|
+
for (const ys of rankedY) {
|
|
248
|
+
const score = structureScore(binaryImage, centre, u, v, pitch, dimension, xs.scale, ys.scale, inverted);
|
|
249
|
+
if (score < 0.78)
|
|
250
|
+
continue;
|
|
251
|
+
// With a single finder there is no direct bottom-right anchor.
|
|
252
|
+
// A compact search around the affine estimate lets the projective
|
|
253
|
+
// sampler account for convergence of the remote edges.
|
|
254
|
+
for (const delta of [[0, 0], [-0.75, 0], [0.75, 0], [0, -0.75], [0, 0.75],
|
|
255
|
+
[-0.75, -0.75], [0.75, -0.75], [-0.75, 0.75], [0.75, 0.75]]) {
|
|
256
|
+
const corners = cornersFor(centre, u, v, pitch, dimension, xs.scale, ys.scale, delta[0], delta[1]);
|
|
257
|
+
let matrix;
|
|
258
|
+
try {
|
|
259
|
+
matrix = sampleQuad(binaryImage, dimension, corners, score < 0.9);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (inverted)
|
|
265
|
+
matrix = invert(matrix);
|
|
266
|
+
try {
|
|
267
|
+
const decoded = decodeMicroQR(matrix);
|
|
268
|
+
const version = decoded.version ?? `M${(dimension - 9) / 2}`;
|
|
269
|
+
const detection = {
|
|
270
|
+
corners,
|
|
271
|
+
dimension,
|
|
272
|
+
version,
|
|
273
|
+
moduleSize: pitch,
|
|
274
|
+
rotation: orientationDegrees(u),
|
|
275
|
+
inverted,
|
|
276
|
+
matrix,
|
|
277
|
+
finderCentre: { x: centre.x, y: centre.y },
|
|
278
|
+
score: fScore + score,
|
|
279
|
+
};
|
|
280
|
+
const key = candidateKey(detection);
|
|
281
|
+
if (!seen.has(key) && !detections.some((entry) => sameCandidate(entry, detection))) {
|
|
282
|
+
seen.add(key);
|
|
283
|
+
detections.push(detection);
|
|
284
|
+
}
|
|
285
|
+
// Decoder validation settled this dimension and orientation.
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
/* Try the next perspective hypothesis. */
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
309
294
|
}
|
|
310
|
-
}
|
|
311
295
|
}
|
|
312
|
-
}
|
|
313
296
|
}
|
|
314
|
-
}
|
|
315
297
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
return detections;
|
|
298
|
+
detections.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
|
|
299
|
+
for (const detection of detections) {
|
|
300
|
+
delete detection.finderCentre;
|
|
301
|
+
delete detection.score;
|
|
302
|
+
}
|
|
303
|
+
return detections;
|
|
324
304
|
}
|
|
325
|
-
|
|
326
305
|
/**
|
|
327
306
|
* Detect and decode all Micro QR symbols in a binarized raster.
|
|
328
307
|
*
|
|
@@ -330,26 +309,31 @@ export function detectMicroQR(binaryImage) {
|
|
|
330
309
|
* @returns {Array<object>}
|
|
331
310
|
*/
|
|
332
311
|
export function detectAndDecodeMicroQR(binaryImage) {
|
|
333
|
-
|
|
334
|
-
try { detections = detectMicroQR(binaryImage); }
|
|
335
|
-
catch (error) { return []; }
|
|
336
|
-
|
|
337
|
-
const results = [];
|
|
338
|
-
const seen = new Set();
|
|
339
|
-
for (const detection of detections) {
|
|
312
|
+
let detections;
|
|
340
313
|
try {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
314
|
+
detections = detectMicroQR(binaryImage);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
const results = [];
|
|
320
|
+
const seen = new Set();
|
|
321
|
+
for (const detection of detections) {
|
|
322
|
+
try {
|
|
323
|
+
const decoded = decodeMicroQR(detection.matrix);
|
|
324
|
+
const key = `${decoded.version ?? detection.version}|${decoded.text ?? ''}`;
|
|
325
|
+
if (seen.has(key))
|
|
326
|
+
continue;
|
|
327
|
+
seen.add(key);
|
|
328
|
+
results.push(Object.assign({
|
|
329
|
+
corners: detection.corners,
|
|
330
|
+
rotation: detection.rotation,
|
|
331
|
+
inverted: detection.inverted,
|
|
332
|
+
}, decoded));
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
/* A failed candidate is a normal no-symbol result. */
|
|
336
|
+
}
|
|
352
337
|
}
|
|
353
|
-
|
|
354
|
-
return results;
|
|
338
|
+
return results;
|
|
355
339
|
}
|