@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.
Files changed (179) hide show
  1. package/LICENSE +5 -4
  2. package/NOTICE.md +2 -2
  3. package/README.md +61 -13
  4. package/bundle/sythos-barcode.esm.js +11658 -11244
  5. package/bundle/sythos-barcode.js +11658 -11244
  6. package/package.json +10 -3
  7. package/src/index.d.ts +1 -1
  8. package/src/index.js +508 -516
  9. package/src/js/aztec/decoder.js +208 -199
  10. package/src/js/aztec/detector.js +153 -126
  11. package/src/js/aztec/encoder.js +172 -153
  12. package/src/js/aztec/high-level.js +106 -101
  13. package/src/js/aztec/index.js +1 -12
  14. package/src/js/aztec/tables.js +106 -120
  15. package/src/js/aztecrune/decoder.js +76 -85
  16. package/src/js/aztecrune/detector.js +107 -93
  17. package/src/js/aztecrune/encoder.js +50 -68
  18. package/src/js/aztecrune/index.js +1 -16
  19. package/src/js/aztecrune/tables.js +59 -54
  20. package/src/js/compactpdf417/decoder.js +69 -74
  21. package/src/js/compactpdf417/detector.js +74 -75
  22. package/src/js/compactpdf417/encoder.js +68 -81
  23. package/src/js/compactpdf417/index.js +1 -13
  24. package/src/js/compactpdf417/tables.js +71 -80
  25. package/src/js/core/bit-buffer.js +106 -122
  26. package/src/js/core/bit-matrix.js +196 -194
  27. package/src/js/core/errors.js +13 -15
  28. package/src/js/core/galois-field.js +119 -133
  29. package/src/js/core/index.js +2 -19
  30. package/src/js/core/reed-solomon.js +195 -204
  31. package/src/js/databar/codec.js +93 -108
  32. package/src/js/databar/decoder.js +149 -140
  33. package/src/js/databar/encoder.js +47 -55
  34. package/src/js/databar/gs1.js +122 -123
  35. package/src/js/databar/index.js +4 -36
  36. package/src/js/databar/patterns.js +81 -85
  37. package/src/js/databar/tables.js +51 -52
  38. package/src/js/datamatrix/decoder.js +216 -182
  39. package/src/js/datamatrix/detector.js +161 -132
  40. package/src/js/datamatrix/encoder.js +192 -133
  41. package/src/js/datamatrix/index.js +1 -9
  42. package/src/js/datamatrix/tables.js +68 -69
  43. package/src/js/frameqr/decoder.js +102 -125
  44. package/src/js/frameqr/detector.js +101 -94
  45. package/src/js/frameqr/encoder.js +67 -80
  46. package/src/js/frameqr/index.js +1 -10
  47. package/src/js/frameqr/tables.js +145 -163
  48. package/src/js/image/binarizer.js +146 -162
  49. package/src/js/image/grid-sampler.js +63 -79
  50. package/src/js/image/index.js +0 -2
  51. package/src/js/image/luminance.js +129 -140
  52. package/src/js/image/perspective.js +112 -149
  53. package/src/js/micropdf417/compaction.js +64 -65
  54. package/src/js/micropdf417/decoder.js +106 -109
  55. package/src/js/micropdf417/detector.js +73 -70
  56. package/src/js/micropdf417/encoder.js +129 -154
  57. package/src/js/micropdf417/error-correction.js +6 -11
  58. package/src/js/micropdf417/index.js +2 -16
  59. package/src/js/micropdf417/tables.js +112 -99
  60. package/src/js/microqr/decoder.js +177 -182
  61. package/src/js/microqr/detector.js +240 -256
  62. package/src/js/microqr/encoder.js +239 -194
  63. package/src/js/microqr/index.js +0 -2
  64. package/src/js/microqr/tables.js +208 -200
  65. package/src/js/oned/addons.js +167 -191
  66. package/src/js/oned/index.js +27 -58
  67. package/src/js/oned/patterns.js +194 -213
  68. package/src/js/oned/reader.js +1003 -984
  69. package/src/js/oned/writers.js +411 -466
  70. package/src/js/pdf417/compaction.js +315 -222
  71. package/src/js/pdf417/decoder.js +71 -36
  72. package/src/js/pdf417/detector.js +377 -361
  73. package/src/js/pdf417/encoder.js +57 -46
  74. package/src/js/pdf417/error-correction.js +5 -8
  75. package/src/js/pdf417/tables.js +248 -250
  76. package/src/js/qr/decoder.js +311 -347
  77. package/src/js/qr/detector.js +378 -395
  78. package/src/js/qr/encoder.js +514 -599
  79. package/src/js/qr/index.js +0 -2
  80. package/src/js/qr/tables.js +375 -422
  81. package/src/js/render/image-data.js +57 -62
  82. package/src/js/render/index.js +21 -23
  83. package/src/js/render/options.js +82 -90
  84. package/src/js/render/png.js +144 -169
  85. package/src/js/render/svg.js +41 -44
  86. package/src/js/render/webgl.js +103 -109
  87. package/src/js/render/webgpu.js +205 -217
  88. package/src/js/rmqr/decoder.js +161 -53
  89. package/src/js/rmqr/detector.js +73 -41
  90. package/src/js/rmqr/encoder.js +253 -104
  91. package/src/js/rmqr/index.js +1 -5
  92. package/src/js/rmqr/tables.js +108 -85
  93. package/src/ts/aztec/decoder.ts +317 -0
  94. package/src/ts/aztec/detector.ts +224 -0
  95. package/src/ts/aztec/encoder.ts +257 -0
  96. package/src/ts/aztec/high-level.ts +211 -0
  97. package/src/ts/aztec/index.ts +45 -0
  98. package/src/ts/aztec/tables.ts +210 -0
  99. package/src/ts/aztecrune/decoder.ts +155 -0
  100. package/src/ts/aztecrune/detector.ts +166 -0
  101. package/src/ts/aztecrune/encoder.ts +121 -0
  102. package/src/ts/aztecrune/index.ts +49 -0
  103. package/src/ts/aztecrune/tables.ts +137 -0
  104. package/src/ts/compactpdf417/decoder.ts +128 -0
  105. package/src/ts/compactpdf417/detector.ts +139 -0
  106. package/src/ts/compactpdf417/encoder.ts +140 -0
  107. package/src/ts/compactpdf417/index.ts +17 -0
  108. package/src/ts/compactpdf417/tables.ts +173 -0
  109. package/src/ts/core/bit-buffer.ts +174 -0
  110. package/src/ts/core/bit-matrix.ts +241 -0
  111. package/src/ts/core/errors.ts +61 -0
  112. package/src/ts/core/galois-field.ts +207 -0
  113. package/src/ts/core/index.ts +56 -0
  114. package/src/ts/core/reed-solomon.ts +327 -0
  115. package/src/ts/databar/codec.ts +181 -0
  116. package/src/ts/databar/decoder.ts +215 -0
  117. package/src/ts/databar/encoder.ts +96 -0
  118. package/src/ts/databar/gs1.ts +177 -0
  119. package/src/ts/databar/index.ts +68 -0
  120. package/src/ts/databar/patterns.ts +143 -0
  121. package/src/ts/databar/tables.ts +124 -0
  122. package/src/ts/datamatrix/decoder.ts +262 -0
  123. package/src/ts/datamatrix/detector.ts +225 -0
  124. package/src/ts/datamatrix/encoder.ts +191 -0
  125. package/src/ts/datamatrix/index.ts +42 -0
  126. package/src/ts/datamatrix/tables.ts +123 -0
  127. package/src/ts/frameqr/decoder.ts +239 -0
  128. package/src/ts/frameqr/detector.ts +192 -0
  129. package/src/ts/frameqr/encoder.ts +156 -0
  130. package/src/ts/frameqr/index.ts +42 -0
  131. package/src/ts/frameqr/tables.ts +270 -0
  132. package/src/ts/image/binarizer.ts +270 -0
  133. package/src/ts/image/grid-sampler.ts +164 -0
  134. package/src/ts/image/index.ts +40 -0
  135. package/src/ts/image/luminance.ts +196 -0
  136. package/src/ts/image/perspective.ts +195 -0
  137. package/src/ts/index.d.ts +1 -1
  138. package/src/ts/index.ts +790 -0
  139. package/src/ts/micropdf417/compaction.ts +116 -0
  140. package/src/ts/micropdf417/decoder.ts +183 -0
  141. package/src/ts/micropdf417/detector.ts +149 -0
  142. package/src/ts/micropdf417/encoder.ts +209 -0
  143. package/src/ts/micropdf417/error-correction.ts +55 -0
  144. package/src/ts/micropdf417/index.ts +49 -0
  145. package/src/ts/micropdf417/tables.ts +184 -0
  146. package/src/ts/microqr/decoder.ts +245 -0
  147. package/src/ts/microqr/detector.ts +355 -0
  148. package/src/ts/microqr/encoder.ts +269 -0
  149. package/src/ts/microqr/index.ts +36 -0
  150. package/src/ts/microqr/tables.ts +316 -0
  151. package/src/ts/oned/addons.ts +420 -0
  152. package/src/ts/oned/index.ts +106 -0
  153. package/src/ts/oned/patterns.ts +384 -0
  154. package/src/ts/oned/reader.ts +1364 -0
  155. package/src/ts/oned/writers.ts +746 -0
  156. package/src/ts/pdf417/compaction.ts +298 -0
  157. package/src/ts/pdf417/decoder.ts +75 -0
  158. package/src/ts/pdf417/detector.ts +468 -0
  159. package/src/ts/pdf417/encoder.ts +91 -0
  160. package/src/ts/pdf417/error-correction.ts +47 -0
  161. package/src/ts/pdf417/index.ts +6 -0
  162. package/src/ts/pdf417/tables.ts +317 -0
  163. package/src/ts/qr/decoder.ts +575 -0
  164. package/src/ts/qr/detector.ts +630 -0
  165. package/src/ts/qr/encoder.ts +958 -0
  166. package/src/ts/qr/index.ts +44 -0
  167. package/src/ts/qr/tables.ts +737 -0
  168. package/src/ts/render/image-data.ts +125 -0
  169. package/src/ts/render/index.ts +130 -0
  170. package/src/ts/render/options.ts +160 -0
  171. package/src/ts/render/png.ts +295 -0
  172. package/src/ts/render/svg.ts +120 -0
  173. package/src/ts/render/webgl.ts +206 -0
  174. package/src/ts/render/webgpu.ts +369 -0
  175. package/src/ts/rmqr/decoder.ts +101 -0
  176. package/src/ts/rmqr/detector.ts +90 -0
  177. package/src/ts/rmqr/encoder.ts +172 -0
  178. package/src/ts/rmqr/index.ts +37 -0
  179. 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
- return { x: -vector.y, y: vector.x };
47
+ return { x: -vector.y, y: vector.x };
71
48
  }
72
-
73
49
  function add(point, a, av, b, bv) {
74
- return { x: point.x + a.x * av + b.x * bv, y: point.y + a.y * av + b.y * bv };
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
- const x = Math.round(point.x);
79
- const y = Math.round(point.y);
80
- if (x < 0 || y < 0 || x >= image.width || y >= image.height) return null;
81
- return image.get(x, y);
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
- return x === 0 || y === 0 || x === 6 || y === 6 ||
86
- (x >= 2 && x <= 4 && y >= 2 && y <= 4);
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
- const seen = new Uint8Array(image.width * image.height);
92
- const result = [];
93
- const maximumArea = Math.max(16, Math.floor(image.width * image.height * 0.08));
94
-
95
- for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
96
- const start = y * image.width + x;
97
- if (seen[start] || image.get(x, y) !== value) continue;
98
-
99
- const queueX = [x];
100
- const queueY = [y];
101
- seen[start] = 1;
102
- let head = 0;
103
- let minX = x; let maxX = x; let minY = y; let maxY = y;
104
-
105
- while (head < queueX.length) {
106
- const px = queueX[head];
107
- const py = queueY[head++];
108
- if (px < minX) minX = px;
109
- if (px > maxX) maxX = px;
110
- if (py < minY) minY = py;
111
- if (py > maxY) maxY = py;
112
- for (const [nx, ny] of [[px - 1, py], [px + 1, py], [px, py - 1], [px, py + 1]]) {
113
- if (nx < 0 || ny < 0 || nx >= image.width || ny >= image.height) continue;
114
- const index = ny * image.width + nx;
115
- if (!seen[index] && image.get(nx, ny) === value) {
116
- seen[index] = 1;
117
- queueX.push(nx);
118
- queueY.push(ny);
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
- let correct = 0;
144
- let total = 0;
145
- for (let y = 0; y < 7; y++) for (let x = 0; x < 7; x++) {
146
- const actual = sample(image, add(centre, u, (x - 3) * pitch, v, (y - 3) * pitch));
147
- if (actual === null) continue;
148
- const wanted = inverted ? !expectedFinder(x, y) : expectedFinder(x, y);
149
- if (actual === wanted) correct++;
150
- total++;
151
- }
152
- return total === 49 ? correct / total : 0;
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
- let correct = 0;
158
- let total = 0;
159
- const check = (x, y, dark) => {
160
- const point = add(centre, u, (x - 3) * pitch * sx, v, (y - 3) * pitch * sy);
161
- const actual = sample(image, point);
162
- if (actual !== null && actual === (inverted ? !dark : dark)) correct++;
163
- total++;
164
- };
165
-
166
- // The light separator lies between the finder and encoding region.
167
- for (let i = 0; i <= 7; i++) {
168
- check(7, i, false);
169
- check(i, 7, false);
170
- }
171
- // Both timing arms start dark at coordinate 8 and alternate to the edge.
172
- for (let i = 8; i < dimension; i++) {
173
- check(i, 0, (i & 1) === 0);
174
- check(0, i, (i & 1) === 0);
175
- }
176
- // A quiet-zone sample just beyond each edge rejects an isolated normal-QR
177
- // finder and most decorative squares without requiring a perfect crop.
178
- for (let i = 0; i < dimension; i += 2) {
179
- check(i, -1.25, false);
180
- check(-1.25, i, false);
181
- check(i, dimension + 0.75, false);
182
- check(dimension + 0.75, i, false);
183
- }
184
- return correct / total;
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
- const out = matrix.clone();
189
- for (let y = 0; y < out.height; y++) for (let x = 0; x < out.width; x++) out.flip(x, y);
190
- return out;
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
- const degrees = Math.atan2(u.y, u.x) * 180 / Math.PI;
195
- return ((Math.round(degrees / 90) * 90) % 360 + 360) % 360;
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
- const tl = add(centre, u, -3.5 * pitch, v, -3.5 * pitch);
200
- const tr = add(tl, u, dimension * pitch * sx, v, 0);
201
- const bl = add(tl, u, 0, v, dimension * pitch * sy);
202
- const br = add(add(tr, v, dimension * pitch * sy, u, 0), u, dx * pitch, v, dy * pitch);
203
- return [tl, tr, br, bl];
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
- const centre = detection.finderCentre;
208
- return `${Math.round(centre.x)},${Math.round(centre.y)},${detection.dimension}`;
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
- if (left.dimension !== right.dimension) return false;
213
- const centre = (detection) => detection.corners.reduce(
214
- (sum, point) => ({ x: sum.x + point.x / 4, y: sum.y + point.y / 4 }),
215
- { x: 0, y: 0 },
216
- );
217
- const a = centre(left);
218
- const b = centre(right);
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
- if (!binaryImage || !binaryImage.width || !binaryImage.height) {
235
- throw new NotFoundError('detectMicroQR: no image supplied');
236
- }
237
-
238
- const detections = [];
239
- const seen = new Set();
240
-
241
- for (const inverted of [false, true]) {
242
- for (const centre of components(binaryImage, !inverted)) {
243
- for (let degrees = 0; degrees < 180; degrees += 3) {
244
- const angle = degrees * Math.PI / 180;
245
- const axis = { x: Math.cos(angle), y: Math.sin(angle) };
246
- const perpendicular = rotateVector(axis);
247
- const footprint = Math.abs(axis.x) + Math.abs(axis.y);
248
- const pitch = ((centre.width + centre.height) / 2) / (3 * footprint);
249
- if (pitch < 0.75) continue;
250
-
251
- // The finder is rotationally symmetric; four turns decide which pair
252
- // of arms points into the encoding region.
253
- for (let turn = 0, u = axis, v = perpendicular; turn < 4; turn++) {
254
- if (turn > 0) { u = v; v = { x: -u.y, y: u.x }; }
255
- const fScore = finderScore(binaryImage, centre, u, v, pitch, inverted);
256
- if (fScore < 0.9) continue;
257
-
258
- for (const dimension of DIMENSIONS) {
259
- const scales = [0.84, 0.92, 1, 1.08, 1.16];
260
- const rankedX = scales.map((scale) => ({
261
- scale,
262
- score: structureScore(binaryImage, centre, u, v, pitch, dimension, scale, 1, inverted),
263
- })).sort((a, b) => b.score - a.score).slice(0, 2);
264
- const rankedY = scales.map((scale) => ({
265
- scale,
266
- score: structureScore(binaryImage, centre, u, v, pitch, dimension, 1, scale, inverted),
267
- })).sort((a, b) => b.score - a.score).slice(0, 2);
268
-
269
- for (const xs of rankedX) for (const ys of rankedY) {
270
- const score = structureScore(binaryImage, centre, u, v, pitch, dimension, xs.scale, ys.scale, inverted);
271
- if (score < 0.78) continue;
272
-
273
- // With a single finder there is no direct bottom-right anchor.
274
- // A compact search around the affine estimate lets the projective
275
- // sampler account for convergence of the remote edges.
276
- for (const delta of [[0, 0], [-0.75, 0], [0.75, 0], [0, -0.75], [0, 0.75],
277
- [-0.75, -0.75], [0.75, -0.75], [-0.75, 0.75], [0.75, 0.75]]) {
278
- const corners = cornersFor(
279
- centre, u, v, pitch, dimension, xs.scale, ys.scale, delta[0], delta[1]
280
- );
281
- let matrix;
282
- try { matrix = sampleQuad(binaryImage, dimension, corners, score < 0.9); }
283
- catch (error) { continue; }
284
- if (inverted) matrix = invert(matrix);
285
-
286
- try {
287
- const decoded = decodeMicroQR(matrix);
288
- const version = decoded.version ?? `M${(dimension - 9) / 2}`;
289
- const detection = {
290
- corners,
291
- dimension,
292
- version,
293
- moduleSize: pitch,
294
- rotation: orientationDegrees(u),
295
- inverted,
296
- matrix,
297
- finderCentre: { x: centre.x, y: centre.y },
298
- score: fScore + score,
299
- };
300
- const key = candidateKey(detection);
301
- if (!seen.has(key) && !detections.some((entry) => sameCandidate(entry, detection))) {
302
- seen.add(key);
303
- detections.push(detection);
304
- }
305
- // Decoder validation settled this dimension and orientation.
306
- break;
307
- } catch (error) {
308
- /* Try the next perspective hypothesis. */
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
- detections.sort((a, b) => b.score - a.score || b.moduleSize - a.moduleSize);
319
- for (const detection of detections) {
320
- delete detection.finderCentre;
321
- delete detection.score;
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
- let detections;
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
- const decoded = decodeMicroQR(detection.matrix);
342
- const key = `${decoded.version ?? detection.version}|${decoded.text ?? ''}`;
343
- if (seen.has(key)) continue;
344
- seen.add(key);
345
- results.push(Object.assign({
346
- corners: detection.corners,
347
- rotation: detection.rotation,
348
- inverted: detection.inverted,
349
- }, decoded));
350
- } catch (error) {
351
- /* A failed candidate is a normal no-symbol result. */
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
  }