scanic 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/scanic.js +1 -2860
- package/dist/scanic.umd.cjs +1 -1
- package/package.json +20 -5
- package/src/scanic.d.ts +30 -0
package/dist/scanic.js
CHANGED
|
@@ -1,2860 +1 @@
|
|
|
1
|
-
const DEFAULTS = {
|
|
2
|
-
// Contour detection params
|
|
3
|
-
MIN_CONTOUR_AREA: 1e3,
|
|
4
|
-
MIN_CONTOUR_POINTS: 10
|
|
5
|
-
};
|
|
6
|
-
const RETR_EXTERNAL = 0;
|
|
7
|
-
const RETR_LIST = 1;
|
|
8
|
-
const CHAIN_APPROX_SIMPLE = 2;
|
|
9
|
-
function detectDocumentContour(edges, options = {}) {
|
|
10
|
-
const width = options.width || Math.sqrt(edges.length);
|
|
11
|
-
const height = options.height || edges.length / width;
|
|
12
|
-
const mode = options.mode !== void 0 ? options.mode : RETR_LIST;
|
|
13
|
-
const method = options.method !== void 0 ? options.method : CHAIN_APPROX_SIMPLE;
|
|
14
|
-
const minArea = options.minArea || DEFAULTS.MIN_CONTOUR_AREA;
|
|
15
|
-
const paddedWidth = width + 2;
|
|
16
|
-
const paddedHeight = height + 2;
|
|
17
|
-
const labels = new Int32Array(paddedWidth * paddedHeight);
|
|
18
|
-
for (let y = 0; y < height; y++) {
|
|
19
|
-
for (let x = 0; x < width; x++) {
|
|
20
|
-
if (edges[y * width + x] > 0) {
|
|
21
|
-
labels[(y + 1) * paddedWidth + (x + 1)] = 1;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const contours = [];
|
|
26
|
-
let nextContourId = 2;
|
|
27
|
-
for (let y = 1; y <= height; y++) {
|
|
28
|
-
for (let x = 1; x <= width; x++) {
|
|
29
|
-
const currentPixelLabel = labels[y * paddedWidth + x];
|
|
30
|
-
const leftPixelLabel = labels[y * paddedWidth + (x - 1)];
|
|
31
|
-
let startPoint = null;
|
|
32
|
-
let isOuter = false;
|
|
33
|
-
let initialDirection = -1;
|
|
34
|
-
if (currentPixelLabel === 1 && leftPixelLabel === 0) {
|
|
35
|
-
isOuter = true;
|
|
36
|
-
startPoint = { x, y };
|
|
37
|
-
initialDirection = 2;
|
|
38
|
-
} else if (currentPixelLabel === 0 && leftPixelLabel >= 1 && leftPixelLabel !== -1) {
|
|
39
|
-
if (leftPixelLabel === 1) {
|
|
40
|
-
isOuter = false;
|
|
41
|
-
startPoint = { x: x - 1, y };
|
|
42
|
-
initialDirection = 6;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (startPoint) {
|
|
46
|
-
if (mode === RETR_EXTERNAL && !isOuter) {
|
|
47
|
-
labels[startPoint.y * paddedWidth + startPoint.x] = -1;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const contourId = nextContourId++;
|
|
51
|
-
const points = traceContour(labels, paddedWidth, paddedHeight, startPoint, initialDirection, contourId);
|
|
52
|
-
if (points && points.length > 0) {
|
|
53
|
-
let finalPoints = points;
|
|
54
|
-
if (method === CHAIN_APPROX_SIMPLE) {
|
|
55
|
-
finalPoints = simplifyChainApproxSimple(points);
|
|
56
|
-
}
|
|
57
|
-
const adjustedPoints = finalPoints.map((p) => ({ x: p.x - 1, y: p.y - 1 }));
|
|
58
|
-
if (adjustedPoints.length >= (method === CHAIN_APPROX_SIMPLE ? 4 : DEFAULTS.MIN_CONTOUR_POINTS)) {
|
|
59
|
-
const contour = {
|
|
60
|
-
id: contourId,
|
|
61
|
-
points: adjustedPoints,
|
|
62
|
-
isOuter
|
|
63
|
-
// Calculate area and bounding box later if needed for filtering/sorting
|
|
64
|
-
};
|
|
65
|
-
contours.push(contour);
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
if (labels[startPoint.y * paddedWidth + startPoint.x] === 1) {
|
|
69
|
-
labels[startPoint.y * paddedWidth + startPoint.x] = contourId;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
contours.forEach((contour) => {
|
|
76
|
-
contour.area = calculateContourArea(contour.points);
|
|
77
|
-
contour.boundingBox = calculateBoundingBox(contour.points);
|
|
78
|
-
});
|
|
79
|
-
const filteredContours = contours.filter((contour) => contour.area >= minArea);
|
|
80
|
-
filteredContours.sort((a, b) => b.area - a.area);
|
|
81
|
-
if (options.debug) {
|
|
82
|
-
options.debug.labels = labels;
|
|
83
|
-
options.debug.rawContours = contours;
|
|
84
|
-
options.debug.finalContours = filteredContours;
|
|
85
|
-
}
|
|
86
|
-
return filteredContours;
|
|
87
|
-
}
|
|
88
|
-
function traceContour(labels, width, height, startPoint, initialDirection, contourId) {
|
|
89
|
-
const points = [];
|
|
90
|
-
const visited = /* @__PURE__ */ new Set();
|
|
91
|
-
let currentX = startPoint.x;
|
|
92
|
-
let currentY = startPoint.y;
|
|
93
|
-
const startX = currentX;
|
|
94
|
-
const startY = currentY;
|
|
95
|
-
let prevDirection = -1;
|
|
96
|
-
labels[startY * width + startX] = contourId;
|
|
97
|
-
let count = 0;
|
|
98
|
-
const maxSteps = width * height;
|
|
99
|
-
const dx = [0, 1, 1, 1, 0, -1, -1, -1];
|
|
100
|
-
const dy = [-1, -1, 0, 1, 1, 1, 0, -1];
|
|
101
|
-
while (count++ < maxSteps) {
|
|
102
|
-
let searchDirection;
|
|
103
|
-
if (prevDirection === -1) {
|
|
104
|
-
let found = false;
|
|
105
|
-
for (let i = 0; i < 8; i++) {
|
|
106
|
-
searchDirection = initialDirection + i & 7;
|
|
107
|
-
const nextX2 = currentX + dx[searchDirection];
|
|
108
|
-
const nextY2 = currentY + dy[searchDirection];
|
|
109
|
-
if (nextX2 >= 0 && nextX2 < width && nextY2 >= 0 && nextY2 < height && labels[nextY2 * width + nextX2] > 0) {
|
|
110
|
-
found = true;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (!found) return null;
|
|
115
|
-
} else {
|
|
116
|
-
searchDirection = prevDirection + 2 & 7;
|
|
117
|
-
}
|
|
118
|
-
let nextX = -1;
|
|
119
|
-
let nextY = -1;
|
|
120
|
-
for (let i = 0; i < 8; i++) {
|
|
121
|
-
const checkDirection = searchDirection + i & 7;
|
|
122
|
-
const checkX = currentX + dx[checkDirection];
|
|
123
|
-
const checkY = currentY + dy[checkDirection];
|
|
124
|
-
if (checkX >= 0 && checkX < width && checkY >= 0 && checkY < height) {
|
|
125
|
-
if (labels[checkY * width + checkX] > 0) {
|
|
126
|
-
nextX = checkX;
|
|
127
|
-
nextY = checkY;
|
|
128
|
-
prevDirection = checkDirection + 4 & 7;
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (nextX === -1) {
|
|
134
|
-
if (points.length === 0) {
|
|
135
|
-
points.push({ x: currentX, y: currentY });
|
|
136
|
-
}
|
|
137
|
-
console.warn(`Contour tracing stopped unexpectedly at (${currentX - 1}, ${currentY - 1}) for contour ${contourId}`);
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
const visitedKey = currentY * width + currentX;
|
|
141
|
-
if (visited.has(visitedKey)) {
|
|
142
|
-
return points;
|
|
143
|
-
}
|
|
144
|
-
points.push({ x: currentX, y: currentY });
|
|
145
|
-
visited.add(visitedKey);
|
|
146
|
-
const nextIdx = nextY * width + nextX;
|
|
147
|
-
if (labels[nextIdx] === 1) {
|
|
148
|
-
labels[nextIdx] = contourId;
|
|
149
|
-
}
|
|
150
|
-
currentX = nextX;
|
|
151
|
-
currentY = nextY;
|
|
152
|
-
if (currentX === startX && currentY === startY) {
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (count >= maxSteps) {
|
|
157
|
-
console.warn(`Contour tracing exceeded max steps for contour ${contourId}`);
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
return points;
|
|
161
|
-
}
|
|
162
|
-
function simplifyChainApproxSimple(points) {
|
|
163
|
-
const n = points.length;
|
|
164
|
-
if (n <= 2) {
|
|
165
|
-
return points;
|
|
166
|
-
}
|
|
167
|
-
const simplifiedPoints = [];
|
|
168
|
-
const lastPoint = points[n - 1];
|
|
169
|
-
const firstPoint = points[0];
|
|
170
|
-
let prevPoint = lastPoint;
|
|
171
|
-
let currentPoint = firstPoint;
|
|
172
|
-
let nextPoint = points[1];
|
|
173
|
-
let dx1 = currentPoint.x - prevPoint.x;
|
|
174
|
-
let dy1 = currentPoint.y - prevPoint.y;
|
|
175
|
-
let dx2 = nextPoint.x - currentPoint.x;
|
|
176
|
-
let dy2 = nextPoint.y - currentPoint.y;
|
|
177
|
-
if (dx1 * dy2 !== dy1 * dx2) {
|
|
178
|
-
simplifiedPoints.push(currentPoint);
|
|
179
|
-
}
|
|
180
|
-
for (let i = 1; i < n - 1; i++) {
|
|
181
|
-
prevPoint = points[i - 1];
|
|
182
|
-
currentPoint = points[i];
|
|
183
|
-
nextPoint = points[i + 1];
|
|
184
|
-
dx1 = currentPoint.x - prevPoint.x;
|
|
185
|
-
dy1 = currentPoint.y - prevPoint.y;
|
|
186
|
-
dx2 = nextPoint.x - currentPoint.x;
|
|
187
|
-
dy2 = nextPoint.y - currentPoint.y;
|
|
188
|
-
if (dx1 * dy2 !== dy1 * dx2) {
|
|
189
|
-
simplifiedPoints.push(currentPoint);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
prevPoint = points[n - 2];
|
|
193
|
-
currentPoint = lastPoint;
|
|
194
|
-
nextPoint = firstPoint;
|
|
195
|
-
dx1 = currentPoint.x - prevPoint.x;
|
|
196
|
-
dy1 = currentPoint.y - prevPoint.y;
|
|
197
|
-
dx2 = nextPoint.x - currentPoint.x;
|
|
198
|
-
dy2 = nextPoint.y - currentPoint.y;
|
|
199
|
-
if (dx1 * dy2 !== dy1 * dx2) {
|
|
200
|
-
simplifiedPoints.push(currentPoint);
|
|
201
|
-
}
|
|
202
|
-
if (simplifiedPoints.length === 0) {
|
|
203
|
-
if (n === 1) return [points[0]];
|
|
204
|
-
if (n === 2) return points;
|
|
205
|
-
let maxDistSq = 0;
|
|
206
|
-
let farthestIdx = 1;
|
|
207
|
-
const p0x = firstPoint.x;
|
|
208
|
-
const p0y = firstPoint.y;
|
|
209
|
-
for (let i = 1; i < n; i++) {
|
|
210
|
-
const pi = points[i];
|
|
211
|
-
const dx = pi.x - p0x;
|
|
212
|
-
const dy = pi.y - p0y;
|
|
213
|
-
const distSq = dx * dx + dy * dy;
|
|
214
|
-
if (distSq > maxDistSq) {
|
|
215
|
-
maxDistSq = distSq;
|
|
216
|
-
farthestIdx = i;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return [firstPoint, points[farthestIdx]];
|
|
220
|
-
}
|
|
221
|
-
return simplifiedPoints;
|
|
222
|
-
}
|
|
223
|
-
function calculateContourArea(points) {
|
|
224
|
-
let area = 0;
|
|
225
|
-
const n = points.length;
|
|
226
|
-
if (n < 3) return 0;
|
|
227
|
-
for (let i = 0; i < n; i++) {
|
|
228
|
-
const j = (i + 1) % n;
|
|
229
|
-
area += points[i].x * points[j].y;
|
|
230
|
-
area -= points[j].x * points[i].y;
|
|
231
|
-
}
|
|
232
|
-
return Math.abs(area) / 2;
|
|
233
|
-
}
|
|
234
|
-
function calculateBoundingBox(points) {
|
|
235
|
-
if (points.length === 0) {
|
|
236
|
-
return { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
237
|
-
}
|
|
238
|
-
let minX = points[0].x;
|
|
239
|
-
let minY = points[0].y;
|
|
240
|
-
let maxX = points[0].x;
|
|
241
|
-
let maxY = points[0].y;
|
|
242
|
-
for (let i = 1; i < points.length; i++) {
|
|
243
|
-
const point = points[i];
|
|
244
|
-
minX = Math.min(minX, point.x);
|
|
245
|
-
minY = Math.min(minY, point.y);
|
|
246
|
-
maxX = Math.max(maxX, point.x);
|
|
247
|
-
maxY = Math.max(maxY, point.y);
|
|
248
|
-
}
|
|
249
|
-
return { minX, minY, maxX, maxY };
|
|
250
|
-
}
|
|
251
|
-
function simplifyContour(points, epsilon = 1) {
|
|
252
|
-
if (points.length <= 2) {
|
|
253
|
-
return points;
|
|
254
|
-
}
|
|
255
|
-
let maxDistance = 0;
|
|
256
|
-
let index = 0;
|
|
257
|
-
const firstPoint = points[0];
|
|
258
|
-
const lastPoint = points[points.length - 1];
|
|
259
|
-
for (let i = 1; i < points.length - 1; i++) {
|
|
260
|
-
const distance2 = perpendicularDistance(points[i], firstPoint, lastPoint);
|
|
261
|
-
if (distance2 > maxDistance) {
|
|
262
|
-
maxDistance = distance2;
|
|
263
|
-
index = i;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (maxDistance > epsilon) {
|
|
267
|
-
const firstSegment = simplifyContour(points.slice(0, index + 1), epsilon);
|
|
268
|
-
const secondSegment = simplifyContour(points.slice(index), epsilon);
|
|
269
|
-
return firstSegment.slice(0, -1).concat(secondSegment);
|
|
270
|
-
} else {
|
|
271
|
-
return [firstPoint, lastPoint];
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function perpendicularDistance(point, lineStart, lineEnd) {
|
|
275
|
-
const dx = lineEnd.x - lineStart.x;
|
|
276
|
-
const dy = lineEnd.y - lineStart.y;
|
|
277
|
-
const lineLengthSq = dx * dx + dy * dy;
|
|
278
|
-
if (lineLengthSq === 0) {
|
|
279
|
-
return Math.sqrt(
|
|
280
|
-
Math.pow(point.x - lineStart.x, 2) + Math.pow(point.y - lineStart.y, 2)
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
const t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / lineLengthSq;
|
|
284
|
-
let closestPointX, closestPointY;
|
|
285
|
-
if (t < 0) {
|
|
286
|
-
closestPointX = lineStart.x;
|
|
287
|
-
closestPointY = lineStart.y;
|
|
288
|
-
} else if (t > 1) {
|
|
289
|
-
closestPointX = lineEnd.x;
|
|
290
|
-
closestPointY = lineEnd.y;
|
|
291
|
-
} else {
|
|
292
|
-
closestPointX = lineStart.x + t * dx;
|
|
293
|
-
closestPointY = lineStart.y + t * dy;
|
|
294
|
-
}
|
|
295
|
-
const distDx = point.x - closestPointX;
|
|
296
|
-
const distDy = point.y - closestPointY;
|
|
297
|
-
return Math.sqrt(distDx * distDx + distDy * distDy);
|
|
298
|
-
}
|
|
299
|
-
function approximatePolygon(contourPoints, epsilon = 0.02) {
|
|
300
|
-
const perimeter = calculateContourPerimeter(contourPoints);
|
|
301
|
-
const actualEpsilon = epsilon * perimeter;
|
|
302
|
-
const simplifiedPoints = simplifyContour(contourPoints, actualEpsilon);
|
|
303
|
-
return simplifiedPoints;
|
|
304
|
-
}
|
|
305
|
-
function calculateContourPerimeter(points) {
|
|
306
|
-
let perimeter = 0;
|
|
307
|
-
const n = points.length;
|
|
308
|
-
if (n < 2) return 0;
|
|
309
|
-
for (let i = 0; i < n; i++) {
|
|
310
|
-
const j = (i + 1) % n;
|
|
311
|
-
const dx = points[i].x - points[j].x;
|
|
312
|
-
const dy = points[i].y - points[j].y;
|
|
313
|
-
perimeter += Math.sqrt(dx * dx + dy * dy);
|
|
314
|
-
}
|
|
315
|
-
return perimeter;
|
|
316
|
-
}
|
|
317
|
-
function distance(p1, p2) {
|
|
318
|
-
return Math.hypot(p2.x - p1.x, p2.y - p1.y);
|
|
319
|
-
}
|
|
320
|
-
function findCenter(points) {
|
|
321
|
-
let sumX = 0;
|
|
322
|
-
let sumY = 0;
|
|
323
|
-
for (const point of points) {
|
|
324
|
-
sumX += point.x;
|
|
325
|
-
sumY += point.y;
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
x: sumX / points.length,
|
|
329
|
-
y: sumY / points.length
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
function findCornerPoints(contour, options = {}) {
|
|
333
|
-
if (!contour || !contour.points || contour.points.length < 4) {
|
|
334
|
-
console.warn("Contour does not have enough points for corner detection");
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
const epsilon = options.epsilon || 0.02;
|
|
338
|
-
const approximation = findBestQuadrilateralApproximation(contour.points, epsilon);
|
|
339
|
-
let corners;
|
|
340
|
-
if (approximation && approximation.length === 4) {
|
|
341
|
-
corners = orderCornerPoints(approximation);
|
|
342
|
-
} else {
|
|
343
|
-
corners = findCornersByQuadrants(contour.points);
|
|
344
|
-
if (!corners || !corners.topLeft || !corners.topRight || !corners.bottomRight || !corners.bottomLeft) {
|
|
345
|
-
corners = findCornersByCoordinateExtremes(contour.points);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (!corners || !corners.topLeft || !corners.topRight || !corners.bottomRight || !corners.bottomLeft) {
|
|
349
|
-
console.warn("Failed to find all four corners.", corners);
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
return corners;
|
|
353
|
-
}
|
|
354
|
-
function findBestQuadrilateralApproximation(points, baseEpsilon) {
|
|
355
|
-
if (!points || points.length < 4) return null;
|
|
356
|
-
const epsilonCandidates = [
|
|
357
|
-
baseEpsilon,
|
|
358
|
-
baseEpsilon * 1.3,
|
|
359
|
-
baseEpsilon * 1.6,
|
|
360
|
-
baseEpsilon * 2
|
|
361
|
-
];
|
|
362
|
-
const seen = /* @__PURE__ */ new Set();
|
|
363
|
-
for (const candidate of epsilonCandidates) {
|
|
364
|
-
const epsilon = Math.min(0.12, candidate);
|
|
365
|
-
const key = epsilon.toFixed(4);
|
|
366
|
-
if (seen.has(key)) continue;
|
|
367
|
-
seen.add(key);
|
|
368
|
-
const approximation = approximatePolygon(points, epsilon);
|
|
369
|
-
const quad = reduceApproximationToQuadrilateral(approximation);
|
|
370
|
-
if (quad && quad.length === 4) {
|
|
371
|
-
return quad;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return null;
|
|
375
|
-
}
|
|
376
|
-
function reduceApproximationToQuadrilateral(approximation) {
|
|
377
|
-
if (!approximation || approximation.length < 4) return null;
|
|
378
|
-
if (approximation.length === 4) return approximation;
|
|
379
|
-
if (approximation.length > 8) return null;
|
|
380
|
-
let points = removeNearDuplicateVertices(approximation, 6);
|
|
381
|
-
if (points.length === 4) return points;
|
|
382
|
-
if (points.length < 4) return null;
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
function removeNearDuplicateVertices(points, tolerance) {
|
|
386
|
-
if (!points || points.length === 0) return [];
|
|
387
|
-
const filtered = [];
|
|
388
|
-
for (const point of points) {
|
|
389
|
-
if (filtered.length === 0 || distance(point, filtered[filtered.length - 1]) > tolerance) {
|
|
390
|
-
filtered.push(point);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
if (filtered.length > 2 && distance(filtered[0], filtered[filtered.length - 1]) <= tolerance) {
|
|
394
|
-
filtered.pop();
|
|
395
|
-
}
|
|
396
|
-
return filtered;
|
|
397
|
-
}
|
|
398
|
-
function findCornersByQuadrants(points) {
|
|
399
|
-
if (!points || points.length < 4) return null;
|
|
400
|
-
const center = findCenter(points);
|
|
401
|
-
let topLeft = null;
|
|
402
|
-
let topRight = null;
|
|
403
|
-
let bottomRight = null;
|
|
404
|
-
let bottomLeft = null;
|
|
405
|
-
let topLeftDist = 0;
|
|
406
|
-
let topRightDist = 0;
|
|
407
|
-
let bottomRightDist = 0;
|
|
408
|
-
let bottomLeftDist = 0;
|
|
409
|
-
for (const point of points) {
|
|
410
|
-
const dist = distance(point, center);
|
|
411
|
-
if (point.x < center.x && point.y < center.y) {
|
|
412
|
-
if (dist > topLeftDist) {
|
|
413
|
-
topLeft = point;
|
|
414
|
-
topLeftDist = dist;
|
|
415
|
-
}
|
|
416
|
-
} else if (point.x > center.x && point.y < center.y) {
|
|
417
|
-
if (dist > topRightDist) {
|
|
418
|
-
topRight = point;
|
|
419
|
-
topRightDist = dist;
|
|
420
|
-
}
|
|
421
|
-
} else if (point.x > center.x && point.y > center.y) {
|
|
422
|
-
if (dist > bottomRightDist) {
|
|
423
|
-
bottomRight = point;
|
|
424
|
-
bottomRightDist = dist;
|
|
425
|
-
}
|
|
426
|
-
} else if (point.x < center.x && point.y > center.y) {
|
|
427
|
-
if (dist > bottomLeftDist) {
|
|
428
|
-
bottomLeft = point;
|
|
429
|
-
bottomLeftDist = dist;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (!topLeft || !topRight || !bottomRight || !bottomLeft) {
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
return {
|
|
437
|
-
topLeft,
|
|
438
|
-
topRight,
|
|
439
|
-
bottomRight,
|
|
440
|
-
bottomLeft
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function findCornersByCoordinateExtremes(points) {
|
|
444
|
-
if (!points || points.length === 0) return null;
|
|
445
|
-
let topLeft = points[0];
|
|
446
|
-
let topRight = points[0];
|
|
447
|
-
let bottomRight = points[0];
|
|
448
|
-
let bottomLeft = points[0];
|
|
449
|
-
let minSum = topLeft.x + topLeft.y;
|
|
450
|
-
let maxDiff = topRight.x - topRight.y;
|
|
451
|
-
let maxSum = bottomRight.x + bottomRight.y;
|
|
452
|
-
let minDiff = bottomLeft.x - bottomLeft.y;
|
|
453
|
-
for (let i = 1; i < points.length; i++) {
|
|
454
|
-
const point = points[i];
|
|
455
|
-
const sum = point.x + point.y;
|
|
456
|
-
const diff = point.x - point.y;
|
|
457
|
-
if (sum < minSum) {
|
|
458
|
-
minSum = sum;
|
|
459
|
-
topLeft = point;
|
|
460
|
-
}
|
|
461
|
-
if (sum > maxSum) {
|
|
462
|
-
maxSum = sum;
|
|
463
|
-
bottomRight = point;
|
|
464
|
-
}
|
|
465
|
-
if (diff > maxDiff) {
|
|
466
|
-
maxDiff = diff;
|
|
467
|
-
topRight = point;
|
|
468
|
-
}
|
|
469
|
-
if (diff < minDiff) {
|
|
470
|
-
minDiff = diff;
|
|
471
|
-
bottomLeft = point;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return {
|
|
475
|
-
topLeft,
|
|
476
|
-
topRight,
|
|
477
|
-
bottomRight,
|
|
478
|
-
bottomLeft
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
function orderCornerPoints(points) {
|
|
482
|
-
if (points.length !== 4) {
|
|
483
|
-
console.warn(`Expected 4 points, got ${points.length}`);
|
|
484
|
-
return null;
|
|
485
|
-
}
|
|
486
|
-
const center = findCenter(points);
|
|
487
|
-
const sortedPoints = [...points].sort((a, b) => {
|
|
488
|
-
const angleA = Math.atan2(a.y - center.y, a.x - center.x);
|
|
489
|
-
const angleB = Math.atan2(b.y - center.y, b.x - center.x);
|
|
490
|
-
return angleA - angleB;
|
|
491
|
-
});
|
|
492
|
-
let minSum = Infinity;
|
|
493
|
-
let minIndex = 0;
|
|
494
|
-
for (let i = 0; i < 4; i++) {
|
|
495
|
-
const sum = sortedPoints[i].x + sortedPoints[i].y;
|
|
496
|
-
if (sum < minSum) {
|
|
497
|
-
minSum = sum;
|
|
498
|
-
minIndex = i;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
const orderedPoints = [
|
|
502
|
-
sortedPoints[minIndex],
|
|
503
|
-
sortedPoints[(minIndex + 1) % 4],
|
|
504
|
-
sortedPoints[(minIndex + 2) % 4],
|
|
505
|
-
sortedPoints[(minIndex + 3) % 4]
|
|
506
|
-
];
|
|
507
|
-
return {
|
|
508
|
-
topLeft: orderedPoints[0],
|
|
509
|
-
topRight: orderedPoints[1],
|
|
510
|
-
bottomRight: orderedPoints[2],
|
|
511
|
-
bottomLeft: orderedPoints[3]
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
let wasm;
|
|
515
|
-
let cachedUint8ArrayMemory0 = null;
|
|
516
|
-
function getUint8ArrayMemory0() {
|
|
517
|
-
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
|
518
|
-
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
519
|
-
}
|
|
520
|
-
return cachedUint8ArrayMemory0;
|
|
521
|
-
}
|
|
522
|
-
let WASM_VECTOR_LEN = 0;
|
|
523
|
-
function passArray8ToWasm0(arg, malloc) {
|
|
524
|
-
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
|
525
|
-
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
526
|
-
WASM_VECTOR_LEN = arg.length;
|
|
527
|
-
return ptr;
|
|
528
|
-
}
|
|
529
|
-
function getArrayU8FromWasm0(ptr, len) {
|
|
530
|
-
ptr = ptr >>> 0;
|
|
531
|
-
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
|
532
|
-
}
|
|
533
|
-
function blur(grayscale, width, height, kernel_size, sigma) {
|
|
534
|
-
const ptr0 = passArray8ToWasm0(grayscale, wasm.__wbindgen_malloc);
|
|
535
|
-
const len0 = WASM_VECTOR_LEN;
|
|
536
|
-
const ret = wasm.blur(ptr0, len0, width, height, kernel_size, sigma);
|
|
537
|
-
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
538
|
-
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
539
|
-
return v2;
|
|
540
|
-
}
|
|
541
|
-
let cachedFloat32ArrayMemory0 = null;
|
|
542
|
-
function getFloat32ArrayMemory0() {
|
|
543
|
-
if (cachedFloat32ArrayMemory0 === null || cachedFloat32ArrayMemory0.byteLength === 0) {
|
|
544
|
-
cachedFloat32ArrayMemory0 = new Float32Array(wasm.memory.buffer);
|
|
545
|
-
}
|
|
546
|
-
return cachedFloat32ArrayMemory0;
|
|
547
|
-
}
|
|
548
|
-
function passArrayF32ToWasm0(arg, malloc) {
|
|
549
|
-
const ptr = malloc(arg.length * 4, 4) >>> 0;
|
|
550
|
-
getFloat32ArrayMemory0().set(arg, ptr / 4);
|
|
551
|
-
WASM_VECTOR_LEN = arg.length;
|
|
552
|
-
return ptr;
|
|
553
|
-
}
|
|
554
|
-
function hysteresis_thresholding(suppressed, width, height, low_threshold, high_threshold) {
|
|
555
|
-
const ptr0 = passArrayF32ToWasm0(suppressed, wasm.__wbindgen_malloc);
|
|
556
|
-
const len0 = WASM_VECTOR_LEN;
|
|
557
|
-
const ret = wasm.hysteresis_thresholding(ptr0, len0, width, height, low_threshold, high_threshold);
|
|
558
|
-
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
559
|
-
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
560
|
-
return v2;
|
|
561
|
-
}
|
|
562
|
-
function dilate(edges, width, height, kernel_size) {
|
|
563
|
-
const ptr0 = passArray8ToWasm0(edges, wasm.__wbindgen_malloc);
|
|
564
|
-
const len0 = WASM_VECTOR_LEN;
|
|
565
|
-
const ret = wasm.dilate(ptr0, len0, width, height, kernel_size);
|
|
566
|
-
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
567
|
-
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
568
|
-
return v2;
|
|
569
|
-
}
|
|
570
|
-
let cachedUint16ArrayMemory0 = null;
|
|
571
|
-
function getUint16ArrayMemory0() {
|
|
572
|
-
if (cachedUint16ArrayMemory0 === null || cachedUint16ArrayMemory0.byteLength === 0) {
|
|
573
|
-
cachedUint16ArrayMemory0 = new Uint16Array(wasm.memory.buffer);
|
|
574
|
-
}
|
|
575
|
-
return cachedUint16ArrayMemory0;
|
|
576
|
-
}
|
|
577
|
-
function passArray16ToWasm0(arg, malloc) {
|
|
578
|
-
const ptr = malloc(arg.length * 2, 2) >>> 0;
|
|
579
|
-
getUint16ArrayMemory0().set(arg, ptr / 2);
|
|
580
|
-
WASM_VECTOR_LEN = arg.length;
|
|
581
|
-
return ptr;
|
|
582
|
-
}
|
|
583
|
-
function getArrayF32FromWasm0(ptr, len) {
|
|
584
|
-
ptr = ptr >>> 0;
|
|
585
|
-
return getFloat32ArrayMemory0().subarray(ptr / 4, ptr / 4 + len);
|
|
586
|
-
}
|
|
587
|
-
function non_maximum_suppression(dx, dy, width, height, l2_gradient) {
|
|
588
|
-
const ptr0 = passArray16ToWasm0(dx, wasm.__wbindgen_malloc);
|
|
589
|
-
const len0 = WASM_VECTOR_LEN;
|
|
590
|
-
const ptr1 = passArray16ToWasm0(dy, wasm.__wbindgen_malloc);
|
|
591
|
-
const len1 = WASM_VECTOR_LEN;
|
|
592
|
-
const ret = wasm.non_maximum_suppression(ptr0, len0, ptr1, len1, width, height, l2_gradient);
|
|
593
|
-
var v3 = getArrayF32FromWasm0(ret[0], ret[1]).slice();
|
|
594
|
-
wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
|
|
595
|
-
return v3;
|
|
596
|
-
}
|
|
597
|
-
function canny_edge_detector_full(grayscale, width, height, low_threshold, high_threshold, kernel_size, sigma, l2_gradient, apply_dilation, dilation_kernel_size) {
|
|
598
|
-
const ptr0 = passArray8ToWasm0(grayscale, wasm.__wbindgen_malloc);
|
|
599
|
-
const len0 = WASM_VECTOR_LEN;
|
|
600
|
-
const ret = wasm.canny_edge_detector_full(ptr0, len0, width, height, low_threshold, high_threshold, kernel_size, sigma, l2_gradient, apply_dilation, dilation_kernel_size);
|
|
601
|
-
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
602
|
-
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
603
|
-
return v2;
|
|
604
|
-
}
|
|
605
|
-
async function __wbg_load(module, imports) {
|
|
606
|
-
if (typeof Response === "function" && module instanceof Response) {
|
|
607
|
-
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
608
|
-
try {
|
|
609
|
-
return await WebAssembly.instantiateStreaming(module, imports);
|
|
610
|
-
} catch (e) {
|
|
611
|
-
if (module.headers.get("Content-Type") != "application/wasm") {
|
|
612
|
-
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
|
613
|
-
} else {
|
|
614
|
-
throw e;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
const bytes = await module.arrayBuffer();
|
|
619
|
-
return await WebAssembly.instantiate(bytes, imports);
|
|
620
|
-
} else {
|
|
621
|
-
const instance = await WebAssembly.instantiate(module, imports);
|
|
622
|
-
if (instance instanceof WebAssembly.Instance) {
|
|
623
|
-
return { instance, module };
|
|
624
|
-
} else {
|
|
625
|
-
return instance;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function __wbg_get_imports() {
|
|
630
|
-
const imports = {};
|
|
631
|
-
imports.wbg = {};
|
|
632
|
-
imports.wbg.__wbindgen_init_externref_table = function() {
|
|
633
|
-
const table = wasm.__wbindgen_export_0;
|
|
634
|
-
const offset = table.grow(4);
|
|
635
|
-
table.set(0, void 0);
|
|
636
|
-
table.set(offset + 0, void 0);
|
|
637
|
-
table.set(offset + 1, null);
|
|
638
|
-
table.set(offset + 2, true);
|
|
639
|
-
table.set(offset + 3, false);
|
|
640
|
-
};
|
|
641
|
-
return imports;
|
|
642
|
-
}
|
|
643
|
-
function __wbg_finalize_init(instance, module) {
|
|
644
|
-
wasm = instance.exports;
|
|
645
|
-
__wbg_init.__wbindgen_wasm_module = module;
|
|
646
|
-
cachedFloat32ArrayMemory0 = null;
|
|
647
|
-
cachedUint16ArrayMemory0 = null;
|
|
648
|
-
cachedUint8ArrayMemory0 = null;
|
|
649
|
-
wasm.__wbindgen_start();
|
|
650
|
-
return wasm;
|
|
651
|
-
}
|
|
652
|
-
async function __wbg_init(module_or_path) {
|
|
653
|
-
if (wasm !== void 0) return wasm;
|
|
654
|
-
if (typeof module_or_path !== "undefined") {
|
|
655
|
-
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
|
656
|
-
({ module_or_path } = module_or_path);
|
|
657
|
-
} else {
|
|
658
|
-
console.warn("using deprecated parameters for the initialization function; pass a single object instead");
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
if (typeof module_or_path === "undefined") {
|
|
662
|
-
throw new Error("scanic: WASM module bytes must be passed to init().");
|
|
663
|
-
}
|
|
664
|
-
const imports = __wbg_get_imports();
|
|
665
|
-
if (typeof module_or_path === "string" || typeof Request === "function" && module_or_path instanceof Request || typeof URL === "function" && module_or_path instanceof URL) {
|
|
666
|
-
module_or_path = fetch(module_or_path);
|
|
667
|
-
}
|
|
668
|
-
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
|
669
|
-
return __wbg_finalize_init(instance, module);
|
|
670
|
-
}
|
|
671
|
-
const wasmBase64 = "AGFzbQEAAAABtQEWYAJ/fwBgAAJ/f2ACf38Bf2ADf39/AX9gAX8AYAN/f38AYAV/f39/fwBgBH9/f38Bf2AGf39/f319An9/YAAAYAF/AX9gB39/f39/f38AYAh/f39/f39/fwBgB39/f39/f30AYAZ/f39/f38AYAR/f39/AGALf39/f319f31/f38Cf39gB39/f39/f38Cf39gBn9/f39/fQJ/f2AFf39/f38Cf39gBH9/f38Cf39gAn9/An9/AicBA3diZx9fX3diaW5kZ2VuX2luaXRfZXh0ZXJucmVmX3RhYmxlAAkDOzoKCwwNBgQDAAIAAAICAAYADgUCBAQEAAUDAA8FEAcEERIICAITABQVBAQAAAAEBQMHAgAAAgICAAAABAkCcAETE28AgAEFAwEAEQYJAX8BQYCAwAALB4QCDQZtZW1vcnkCAARibHVyACESZWRnZV9tYXBfdG9fYmluYXJ5ACgXaHlzdGVyZXNpc190aHJlc2hvbGRpbmcAIh5oeXN0ZXJlc2lzX3RocmVzaG9sZGluZ19iaW5hcnkAIxNjYWxjdWxhdGVfZ3JhZGllbnRzACcGZGlsYXRlACUYY2FubnlfZWRnZV9kZXRlY3Rvcl9mdWxsAB0Xbm9uX21heGltdW1fc3VwcHJlc3Npb24AIBNfX3diaW5kZ2VuX2V4cG9ydF8wAQERX193YmluZGdlbl9tYWxsb2MAJA9fX3diaW5kZ2VuX2ZyZWUALxBfX3diaW5kZ2VuX3N0YXJ0AAAJGAEAQQELEi4XDCoZDTcyGjM4KRMOEDosKwwBAgrGiwI6ySUCCX8BfiMAQRBrIggkAAJAAkACQAJAAkAgAEH1AU8EQCAAQcz/e0sEQEEAIQAMBgsgAEELaiICQXhxIQVBvJvAACgCACIJRQ0EQR8hBkEAIAVrIQMgAEH0//8HTQRAIAVBJiACQQh2ZyIAa3ZBAXEgAEEBdGtBPmohBgsgBkECdEGgmMAAaigCACICRQRAQQAhAAwCCyAFQRkgBkEBdmtBACAGQR9HG3QhBEEAIQADQAJAIAIoAgRBeHEiByAFSQ0AIAcgBWsiByADTw0AIAIhASAHIgMNAEEAIQMgASEADAQLIAIoAhQiByAAIAcgAiAEQR12QQRxaigCECICRxsgACAHGyEAIARBAXQhBCACDQALDAELAkACQAJAAkACQEG4m8AAKAIAIgRBECAAQQtqQfgDcSAAQQtJGyIFQQN2IgB2IgFBA3EEQCABQX9zQQFxIABqIgdBA3QiAUGwmcAAaiIAIAFBuJnAAGooAgAiAigCCCIDRg0BIAMgADYCDCAAIAM2AggMAgsgBUHAm8AAKAIATQ0IIAENAkG8m8AAKAIAIgBFDQggAGhBAnRBoJjAAGooAgAiAigCBEF4cSAFayEDIAIhAQNAAkAgASgCECIADQAgASgCFCIADQAgAigCGCEGAkACQCACIAIoAgwiAEYEQCACQRRBECACKAIUIgAbaigCACIBDQFBACEADAILIAIoAggiASAANgIMIAAgATYCCAwBCyACQRRqIAJBEGogABshBANAIAQhByABIgBBFGogAEEQaiAAKAIUIgEbIQQgAEEUQRAgARtqKAIAIgENAAsgB0EANgIACyAGRQ0GAkAgAigCHEECdEGgmMAAaiIBKAIAIAJHBEAgAiAGKAIQRwRAIAYgADYCFCAADQIMCQsgBiAANgIQIAANAQwICyABIAA2AgAgAEUNBgsgACAGNgIYIAIoAhAiAQRAIAAgATYCECABIAA2AhgLIAIoAhQiAUUNBiAAIAE2AhQgASAANgIYDAYLIAAoAgRBeHEgBWsiASADIAEgA0kiARshAyAAIAIgARshAiAAIQEMAAsAC0G4m8AAIARBfiAHd3E2AgALIAJBCGohACACIAFBA3I2AgQgASACaiIBIAEoAgRBAXI2AgQMBwsCQEECIAB0IgJBACACa3IgASAAdHFoIgdBA3QiAUGwmcAAaiICIAFBuJnAAGooAgAiACgCCCIDRwRAIAMgAjYCDCACIAM2AggMAQtBuJvAACAEQX4gB3dxNgIACyAAIAVBA3I2AgQgACAFaiIGIAEgBWsiB0EBcjYCBCAAIAFqIAc2AgBBwJvAACgCACICBEBByJvAACgCACEBAkBBuJvAACgCACIEQQEgAkEDdnQiA3FFBEBBuJvAACADIARyNgIAIAJBeHFBsJnAAGoiAyEEDAELIAJBeHEiAkGwmcAAaiEEIAJBuJnAAGooAgAhAwsgBCABNgIIIAMgATYCDCABIAQ2AgwgASADNgIICyAAQQhqIQBByJvAACAGNgIAQcCbwAAgBzYCAAwGC0G8m8AAQbybwAAoAgBBfiACKAIcd3E2AgALAkACQCADQRBPBEAgAiAFQQNyNgIEIAIgBWoiByADQQFyNgIEIAMgB2ogAzYCAEHAm8AAKAIAIgFFDQFByJvAACgCACEAAkBBuJvAACgCACIEQQEgAUEDdnQiBnFFBEBBuJvAACAEIAZyNgIAIAFBeHFBsJnAAGoiBCEBDAELIAFBeHEiBEGwmcAAaiEBIARBuJnAAGooAgAhBAsgASAANgIIIAQgADYCDCAAIAE2AgwgACAENgIIDAELIAIgAyAFaiIAQQNyNgIEIAAgAmoiACAAKAIEQQFyNgIEDAELQcibwAAgBzYCAEHAm8AAIAM2AgALIAJBCGoiAEUNAwwECyAAIAFyRQRAQQAhAUECIAZ0IgBBACAAa3IgCXEiAEUNAyAAaEECdEGgmMAAaigCACEACyAARQ0BCwNAIAMgACgCBEF4cSICIAVrIgQgAyADIARLIgQbIAIgBUkiAhshAyABIAAgASAEGyACGyEBIAAoAhAiAgR/IAIFIAAoAhQLIgANAAsLIAFFDQAgBUHAm8AAKAIAIgBNIAMgACAFa09xDQAgASgCGCEGAkACQCABIAEoAgwiAEYEQCABQRRBECABKAIUIgAbaigCACICDQFBACEADAILIAEoAggiAiAANgIMIAAgAjYCCAwBCyABQRRqIAFBEGogABshBANAIAQhByACIgBBFGogAEEQaiAAKAIUIgIbIQQgAEEUQRAgAhtqKAIAIgINAAsgB0EANgIACwJAIAZFDQACQAJAIAEoAhxBAnRBoJjAAGoiAigCACABRwRAIAEgBigCEEcEQCAGIAA2AhQgAA0CDAQLIAYgADYCECAADQEMAwsgAiAANgIAIABFDQELIAAgBjYCGCABKAIQIgIEQCAAIAI2AhAgAiAANgIYCyABKAIUIgJFDQEgACACNgIUIAIgADYCGAwBC0G8m8AAQbybwAAoAgBBfiABKAIcd3E2AgALAkAgA0EQTwRAIAEgBUEDcjYCBCABIAVqIgAgA0EBcjYCBCAAIANqIAM2AgAgA0GAAk8EQCAAIAMQCwwCCwJAQbibwAAoAgAiAkEBIANBA3Z0IgRxRQRAQbibwAAgAiAEcjYCACADQfgBcUGwmcAAaiIDIQIMAQsgA0H4AXEiBEGwmcAAaiECIARBuJnAAGooAgAhAwsgAiAANgIIIAMgADYCDCAAIAI2AgwgACADNgIIDAELIAEgAyAFaiIAQQNyNgIEIAAgAWoiACAAKAIEQQFyNgIECyABQQhqIgANAQsCQAJAAkACQAJAIAVBwJvAACgCACIBSwRAIAVBxJvAACgCACIATwRAIAhBBGohAAJ/IAVBr4AEakGAgHxxIgFBEHYgAUH//wNxQQBHaiIBQAAiBEF/RgRAQQAhAUEADAELIAFBEHQiAkEQayACIARBEHQiAUEAIAJrRhsLIQIgAEEANgIIIAAgAjYCBCAAIAE2AgAgCCgCBCIBRQRAQQAhAAwICyAIKAIMIQdB0JvAACAIKAIIIgRB0JvAACgCAGoiADYCAEHUm8AAIABB1JvAACgCACICIAAgAksbNgIAAkACQEHMm8AAKAIAIgIEQEGgmcAAIQADQCABIAAoAgAiAyAAKAIEIgZqRg0CIAAoAggiAA0ACwwCC0Hcm8AAKAIAIgBBACAAIAFNG0UEQEHcm8AAIAE2AgALQeCbwABB/x82AgBBrJnAACAHNgIAQaSZwAAgBDYCAEGgmcAAIAE2AgBBvJnAAEGwmcAANgIAQcSZwABBuJnAADYCAEG4mcAAQbCZwAA2AgBBzJnAAEHAmcAANgIAQcCZwABBuJnAADYCAEHUmcAAQciZwAA2AgBByJnAAEHAmcAANgIAQdyZwABB0JnAADYCAEHQmcAAQciZwAA2AgBB5JnAAEHYmcAANgIAQdiZwABB0JnAADYCAEHsmcAAQeCZwAA2AgBB4JnAAEHYmcAANgIAQfSZwABB6JnAADYCAEHomcAAQeCZwAA2AgBB/JnAAEHwmcAANgIAQfCZwABB6JnAADYCAEH4mcAAQfCZwAA2AgBBhJrAAEH4mcAANgIAQYCawABB+JnAADYCAEGMmsAAQYCawAA2AgBBiJrAAEGAmsAANgIAQZSawABBiJrAADYCAEGQmsAAQYiawAA2AgBBnJrAAEGQmsAANgIAQZiawABBkJrAADYCAEGkmsAAQZiawAA2AgBBoJrAAEGYmsAANgIAQayawABBoJrAADYCAEGomsAAQaCawAA2AgBBtJrAAEGomsAANgIAQbCawABBqJrAADYCAEG8msAAQbCawAA2AgBBxJrAAEG4msAANgIAQbiawABBsJrAADYCAEHMmsAAQcCawAA2AgBBwJrAAEG4msAANgIAQdSawABByJrAADYCAEHImsAAQcCawAA2AgBB3JrAAEHQmsAANgIAQdCawABByJrAADYCAEHkmsAAQdiawAA2AgBB2JrAAEHQmsAANgIAQeyawABB4JrAADYCAEHgmsAAQdiawAA2AgBB9JrAAEHomsAANgIAQeiawABB4JrAADYCAEH8msAAQfCawAA2AgBB8JrAAEHomsAANgIAQYSbwABB+JrAADYCAEH4msAAQfCawAA2AgBBjJvAAEGAm8AANgIAQYCbwABB+JrAADYCAEGUm8AAQYibwAA2AgBBiJvAAEGAm8AANgIAQZybwABBkJvAADYCAEGQm8AAQYibwAA2AgBBpJvAAEGYm8AANgIAQZibwABBkJvAADYCAEGsm8AAQaCbwAA2AgBBoJvAAEGYm8AANgIAQbSbwABBqJvAADYCAEGom8AAQaCbwAA2AgBBzJvAACABQQ9qQXhxIgBBCGsiAjYCAEGwm8AAQaibwAA2AgBBxJvAACAEQShrIgQgASAAa2pBCGoiADYCACACIABBAXI2AgQgASAEakEoNgIEQdibwABBgICAATYCAAwICyACIANJIAEgAk1yDQAgACgCDCIDQQFxDQAgA0EBdiAHRg0DC0Hcm8AAQdybwAAoAgAiACABIAAgAUkbNgIAIAEgBGohA0GgmcAAIQACQAJAA0AgAyAAKAIAIgZHBEAgACgCCCIADQEMAgsLIAAoAgwiA0EBcQ0AIANBAXYgB0YNAQtBoJnAACEAA0ACQCACIAAoAgAiA08EQCACIAMgACgCBGoiBkkNAQsgACgCCCEADAELC0HMm8AAIAFBD2pBeHEiAEEIayIDNgIAQcSbwAAgBEEoayIJIAEgAGtqQQhqIgA2AgAgAyAAQQFyNgIEIAEgCWpBKDYCBEHYm8AAQYCAgAE2AgAgAiAGQSBrQXhxQQhrIgAgACACQRBqSRsiA0EbNgIEQaCZwAApAgAhCiADQRBqQaiZwAApAgA3AgAgA0EIaiIAIAo3AgBBrJnAACAHNgIAQaSZwAAgBDYCAEGgmcAAIAE2AgBBqJnAACAANgIAIANBHGohAANAIABBBzYCACAAQQRqIgAgBkkNAAsgAiADRg0HIAMgAygCBEF+cTYCBCACIAMgAmsiAEEBcjYCBCADIAA2AgAgAEGAAk8EQCACIAAQCwwICwJAQbibwAAoAgAiAUEBIABBA3Z0IgRxRQRAQbibwAAgASAEcjYCACAAQfgBcUGwmcAAaiIAIQEMAQsgAEH4AXEiAEGwmcAAaiEBIABBuJnAAGooAgAhAAsgASACNgIIIAAgAjYCDCACIAE2AgwgAiAANgIIDAcLIAAgATYCACAAIAAoAgQgBGo2AgQgAUEPakF4cUEIayIEIAVBA3I2AgQgBkEPakF4cUEIayIDIAQgBWoiAGshBSADQcybwAAoAgBGDQMgA0HIm8AAKAIARg0EIAMoAgQiAkEDcUEBRgRAIAMgAkF4cSIBEAogASAFaiEFIAEgA2oiAygCBCECCyADIAJBfnE2AgQgACAFQQFyNgIEIAAgBWogBTYCACAFQYACTwRAIAAgBRALDAYLAkBBuJvAACgCACIBQQEgBUEDdnQiAnFFBEBBuJvAACABIAJyNgIAIAVB+AFxQbCZwABqIgUhAwwBCyAFQfgBcSIBQbCZwABqIQMgAUG4mcAAaigCACEFCyADIAA2AgggBSAANgIMIAAgAzYCDCAAIAU2AggMBQtBxJvAACAAIAVrIgE2AgBBzJvAAEHMm8AAKAIAIgAgBWoiAjYCACACIAFBAXI2AgQgACAFQQNyNgIEIABBCGohAAwGC0HIm8AAKAIAIQACQCABIAVrIgJBD00EQEHIm8AAQQA2AgBBwJvAAEEANgIAIAAgAUEDcjYCBCAAIAFqIgEgASgCBEEBcjYCBAwBC0HAm8AAIAI2AgBByJvAACAAIAVqIgQ2AgAgBCACQQFyNgIEIAAgAWogAjYCACAAIAVBA3I2AgQLIABBCGohAAwFCyAAIAQgBmo2AgRBzJvAAEHMm8AAKAIAIgBBD2pBeHEiAUEIayICNgIAQcSbwABBxJvAACgCACAEaiIEIAAgAWtqQQhqIgE2AgAgAiABQQFyNgIEIAAgBGpBKDYCBEHYm8AAQYCAgAE2AgAMAwtBzJvAACAANgIAQcSbwABBxJvAACgCACAFaiIBNgIAIAAgAUEBcjYCBAwBC0HIm8AAIAA2AgBBwJvAAEHAm8AAKAIAIAVqIgE2AgAgACABQQFyNgIEIAAgAWogATYCAAsgBEEIaiEADAELQQAhAEHEm8AAKAIAIgEgBU0NAEHEm8AAIAEgBWsiATYCAEHMm8AAQcybwAAoAgAiACAFaiICNgIAIAIgAUEBcjYCBCAAIAVBA3I2AgQgAEEIaiEACyAIQRBqJAAgAAuGEgIlfwF7AkACQAJAAkAgAiADbCIOQQBIDQBBASERIA4EQEEBIQcgDkEBEDYiEUUNAQsgBEEBdiESAkACQAJAIANFDQAgAkUNBCACQQFrIQwgBEUEQEEAIQEgESEAA0BBACEIIA4gAiAKbGsiB0EAIAcgDk0bIgcgDCAHIAxJG0EBaiIHQRFPBEAgByAHQQ9xIgdBECAHGyIHayEIIAkgDiAJIA5LGyABaiILIAwgCyAMSRsgB2tBAWohCyAAIQcDQCAH/QwAAAAAAAAAAAAAAAAAAAAA/QsAACAHQRBqIQcgC0EQayILDQALCyAKQQFqIQogCSARaiELA0AgCCAJaiIHIA5PDQQgCCALakEAOgAAIAIgCEEBaiIIRw0ACyABIAJrIQEgAiAJaiEJIAAgAmohACADIApHDQALDAELIAxBAEgNBkEAIBJrIQoDQCACIBRsIRAgFEEBaiEUQQAhDyAKIQ0DQCAPQQFqIA0hByAEIQlBACEIA0AgByAMIAcgDEkbQQAgB0EAThsgEGoiEyABTw0FIAAgE2otAAAiEyAIQf8BcSIIIAggE0kbIQggB0EBaiEHIAlBAWsiCQ0ACyAPIBBqIgcgDk8NAyAHIBFqIAg6AAAgDUEBaiENIg8gAkcNAAsgAyAURw0ACwsgAyASayIAQQAgACADTRshDCACQQR2ISAgBEECSQ0EIAJFBEBBACEgDAULAkACQCADQQFrIgtBAE4EQEEAIBJrIQBBACENA0AgAiANbCEQIA1BAWohDUEAIQoDQCAKQQFqIAAhByAEIQlBACEIA0AgByALIAcgC0kbQQAgB0EAThsgAmwgCmoiDyAOTw0EIA8gEWotAAAiDyAIQf8BcSIIIAggD0kbIQggB0EBaiEHIAlBAWsiCQ0ACyAKIBBqIgcgBk8NBCAFIAdqIAg6AAAiCiACRw0ACyAAQQFqIQAgDSASRw0ACwwHCwwHCyAPIA5BzIzAABAYAAsgByAGQbyMwAAQGAALIAcgDkHcjMAAEBgACyATIAFB7IzAABAYAAsgByAOEC0ACyADIBJrIgBBACAAIANNGyEMCyAMIBJLBEBBACACIBJsIiEgAkFwcSIAaiIZayEaIAUgGWohGyAAQX9zIAIgAEEBciIBIAEgAkkbaiEWIAIgEWohHCAMIBJrISMgBSAhaiEkIBEgAkEBdGohHSARIAJBA2xqIRQgESACQQJ0IiVqIR4gBEEBayIBQXxxISYgAUEDcSEiIAJBEEkhJyAEQQJrQQNJISggGSEXIAAhASASIRMDQCACIBhsIRUCQCAnDQAgBEECTwRAIAUgAiATbGohKSARIBMgEmsgAmxqISpBACEPIBEhDSAcIQkgHSEQIBQhCyAeIQoDQCAqIA9BBHQiK2r9AAAAISxBASEHIChFBEBBACEHQQAhCANAICwgByAJav0AAAD9eSAHIBBq/QAAAP15IAcgC2r9AAAA/XkgByAKav0AAAD9eSEsIAcgJWohByAmIAhBBGoiCEcNAAsgCEEBaiEHCyAiBEAgDSACIAcgGGpsaiEHICIhCANAICwgB/0AAAD9eSEsIAIgB2ohByAIQQFrIggNAAsLICkgK2ogLP0LAAAgDUEQaiENIAlBEGohCSAQQRBqIRAgC0EQaiELIApBEGohCiAgIA9BAWoiD0cNAAsMAQsgAEUNACAFIBIgGGogAmxqIBEgFWogAPwKAAALAkAgACACRg0AAkACQAJAIAQEQCACIBNsIQ0gASELIAAhDwwBCyAAIQggFiAGIBUgGWoiByAGIAdLGyAHayIHIAcgFksbQQFqIgdBEE0NASAHQQ9xIglBECAJGyIKIBYgBiAXIAYgF0sbIBpqIgkgCSAWSxtBf3NqIQkgCCAHIApraiEIIBshBwNAIAf9DAAAAAAAAAAAAAAAAAAAAAD9CwAAIAdBEGohByAJQRBqIgkNAAsMAQsDQCALIBFqIRAgD0EBaiEKQQAhByAEIQlBACEIAkADQCAHIAtqIhUgDk8NASAHIBBqLQAAIhUgCEH/AXEiCCAIIBVJGyEIIAIgB2ohByAJQQFrIgkNAAsgDSAPaiIHIAZPDQMgBSAHaiAIOgAAIAtBAWohCyAKIg8gAk8NBAwBCwsgFSAOQayMwAAQGAALIB8gJGohCSAfICFqIQoDQCAIIApqIgcgBk8NASAIIAlqQQA6AAAgAiAIQQFqIghLDQALDAELIAcgBkGcjMAAEBgACyATQQFqIRMgAiAfaiEfIBogAmshGiACIBdqIRcgAiAbaiEbIAEgAmohASACIBxqIRwgAiAdaiEdIAIgFGohFCACIB5qIR4gGEEBaiIYICNHDQALCwJAAkACQCACRSADIAxNcg0AIARFBEAgAkEBayEKQQAgAiAMbCIQayEEIAUgEGohC0EAIQAgDCEBA0BBACEJIAYgACAMaiACbGsiBUEAIAUgBk0bIgUgCiAFIApJG0EBaiIFQRFPBEAgBUEPcSIHQRAgBxsiByAGIBAgBiAQSxsgBGoiCSAKIAkgCkkbQX9zaiEIIAUgB2shCSALIQcDQCAH/QwAAAAAAAAAAAAAAAAAAAAA/QsAACAHQRBqIQcgCEEQaiIIDQALCyABQQFqIQEgCSALaiEIIAkgEGohByACIAlrIQkDQCAGIAdNDQUgCEEAOgAAIAhBAWohCCAHQQFqIQcgCUEBayIJDQALIAQgAmshBCACIBBqIRAgAiALaiELIABBAWohACABIANHDQALDAELIANBAWsiC0EASA0DIAwgEmshAANAIAIgDGwhDyAMQQFqIQxBACEKA0AgCkEBaiAAIQcgBCEJQQAhCANAIAcgCyAHIAtJG0EAIAdBAE4bIAJsIApqIg0gDk8NBCANIBFqLQAAIg0gCEH/AXEiCCAIIA1JGyEIIAdBAWohByAJQQFrIgkNAAsgCiAPaiIHIAZPDQQgBSAHaiAIOgAAIgogAkcNAAsgAEEBaiEAIAMgDEcNAAsLIA4EQCARIA4QNAsPCyANIA5BjIzAABAYAAsgByAGQfyLwAAQGAALQbCKwABBHEHMisAAEBwAC68QAxh/AnsDfSAFIAZsIgtBAnQhCAJAAkAgC0H/////A0sgCEH8////B0tyDQACQCAIRQRAQQQhDUEEIQ8MAQtBBCEPIAhBBBA2Ig1FDQEgCyEXIAhBBBA2Ig9FDQILIAAgCzYCCCAAIA82AgQgACAXNgIAQQAhCCACQQRPBEAgAkECdiEMIA0hAANAIAACewJAAkACQAJAAkACQAJAAkAgAiAISwRAIAhBAWoiCSACTw0BIAhBAmoiDiACTw0CIAhBA2oiECACTw0DIAQgCE0NBCAEIAlNDQUgBCAOTQ0GIAQgEE0NByABIApqIgkuAQCy/RMgCUECai4BALL9IAEgCUEEai4BALL9IAIgCUEGai4BALL9IAMhICADIApqIgkuAQCy/RMgCUECai4BALL9IAEgCUEEai4BALL9IAIgCUEGai4BALL9IAMhISAHDQggIP3gASAh/eAB/eQBDAkLIAggAkHcjsAAEBgACyAIQQFqIAJB7I7AABAYAAsgCEECaiACQfyOwAAQGAALIAhBA2ogAkGMj8AAEBgACyAIIARBnI/AABAYAAsgCEEBaiAEQayPwAAQGAALIAhBAmogBEG8j8AAEBgACyAIQQNqIARBzI/AABAYAAsgICAg/eYBICEgIf3mAf3kAf3jAQv9CwIAIABBEGohACAKQQhqIQogCEEEaiEIIAxBAWsiDA0ACwsCQCACQXxxIgggAkYNACAIQX9zIgwgAiAIQQFyIgAgACACSRtqIgogCyAIIAggC0kbIg4gCGsiACAAIApLGyIJIAQgCCAEIAhLGyIQIAhrIgogCSAKSRtBAWohCQJAAkACQCAHRQRAIAlBBE0NASAJQQNxIgdBBCAHGyISIAIgCEEBaiIHIAIgB0sbIAxqIgcgACAAIAdLGyIAIAogACAKSRtBf3NqIQwgASACQQJ2IgBBA3QiB2ohCiADIAdqIQcgDSAAQQR0aiEAIAggCSASa2ohCANAIAAgCv0DAQD9+gH94AEgB/0DAQD9+gH94AH95AH9CwIAIApBCGohCiAHQQhqIQcgAEEQaiEAIAxBBGoiDA0ACwwBCyAJQQVPBEAgCUEDcSIHQQQgBxsiEiAIQX9zIAIgCEEBaiIHIAIgB0sbaiIHIAAgACAHSxsiACAKIAAgCkkbQX9zaiEMIAEgAkECdiIAQQN0IgdqIQogAyAHaiEHIA0gAEEEdGohACAIIAkgEmtqIQgDQCAAIAr9AwEA/foBIiAgIP3mASAH/QMBAP36ASIgICD95gH95AH94wH9CwIAIApBCGohCiAHQQhqIQcgAEEQaiEAIAxBBGoiDA0ACwsgASAIQQF0IgBqIQogDSAIQQJ0aiEHIAAgA2ohAANAIAggEEYNAiAIIA5HBEAgByAKLgEAsiIiICKUIAAuAQCyIiIgIpSSkTgCACAKQQJqIQogB0EEaiEHIABBAmohACAIQQFqIgggAkkNAQwFCwsgDiALQcyOwAAQGAALIAEgCEEBdCIAaiEKIA0gCEECdGohByAAIANqIQADQCAIIBBGDQEgCCAORg0CIAcgCi4BALKLIAAuAQCyi5I4AgAgCkECaiEKIAdBBGohByAAQQJqIQAgCEEBaiIIIAJJDQALDAILIBAgBEGsjsAAEBgACyAOIAtBvI7AABAYAAsCQAJAAkACQAJAAkACQAJAAkACQAJAAkAgBkEBayIaQQJJDQAgBUEBayIbQQJJDQAgBUEBaiEcIAVBf3MhHSABQQJqIQ4gA0ECaiEQIAVBAmshHiAPIAVBAnQiGUEEaiIAaiEPIAAgDWohEiAFQQF0IhUhEyAFIRhBAiEMQQEhBgNAIAwhCCAFIAZsIR9BASEJIA8hACASIQYgDiEDIBAhAUEAIQxBAiEHA0AgDCAYaiIRQQFqIhQgC08NAyAHIQogACAGKgIAIiJDAAAAAFwEfSACIBRNDQUgBCAUTQ0GAkAgASAVai4BACIHsosiIyADIBVqLgEAIhSyiyIkQ0GCGkCUXkUEQCAJIB9qIQkgJCAjQ0GCGkCUXkUEQCAUQQBKIAdBAEpxRSAHIBRxQQBOcUUEQCAMIBZqIgdBAmogC08NDSAMIBNqIgcgC08NDiAJIAVrQQFqIQcgCSAbaiEJDAMLIAwgFmoiByALTw0KIAwgE2oiB0ECaiALTw0LIAkgHWohByAJIBxqIQkMAgsgCyARTQ0NIBFBAmogC08NDiAJQQFrIQcgCUEBaiEJDAELIAwgFmoiCUEBaiIHIAtPDQ4gDCATaiIRQQFqIgkgC08NDwsgIkMAAAAAICIgDSAJQQJ0aioCAGAbQwAAAAAgIiANIAdBAnRqKgIAYBsFQwAAAAALOAIAIABBBGohACAGQQRqIQYgA0ECaiEDIAFBAmohASAKQQFqIQcgCiEJIB4gDEEBaiIMRw0ACyAPIBlqIQ8gEiAZaiESIA4gFWohDiAFIBNqIRMgBSAWaiEWIBAgFWohECAFIBhqIRggCCAIIBpJIgBqIQwgCCEGIAANAAsLIBcEQCANIBdBAnQQNAsPCyARQQFqIAtB/IzAABAYAAsgEUEBaiACQYyNwAAQGAALIBFBAWogBEGcjcAAEBgACyAHIAtBrI3AABAYAAsgB0ECaiALQbyNwAAQGAALIAdBAmogC0HMjcAAEBgACyAHIAtB3I3AABAYAAsgESALQeyNwAAQGAALIBFBAmogC0H8jcAAEBgACyAJQQFqIAtBjI7AABAYAAsgEUEBaiALQZyOwAAQGAALIA8gCBAtAAtBBCAIEC0AC/tEBD5/CH4JewR9IwBBMGsiDyQAAkACQAJAAkACQAJAIAMgBGwgAkYEQCAFQQFxRQ0BIAZDAAAAAF8EQCAFQQFrs0MAAAA/lEMAAIC/kkOamZk+lEPNzEw/kiEGCyAFQf////8DSyAFQQJ0IglB/P///wdLcg0CQQQhECAJQQQQNSIeRQ0CIA9BADYCLCAPIB42AiggDyAFNgIkIAlBBBA1IgpFDQUgD0EANgIUIA8gCjYCECAPIAU2AgxBACAFQQF2ayEVQwAAgL8gBiAGIAaSlJUhWUMAAAAAIQZBACEQA0AgDygCDCEWAn1DAAAAACFWQwAAAAAhWCMAQRBrIQcgWSAQIBVqIgogCmyylCJXvCILQR92IQ0CQAJ9IFcCfwJAAkACQAJAIAtB/////wdxIgpB0Ni6lQRPBEAgVyAKQYCAgPwHSw0IGiALQQBIIgtFIApBl+TFlQRLcQ0CIAtFDQEgB0MAAICAIFeVOAIIIAcqAggaIApBtOO/lgRNDQEMBwsgCkGY5MX1A00EQCAKQYCAgMgDTQ0DQQAhCiBXDAYLIApBkquU/ANNDQMLIFdDO6q4P5QgDUECdCoC9JdAkvwADAMLIFdDAAAAf5QMBQsgByBXQwAAAH+SOAIMIAcqAgwaIFdDAACAP5IMBAsgDUUgDWsLIgqyIlZDAHIxv5SSIlcgVkOOvr81lCJYkwshViBXIFYgViBWIFaUIlYgVkMVUjW7lEOPqio+kpSTIlaUQwAAAEAgVpOVIFiTkkMAAIA/kiFWIApFDQACQAJAAkAgCkH/AEwEQCAKQYJ/Tg0DIFZDAACADJQhViAKQZt+TQ0BIApB5gBqIQoMAwsgVkMAAAB/lCFWIApB/gFLDQEgCkH/AGshCgwCCyBWQwAAgAyUIVZBtn0gCiAKQbZ9TRtBzAFqIQoMAQsgVkMAAAB/lCFWQf0CIAogCkH9Ak8bQf4BayEKCyBWIApBF3RBgICA/ANqQYCAgPwHcb6UIVYLIFYLIVYgECAWRgRAIA9BDGoQFAsgDygCECARaiBWOAIAIBFBBGohESAGIFaSIQYgDyAQQQFqIhA2AhQgBSAQRw0ACwJ/IAVFBEBBACERQQAMAQtDAACAPyAGlSEGIA8oAhAhBUEAIRBBACERA0AgBiAFIBBqKgIAlEMAAIBHlEMAAAA/kvwBIQogDygCJCARRgRAIA9BJGoQFAsgDygCKCAQaiAKNgIAIA8gEUEBaiIRNgIsIAkgEEEEaiIQRw0ACyAPKAIoIR4gDygCJAshLSAPKAIMIgUEQCAPKAIQIAVBAnQQNAtBACEHIAJB/////wNLIAJBAnQiCkH8////B0tyDQMCfyAKRQRAQQQhBUEADAELQQQhByAKQQQQNiIFRQ0EIAILIS4CQCACRQRAQQEhEAwBCyACQQEQNiIQRQ0FCyAAIAI2AgggACAQNgIEIAAgAjYCACABIRUgBSEKIAMhACAeIQtBACEJAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBFBA2sOAwEACAALIARFDQogAEEEayIBQQAgACABTxshEiARRQRAIABBAnQhBwNAIAAgDGwiCSAAaiIBIAlJBEAgASEADAcLIAEgAksEQCABIQAMBwsgASACSw0IIAxBAWohDEEAIQkgCiEIIAchAQNAIAj9DAAAAAAAAAAAAAAAAAAAAAD9CwIAIAhBEGohCCABQRBrIQEgCUEEaiIJIBJNDQALIAFFIAAgCU1yRQRAIAhBACAB/AsACyAHIApqIQogBCAMRw0ACwwLCyAAQQFrIhNBAEgNA0EEIBFBAXYiAWshFkEAIAFrIRcDQCAAIBRsIgkgAGoiASAJSQRAIAEhAAwGCyABIAJLBEAgASEADAYLIAEgAksNByAUQQFqIRQgCSAVaiEaIAogCUECdGohGEEAIQggFiEJIBchDQNAIAkhDv0MAAAAAAAAAAAAAAAAAAAAACFNIAshAUEAIQcDQCAHIA1qIgkgEyAJIBNJGyIMQQAgCUEAThsiGyAATw0EIAlBAWoiGSATIBMgGUsbIgxBACAZQQBOGyIcIABPDQQgCUECaiIZIBMgEyAZSxsiDEEAIBlBAE4bIhkgAE8NBCAJQQNqIgkgEyAJIBNJGyIMQQAgCUEAThsiCSAATw0EIBogG2otAAD9ESAaIBxqLQAA/RwBIBkgGmotAAD9HAIgCSAaai0AAP0cAyAB/QkCAP21ASBN/a4BIU0gAUEEaiEBIBEgB0EBaiIHRw0ACyAYIAhBAnRqIE1BCP2tAf0LAgAgDkEEaiEJIA1BBGohDSAIQQRqIgggEk0NAAsgACAISwRAA0AgCEEBakIAIUUgDiEJIBEhByALIQwDQCAJIBMgCSATSRtBACAJQQBOGyINIABPDQYgCUEBaiEJIAw1AgAgDSAaajEAAH4gRXwhRSAMQQRqIQwgB0EBayIHDQALIBggCEECdGogRUIIiD4CACAOQQFqIQ4iCCAARw0ACwsgBCAURw0ACwwKCyAERQ0JAkACQAJAIAAOAgABAgtBAEEAQdSGwAAQGAALIAJFBEBBASEJDAoLIAJFBEBBASEJDAkLQQFBAUHkhsAAEBgACyALKAIIIhP9ESFQIAsoAgQiAf0RIVEgCygCACII/REhUiAAQQVrIgxBACAAIAxPGyEYIABBAWshFCABIAhqIRkgE60iRSABrSJIfCFGQQEgAGshGyAVQQFrIRYgAEECdCEaIApBBGohDCAAQQJrIRwgRf0SIU0gSP0SIU8gCK0iR/0SIU4gAEEGSSEfIAohCyAVIQ0CQAJAA0AgACAObCIIIABqIgkgCEkgAiAJSSIBcg0LIAENCiAKIAhBAnRqIiAgEyAIIBVqIhctAAFsIBkgFy0AACIJbGo2AgBBASEHAkACQAJAAkACQAJAAkAgH0UEQEECIQggDCEBA0AgCEEBayAATw0DIAAgCE0NBCAIQQFqIABPDQUgCEECaiAATw0CIAlB/wFxIR0gASAIIA1qIglBAWstAAAiIf0RIAktAAAiB/0cASAJQQFqLQAAIhL9HAIgCUECai0AACIJ/RwDIFH9tQEgHf0RICH9HAEgB/0cAiAS/RwDIFL9tQH9rgEgB/0RIBL9HAEgCf0cAiAXIBQgCEEDaiIHIAcgFEsbai0AAP0cAyBQ/bUB/a4BQQj9rQH9CwIAIAFBEGohASAIQQRqIQggByAYTQ0ACyAIQQFrIQcLIAcgFE8NBSAHQX9zIAAgB0EBaiIBIAAgAUsbaiIBIBwgB2siCCABIAhJGyIBIAAgB0EBayIIIAAgCEsbIAdrQQFqIgggASAISRsiASAHIAAgACAHSRsiEiAHayIIIAEgCEkbQQFqIgFBBE0NBCAHIBZqIQggCyAHQQJ0aiEJIAcgASABQQNxIgFBBCABG2siAWohBwNAIAkgTyAIQQFq/VwAACJT/YkB/akB/ckB/dUBIE4gCP1cAAAiVP2JAf2pAf3JAf3VAf3OASBNIAhBAmr9XAAAIlX9iQH9qQH9yQH91QH9zgFBCP3NASBPIFMgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VASBOIFQgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OASBNIFUgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OAUEI/c0B/Q0AAQIDCAkKCxAREhMYGRob/QsCACAIQQRqIQggCUEQaiEJIAFBBGsiAQ0ACwwECyAIQQJqIABB1IfAABAYAAsgCEEBayAAQaSHwAAQGAALIAggAEG0h8AAEBgACyAIQQFqIABBxIfAABAYAAsgB0EBayEIQQAgEmshHSALIAdBAnRqIQkDQCAAIAhNDQIgCCAdakF/Rg0EIAhBAmogAE8NBSAJIAggDWoiAUEBajEAACBIfiABMQAAIEd+fCABQQJqMQAAIEV+fEIIiD4CACAJQQRqIQkgGyAIQQFqIghqQX9HDQALCyAgIBRBAnRqIEYgFCAXajEAAH4gACAXakECazEAACBHfnxCCIg+AgAgACAWaiEWIAsgGmohCyAMIBpqIQwgACANaiENIA5BAWoiDiAERw0BDA0LCyAIIABB9IbAABAYAAsgEiAAQYSHwAAQGAALIAhBAmogAEGUh8AAEBgACyAMIABBxITAABAYAAsgDSAAQbSEwAAQGAALIAAgAk0NAQsgCSAAIAJBlITAABAbAAsgACACTQ0LIAAhAQsgCSABIAJBpITAABAbAAsgBEUgAEVyDQICQAJAAkAgAEEBayINQQBOBEAgDUEARyIUIABPBEAgACACSwRAQQEhBwwFC0EBIQcgACACTQ0CDAMLQQIgDSANQQJPGyEWIABBAUcEQEEDIA0gDUEDTxshEyAAQQJ0IRogCkEIaiEMA0AgACAIbCIJIABqIgcgCUkgAiAHSSIBcg0FIAENBCAKIAlBAnRqIgEgCSAVaiIOMQAAIkcgCzUCACJGIAs1AgQiSXx+IkogDiAUajEAACJLIAs1AgwiRX58IEcgCzUCCCJIfnwgCzUCECJHIA4gFmoxAAAiTH58QgiIPgIAIAEgSiBIIEt+fCBFIEx+fCBHIA4gE2oxAAB+fEIIiD4CBCAAQQJHBEBBBCEJIAwhAQNAIAlBA2siByANIAcgDUkbIgdBACAJQQJrIhdBAEobIhIgAE8NBSAJQQFrIgcgDSAHIA1JGyIHQQAgF0EBaiIYQQBOGyIZIABPDQUgCSANIAkgDUkbIgdBACAYQQFqIhhBAE4bIhsgAE8NBSABIA4gFyANIA0gF0sbajEAACBIfiAOIBlqMQAAIEV+fCAOIBJqMQAAIEl+fCAOIAlBBGsiByANIAcgDUkbajEAACBGfnwgDiAbajEAACBHfnxCCIg+AgAgCUEBaiEJIAFBBGohASAYQQFrIABHDQALCyAMIBpqIQwgCEEBaiIIIARHDQALDAcLIAIgBEEBayIAIAAgAksbQQFqIgFBBU8EQCALNQIAIAs1AgR8/RIhTSALNQIQ/RIhTyALNQII/RIhTiALNQIM/RIhUCAVIQggCiEAIAEgAUEDcSIBQQQgARtrIgkhAQNAIAAgTSAI/VwAACJR/YkB/akB/ckBIlL91QEgCCAUav1cAAAiU/2JAf2pAf3JASBQ/dUB/c4BIFIgTv3VAf3OASBPIAggFmr9XAAAIlL9iQH9qQH9yQH91QH9zgFBCP3NASBNIFEgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JASJR/dUBIFMgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JASBQ/dUB/c4BIFEgTv3VAf3OASBPIFIgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OAUEI/c0B/Q0AAQIDCAkKCxAREhMYGRob/QsCACAIQQRqIQggAEEQaiEAIAFBBGsiAQ0ACwsDQCAJQQFqIQcgAiAJTQ0EIAIgB0kNAyAKIAlBAnRqIAkgFWoiADEAACJFIAs1AgAgCzUCBHx+IAAgFGoxAAAgCzUCDH58IEUgCzUCCH58IAs1AhAgACAWajEAAH58QgiIPgIAIAciCSAERw0ACwwGCyAAIAJLBEAgACEHDAMLIAAgAksEQCAAIQcMAgsMDAsgByAAQYSIwAAQGAALIAkgByACQfSHwAAQGwALIAkgByACQeSHwAAQGwALIAggCSACQcSGwAAQGwALIAggCSACQbSGwAAQGwALIBAhCSACIQcgBCEYIB4hAUEAIQJBACEVAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBFBA0cEQCAERQ0SIANBBGsiEEEAIAMgEE8bIRRBACARQQF2ayEIIARBAWshDAwBCyADIAdNBEAgATUCCCFFIAE1AgQhRyABNQIAIUggA0UNAyBHIEh8IUYgByADayIAQQAgACAHTRsiACADQQFrIgEgACABSRtBAWoiAEEETQ0CIANBAnQhBCBF/RIhTSBG/RIhTyAJIQEgACAAQQNxIgBBBCAAG2siAiELIAUhAANAIAEgTSAAIARq/QACACJQ/ckB/dUBIE8gAP0AAgAiUf3JAf3VAf3OAUEY/c0BIk79DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgTv0dAEL/AVQbQn9CACBO/R0BQv8BVBv9HgH9UiBNIFD9ygH91QEgTyBR/coB/dUB/c4BQRj9zQEiTv0M/wAAAAAAAAD/AAAAAAAAAP0M//////////////////////0MAAAAAAAAAAAAAAAAAAAAACBO/R0AQv8BVBtCf0IAIE79HQFC/wFUG/0eAf1S/Q0ACBAYAAAAAAAAAAAAAAAA/VoAAAAgAUEEaiEBIABBEGohACALQQRrIgsNAAsMAgtBACADIAdB1ITAABAbAAsDQAJAAkACQAJAIAMgFWwiDiAVQQFqIhUgA2wiAEsgACAHS3JFBEAgCSAOaiENQQAhAEEAIQQCQANAAkAgACEKAkAgEUUEQP0MAAAAAAAAAAAAAAAAAAAAACJNIU8MAQsgDEEASA0BIAUgBEECdGohFiAIIQAgESELIAEhAv0MAAAAAAAAAAAAAAAAAAAAACJPIU0DQCACNQIA/RIiTiAWIAAgDCAAIAxJG0EAIABBAE4bIANsQQJ0aiIX/QYCCP3VASBN/c4BIU0gTiAX/QYCAP3VASBP/c4BIU8gAEEBaiEAIAJBBGohAiALQQFrIgsNAAsLIAMgBE0NBCAEIA1qQv8BIE/9HQBCGIgiRSBFQv8BWhs8AAAgBEEBciIAIANPDQUgACANakL/ASBP/R0BQhiIIkUgRUL/AVobPAAAIARBAnIiACADTw0GIAAgDWpC/wEgTf0dAEIYiCJFIEVC/wFaGzwAACAEQQNyIgAgA08NAiAAIA1qQv8BIE39HQFCGIgiRSBFQv8BWhs8AAAgCkEBaiEAIBQgBEEEaiIETw0BDAcLCwweCyAAIANBhITAABAYAAsgDiAAIAdBtIPAABAbAAsgBCADQdSDwAAQGAALIAAgA0Hkg8AAEBgACyAAIANB9IPAABAYAAsCQCADIARNDQAgEUUEQCAQIApBAnQiAGsiAkUNASAAIAlqIA5qQQRqQQAgAvwLAAwBCwJAIAxBAE4EQANAIARBAWpBACEAQgAhRSABIQIDQCAAIAhqIg4gDCAMIA5LG0EAIA5BAE4bIANsIARqIg4gB08NAyACNQIAIAUgDkECdGo1AgB+IEV8IUUgAkEEaiECIBEgAEEBaiIARw0ACyAEIA1qQv8BIEVCGIgiRSBFQv8BWhs8AAAiBCADRw0ADAMLAAsMGgsgDiAHQcSDwAAQGAALIAhBAWohCCAVIBhHDQALDBALQQAgA2shEiAFIAJBAnRqIQEgAyAHIAMgB0kbIAJqIQggBSACIANqQQJ0aiELIAMhBCAHIQAgCSEKA0AgACACRg0CIAAgCEYEQCACIBJrIAdBpIbAABAYAAsgAiAKakL/ASALNQIAIEV+IEYgATUCAH58QhiIIkkgSUL/AVobPAAAIABBAWshACALQQRqIQsgEkEBayESIApBAWohCiABQQRqIQEgAiAEQQFrIgRHDQALCyAYQQFrIgBBACAAIBhNGyIvQQJJDQ0CQCADQQRPBEAgRf0SIU0gR/0SIU8gSP0SIU4gBUEQaiIZIANBAnQiJGohHyAZIANBA3RqISAgAyAJaiElQXwgA2shHSADQQVrITAgCSADQQRqIjFqISFBfCADQQF0IjJrISYgAyEbQXwhJ0EEISggMiIcQQRqIjghKSAxIRFBASEqDAELIANFDQ4gA0EDdCERIANBAXQhDSADIAlqIQ4gA0ECdCEQQQAhCiAFIQFBASELA0AgCiANaiICIAMgCmoiBCISSSACIAdLcg0NIAcgCk0NDCAHIBJNDQkgAiAHTw0DIAogDmoiCEL/ASABIgAgEGoiATUCACBHfiAANQIAIEh+fCAAIBFqIgw1AgAgRX58QhiIIkYgRkL/AVobPAAAAkAgA0EBRg0AIApBAWogB08NDCAEQQFqIAdPDQkgAkEBaiAHTw0GIAhBAWpC/wEgAUEEajUCACBHfiAAQQRqNQIAIEh+fCAMQQRqNQIAIEV+fEIYiCJGIEZC/wFaGzwAACADQQJGDQAgCkECaiAHTw0LIARBAmogB08NCCACQQJqIAdPDQUgCEECakL/ASABQQhqNQIAIEd+IABBCGo1AgAgSH58IAxBCGo1AgAgRX58QhiIIkYgRkL/AVobPAAACyADIApqIQogC0EBaiILIC9JDQALDA4LA0AgKiIBQQFqIiogA2wiACABIANsIgJJIAAgB0tyDQ0gAyAzbCIiQQRqITkgIiAyaiE6ICIgOGohOyADICJqITwgIiAxaiE9IAIgCWohIyAFIABBAnRqIT4gBSACQQJ0aiE/IAUgAUEBayADbEECdGohQEEEIQtBACEIICEhDCAZIRAgHyENICAhASAnIRUgKCEXICYhDiApIRMgHSEWIBEhGiAwIRRBACEAAkACQAJAAkACQAJAA0AgCyECIAghQSAUITQgGiE1IBYhQiATITYgDiFDIBchNyAVIUQgASEKIA0hEiAQIQQgDCErIAAgA08NASAAICNqQv8BIE8gPyAAQQJ0Igtq/QACACJQ/ckB/dUBIE4gCyBAav0AAgAiUf3JAf3VAf3OASBNIAsgPmr9AAIAIlL9yQH91QH9zgEiU/0dAEIYiCJGIEZC/wFaGzwAACAAQQFyIgsgA08NAiALICNqQv8BIFP9HQFCGIgiRiBGQv8BWhs8AAAgAEECciILIANPDQMgCyAjakL/ASBPIFD9ygH91QEgTiBR/coB/dUB/c4BIE0gUv3KAf3VAf3OASJQ/R0AQhiIIkYgRkL/AVobPAAAIABBA3IiACADTw0EIAAgI2pC/wEgUP0dAUIYiCJGIEZC/wFaGzwAACAMQQRqIQwgBEEQaiEQIA1BEGohDSABQRBqIQEgFUEEayEVIBdBBGohFyAOQQRrIQ4gE0EEaiETIBZBBGshFiAaQQRqIRogFEEEayEUIAhBAWohCCACIgBBBGoiCyADTQ0ACyAAIANPDQUgMCBBQQJ0IgBrIghBACAAayIBIDxrIAcgACA9aiIMIAcgDEsbakEEayIMIAggDEkbIgggASA6ayAHIAAgO2oiDCAHIAxLG2pBBGsiDCAIIAxJGyIIIAEgImsgByAAIDlqIgAgACAHSRtqQQRrIgAgACAISxtBAWoiAUEETQ0EIAFBA3EiAEEEIAAbIgggNCAHIDUgByA1SxsgQmoiACAAIDRLGyIAIAcgNiAHIDZLGyBDaiIMIAAgDEkbIgAgByA3IAcgN0sbIERqIgwgACAMSRtBf3NqIQAgAiABIAhraiECA0AgKyBPIBL9AAIAIlH9yQH91QEgTiAE/QACACJS/ckB/dUB/c4BIE0gCv0AAgAiU/3JAf3VAf3OAUEY/c0BIlD9DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgUP0dAEL/AVQbQn9CACBQ/R0BQv8BVBv9HgH9UiBPIFH9ygH91QEgTiBS/coB/dUB/c4BIE0gU/3KAf3VAf3OAUEY/c0BIlD9DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgUP0dAEL/AVQbQn9CACBQ/R0BQv8BVBv9HgH9Uv0NAAgQGAAAAAAAAAAAAAAAAP1aAAAAICtBBGohKyAEQRBqIQQgEkEQaiESIApBEGohCiAAQQRqIgANAAsMBAsgACADQdSFwAAQGAALIAsgA0HkhcAAEBgACyALIANB9IXAABAYAAsgACADQYSGwAAQGAALIAUgAiAsakECdGohACAFIAIgG2pBAnRqIQEgBSACIBxqQQJ0aiELA0AgAiAsaiIKIAdPDQ0gAiAbaiISIAdPDQogAiAcaiIKIAdPDQcgAiAlakL/ASABNQIAIEd+IAA1AgAgSH58IAs1AgAgRX58QhiIIkYgRkL/AVobPAAAIABBBGohACABQQRqIQEgC0EEaiELIAJBAWoiAiADRw0ACwsgAyAsaiEsIAMgG2ohGyADIBxqIRwgAyAlaiElIAMgIWohISAZICRqIRkgHyAkaiEfICAgJGohICAnIANrIScgAyAoaiEoICYgA2shJiADIClqISkgHSADayEdIAMgEWohESAzQQFqITMgKiAvSQ0ACwwNCyAHIAdBlIbAABAYAAsgA0EBdCAKaiEKDAILIANBAXQgCmpBAmohCgwBCyADQQF0IApqQQFqIQoLIAogB0HEhcAAEBgACyAEQQJqIRIMAQsgBEEBaiESCyASIAdBtIXAABAYAAsgCkECaiEKDAELIApBAWohCgsgCiAHQaSFwAAQGAALIAMgCmohAiADQQF0IApqIQALIAIgACAHQZSFwAAQGwALIBhBAkkNACAHIAMgGGwiAE8gACAYQQFrIANsIghPcUUEQCAIIAAgB0HkhMAAEBsACyADRQ0AQQAhBCBFIEd8IUUgByAIayIAQQAgACAHTRsiACAHIBhBAmsgA2wiDGsiAUEAIAEgB00bIgEgACABSRsiACADQQFrIgEgACABSRsiAUEBaiIKQQVPBEAgCCAJaiEAIApBA3EiAkEEIAIbIgQgAUF/c2ohCyAFIAhBAnRqIQIgBSAMQQJ0aiEBIAogBGshBCBI/RIhTSBF/RIhTwNAIAAgTyAC/QACACJQ/ckB/dUBIE0gAf0AAgAiUf3JAf3VAf3OAUEY/c0BIk79DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgTv0dAEL/AVQbQn9CACBO/R0BQv8BVBv9HgH9UiBPIFD9ygH91QEgTSBR/coB/dUB/c4BQRj9zQEiTv0M/wAAAAAAAAD/AAAAAAAAAP0M//////////////////////0MAAAAAAAAAAAAAAAAAAAAACBO/R0AQv8BVBtCf0IAIE79HQFC/wFUG/0eAf1S/Q0ACBAYAAAAAAAAAAAAAAAA/VoAAAAgAEEEaiEAIAFBEGohASACQRBqIQIgC0EEaiILDQALCyAFIARBAnQiACAMQQJ0amohASAFIAhBAnQgAGpqIQsgAyAEayEKIAQgDGohAiAEIAhqIQACQAJAA0AgAiAHTw0CIAAgB08NASAAIAlqQv8BIEUgCzUCAH4gATUCACBIfnxCGIgiRyBHQv8BWhs8AAAgAUEEaiEBIAJBAWohAiALQQRqIQsgAEEBaiEAIApBAWsiCg0ACwwCCyAAIAdBhIXAABAYAAsgAiAHQfSEwAAQGAALIC4EQCAFIC5BAnQQNAsgLQRAIB4gLUECdBA0CyAPQTBqJAAPCyAPQQA2AhwgD0EBNgIQIA9BiInAADYCDCAPQgQ3AhQgD0EMakGQicAAECYACyAPQQA2AhwgD0EBNgIQIA9BwIjAADYCDCAPQgQ3AhQgD0EMakHIiMAAECYACyAQIAkQLQALIAcgChAtAAtBASACEC0AC0EEIAkQLQALQYCAwABBHEGkg8AAEBwAC7QFARV/IANBAXQiCyAEbCIIQQF0IQYCQCAIQQBIIAZB/v///wdLcg0AAn8gBkUEQEECIQlBAAwBC0ECIQogBkECEDYiCUUNASAICyEKIAAgCDYCCCAAIAk2AgQgACAKNgIAAkACQAJAAkACQAJAAkACQAJAAkAgBEEBayISQQJJIANBAWtBAklyRQRAIAEgA2ohDSALQQJqIQogASALaiEOIANBAmshEyADQQJ0IhQgCWpBBGohCSADIQYgCyEPQQEhEANAIBBBAWohECAJIQAgCiEEQQAhBwNAIAcgEWoiBSACTw0DIAVBAWogAk8NBCAFQQJqIAJPDQUgBiAHaiIFIAJPDQYgBUECaiACTw0HIAcgD2oiBSACTw0IIAVBAWogAk8NCSAFQQJqIAJPDQogBCAITw0LIAEgB2oiBUEBai0AACEVIAcgDmoiDEEBai0AACEWIAAgDEECai0AACIXIAVBAmotAAAiGCAHIA1qIhlBAmotAAAgGS0AAGtBAXRqIAUtAAAiBSAMLQAAIgxqa2o7AQAgBEEBaiAITw0MIABBAmogDCAFIBhqayAXaiAWIBVrQQF0ajsBACAAQQRqIQAgBEECaiEEIBMgB0EBaiIHRw0ACyADIA1qIQ0gAyARaiERIAMgBmohBiAJIBRqIQkgCiALaiEKIAMgDmohDiABIANqIQEgAyAPaiEPIBAgEkcNAAsLDwsgBSACQdyKwAAQGAALIAVBAWogAkHsisAAEBgACyAFQQJqIAJB/IrAABAYAAsgBSACQYyLwAAQGAALIAVBAmogAkGci8AAEBgACyAFIAJBrIvAABAYAAsgBUEBaiACQbyLwAAQGAALIAVBAmogAkHMi8AAEBgACyAEIAhB3IvAABAYAAsgBEEBaiAIQeyLwAAQGAALIAogBhAtAAuUBgEFfyAAQQhrIgEgAEEEaygCACIDQXhxIgBqIQICQAJAIANBAXENACADQQJxRQ0BIAEoAgAiAyAAaiEAIAEgA2siAUHIm8AAKAIARgRAIAIoAgRBA3FBA0cNAUHAm8AAIAA2AgAgAiACKAIEQX5xNgIEIAEgAEEBcjYCBCACIAA2AgAPCyABIAMQCgsCQAJAAkACQAJAIAIoAgQiA0ECcUUEQCACQcybwAAoAgBGDQIgAkHIm8AAKAIARg0DIAIgA0F4cSICEAogASAAIAJqIgBBAXI2AgQgACABaiAANgIAIAFByJvAACgCAEcNAUHAm8AAIAA2AgAPCyACIANBfnE2AgQgASAAQQFyNgIEIAAgAWogADYCAAsgAEGAAkkNAiABIAAQC0EAIQFB4JvAAEHgm8AAKAIAQQFrIgA2AgAgAA0EQaiZwAAoAgAiAARAA0AgAUEBaiEBIAAoAggiAA0ACwtB4JvAAEH/HyABIAFB/x9NGzYCAA8LQcybwAAgATYCAEHEm8AAQcSbwAAoAgAgAGoiADYCACABIABBAXI2AgRByJvAACgCACABRgRAQcCbwABBADYCAEHIm8AAQQA2AgALIABB2JvAACgCACIDTQ0DQcybwAAoAgAiAkUNA0EAIQBBxJvAACgCACIEQSlJDQJBoJnAACEBA0AgAiABKAIAIgVPBEAgAiAFIAEoAgRqSQ0ECyABKAIIIQEMAAsAC0HIm8AAIAE2AgBBwJvAAEHAm8AAKAIAIABqIgA2AgAgASAAQQFyNgIEIAAgAWogADYCAA8LAkBBuJvAACgCACICQQEgAEEDdnQiA3FFBEBBuJvAACACIANyNgIAIABB+AFxQbCZwABqIgAhAgwBCyAAQfgBcSIAQbCZwABqIQIgAEG4mcAAaigCACEACyACIAE2AgggACABNgIMIAEgAjYCDCABIAA2AggPC0GomcAAKAIAIgEEQANAIABBAWohACABKAIIIgENAAsLQeCbwABB/x8gACAAQf8fTRs2AgAgAyAETw0AQdibwABBfzYCAAsLuAQBCH8jAEEQayIDJAAgAyABNgIEIAMgADYCACADQqCAgIAONwIIAn8CQAJAAkAgAigCECIJBEAgAigCFCIADQEMAgsgAigCDCIARQ0BIAIoAggiASAAQQN0IgBqIQQgAEEIa0EDdkEBaiEGIAIoAgAhAANAAkAgAEEEaigCACIFRQ0AIAMoAgAgACgCACAFIAMoAgQoAgwRAwBFDQBBAQwFC0EBIAEoAgAgAyABQQRqKAIAEQIADQQaIABBCGohACAEIAFBCGoiAUcNAAsMAgsgAEEYbCEKIABBAWtB/////wFxQQFqIQYgAigCCCEEIAIoAgAhAANAAkAgAEEEaigCACIBRQ0AIAMoAgAgACgCACABIAMoAgQoAgwRAwBFDQBBAQwEC0EAIQdBACEIAkACQAJAIAUgCWoiAUEIai8BAEEBaw4CAQIACyABQQpqLwEAIQgMAQsgBCABQQxqKAIAQQN0ai8BBCEICwJAAkACQCABLwEAQQFrDgIBAgALIAFBAmovAQAhBwwBCyAEIAFBBGooAgBBA3RqLwEEIQcLIAMgBzsBDiADIAg7AQwgAyABQRRqKAIANgIIQQEgBCABQRBqKAIAQQN0aiIBKAIAIAMgASgCBBECAA0DGiAAQQhqIQAgBUEYaiIFIApHDQALDAELCwJAIAYgAigCBE8NACADKAIAIAIoAgAgBkEDdGoiACgCACAAKAIEIAMoAgQoAgwRAwBFDQBBAQwBC0EACyADQRBqJAALjwQBAn8gACABaiECAkACQCAAKAIEIgNBAXENACADQQJxRQ0BIAAoAgAiAyABaiEBIAAgA2siAEHIm8AAKAIARgRAIAIoAgRBA3FBA0cNAUHAm8AAIAE2AgAgAiACKAIEQX5xNgIEIAAgAUEBcjYCBCACIAE2AgAMAgsgACADEAoLAkACQAJAIAIoAgQiA0ECcUUEQCACQcybwAAoAgBGDQIgAkHIm8AAKAIARg0DIAIgA0F4cSICEAogACABIAJqIgFBAXI2AgQgACABaiABNgIAIABByJvAACgCAEcNAUHAm8AAIAE2AgAPCyACIANBfnE2AgQgACABQQFyNgIEIAAgAWogATYCAAsgAUGAAk8EQCAAIAEQCw8LAkBBuJvAACgCACICQQEgAUEDdnQiA3FFBEBBuJvAACACIANyNgIAIAFB+AFxQbCZwABqIgEhAgwBCyABQfgBcSIBQbCZwABqIQIgAUG4mcAAaigCACEBCyACIAA2AgggASAANgIMIAAgAjYCDCAAIAE2AggPC0HMm8AAIAA2AgBBxJvAAEHEm8AAKAIAIAFqIgE2AgAgACABQQFyNgIEIABByJvAACgCAEcNAUHAm8AAQQA2AgBByJvAAEEANgIADwtByJvAACAANgIAQcCbwABBwJvAACgCACABaiIBNgIAIAAgAUEBcjYCBCAAIAFqIAE2AgALC+cCAQV/AkAgAUHN/3tBECAAIABBEE0bIgBrTw0AIABBECABQQtqQXhxIAFBC0kbIgRqQQxqEAEiAkUNACACQQhrIQECQCAAQQFrIgMgAnFFBEAgASEADAELIAJBBGsiBSgCACIGQXhxIAIgA2pBACAAa3FBCGsiAiAAQQAgAiABa0EQTRtqIgAgAWsiAmshAyAGQQNxBEAgACADIAAoAgRBAXFyQQJyNgIEIAAgA2oiAyADKAIEQQFyNgIEIAUgAiAFKAIAQQFxckECcjYCACABIAJqIgMgAygCBEEBcjYCBCABIAIQCAwBCyABKAIAIQEgACADNgIEIAAgASACajYCAAsCQCAAKAIEIgFBA3FFDQAgAUF4cSICIARBEGpNDQAgACAEIAFBAXFyQQJyNgIEIAAgBGoiASACIARrIgRBA3I2AgQgACACaiICIAIoAgRBAXI2AgQgASAEEAgLIABBCGohAwsgAwuCAwEEfyAAKAIMIQICQAJAAkAgAUGAAk8EQCAAKAIYIQMCQAJAIAAgAkYEQCAAQRRBECAAKAIUIgIbaigCACIBDQFBACECDAILIAAoAggiASACNgIMIAIgATYCCAwBCyAAQRRqIABBEGogAhshBANAIAQhBSABIgJBFGogAkEQaiACKAIUIgEbIQQgAkEUQRAgARtqKAIAIgENAAsgBUEANgIACyADRQ0CAkAgACgCHEECdEGgmMAAaiIBKAIAIABHBEAgAygCECAARg0BIAMgAjYCFCACDQMMBAsgASACNgIAIAJFDQQMAgsgAyACNgIQIAINAQwCCyAAKAIIIgAgAkcEQCAAIAI2AgwgAiAANgIIDwtBuJvAAEG4m8AAKAIAQX4gAUEDdndxNgIADwsgAiADNgIYIAAoAhAiAQRAIAIgATYCECABIAI2AhgLIAAoAhQiAEUNACACIAA2AhQgACACNgIYDwsPC0G8m8AAQbybwAAoAgBBfiAAKAIcd3E2AgALxAIBBH8gAEIANwIQIAACf0EAIAFBgAJJDQAaQR8gAUH///8HSw0AGiABQSYgAUEIdmciA2t2QQFxIANBAXRrQT5qCyICNgIcIAJBAnRBoJjAAGohBEEBIAJ0IgNBvJvAACgCAHFFBEAgBCAANgIAIAAgBDYCGCAAIAA2AgwgACAANgIIQbybwABBvJvAACgCACADcjYCAA8LAkACQCABIAQoAgAiAygCBEF4cUYEQCADIQIMAQsgAUEZIAJBAXZrQQAgAkEfRxt0IQUDQCADIAVBHXZBBHFqIgQoAhAiAkUNAiAFQQF0IQUgAiEDIAIoAgRBeHEgAUcNAAsLIAIoAggiASAANgIMIAIgADYCCCAAQQA2AhggACACNgIMIAAgATYCCA8LIARBEGogADYCACAAIAM2AhggACAANgIMIAAgADYCCAv7BQIKfwF+IwBBEGsiCCQAQQohAiAAKAIAIgQhAyAEQegHTwRAIAQhAANAIAhBBmogAmoiBkEEayAAIABBkM4AbiIDQZDOAGxrIgdB//8DcUHkAG4iBUEBdC8AqJRAOwAAIAZBAmsgByAFQeQAbGtB//8DcUEBdC8AqJRAOwAAIAJBBGshAiAAQf+s4gRLIAMhAA0ACwsCQCADQQlNBEAgAyEADAELIAJBAmsiAiAIQQZqaiADIANB//8DcUHkAG4iAEHkAGxrQf//A3FBAXQvAKiUQDsAAAtBACAEIAAbRQRAIAJBAWsiAiAIQQZqaiAAQQF0LQCplEA6AAALAn8gCEEGaiACaiEKQQogAmshBkEAIQRBAUErQYCAxAAgASgCCCICQYCAgAFxIgAbIQtBACACQYCAgARxGyEHAkAgAEEVdiAGaiIAIAEvAQwiA0kEQAJAAkAgAkGAgIAIcUUEQCADIABrIQNBACEAAkACQAJAIAJBHXZBA3FBAWsOAwABAAILIAMhAAwBCyADQf7/A3FBAXYhAAsgAkH///8AcSEJIAEoAgQhBSABKAIAIQEDQCAEQf//A3EgAEH//wNxTw0CQQEhAiAEQQFqIQQgASAJIAUoAhARAgBFDQALDAQLIAEgASkCCCIMp0GAgID/eXFBsICAgAJyNgIIQQEhAiABKAIAIgUgASgCBCIJIAsgBxAeDQMgAyAAa0H//wNxIQADQCAEQf//A3EgAE8NAiAEQQFqIQQgBUEwIAkoAhARAgBFDQALDAMLQQEhAiABIAUgCyAHEB4NAiABIAogBiAFKAIMEQMADQJBACEEIAMgAGtB//8DcSEAA0AgBEH//wNxIgMgAEkhAiAAIANNDQMgBEEBaiEEIAEgCSAFKAIQEQIARQ0ACwwCCyAFIAogBiAJKAIMEQMADQEgASAMNwIIQQAMAgtBASECIAEoAgAiACABKAIEIgEgCyAHEB4NACAAIAogBiABKAIMEQMAIQILIAILIAhBEGokAAuIAgEGfyAAKAIIIgQhAgJ/QQEgAUGAAUkNABpBAiABQYAQSQ0AGkEDQQQgAUGAgARJGwsiBiAAKAIAIARrSwR/IAAgBCAGEBIgACgCCAUgAgsgACgCBGohAgJAIAFBgAFPBEAgAUE/cUGAf3IhBSABQQZ2IQMgAUGAEEkEQCACIAU6AAEgAiADQcABcjoAAAwCCyABQQx2IQcgA0E/cUGAf3IhAyABQf//A00EQCACIAU6AAIgAiADOgABIAIgB0HgAXI6AAAMAgsgAiAFOgADIAIgAzoAAiACIAdBP3FBgH9yOgABIAIgAUESdkFwcjoAAAwBCyACIAE6AAALIAAgBCAGajYCCEEAC58CAgN/AX4jAEFAaiICJAAgASgCAEGAgICAeEYEQCABKAIMIQMgAkEkaiIEQQA2AgAgAkKAgICAEDcCHCACQTBqIAMoAgAiA0EIaikCADcDACACQThqIANBEGopAgA3AwAgAiADKQIANwMoIAJBHGpBjJLAACACQShqEAcaIAJBGGogBCgCACIDNgIAIAIgAikCHCIFNwMQIAFBCGogAzYCACABIAU3AgALIAEpAgAhBSABQoCAgIAQNwIAIAJBCGoiAyABQQhqIgEoAgA2AgAgAUEANgIAIAIgBTcDAEEMQQQQNSIBRQRAQQRBDBA5AAsgASACKQMANwIAIAFBCGogAygCADYCACAAQeyTwAA2AgQgACABNgIAIAJBQGskAAuUAgECfyMAQSBrIgUkAEHwm8AAQfCbwAAoAgAiBkEBajYCAAJAAn9BACAGQQBIDQAaQQFB7JvAAC0AAA0AGkHsm8AAQQE6AABB6JvAAEHom8AAKAIAQQFqNgIAQQILQf8BcSIGQQJHBEAgBkEBcUUNASAFQQhqIAAgASgCGBEAAAwBC0H0m8AAKAIAIgZBAEgNAEH0m8AAIAZBAWo2AgBB+JvAACgCAARAIAUgACABKAIUEQAAIAUgBDoAHSAFIAM6ABwgBSACNgIYIAUgBSkDADcCEEH4m8AAKAIAIAVBEGpB/JvAACgCACgCFBEAAAtB9JvAAEH0m8AAKAIAQQFrNgIAQeybwABBADoAACADRQ0AAAsAC8EBAgN/AX4jAEEwayICJAAgASgCAEGAgICAeEYEQCABKAIMIQMgAkEUaiIEQQA2AgAgAkKAgICAEDcCDCACQSBqIAMoAgAiA0EIaikCADcDACACQShqIANBEGopAgA3AwAgAiADKQIANwMYIAJBDGpBjJLAACACQRhqEAcaIAJBCGogBCgCACIDNgIAIAIgAikCDCIFNwMAIAFBCGogAzYCACABIAU3AgALIABB7JPAADYCBCAAIAE2AgAgAkEwaiQAC6gBAgJ/AX5BASEHQQQhBgJAIAQgBWpBAWtBACAEa3GtIAOtfiIIQiCIUEUEQEEAIQMMAQsgCKciA0GAgICAeCAEa0sEQEEAIQMMAQsCQAJAAn8gAQRAIAIgASAFbCAEIAMQMQwBCyADRQRAIAQhBgwCCyADIAQQNQsiBg0AIAAgBDYCBAwBCyAAIAY2AgRBACEHC0EIIQYLIAAgBmogAzYCACAAIAc2AgALhwEBAX8jAEEQayIDJAAgAiABIAJqIgFLBEBBAEEAEC0ACyADQQRqIAAoAgAiAiAAKAIEQQggASACQQF0IgIgASACSxsiASABQQhNGyIBQQFBARARIAMoAgRBAUYEQCADKAIIIAMoAgwQLQALIAMoAgghAiAAIAE2AgAgACACNgIEIANBEGokAAt5AQF/IwBBIGsiAiQAAn8gACgCAEGAgICAeEcEQCABIAAoAgQgACgCCBAwDAELIAJBEGogACgCDCgCACIAQQhqKQIANwMAIAJBGGogAEEQaikCADcDACACIAApAgA3AwggASgCACABKAIEIAJBCGoQBwsgAkEgaiQAC2kBA38jAEEQayIBJAAgAUEEaiAAKAIAIgIgACgCBEEEIAJBAXQiAiACQQRNGyICQQRBBBARIAEoAgRBAUYEQCABKAIIIAEoAgwQLQALIAEoAgghAyAAIAI2AgAgACADNgIEIAFBEGokAAtpAQN/IwBBEGsiASQAIAFBBGogACgCACICIAAoAgRBBCACQQF0IgIgAkEETRsiAkEEQQgQESABKAIEQQFGBEAgASgCCCABKAIMEC0ACyABKAIIIQMgACACNgIAIAAgAzYCBCABQRBqJAALaQEDfyMAQRBrIgEkACABQQRqIAAoAgAiAiAAKAIEQQQgAkEBdCICIAJBBE0bIgJBAkECEBEgASgCBEEBRgRAIAEoAgggASgCDBAtAAsgASgCCCEDIAAgAjYCACAAIAM2AgQgAUEQaiQACxIAIwBBMGsiACQAIABBMGokAAtoAgF/AX4jAEEwayIDJAAgAyABNgIEIAMgADYCACADQQI2AgwgA0Hkl8AANgIIIANCAjcCFCADQoCAgIAwIgQgA62ENwMoIAMgBCADQQRqrYQ3AyAgAyADQSBqNgIQIANBCGogAhAmAAtHAQF/IAAoAgAgACgCCCIDayACSQRAIAAgAyACEBIgACgCCCEDCyACBEAgACgCBCADaiABIAL8CgAACyAAIAIgA2o2AghBAAtEAQJ/IAEoAgQhAiABKAIAIQNBCEEEEDUiAUUEQEEEQQgQOQALIAEgAjYCBCABIAM2AgAgAEHcksAANgIEIAAgATYCAAvGAgACQCAAIAJNBEAgACABTSABIAJLcg0BIwBBMGsiAiQAIAIgATYCBCACIAA2AgAgAkECNgIMIAJB2JbAADYCCCACQgI3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IAAAABwAEAAMAAAAEgAAABcAAAAcABAADAAAABcAAAAZAAAAHAAQAAwAAAAUAAAAGQAAABwAEAAMAAAAUQAAAB4AAAAcABAADAAAAFIAAAAeAAAATGF6eSBpbnN0YW5jZSBoYXMgcHJldmlvdXNseSBiZWVuIHBvaXNvbmVkAAA8CBAAKgAAAEgBEABaAAAACAMAABkAAAByZWVudHJhbnQgaW5pdAAAgAgQAA4AAABIARAAWgAAAHoCAAANAAAAfP2LMlfmV/kC30S/40jnr21dy9YsUOtjeEGmV3Ebi7ltZW1vcnkgYWxsb2NhdGlvbiBvZiAgYnl0ZXMgZmFpbGVkAADICBAAFQAAAN0IEAANAAAALwEQABgAAABkAQAACQAAAAQAAAAMAAAABAAAAAUAAAAGAAAABwAAAAAAAAAIAAAABAAAAAgAAAAJAAAACgAAAAsAAAAMAAAAEAAAAAQAAAANAAAADgAAAA8AAAAQAAAAAAAAAAgAAAAEAAAAEQAAAGFzc2VydGlvbiBmYWlsZWQ6IHBzaXplID49IHNpemUgKyBtaW5fb3ZlcmhlYWQAAAQBEAAqAAAAsQQAAAkAAABhc3NlcnRpb24gZmFpbGVkOiBwc2l6ZSA8PSBzaXplICsgbWF4X292ZXJoZWFkAAAEARAAKgAAALcEAAANAAAABAAAAAwAAAAEAAAAEgAAAGNhcGFjaXR5IG92ZXJmbG93AAAA/AkQABEAAADjABAAIAAAABwAAAAFAAAAMDAwMTAyMDMwNDA1MDYwNzA4MDkxMDExMTIxMzE0MTUxNjE3MTgxOTIwMjEyMjIzMjQyNTI2MjcyODI5MzAzMTMyMzMzNDM1MzYzNzM4Mzk0MDQxNDI0MzQ0NDU0NjQ3NDg0OTUwNTE1MjUzNTQ1NTU2NTc1ODU5NjA2MTYyNjM2NDY1NjY2NzY4Njk3MDcxNzI3Mzc0NzU3Njc3Nzg3OTgwODE4MjgzODQ4NTg2ODc4ODg5OTA5MTkyOTM5NDk1OTY5Nzk4OTlyYW5nZSBlbmQgaW5kZXggIG91dCBvZiByYW5nZSBmb3Igc2xpY2Ugb2YgbGVuZ3RoIAAA8AoQABAAAAAACxAAIgAAAHNsaWNlIGluZGV4IHN0YXJ0cyBhdCAgYnV0IGVuZHMgYXQgADQLEAAWAAAASgsQAA0AAAByYW5nZSBzdGFydCBpbmRleCAAAGgLEAASAAAAAAsQACIAAABhdHRlbXB0IHRvIGRpdmlkZSBieSB6ZXJvAAAAjAsQABkAAABpbmRleCBvdXQgb2YgYm91bmRzOiB0aGUgbGVuIGlzICBidXQgdGhlIGluZGV4IGlzIAAAsAsQACAAAADQCxAAEgAAAAAAAD8AAAC/AEGUmMAACwEBAHwJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVydXN0Yx0xLjkyLjAgKGRlZDVjMDZjZiAyMDI1LTEyLTA4KQZ3YWxydXMGMC4yMy4zDHdhc20tYmluZGdlbhMwLjIuMTAwICgyNDA1ZWMyYjQpAHQPdGFyZ2V0X2ZlYXR1cmVzBysPbXV0YWJsZS1nbG9iYWxzKxNub250cmFwcGluZy1mcHRvaW50KwdzaW1kMTI4KwtidWxrLW1lbW9yeSsIc2lnbi1leHQrD3JlZmVyZW5jZS10eXBlcysKbXVsdGl2YWx1ZQ==";
|
|
672
|
-
let wasmReadyPromise = null;
|
|
673
|
-
let hasLoggedFullCannyFallback = false;
|
|
674
|
-
let wasmSupportCache = null;
|
|
675
|
-
function wasmBytesFromBase64(base64) {
|
|
676
|
-
if (typeof atob === "function") {
|
|
677
|
-
const binary = atob(base64);
|
|
678
|
-
const bytes = new Uint8Array(binary.length);
|
|
679
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
680
|
-
return bytes;
|
|
681
|
-
}
|
|
682
|
-
return Uint8Array.from(Buffer.from(base64, "base64"));
|
|
683
|
-
}
|
|
684
|
-
function wasmModuleSupported() {
|
|
685
|
-
if (wasmSupportCache !== null) return wasmSupportCache;
|
|
686
|
-
let supported = false;
|
|
687
|
-
if (typeof WebAssembly === "object" && typeof WebAssembly.validate === "function") {
|
|
688
|
-
const simd = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11]);
|
|
689
|
-
const referenceTypes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 111, 3, 2, 1, 0, 10, 6, 1, 4, 0, 208, 111, 11]);
|
|
690
|
-
try {
|
|
691
|
-
supported = WebAssembly.validate(simd) && WebAssembly.validate(referenceTypes);
|
|
692
|
-
} catch {
|
|
693
|
-
supported = false;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
wasmSupportCache = supported;
|
|
697
|
-
return supported;
|
|
698
|
-
}
|
|
699
|
-
async function initializeWasmInternal() {
|
|
700
|
-
if (!wasmModuleSupported()) {
|
|
701
|
-
throw new Error("scanic: WebAssembly features required by the WASM module (reference-types, SIMD) are unavailable in this engine; using the JavaScript fallback.");
|
|
702
|
-
}
|
|
703
|
-
return await __wbg_init({ module_or_path: wasmBytesFromBase64(wasmBase64) });
|
|
704
|
-
}
|
|
705
|
-
function initializeWasm() {
|
|
706
|
-
if (!wasmReadyPromise) {
|
|
707
|
-
wasmReadyPromise = initializeWasmInternal().catch((error) => {
|
|
708
|
-
wasmReadyPromise = null;
|
|
709
|
-
throw error;
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
return wasmReadyPromise;
|
|
713
|
-
}
|
|
714
|
-
function convertToGrayscale(imageData) {
|
|
715
|
-
const { width, height, data } = imageData;
|
|
716
|
-
const grayscale = new Uint8ClampedArray(width * height);
|
|
717
|
-
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
|
|
718
|
-
grayscale[j] = data[i] * 54 + data[i + 1] * 183 + data[i + 2] * 19 >> 8;
|
|
719
|
-
}
|
|
720
|
-
return grayscale;
|
|
721
|
-
}
|
|
722
|
-
function gaussianBlurGrayscale(grayscale, width, height, kernelSize = 5, sigma = 0) {
|
|
723
|
-
if (sigma === 0) {
|
|
724
|
-
sigma = 0.3 * ((kernelSize - 1) * 0.5 - 1) + 0.8;
|
|
725
|
-
}
|
|
726
|
-
const halfKernel = Math.floor(kernelSize / 2);
|
|
727
|
-
const kernel = createGaussianKernel(kernelSize, sigma);
|
|
728
|
-
const tempArray = new Uint8ClampedArray(width * height);
|
|
729
|
-
const blurred = new Uint8ClampedArray(width * height);
|
|
730
|
-
for (let y = 0; y < height; y++) {
|
|
731
|
-
const rowOffset = y * width;
|
|
732
|
-
for (let x = 0; x < width; x++) {
|
|
733
|
-
let sum = 0;
|
|
734
|
-
for (let k = -halfKernel; k <= halfKernel; k++) {
|
|
735
|
-
const xOffset = Math.min(width - 1, Math.max(0, x + k));
|
|
736
|
-
sum += grayscale[rowOffset + xOffset] * kernel[halfKernel + k];
|
|
737
|
-
}
|
|
738
|
-
tempArray[rowOffset + x] = sum;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
for (let x = 0; x < width; x++) {
|
|
742
|
-
for (let y = 0; y < height; y++) {
|
|
743
|
-
let sum = 0;
|
|
744
|
-
for (let k = -halfKernel; k <= halfKernel; k++) {
|
|
745
|
-
const yOffset = Math.min(height - 1, Math.max(0, y + k));
|
|
746
|
-
sum += tempArray[yOffset * width + x] * kernel[halfKernel + k];
|
|
747
|
-
}
|
|
748
|
-
blurred[y * width + x] = Math.round(sum);
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return blurred;
|
|
752
|
-
}
|
|
753
|
-
function createGaussianKernel(size, sigma) {
|
|
754
|
-
const kernel = new Float32Array(size);
|
|
755
|
-
const halfSize = Math.floor(size / 2);
|
|
756
|
-
let sum = 0;
|
|
757
|
-
for (let i = 0; i < size; i++) {
|
|
758
|
-
const x = i - halfSize;
|
|
759
|
-
kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma));
|
|
760
|
-
sum += kernel[i];
|
|
761
|
-
}
|
|
762
|
-
for (let i = 0; i < size; i++) {
|
|
763
|
-
kernel[i] /= sum;
|
|
764
|
-
}
|
|
765
|
-
return kernel;
|
|
766
|
-
}
|
|
767
|
-
function calculateGradients(blurred, width, height) {
|
|
768
|
-
const dx = new Int16Array(width * height);
|
|
769
|
-
const dy = new Int16Array(width * height);
|
|
770
|
-
for (let y = 1; y < height - 1; y++) {
|
|
771
|
-
const rowOffset = y * width;
|
|
772
|
-
const prevRowOffset = (y - 1) * width;
|
|
773
|
-
const nextRowOffset = (y + 1) * width;
|
|
774
|
-
for (let x = 1; x < width - 1; x++) {
|
|
775
|
-
const currentIdx = rowOffset + x;
|
|
776
|
-
const p0 = blurred[prevRowOffset + x - 1];
|
|
777
|
-
const p1 = blurred[prevRowOffset + x];
|
|
778
|
-
const p2 = blurred[prevRowOffset + x + 1];
|
|
779
|
-
const p3 = blurred[rowOffset + x - 1];
|
|
780
|
-
const p5 = blurred[rowOffset + x + 1];
|
|
781
|
-
const p6 = blurred[nextRowOffset + x - 1];
|
|
782
|
-
const p7 = blurred[nextRowOffset + x];
|
|
783
|
-
const p8 = blurred[nextRowOffset + x + 1];
|
|
784
|
-
const gx = p2 - p0 + 2 * (p5 - p3) + (p8 - p6);
|
|
785
|
-
const gy = p6 + 2 * p7 + p8 - (p0 + 2 * p1 + p2);
|
|
786
|
-
dx[currentIdx] = gx;
|
|
787
|
-
dy[currentIdx] = gy;
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
return { dx, dy };
|
|
791
|
-
}
|
|
792
|
-
function nonMaximumSuppression(dx, dy, width, height, L2gradient) {
|
|
793
|
-
const magnitude = new Float32Array(width * height);
|
|
794
|
-
const suppressed = new Float32Array(width * height);
|
|
795
|
-
for (let i = 0; i < dx.length; i++) {
|
|
796
|
-
const gx = dx[i];
|
|
797
|
-
const gy = dy[i];
|
|
798
|
-
if (L2gradient) {
|
|
799
|
-
magnitude[i] = Math.sqrt(gx * gx + gy * gy);
|
|
800
|
-
} else {
|
|
801
|
-
magnitude[i] = Math.abs(gx) + Math.abs(gy);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
for (let y = 1; y < height - 1; y++) {
|
|
805
|
-
for (let x = 1; x < width - 1; x++) {
|
|
806
|
-
const idx = y * width + x;
|
|
807
|
-
const mag = magnitude[idx];
|
|
808
|
-
if (mag === 0) {
|
|
809
|
-
suppressed[idx] = 0;
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
const gx = dx[idx];
|
|
813
|
-
const gy = dy[idx];
|
|
814
|
-
let neighbor1 = 0, neighbor2 = 0;
|
|
815
|
-
const absGx = Math.abs(gx);
|
|
816
|
-
const absGy = Math.abs(gy);
|
|
817
|
-
if (absGy > absGx * 2.4142) {
|
|
818
|
-
neighbor1 = magnitude[idx - width];
|
|
819
|
-
neighbor2 = magnitude[idx + width];
|
|
820
|
-
} else if (absGx > absGy * 2.4142) {
|
|
821
|
-
neighbor1 = magnitude[idx - 1];
|
|
822
|
-
neighbor2 = magnitude[idx + 1];
|
|
823
|
-
} else {
|
|
824
|
-
const s = (gx ^ gy) < 0 ? -1 : 1;
|
|
825
|
-
if (gy > 0) {
|
|
826
|
-
neighbor1 = magnitude[(y - 1) * width + (x - s)];
|
|
827
|
-
neighbor2 = magnitude[(y + 1) * width + (x + s)];
|
|
828
|
-
} else {
|
|
829
|
-
neighbor1 = magnitude[(y + 1) * width + (x - s)];
|
|
830
|
-
neighbor2 = magnitude[(y - 1) * width + (x + s)];
|
|
831
|
-
}
|
|
832
|
-
if (gx > 0 && gy > 0 || gx < 0 && gy < 0) {
|
|
833
|
-
neighbor1 = magnitude[(y - 1) * width + (x + 1)];
|
|
834
|
-
neighbor2 = magnitude[(y + 1) * width + (x - 1)];
|
|
835
|
-
} else {
|
|
836
|
-
neighbor1 = magnitude[(y - 1) * width + (x - 1)];
|
|
837
|
-
neighbor2 = magnitude[(y + 1) * width + (x + 1)];
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
if (mag >= neighbor1 && mag >= neighbor2) {
|
|
841
|
-
suppressed[idx] = mag;
|
|
842
|
-
} else {
|
|
843
|
-
suppressed[idx] = 0;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
return suppressed;
|
|
848
|
-
}
|
|
849
|
-
function hysteresisThresholding(suppressed, width, height, lowThreshold, highThreshold) {
|
|
850
|
-
const edgeMap = new Uint8Array(width * height);
|
|
851
|
-
const stack = [];
|
|
852
|
-
for (let y = 1; y < height - 1; y++) {
|
|
853
|
-
for (let x = 1; x < width - 1; x++) {
|
|
854
|
-
const idx = y * width + x;
|
|
855
|
-
const mag = suppressed[idx];
|
|
856
|
-
if (mag >= highThreshold) {
|
|
857
|
-
edgeMap[idx] = 2;
|
|
858
|
-
stack.push({ x, y });
|
|
859
|
-
} else if (mag >= lowThreshold) {
|
|
860
|
-
edgeMap[idx] = 0;
|
|
861
|
-
} else {
|
|
862
|
-
edgeMap[idx] = 1;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
for (let x = 0; x < width; x++) {
|
|
867
|
-
edgeMap[x] = 1;
|
|
868
|
-
edgeMap[(height - 1) * width + x] = 1;
|
|
869
|
-
}
|
|
870
|
-
for (let y = 1; y < height - 1; y++) {
|
|
871
|
-
edgeMap[y * width] = 1;
|
|
872
|
-
edgeMap[y * width + width - 1] = 1;
|
|
873
|
-
}
|
|
874
|
-
const dxNeighbors = [-1, 0, 1, -1, 1, -1, 0, 1];
|
|
875
|
-
const dyNeighbors = [-1, -1, -1, 0, 0, 1, 1, 1];
|
|
876
|
-
while (stack.length > 0) {
|
|
877
|
-
const { x, y } = stack.pop();
|
|
878
|
-
for (let i = 0; i < 8; i++) {
|
|
879
|
-
const nx = x + dxNeighbors[i];
|
|
880
|
-
const ny = y + dyNeighbors[i];
|
|
881
|
-
const nidx = ny * width + nx;
|
|
882
|
-
if (edgeMap[nidx] === 0) {
|
|
883
|
-
edgeMap[nidx] = 2;
|
|
884
|
-
stack.push({ x: nx, y: ny });
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
return edgeMap;
|
|
889
|
-
}
|
|
890
|
-
function dilateEdges(edges, width, height, kernelSize = 5) {
|
|
891
|
-
const halfKernel = Math.floor(kernelSize / 2);
|
|
892
|
-
const temp = new Uint8ClampedArray(width * height);
|
|
893
|
-
const dilated = new Uint8ClampedArray(width * height);
|
|
894
|
-
for (let y = 0; y < height; y++) {
|
|
895
|
-
const rowOffset = y * width;
|
|
896
|
-
for (let x = 0; x < width; x++) {
|
|
897
|
-
let maxVal = 0;
|
|
898
|
-
for (let k = -halfKernel; k <= halfKernel; k++) {
|
|
899
|
-
const nx = x + k;
|
|
900
|
-
if (nx >= 0 && nx < width) {
|
|
901
|
-
const val = edges[rowOffset + nx];
|
|
902
|
-
if (val > maxVal) {
|
|
903
|
-
maxVal = val;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
temp[rowOffset + x] = maxVal;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
for (let x = 0; x < width; x++) {
|
|
911
|
-
for (let y = 0; y < height; y++) {
|
|
912
|
-
let maxVal = 0;
|
|
913
|
-
for (let k = -halfKernel; k <= halfKernel; k++) {
|
|
914
|
-
const ny = y + k;
|
|
915
|
-
if (ny >= 0 && ny < height) {
|
|
916
|
-
const val = temp[ny * width + x];
|
|
917
|
-
if (val > maxVal) {
|
|
918
|
-
maxVal = val;
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
dilated[y * width + x] = maxVal;
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
return dilated;
|
|
926
|
-
}
|
|
927
|
-
async function cannyEdgeDetector(input, options = {}) {
|
|
928
|
-
const storeFullDebug = !!(options.debug && !options.debug._timingsOnly);
|
|
929
|
-
const timings = [];
|
|
930
|
-
const tStart = performance.now();
|
|
931
|
-
const skipGrayscale = options.skipGrayscale || false;
|
|
932
|
-
let width, height, grayscale;
|
|
933
|
-
if (skipGrayscale) {
|
|
934
|
-
width = options.width;
|
|
935
|
-
height = options.height;
|
|
936
|
-
grayscale = input;
|
|
937
|
-
if (storeFullDebug) options.debug.grayscale = grayscale;
|
|
938
|
-
} else {
|
|
939
|
-
width = input.width;
|
|
940
|
-
height = input.height;
|
|
941
|
-
let t02 = performance.now();
|
|
942
|
-
grayscale = convertToGrayscale(input);
|
|
943
|
-
let t12 = performance.now();
|
|
944
|
-
timings.push({ step: "Grayscale", ms: (t12 - t02).toFixed(2) });
|
|
945
|
-
if (storeFullDebug) options.debug.grayscale = grayscale;
|
|
946
|
-
}
|
|
947
|
-
let lowThreshold = options.lowThreshold !== void 0 ? options.lowThreshold : null;
|
|
948
|
-
let highThreshold = options.highThreshold !== void 0 ? options.highThreshold : null;
|
|
949
|
-
if (lowThreshold === null || highThreshold === null) {
|
|
950
|
-
const HIST_SIZE = 1024;
|
|
951
|
-
const hist = new Uint32Array(HIST_SIZE);
|
|
952
|
-
let nonZeroCount = 0;
|
|
953
|
-
for (let y = 1; y < height - 1; y++) {
|
|
954
|
-
for (let x = 1; x < width - 1; x++) {
|
|
955
|
-
const idx = y * width + x;
|
|
956
|
-
const gx = -grayscale[idx - width - 1] + grayscale[idx - width + 1] - 2 * grayscale[idx - 1] + 2 * grayscale[idx + 1] - grayscale[idx + width - 1] + grayscale[idx + width + 1];
|
|
957
|
-
const gy = -grayscale[idx - width - 1] - 2 * grayscale[idx - width] - grayscale[idx - width + 1] + grayscale[idx + width - 1] + 2 * grayscale[idx + width] + grayscale[idx + width + 1];
|
|
958
|
-
let mag = Math.abs(gx) + Math.abs(gy);
|
|
959
|
-
if (mag >= HIST_SIZE) mag = HIST_SIZE - 1;
|
|
960
|
-
if (mag > 0) {
|
|
961
|
-
hist[mag]++;
|
|
962
|
-
nonZeroCount++;
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
const targetCount = Math.round(nonZeroCount * 0.7);
|
|
967
|
-
let cumulative = 0;
|
|
968
|
-
let pivot = 75;
|
|
969
|
-
for (let i = 1; i < HIST_SIZE; i++) {
|
|
970
|
-
cumulative += hist[i];
|
|
971
|
-
if (cumulative >= targetCount) {
|
|
972
|
-
pivot = i;
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
if (lowThreshold === null) lowThreshold = Math.max(5, Math.round(pivot * 0.5));
|
|
977
|
-
if (highThreshold === null) highThreshold = Math.min(500, Math.round(pivot * 1.5));
|
|
978
|
-
}
|
|
979
|
-
const kernelSize = options.kernelSize || 5;
|
|
980
|
-
const sigma = options.sigma || 0;
|
|
981
|
-
const L2gradient = options.L2gradient === void 0 ? false : options.L2gradient;
|
|
982
|
-
const applyDilation = options.applyDilation !== void 0 ? options.applyDilation : true;
|
|
983
|
-
const dilationKernelSize = options.dilationKernelSize || 5;
|
|
984
|
-
const useWasmHysteresis = options.useWasmHysteresis !== void 0 ? options.useWasmHysteresis : false;
|
|
985
|
-
const useWasmFullCanny = options.useWasmFullCanny !== void 0 ? options.useWasmFullCanny : false;
|
|
986
|
-
if (lowThreshold >= highThreshold) {
|
|
987
|
-
console.warn(`Canny Edge Detector: lowThreshold (${lowThreshold}) should be lower than highThreshold (${highThreshold}). Swapping them.`);
|
|
988
|
-
[lowThreshold, highThreshold] = [highThreshold, lowThreshold];
|
|
989
|
-
}
|
|
990
|
-
if (useWasmFullCanny) {
|
|
991
|
-
try {
|
|
992
|
-
await initializeWasm();
|
|
993
|
-
const t0w = performance.now();
|
|
994
|
-
const finalEdges2 = new Uint8ClampedArray(
|
|
995
|
-
canny_edge_detector_full(
|
|
996
|
-
grayscale,
|
|
997
|
-
width,
|
|
998
|
-
height,
|
|
999
|
-
lowThreshold,
|
|
1000
|
-
highThreshold,
|
|
1001
|
-
kernelSize,
|
|
1002
|
-
sigma,
|
|
1003
|
-
L2gradient,
|
|
1004
|
-
applyDilation,
|
|
1005
|
-
dilationKernelSize
|
|
1006
|
-
)
|
|
1007
|
-
);
|
|
1008
|
-
const t1w = performance.now();
|
|
1009
|
-
const wasmMs = (t1w - t0w).toFixed(2);
|
|
1010
|
-
timings.push({ step: "Edge Processing (WASM)", ms: wasmMs });
|
|
1011
|
-
if (storeFullDebug) {
|
|
1012
|
-
options.debug.finalEdges = finalEdges2;
|
|
1013
|
-
}
|
|
1014
|
-
if (options.debug) options.debug.timings = timings;
|
|
1015
|
-
else options.debug = { timings };
|
|
1016
|
-
const tEnd2 = performance.now();
|
|
1017
|
-
timings.unshift({ step: "Edge Detection Total", ms: (tEnd2 - tStart).toFixed(2) });
|
|
1018
|
-
return finalEdges2;
|
|
1019
|
-
} catch (error) {
|
|
1020
|
-
if (!hasLoggedFullCannyFallback) {
|
|
1021
|
-
hasLoggedFullCannyFallback = true;
|
|
1022
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
1023
|
-
console.warn(`Full WASM Canny unavailable, using step-by-step path: ${reason}`);
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
let t0, t1;
|
|
1028
|
-
let blurred;
|
|
1029
|
-
t0 = performance.now();
|
|
1030
|
-
{
|
|
1031
|
-
try {
|
|
1032
|
-
await initializeWasm();
|
|
1033
|
-
blurred = blur(grayscale, width, height, kernelSize, sigma);
|
|
1034
|
-
} catch (e) {
|
|
1035
|
-
blurred = gaussianBlurGrayscale(grayscale, width, height, kernelSize, sigma);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
t1 = performance.now();
|
|
1039
|
-
timings.push({ step: "Gaussian Blur", ms: (t1 - t0).toFixed(2) });
|
|
1040
|
-
if (storeFullDebug) {
|
|
1041
|
-
options.debug.blurred = blurred;
|
|
1042
|
-
}
|
|
1043
|
-
t0 = performance.now();
|
|
1044
|
-
let dx, dy;
|
|
1045
|
-
{
|
|
1046
|
-
const gradients = calculateGradients(blurred, width, height);
|
|
1047
|
-
dx = gradients.dx;
|
|
1048
|
-
dy = gradients.dy;
|
|
1049
|
-
}
|
|
1050
|
-
t1 = performance.now();
|
|
1051
|
-
timings.push({ step: "Gradients", ms: (t1 - t0).toFixed(2) });
|
|
1052
|
-
t0 = performance.now();
|
|
1053
|
-
let suppressed;
|
|
1054
|
-
{
|
|
1055
|
-
try {
|
|
1056
|
-
await initializeWasm();
|
|
1057
|
-
suppressed = await non_maximum_suppression(dx, dy, width, height, L2gradient);
|
|
1058
|
-
} catch (e) {
|
|
1059
|
-
suppressed = nonMaximumSuppression(dx, dy, width, height, L2gradient);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
t1 = performance.now();
|
|
1063
|
-
timings.push({ step: "Non-Max Suppression", ms: (t1 - t0).toFixed(2) });
|
|
1064
|
-
t0 = performance.now();
|
|
1065
|
-
const finalLowThreshold = L2gradient ? lowThreshold * lowThreshold : lowThreshold;
|
|
1066
|
-
const finalHighThreshold = L2gradient ? highThreshold * highThreshold : highThreshold;
|
|
1067
|
-
let edgeMap;
|
|
1068
|
-
if (useWasmHysteresis) {
|
|
1069
|
-
try {
|
|
1070
|
-
await initializeWasm();
|
|
1071
|
-
edgeMap = hysteresis_thresholding(suppressed, width, height, finalLowThreshold, finalHighThreshold);
|
|
1072
|
-
} catch (e) {
|
|
1073
|
-
console.warn("WASM hysteresis failed, falling back to JS:", e);
|
|
1074
|
-
edgeMap = hysteresisThresholding(suppressed, width, height, finalLowThreshold, finalHighThreshold);
|
|
1075
|
-
}
|
|
1076
|
-
} else {
|
|
1077
|
-
edgeMap = hysteresisThresholding(suppressed, width, height, finalLowThreshold, finalHighThreshold);
|
|
1078
|
-
}
|
|
1079
|
-
t1 = performance.now();
|
|
1080
|
-
timings.push({ step: "Hysteresis", ms: (t1 - t0).toFixed(2) });
|
|
1081
|
-
t0 = performance.now();
|
|
1082
|
-
const cannyEdges = new Uint8ClampedArray(width * height);
|
|
1083
|
-
for (let i = 0; i < edgeMap.length; i++) {
|
|
1084
|
-
cannyEdges[i] = edgeMap[i] === 2 ? 255 : 0;
|
|
1085
|
-
}
|
|
1086
|
-
t1 = performance.now();
|
|
1087
|
-
timings.push({ step: "Binary Image", ms: (t1 - t0).toFixed(2) });
|
|
1088
|
-
t0 = performance.now();
|
|
1089
|
-
let finalEdges = cannyEdges;
|
|
1090
|
-
if (applyDilation) {
|
|
1091
|
-
{
|
|
1092
|
-
try {
|
|
1093
|
-
await initializeWasm();
|
|
1094
|
-
finalEdges = dilate(cannyEdges, width, height, dilationKernelSize);
|
|
1095
|
-
} catch (e) {
|
|
1096
|
-
finalEdges = dilateEdges(cannyEdges, width, height, dilationKernelSize);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
t1 = performance.now();
|
|
1101
|
-
timings.push({ step: "Dilation", ms: (t1 - t0).toFixed(2) });
|
|
1102
|
-
if (storeFullDebug) {
|
|
1103
|
-
options.debug.dx = dx;
|
|
1104
|
-
options.debug.dy = dy;
|
|
1105
|
-
const magnitude = new Float32Array(width * height);
|
|
1106
|
-
for (let i = 0; i < dx.length; i++) {
|
|
1107
|
-
const gx = dx[i];
|
|
1108
|
-
const gy = dy[i];
|
|
1109
|
-
magnitude[i] = L2gradient ? Math.sqrt(gx * gx + gy * gy) : Math.abs(gx) + Math.abs(gy);
|
|
1110
|
-
}
|
|
1111
|
-
options.debug.magnitude = magnitude;
|
|
1112
|
-
options.debug.suppressed = suppressed;
|
|
1113
|
-
options.debug.edgeMap = edgeMap;
|
|
1114
|
-
options.debug.cannyEdges = cannyEdges;
|
|
1115
|
-
options.debug.finalEdges = finalEdges;
|
|
1116
|
-
}
|
|
1117
|
-
if (options.debug) {
|
|
1118
|
-
options.debug.timings = timings;
|
|
1119
|
-
} else if (!options.debug) {
|
|
1120
|
-
options.debug = { timings };
|
|
1121
|
-
}
|
|
1122
|
-
const tEnd = performance.now();
|
|
1123
|
-
timings.unshift({ step: "Edge Detection Total", ms: (tEnd - tStart).toFixed(2) });
|
|
1124
|
-
return finalEdges;
|
|
1125
|
-
}
|
|
1126
|
-
function cloneCorners(corners) {
|
|
1127
|
-
return {
|
|
1128
|
-
topLeft: { x: corners.topLeft.x, y: corners.topLeft.y },
|
|
1129
|
-
topRight: { x: corners.topRight.x, y: corners.topRight.y },
|
|
1130
|
-
bottomRight: { x: corners.bottomRight.x, y: corners.bottomRight.y },
|
|
1131
|
-
bottomLeft: { x: corners.bottomLeft.x, y: corners.bottomLeft.y }
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
function pointDistance$1(a, b) {
|
|
1135
|
-
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
1136
|
-
}
|
|
1137
|
-
function cornersAreFiniteAndDistinct$1(corners, minDistance = 4) {
|
|
1138
|
-
const points = [corners == null ? void 0 : corners.topLeft, corners == null ? void 0 : corners.topRight, corners == null ? void 0 : corners.bottomRight, corners == null ? void 0 : corners.bottomLeft];
|
|
1139
|
-
if (points.some((p) => !p || !Number.isFinite(p.x) || !Number.isFinite(p.y))) {
|
|
1140
|
-
return false;
|
|
1141
|
-
}
|
|
1142
|
-
for (let i = 0; i < points.length; i++) {
|
|
1143
|
-
for (let j = i + 1; j < points.length; j++) {
|
|
1144
|
-
if (pointDistance$1(points[i], points[j]) < minDistance) {
|
|
1145
|
-
return false;
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
return true;
|
|
1150
|
-
}
|
|
1151
|
-
function isConvexQuadrilateral$1(corners) {
|
|
1152
|
-
const points = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft];
|
|
1153
|
-
const crossSigns = [];
|
|
1154
|
-
for (let i = 0; i < points.length; i++) {
|
|
1155
|
-
const p0 = points[i];
|
|
1156
|
-
const p1 = points[(i + 1) % points.length];
|
|
1157
|
-
const p2 = points[(i + 2) % points.length];
|
|
1158
|
-
const cross = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
|
|
1159
|
-
if (Math.abs(cross) < 1e-6) continue;
|
|
1160
|
-
crossSigns.push(Math.sign(cross));
|
|
1161
|
-
}
|
|
1162
|
-
if (crossSigns.length < 3) {
|
|
1163
|
-
return false;
|
|
1164
|
-
}
|
|
1165
|
-
const firstSign = crossSigns[0];
|
|
1166
|
-
return crossSigns.every((s) => s === firstSign);
|
|
1167
|
-
}
|
|
1168
|
-
function createDefaultCorners(imageWidth, imageHeight, insetRatio = 0.08) {
|
|
1169
|
-
const inset = Math.max(8, Math.min(imageWidth, imageHeight) * insetRatio);
|
|
1170
|
-
return {
|
|
1171
|
-
topLeft: { x: inset, y: inset },
|
|
1172
|
-
topRight: { x: imageWidth - inset, y: inset },
|
|
1173
|
-
bottomRight: { x: imageWidth - inset, y: imageHeight - inset },
|
|
1174
|
-
bottomLeft: { x: inset, y: imageHeight - inset }
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
function normalizeImageToCanvas(image) {
|
|
1178
|
-
if (!image) {
|
|
1179
|
-
throw new Error("No image provided");
|
|
1180
|
-
}
|
|
1181
|
-
const isImageData = image && typeof image.width === "number" && typeof image.height === "number" && image.data;
|
|
1182
|
-
let width = 0;
|
|
1183
|
-
let height = 0;
|
|
1184
|
-
if (isImageData) {
|
|
1185
|
-
width = image.width;
|
|
1186
|
-
height = image.height;
|
|
1187
|
-
} else {
|
|
1188
|
-
width = image.width || image.naturalWidth;
|
|
1189
|
-
height = image.height || image.naturalHeight;
|
|
1190
|
-
}
|
|
1191
|
-
if (!width || !height) {
|
|
1192
|
-
throw new Error("Image must be loaded before creating the corner editor");
|
|
1193
|
-
}
|
|
1194
|
-
const canvas = document.createElement("canvas");
|
|
1195
|
-
canvas.width = width;
|
|
1196
|
-
canvas.height = height;
|
|
1197
|
-
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
1198
|
-
if (!ctx) {
|
|
1199
|
-
throw new Error("Failed to create 2D canvas context for source image");
|
|
1200
|
-
}
|
|
1201
|
-
if (isImageData) {
|
|
1202
|
-
ctx.putImageData(image, 0, 0);
|
|
1203
|
-
} else {
|
|
1204
|
-
ctx.drawImage(image, 0, 0, width, height);
|
|
1205
|
-
}
|
|
1206
|
-
return { canvas, width, height };
|
|
1207
|
-
}
|
|
1208
|
-
const STYLE_ID = "scanic-corner-editor-styles";
|
|
1209
|
-
const EDITOR_CSS = `
|
|
1210
|
-
.scanic-corner-editor {
|
|
1211
|
-
--scanic-accent: #6366f1;
|
|
1212
|
-
--scanic-mask: rgba(15, 23, 42, 0.45);
|
|
1213
|
-
--scanic-edge-color: var(--scanic-accent);
|
|
1214
|
-
--scanic-edge-width: 2.5;
|
|
1215
|
-
--scanic-handle-size: 20px;
|
|
1216
|
-
--scanic-handle-hit: 44px;
|
|
1217
|
-
--scanic-handle-color: #ffffff;
|
|
1218
|
-
--scanic-handle-ring: 2px;
|
|
1219
|
-
--scanic-handle-ring-color: var(--scanic-accent);
|
|
1220
|
-
--scanic-handle-shadow: 0 1px 4px rgba(15, 23, 42, 0.45);
|
|
1221
|
-
--scanic-handle-active-shadow: 0 8px 22px rgba(15, 23, 42, 0.5);
|
|
1222
|
-
--scanic-handle-active-scale: 1.28;
|
|
1223
|
-
--scanic-surface: rgba(15, 23, 42, 0.92);
|
|
1224
|
-
--scanic-surface-fg: #e2e8f0;
|
|
1225
|
-
--scanic-surface-radius: 12px;
|
|
1226
|
-
}
|
|
1227
|
-
.scanic-handle {
|
|
1228
|
-
position: absolute;
|
|
1229
|
-
box-sizing: border-box;
|
|
1230
|
-
width: var(--scanic-handle-size);
|
|
1231
|
-
height: var(--scanic-handle-size);
|
|
1232
|
-
margin: 0;
|
|
1233
|
-
padding: 0;
|
|
1234
|
-
border: var(--scanic-handle-ring) solid var(--scanic-handle-ring-color);
|
|
1235
|
-
border-radius: 50%;
|
|
1236
|
-
background: var(--scanic-handle-color);
|
|
1237
|
-
box-shadow: var(--scanic-handle-shadow);
|
|
1238
|
-
transform: translate(-50%, -50%);
|
|
1239
|
-
cursor: grab;
|
|
1240
|
-
touch-action: none;
|
|
1241
|
-
z-index: 2;
|
|
1242
|
-
transition: transform 0.12s ease, box-shadow 0.12s ease,
|
|
1243
|
-
background 0.12s ease, border-color 0.12s ease;
|
|
1244
|
-
}
|
|
1245
|
-
/* Enlarges the pointer/touch target without changing the visual size. */
|
|
1246
|
-
.scanic-handle::after {
|
|
1247
|
-
content: '';
|
|
1248
|
-
position: absolute;
|
|
1249
|
-
left: 50%;
|
|
1250
|
-
top: 50%;
|
|
1251
|
-
width: var(--scanic-handle-hit);
|
|
1252
|
-
height: var(--scanic-handle-hit);
|
|
1253
|
-
transform: translate(-50%, -50%);
|
|
1254
|
-
border-radius: 50%;
|
|
1255
|
-
}
|
|
1256
|
-
/* Soft accent halo that blooms when a handle is grabbed — the "depth" layer. */
|
|
1257
|
-
.scanic-handle::before {
|
|
1258
|
-
content: '';
|
|
1259
|
-
position: absolute;
|
|
1260
|
-
left: 50%;
|
|
1261
|
-
top: 50%;
|
|
1262
|
-
width: 0;
|
|
1263
|
-
height: 0;
|
|
1264
|
-
border-radius: 50%;
|
|
1265
|
-
background: var(--scanic-accent);
|
|
1266
|
-
opacity: 0;
|
|
1267
|
-
transform: translate(-50%, -50%);
|
|
1268
|
-
transition: opacity 0.15s ease, width 0.15s ease, height 0.15s ease;
|
|
1269
|
-
pointer-events: none;
|
|
1270
|
-
}
|
|
1271
|
-
.scanic-handle:hover {
|
|
1272
|
-
transform: translate(-50%, -50%) scale(1.12);
|
|
1273
|
-
}
|
|
1274
|
-
.scanic-handle:focus-visible {
|
|
1275
|
-
outline: none;
|
|
1276
|
-
box-shadow: 0 0 0 3px color-mix(in srgb, var(--scanic-accent) 45%, transparent),
|
|
1277
|
-
var(--scanic-handle-shadow);
|
|
1278
|
-
}
|
|
1279
|
-
/* The currently selected corner — the target of nudges / keyboard. */
|
|
1280
|
-
.scanic-handle.is-selected {
|
|
1281
|
-
border-color: var(--scanic-accent);
|
|
1282
|
-
transform: translate(-50%, -50%) scale(1.12);
|
|
1283
|
-
}
|
|
1284
|
-
.scanic-handle.is-selected::before {
|
|
1285
|
-
opacity: 0.16;
|
|
1286
|
-
width: calc(var(--scanic-handle-size) * 2);
|
|
1287
|
-
height: calc(var(--scanic-handle-size) * 2);
|
|
1288
|
-
}
|
|
1289
|
-
.scanic-handle.is-active {
|
|
1290
|
-
cursor: grabbing;
|
|
1291
|
-
background: var(--scanic-accent);
|
|
1292
|
-
border-color: #ffffff;
|
|
1293
|
-
transform: translate(-50%, -50%) scale(var(--scanic-handle-active-scale));
|
|
1294
|
-
box-shadow: var(--scanic-handle-active-shadow);
|
|
1295
|
-
}
|
|
1296
|
-
.scanic-handle.is-active::before {
|
|
1297
|
-
opacity: 0.22;
|
|
1298
|
-
width: calc(var(--scanic-handle-size) * 2.8);
|
|
1299
|
-
height: calc(var(--scanic-handle-size) * 2.8);
|
|
1300
|
-
}
|
|
1301
|
-
.scanic-toolbar,
|
|
1302
|
-
.scanic-nudges {
|
|
1303
|
-
position: absolute;
|
|
1304
|
-
z-index: 3;
|
|
1305
|
-
display: flex;
|
|
1306
|
-
gap: 4px;
|
|
1307
|
-
padding: 5px;
|
|
1308
|
-
background: var(--scanic-surface);
|
|
1309
|
-
border-radius: var(--scanic-surface-radius);
|
|
1310
|
-
-webkit-backdrop-filter: blur(8px);
|
|
1311
|
-
backdrop-filter: blur(8px);
|
|
1312
|
-
box-shadow: 0 6px 20px rgba(15, 23, 42, 0.35);
|
|
1313
|
-
}
|
|
1314
|
-
.scanic-toolbar {
|
|
1315
|
-
left: 50%;
|
|
1316
|
-
bottom: 12px;
|
|
1317
|
-
transform: translateX(-50%);
|
|
1318
|
-
}
|
|
1319
|
-
.scanic-nudges {
|
|
1320
|
-
top: 10px;
|
|
1321
|
-
right: 10px;
|
|
1322
|
-
display: grid;
|
|
1323
|
-
grid-template-columns: repeat(4, auto);
|
|
1324
|
-
gap: 3px;
|
|
1325
|
-
}
|
|
1326
|
-
.scanic-toolbar button,
|
|
1327
|
-
.scanic-nudges button {
|
|
1328
|
-
display: grid;
|
|
1329
|
-
place-items: center;
|
|
1330
|
-
margin: 0;
|
|
1331
|
-
padding: 0;
|
|
1332
|
-
font-family: inherit;
|
|
1333
|
-
font-size: 15px;
|
|
1334
|
-
font-weight: 600;
|
|
1335
|
-
line-height: 1;
|
|
1336
|
-
color: var(--scanic-surface-fg);
|
|
1337
|
-
background: transparent;
|
|
1338
|
-
border: 0;
|
|
1339
|
-
border-radius: 8px;
|
|
1340
|
-
cursor: pointer;
|
|
1341
|
-
transition: background 0.12s ease, color 0.12s ease, filter 0.12s ease;
|
|
1342
|
-
}
|
|
1343
|
-
.scanic-toolbar button {
|
|
1344
|
-
width: 34px;
|
|
1345
|
-
height: 34px;
|
|
1346
|
-
}
|
|
1347
|
-
.scanic-nudges button {
|
|
1348
|
-
width: 30px;
|
|
1349
|
-
height: 30px;
|
|
1350
|
-
}
|
|
1351
|
-
.scanic-toolbar button svg {
|
|
1352
|
-
width: 18px;
|
|
1353
|
-
height: 18px;
|
|
1354
|
-
display: block;
|
|
1355
|
-
}
|
|
1356
|
-
.scanic-toolbar button:hover,
|
|
1357
|
-
.scanic-nudges button:hover {
|
|
1358
|
-
background: rgba(255, 255, 255, 0.14);
|
|
1359
|
-
}
|
|
1360
|
-
.scanic-toolbar button:focus-visible,
|
|
1361
|
-
.scanic-nudges button:focus-visible {
|
|
1362
|
-
outline: 2px solid var(--scanic-accent);
|
|
1363
|
-
outline-offset: 1px;
|
|
1364
|
-
}
|
|
1365
|
-
.scanic-toolbar .scanic-btn-apply {
|
|
1366
|
-
background: var(--scanic-accent);
|
|
1367
|
-
color: #ffffff;
|
|
1368
|
-
}
|
|
1369
|
-
.scanic-toolbar .scanic-btn-apply:hover {
|
|
1370
|
-
background: var(--scanic-accent);
|
|
1371
|
-
filter: brightness(1.1);
|
|
1372
|
-
}
|
|
1373
|
-
.scanic-toolbar .scanic-btn-expert.is-on {
|
|
1374
|
-
background: color-mix(in srgb, var(--scanic-accent) 32%, transparent);
|
|
1375
|
-
color: #ffffff;
|
|
1376
|
-
}
|
|
1377
|
-
`;
|
|
1378
|
-
const ICONS = {
|
|
1379
|
-
reset: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 4 3 10 9 10"/><path d="M3.5 14a8.5 8.5 0 1 0 2-7.4L3 10"/></svg>',
|
|
1380
|
-
cancel: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
|
|
1381
|
-
apply: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
|
|
1382
|
-
expert: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>'
|
|
1383
|
-
};
|
|
1384
|
-
function injectStyles(doc) {
|
|
1385
|
-
if (!doc || typeof doc.getElementById !== "function") return;
|
|
1386
|
-
if (doc.getElementById(STYLE_ID)) return;
|
|
1387
|
-
const style = doc.createElement("style");
|
|
1388
|
-
style.id = STYLE_ID;
|
|
1389
|
-
style.textContent = EDITOR_CSS;
|
|
1390
|
-
(doc.head || doc.documentElement).appendChild(style);
|
|
1391
|
-
}
|
|
1392
|
-
function createCornerEditor(options = {}) {
|
|
1393
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
1394
|
-
const container = options.container;
|
|
1395
|
-
if (!container || typeof container.appendChild !== "function") {
|
|
1396
|
-
throw new Error("createCornerEditor requires a valid container element");
|
|
1397
|
-
}
|
|
1398
|
-
const doc = container.ownerDocument || (typeof document !== "undefined" ? document : null);
|
|
1399
|
-
const runtimeGlobal = typeof window !== "undefined" ? window : globalThis;
|
|
1400
|
-
if (options.injectStyles !== false) {
|
|
1401
|
-
injectStyles(doc);
|
|
1402
|
-
}
|
|
1403
|
-
const { canvas: sourceCanvas, width: imageWidth, height: imageHeight } = normalizeImageToCanvas(options.image);
|
|
1404
|
-
const addedRootClass = !container.classList.contains("scanic-corner-editor");
|
|
1405
|
-
container.classList.add("scanic-corner-editor");
|
|
1406
|
-
if ((_a = options.classNames) == null ? void 0 : _a.root) container.classList.add(options.classNames.root);
|
|
1407
|
-
const restoreContainerStyle = {
|
|
1408
|
-
position: container.style.position,
|
|
1409
|
-
minHeight: container.style.minHeight
|
|
1410
|
-
};
|
|
1411
|
-
let changedContainerPosition = false;
|
|
1412
|
-
let changedContainerMinHeight = false;
|
|
1413
|
-
if (getComputedStyle(container).position === "static") {
|
|
1414
|
-
container.style.position = "relative";
|
|
1415
|
-
changedContainerPosition = true;
|
|
1416
|
-
}
|
|
1417
|
-
applyThemeOption(options.theme);
|
|
1418
|
-
const editorCanvas = doc.createElement("canvas");
|
|
1419
|
-
editorCanvas.style.position = "absolute";
|
|
1420
|
-
editorCanvas.style.top = "0";
|
|
1421
|
-
editorCanvas.style.left = "0";
|
|
1422
|
-
editorCanvas.style.display = "block";
|
|
1423
|
-
editorCanvas.style.touchAction = "none";
|
|
1424
|
-
editorCanvas.style.userSelect = "none";
|
|
1425
|
-
editorCanvas.style.webkitUserSelect = "none";
|
|
1426
|
-
editorCanvas.style.cursor = "default";
|
|
1427
|
-
container.appendChild(editorCanvas);
|
|
1428
|
-
const ctx = editorCanvas.getContext("2d");
|
|
1429
|
-
if (!ctx) {
|
|
1430
|
-
throw new Error("Failed to create 2D canvas context for corner editor");
|
|
1431
|
-
}
|
|
1432
|
-
const magnifier = {
|
|
1433
|
-
enabled: ((_b = options.magnifier) == null ? void 0 : _b.enabled) !== false,
|
|
1434
|
-
size: ((_c = options.magnifier) == null ? void 0 : _c.size) || 120,
|
|
1435
|
-
zoom: ((_d = options.magnifier) == null ? void 0 : _d.zoom) || 2,
|
|
1436
|
-
margin: ((_e = options.magnifier) == null ? void 0 : _e.margin) || 8,
|
|
1437
|
-
borderColor: ((_f = options.magnifier) == null ? void 0 : _f.borderColor) || "#ffffff",
|
|
1438
|
-
borderWidth: ((_g = options.magnifier) == null ? void 0 : _g.borderWidth) || 2,
|
|
1439
|
-
crosshairColor: ((_h = options.magnifier) == null ? void 0 : _h.crosshairColor) || "#ffffff",
|
|
1440
|
-
crosshairSize: ((_i = options.magnifier) == null ? void 0 : _i.crosshairSize) || 18
|
|
1441
|
-
};
|
|
1442
|
-
const nudges = {
|
|
1443
|
-
enabled: !!((_j = options.nudges) == null ? void 0 : _j.enabled),
|
|
1444
|
-
steps: (((_k = options.nudges) == null ? void 0 : _k.steps) && options.nudges.steps.length ? options.nudges.steps : [1, 10]).map((v) => Math.max(1, Math.round(v)))
|
|
1445
|
-
};
|
|
1446
|
-
const toolbar = {
|
|
1447
|
-
enabled: ((_l = options.toolbar) == null ? void 0 : _l.enabled) !== false,
|
|
1448
|
-
reset: ((_m = options.toolbar) == null ? void 0 : _m.reset) !== false,
|
|
1449
|
-
cancel: ((_n = options.toolbar) == null ? void 0 : _n.cancel) !== false,
|
|
1450
|
-
apply: ((_o = options.toolbar) == null ? void 0 : _o.apply) !== false,
|
|
1451
|
-
labels: {
|
|
1452
|
-
reset: ((_q = (_p = options.toolbar) == null ? void 0 : _p.labels) == null ? void 0 : _q.reset) || "Reset",
|
|
1453
|
-
cancel: ((_s = (_r = options.toolbar) == null ? void 0 : _r.labels) == null ? void 0 : _s.cancel) || "Cancel",
|
|
1454
|
-
apply: ((_u = (_t = options.toolbar) == null ? void 0 : _t.labels) == null ? void 0 : _u.apply) || "Apply"
|
|
1455
|
-
}
|
|
1456
|
-
};
|
|
1457
|
-
const keyboardEnabled = options.keyboard !== false;
|
|
1458
|
-
const defaultCorners = createDefaultCorners(imageWidth, imageHeight);
|
|
1459
|
-
const requestedCorners = options.corners ? cloneCorners(options.corners) : defaultCorners;
|
|
1460
|
-
let corners = cornersAreFiniteAndDistinct$1(requestedCorners) && isConvexQuadrilateral$1(requestedCorners) ? requestedCorners : defaultCorners;
|
|
1461
|
-
const initialCorners = cloneCorners(corners);
|
|
1462
|
-
let isDestroyed = false;
|
|
1463
|
-
let activeCornerKey = null;
|
|
1464
|
-
let focusedCornerKey = "topLeft";
|
|
1465
|
-
let dragPointerId = null;
|
|
1466
|
-
let lastPointerPosition = null;
|
|
1467
|
-
Math.max(24, options.handleHitArea || 44);
|
|
1468
|
-
const cornerOrder = ["topLeft", "topRight", "bottomRight", "bottomLeft"];
|
|
1469
|
-
const cornerLabels = {
|
|
1470
|
-
topLeft: "Top-left corner",
|
|
1471
|
-
topRight: "Top-right corner",
|
|
1472
|
-
bottomRight: "Bottom-right corner",
|
|
1473
|
-
bottomLeft: "Bottom-left corner"
|
|
1474
|
-
};
|
|
1475
|
-
let view = { scale: 1, offsetX: 0, offsetY: 0, width: 1, height: 1 };
|
|
1476
|
-
const resolved = { mask: "rgba(15,23,42,0.45)", edgeColor: "#6366f1", edgeWidth: 2.5 };
|
|
1477
|
-
const handleEls = {};
|
|
1478
|
-
for (const key of cornerOrder) {
|
|
1479
|
-
const el = doc.createElement("button");
|
|
1480
|
-
el.type = "button";
|
|
1481
|
-
el.className = "scanic-handle" + (((_v = options.classNames) == null ? void 0 : _v.handle) ? " " + options.classNames.handle : "");
|
|
1482
|
-
el.dataset.corner = key;
|
|
1483
|
-
el.setAttribute("aria-label", cornerLabels[key]);
|
|
1484
|
-
if (!keyboardEnabled) el.tabIndex = -1;
|
|
1485
|
-
el.addEventListener("pointerdown", (e) => onHandlePointerDown(key, e));
|
|
1486
|
-
el.addEventListener("pointermove", (e) => onHandlePointerMove(key, e));
|
|
1487
|
-
el.addEventListener("pointerup", (e) => onHandlePointerUp(key, e));
|
|
1488
|
-
el.addEventListener("pointercancel", (e) => onHandlePointerUp(key, e));
|
|
1489
|
-
el.addEventListener("focus", () => {
|
|
1490
|
-
focusedCornerKey = key;
|
|
1491
|
-
updateSelected();
|
|
1492
|
-
});
|
|
1493
|
-
if (keyboardEnabled) el.addEventListener("keydown", (e) => onHandleKeyDown(key, e));
|
|
1494
|
-
container.appendChild(el);
|
|
1495
|
-
handleEls[key] = el;
|
|
1496
|
-
}
|
|
1497
|
-
function resolveColor(expr, fallback) {
|
|
1498
|
-
const prev = editorCanvas.style.color;
|
|
1499
|
-
editorCanvas.style.color = `var(${expr}, ${fallback})`;
|
|
1500
|
-
const value = getComputedStyle(editorCanvas).color || fallback;
|
|
1501
|
-
editorCanvas.style.color = prev;
|
|
1502
|
-
return value;
|
|
1503
|
-
}
|
|
1504
|
-
function resolveTheme() {
|
|
1505
|
-
resolved.mask = resolveColor("--scanic-mask", "rgba(15, 23, 42, 0.45)");
|
|
1506
|
-
resolved.edgeColor = resolveColor("--scanic-edge-color", "#6366f1");
|
|
1507
|
-
const widthRaw = getComputedStyle(container).getPropertyValue("--scanic-edge-width");
|
|
1508
|
-
const parsed = parseFloat(widthRaw);
|
|
1509
|
-
resolved.edgeWidth = Number.isFinite(parsed) && parsed > 0 ? parsed : 2.5;
|
|
1510
|
-
}
|
|
1511
|
-
function applyThemeOption(theme) {
|
|
1512
|
-
if (!theme || typeof theme !== "object") return;
|
|
1513
|
-
const pxKeys = /* @__PURE__ */ new Set(["handleSize", "handleHit"]);
|
|
1514
|
-
const map = {
|
|
1515
|
-
accent: "--scanic-accent",
|
|
1516
|
-
mask: "--scanic-mask",
|
|
1517
|
-
edgeColor: "--scanic-edge-color",
|
|
1518
|
-
edgeWidth: "--scanic-edge-width",
|
|
1519
|
-
handleSize: "--scanic-handle-size",
|
|
1520
|
-
handleHit: "--scanic-handle-hit",
|
|
1521
|
-
handleColor: "--scanic-handle-color",
|
|
1522
|
-
handleRingColor: "--scanic-handle-ring-color",
|
|
1523
|
-
surface: "--scanic-surface",
|
|
1524
|
-
surfaceColor: "--scanic-surface-fg",
|
|
1525
|
-
radius: "--scanic-surface-radius"
|
|
1526
|
-
};
|
|
1527
|
-
for (const [key, cssVar] of Object.entries(map)) {
|
|
1528
|
-
const value = theme[key];
|
|
1529
|
-
if (value == null) continue;
|
|
1530
|
-
const cssValue = typeof value === "number" && pxKeys.has(key) ? `${value}px` : String(value);
|
|
1531
|
-
container.style.setProperty(cssVar, cssValue);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
function emitChange() {
|
|
1535
|
-
if (typeof options.onChange === "function") {
|
|
1536
|
-
options.onChange(cloneCorners(corners));
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
let lastDisplayWidth = 0;
|
|
1540
|
-
let lastDisplayHeight = 0;
|
|
1541
|
-
function computeDisplaySize() {
|
|
1542
|
-
const width = Math.max(1, Math.round(container.clientWidth));
|
|
1543
|
-
let height = Math.round(container.clientHeight);
|
|
1544
|
-
if (height < 80) {
|
|
1545
|
-
const aspect = imageHeight / Math.max(1, imageWidth);
|
|
1546
|
-
const viewportCap = (runtimeGlobal.innerHeight || 800) * 0.7;
|
|
1547
|
-
height = Math.max(240, Math.round(Math.min(width * aspect, viewportCap)));
|
|
1548
|
-
container.style.minHeight = height + "px";
|
|
1549
|
-
changedContainerMinHeight = true;
|
|
1550
|
-
}
|
|
1551
|
-
return { width, height: Math.max(1, height) };
|
|
1552
|
-
}
|
|
1553
|
-
function updateCanvasSize() {
|
|
1554
|
-
const { width, height } = computeDisplaySize();
|
|
1555
|
-
const dpr = runtimeGlobal.devicePixelRatio || 1;
|
|
1556
|
-
lastDisplayWidth = width;
|
|
1557
|
-
lastDisplayHeight = height;
|
|
1558
|
-
editorCanvas.style.width = width + "px";
|
|
1559
|
-
editorCanvas.style.height = height + "px";
|
|
1560
|
-
const bufferWidth = Math.round(width * dpr);
|
|
1561
|
-
const bufferHeight = Math.round(height * dpr);
|
|
1562
|
-
if (editorCanvas.width !== bufferWidth) editorCanvas.width = bufferWidth;
|
|
1563
|
-
if (editorCanvas.height !== bufferHeight) editorCanvas.height = bufferHeight;
|
|
1564
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1565
|
-
const padding = 12;
|
|
1566
|
-
const availableWidth = Math.max(1, width - padding * 2);
|
|
1567
|
-
const availableHeight = Math.max(1, height - padding * 2);
|
|
1568
|
-
const scale = Math.min(availableWidth / imageWidth, availableHeight / imageHeight);
|
|
1569
|
-
const drawWidth = imageWidth * scale;
|
|
1570
|
-
const drawHeight = imageHeight * scale;
|
|
1571
|
-
view = {
|
|
1572
|
-
scale,
|
|
1573
|
-
offsetX: Math.round((width - drawWidth) / 2),
|
|
1574
|
-
offsetY: Math.round((height - drawHeight) / 2),
|
|
1575
|
-
width,
|
|
1576
|
-
height
|
|
1577
|
-
};
|
|
1578
|
-
resolveTheme();
|
|
1579
|
-
}
|
|
1580
|
-
function imageToView(point) {
|
|
1581
|
-
return {
|
|
1582
|
-
x: view.offsetX + point.x * view.scale,
|
|
1583
|
-
y: view.offsetY + point.y * view.scale
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
function viewToImage(x, y) {
|
|
1587
|
-
const px = (x - view.offsetX) / view.scale;
|
|
1588
|
-
const py = (y - view.offsetY) / view.scale;
|
|
1589
|
-
return {
|
|
1590
|
-
x: Math.max(0, Math.min(imageWidth, px)),
|
|
1591
|
-
y: Math.max(0, Math.min(imageHeight, py))
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
function cornersValid(nextCorners) {
|
|
1595
|
-
return cornersAreFiniteAndDistinct$1(nextCorners) && isConvexQuadrilateral$1(nextCorners);
|
|
1596
|
-
}
|
|
1597
|
-
function getEventCanvasPoint(event) {
|
|
1598
|
-
const rect = editorCanvas.getBoundingClientRect();
|
|
1599
|
-
return {
|
|
1600
|
-
x: event.clientX - rect.left,
|
|
1601
|
-
y: event.clientY - rect.top
|
|
1602
|
-
};
|
|
1603
|
-
}
|
|
1604
|
-
function drawOverlay() {
|
|
1605
|
-
const points = cornerOrder.map((key) => imageToView(corners[key]));
|
|
1606
|
-
ctx.save();
|
|
1607
|
-
ctx.fillStyle = resolved.mask;
|
|
1608
|
-
ctx.beginPath();
|
|
1609
|
-
ctx.rect(0, 0, view.width, view.height);
|
|
1610
|
-
ctx.moveTo(points[0].x, points[0].y);
|
|
1611
|
-
for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
|
|
1612
|
-
ctx.closePath();
|
|
1613
|
-
ctx.fill("evenodd");
|
|
1614
|
-
ctx.restore();
|
|
1615
|
-
ctx.save();
|
|
1616
|
-
ctx.strokeStyle = resolved.edgeColor;
|
|
1617
|
-
ctx.lineWidth = resolved.edgeWidth;
|
|
1618
|
-
ctx.lineJoin = "round";
|
|
1619
|
-
ctx.beginPath();
|
|
1620
|
-
ctx.moveTo(points[0].x, points[0].y);
|
|
1621
|
-
for (let i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);
|
|
1622
|
-
ctx.closePath();
|
|
1623
|
-
ctx.stroke();
|
|
1624
|
-
ctx.restore();
|
|
1625
|
-
}
|
|
1626
|
-
function drawMagnifier() {
|
|
1627
|
-
if (!magnifier.enabled || !activeCornerKey || !lastPointerPosition) {
|
|
1628
|
-
return;
|
|
1629
|
-
}
|
|
1630
|
-
const active = corners[activeCornerKey];
|
|
1631
|
-
const size = magnifier.size;
|
|
1632
|
-
const radius = size / 2;
|
|
1633
|
-
const zoom = Math.max(1.1, magnifier.zoom);
|
|
1634
|
-
let lensX = lastPointerPosition.x + radius + magnifier.margin;
|
|
1635
|
-
let lensY = lastPointerPosition.y - radius - magnifier.margin;
|
|
1636
|
-
if (lensX + radius > view.width) {
|
|
1637
|
-
lensX = lastPointerPosition.x - radius - magnifier.margin;
|
|
1638
|
-
}
|
|
1639
|
-
if (lensY - radius < 0) {
|
|
1640
|
-
lensY = lastPointerPosition.y + radius + magnifier.margin;
|
|
1641
|
-
}
|
|
1642
|
-
lensX = Math.max(radius + 2, Math.min(view.width - radius - 2, lensX));
|
|
1643
|
-
lensY = Math.max(radius + 2, Math.min(view.height - radius - 2, lensY));
|
|
1644
|
-
const sampleSize = size / zoom;
|
|
1645
|
-
const sx = Math.max(0, Math.min(imageWidth - sampleSize, active.x - sampleSize / 2));
|
|
1646
|
-
const sy = Math.max(0, Math.min(imageHeight - sampleSize, active.y - sampleSize / 2));
|
|
1647
|
-
ctx.save();
|
|
1648
|
-
ctx.beginPath();
|
|
1649
|
-
ctx.arc(lensX, lensY, radius, 0, Math.PI * 2);
|
|
1650
|
-
ctx.clip();
|
|
1651
|
-
ctx.drawImage(sourceCanvas, sx, sy, sampleSize, sampleSize, lensX - radius, lensY - radius, size, size);
|
|
1652
|
-
ctx.restore();
|
|
1653
|
-
ctx.save();
|
|
1654
|
-
ctx.beginPath();
|
|
1655
|
-
ctx.arc(lensX, lensY, radius, 0, Math.PI * 2);
|
|
1656
|
-
ctx.lineWidth = magnifier.borderWidth;
|
|
1657
|
-
ctx.strokeStyle = magnifier.borderColor;
|
|
1658
|
-
ctx.stroke();
|
|
1659
|
-
const ch = magnifier.crosshairSize / 2;
|
|
1660
|
-
ctx.strokeStyle = magnifier.crosshairColor;
|
|
1661
|
-
ctx.lineWidth = 1.5;
|
|
1662
|
-
ctx.beginPath();
|
|
1663
|
-
ctx.moveTo(lensX - ch, lensY);
|
|
1664
|
-
ctx.lineTo(lensX + ch, lensY);
|
|
1665
|
-
ctx.moveTo(lensX, lensY - ch);
|
|
1666
|
-
ctx.lineTo(lensX, lensY + ch);
|
|
1667
|
-
ctx.stroke();
|
|
1668
|
-
ctx.restore();
|
|
1669
|
-
}
|
|
1670
|
-
function positionHandles() {
|
|
1671
|
-
for (const key of cornerOrder) {
|
|
1672
|
-
const p = imageToView(corners[key]);
|
|
1673
|
-
const el = handleEls[key];
|
|
1674
|
-
el.style.left = p.x + "px";
|
|
1675
|
-
el.style.top = p.y + "px";
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
function updateSelected() {
|
|
1679
|
-
for (const key of cornerOrder) {
|
|
1680
|
-
handleEls[key].classList.toggle("is-selected", key === focusedCornerKey);
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
function render() {
|
|
1684
|
-
if (isDestroyed) return;
|
|
1685
|
-
ctx.clearRect(0, 0, view.width, view.height);
|
|
1686
|
-
ctx.drawImage(
|
|
1687
|
-
sourceCanvas,
|
|
1688
|
-
0,
|
|
1689
|
-
0,
|
|
1690
|
-
imageWidth,
|
|
1691
|
-
imageHeight,
|
|
1692
|
-
view.offsetX,
|
|
1693
|
-
view.offsetY,
|
|
1694
|
-
imageWidth * view.scale,
|
|
1695
|
-
imageHeight * view.scale
|
|
1696
|
-
);
|
|
1697
|
-
drawOverlay();
|
|
1698
|
-
drawMagnifier();
|
|
1699
|
-
positionHandles();
|
|
1700
|
-
}
|
|
1701
|
-
const raf = typeof runtimeGlobal.requestAnimationFrame === "function" ? runtimeGlobal.requestAnimationFrame.bind(runtimeGlobal) : null;
|
|
1702
|
-
const caf = typeof runtimeGlobal.cancelAnimationFrame === "function" ? runtimeGlobal.cancelAnimationFrame.bind(runtimeGlobal) : null;
|
|
1703
|
-
let rafId = null;
|
|
1704
|
-
function scheduleRender() {
|
|
1705
|
-
if (isDestroyed) return;
|
|
1706
|
-
if (!raf) {
|
|
1707
|
-
render();
|
|
1708
|
-
return;
|
|
1709
|
-
}
|
|
1710
|
-
if (rafId !== null) return;
|
|
1711
|
-
rafId = raf(() => {
|
|
1712
|
-
rafId = null;
|
|
1713
|
-
render();
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
|
-
function setCorner(nextCornerKey, nextPoint) {
|
|
1717
|
-
const candidate = cloneCorners(corners);
|
|
1718
|
-
candidate[nextCornerKey] = {
|
|
1719
|
-
x: Math.max(0, Math.min(imageWidth, nextPoint.x)),
|
|
1720
|
-
y: Math.max(0, Math.min(imageHeight, nextPoint.y))
|
|
1721
|
-
};
|
|
1722
|
-
if (!cornersValid(candidate)) {
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
corners = candidate;
|
|
1726
|
-
focusedCornerKey = nextCornerKey;
|
|
1727
|
-
updateSelected();
|
|
1728
|
-
emitChange();
|
|
1729
|
-
scheduleRender();
|
|
1730
|
-
return true;
|
|
1731
|
-
}
|
|
1732
|
-
function onHandlePointerDown(key, event) {
|
|
1733
|
-
if (isDestroyed) return;
|
|
1734
|
-
if (typeof event.preventDefault === "function") event.preventDefault();
|
|
1735
|
-
activeCornerKey = key;
|
|
1736
|
-
focusedCornerKey = key;
|
|
1737
|
-
updateSelected();
|
|
1738
|
-
dragPointerId = event.pointerId;
|
|
1739
|
-
lastPointerPosition = getEventCanvasPoint(event);
|
|
1740
|
-
handleEls[key].classList.add("is-active");
|
|
1741
|
-
if (keyboardEnabled && typeof handleEls[key].focus === "function") {
|
|
1742
|
-
try {
|
|
1743
|
-
handleEls[key].focus({ preventScroll: true });
|
|
1744
|
-
} catch (_) {
|
|
1745
|
-
handleEls[key].focus();
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
if (handleEls[key].setPointerCapture && event.pointerId != null) {
|
|
1749
|
-
try {
|
|
1750
|
-
handleEls[key].setPointerCapture(event.pointerId);
|
|
1751
|
-
} catch (_) {
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
scheduleRender();
|
|
1755
|
-
}
|
|
1756
|
-
function onHandlePointerMove(key, event) {
|
|
1757
|
-
if (isDestroyed || activeCornerKey !== key) return;
|
|
1758
|
-
if (dragPointerId !== null && event.pointerId !== dragPointerId) return;
|
|
1759
|
-
lastPointerPosition = getEventCanvasPoint(event);
|
|
1760
|
-
setCorner(key, viewToImage(lastPointerPosition.x, lastPointerPosition.y));
|
|
1761
|
-
}
|
|
1762
|
-
function onHandlePointerUp(key, event) {
|
|
1763
|
-
if (activeCornerKey !== key) return;
|
|
1764
|
-
if (dragPointerId !== null && event.pointerId !== dragPointerId) return;
|
|
1765
|
-
if (handleEls[key].releasePointerCapture && dragPointerId != null) {
|
|
1766
|
-
try {
|
|
1767
|
-
handleEls[key].releasePointerCapture(dragPointerId);
|
|
1768
|
-
} catch (_) {
|
|
1769
|
-
}
|
|
1770
|
-
}
|
|
1771
|
-
handleEls[key].classList.remove("is-active");
|
|
1772
|
-
activeCornerKey = null;
|
|
1773
|
-
dragPointerId = null;
|
|
1774
|
-
lastPointerPosition = null;
|
|
1775
|
-
scheduleRender();
|
|
1776
|
-
}
|
|
1777
|
-
function onHandleKeyDown(key, event) {
|
|
1778
|
-
if (isDestroyed || !keyboardEnabled) return;
|
|
1779
|
-
if (event.key === "Enter") {
|
|
1780
|
-
event.preventDefault();
|
|
1781
|
-
confirmEditor();
|
|
1782
|
-
return;
|
|
1783
|
-
}
|
|
1784
|
-
if (event.key === "Escape") {
|
|
1785
|
-
event.preventDefault();
|
|
1786
|
-
cancelEditor();
|
|
1787
|
-
return;
|
|
1788
|
-
}
|
|
1789
|
-
let dx = 0;
|
|
1790
|
-
let dy = 0;
|
|
1791
|
-
if (event.key === "ArrowLeft") dx = -1;
|
|
1792
|
-
else if (event.key === "ArrowRight") dx = 1;
|
|
1793
|
-
else if (event.key === "ArrowUp") dy = -1;
|
|
1794
|
-
else if (event.key === "ArrowDown") dy = 1;
|
|
1795
|
-
else return;
|
|
1796
|
-
event.preventDefault();
|
|
1797
|
-
const step = event.shiftKey ? nudges.steps[nudges.steps.length - 1] || 10 : 1;
|
|
1798
|
-
setCorner(key, { x: corners[key].x + dx * step, y: corners[key].y + dy * step });
|
|
1799
|
-
}
|
|
1800
|
-
let toolbarEl = null;
|
|
1801
|
-
let nudgeControls = null;
|
|
1802
|
-
let expertBtn = null;
|
|
1803
|
-
let expertVisible = false;
|
|
1804
|
-
const hasExpertToggle = toolbar.enabled && nudges.enabled;
|
|
1805
|
-
function makeButton({ html, text, title, className, onClick }) {
|
|
1806
|
-
const btn = doc.createElement("button");
|
|
1807
|
-
btn.type = "button";
|
|
1808
|
-
if (html != null) btn.innerHTML = html;
|
|
1809
|
-
else btn.textContent = text;
|
|
1810
|
-
if (title) {
|
|
1811
|
-
btn.title = title;
|
|
1812
|
-
btn.setAttribute("aria-label", title);
|
|
1813
|
-
}
|
|
1814
|
-
if (className) btn.className = className;
|
|
1815
|
-
btn.addEventListener("click", onClick);
|
|
1816
|
-
return btn;
|
|
1817
|
-
}
|
|
1818
|
-
function setExpert(on) {
|
|
1819
|
-
expertVisible = on;
|
|
1820
|
-
if (nudgeControls) nudgeControls.style.display = on ? "" : "none";
|
|
1821
|
-
if (expertBtn) {
|
|
1822
|
-
expertBtn.classList.toggle("is-on", on);
|
|
1823
|
-
expertBtn.setAttribute("aria-pressed", String(on));
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
function buildToolbar() {
|
|
1827
|
-
var _a2;
|
|
1828
|
-
if (!toolbar.enabled) return;
|
|
1829
|
-
if (!toolbar.reset && !toolbar.cancel && !toolbar.apply && !hasExpertToggle) return;
|
|
1830
|
-
toolbarEl = doc.createElement("div");
|
|
1831
|
-
toolbarEl.className = "scanic-toolbar" + (((_a2 = options.classNames) == null ? void 0 : _a2.toolbar) ? " " + options.classNames.toolbar : "");
|
|
1832
|
-
if (toolbar.reset) {
|
|
1833
|
-
toolbarEl.appendChild(makeButton({ html: ICONS.reset, title: toolbar.labels.reset, className: "scanic-btn-reset", onClick: () => publicReset() }));
|
|
1834
|
-
}
|
|
1835
|
-
if (hasExpertToggle) {
|
|
1836
|
-
expertBtn = makeButton({ html: ICONS.expert, title: "Precision nudge (expert)", className: "scanic-btn-expert", onClick: () => setExpert(!expertVisible) });
|
|
1837
|
-
expertBtn.setAttribute("aria-pressed", "false");
|
|
1838
|
-
toolbarEl.appendChild(expertBtn);
|
|
1839
|
-
}
|
|
1840
|
-
if (toolbar.cancel) {
|
|
1841
|
-
toolbarEl.appendChild(makeButton({ html: ICONS.cancel, title: toolbar.labels.cancel, className: "scanic-btn-cancel", onClick: () => cancelEditor() }));
|
|
1842
|
-
}
|
|
1843
|
-
if (toolbar.apply) {
|
|
1844
|
-
toolbarEl.appendChild(makeButton({ html: ICONS.apply, title: toolbar.labels.apply, className: "scanic-btn-apply", onClick: () => confirmEditor() }));
|
|
1845
|
-
}
|
|
1846
|
-
container.appendChild(toolbarEl);
|
|
1847
|
-
}
|
|
1848
|
-
function buildNudgeControls() {
|
|
1849
|
-
var _a2;
|
|
1850
|
-
if (!nudges.enabled) return;
|
|
1851
|
-
nudgeControls = doc.createElement("div");
|
|
1852
|
-
nudgeControls.className = "scanic-nudges" + (((_a2 = options.classNames) == null ? void 0 : _a2.nudges) ? " " + options.classNames.nudges : "");
|
|
1853
|
-
const makeNudge = (glyph, label, dx, dy, step) => makeButton({
|
|
1854
|
-
text: glyph,
|
|
1855
|
-
title: label,
|
|
1856
|
-
className: "scanic-btn-nudge",
|
|
1857
|
-
onClick: () => {
|
|
1858
|
-
const current = corners[focusedCornerKey];
|
|
1859
|
-
setCorner(focusedCornerKey, { x: current.x + dx * step, y: current.y + dy * step });
|
|
1860
|
-
}
|
|
1861
|
-
});
|
|
1862
|
-
for (const step of nudges.steps) {
|
|
1863
|
-
nudgeControls.appendChild(makeNudge("←", `Move left ${step}px`, -1, 0, step));
|
|
1864
|
-
nudgeControls.appendChild(makeNudge("↑", `Move up ${step}px`, 0, -1, step));
|
|
1865
|
-
nudgeControls.appendChild(makeNudge("↓", `Move down ${step}px`, 0, 1, step));
|
|
1866
|
-
nudgeControls.appendChild(makeNudge("→", `Move right ${step}px`, 1, 0, step));
|
|
1867
|
-
}
|
|
1868
|
-
container.appendChild(nudgeControls);
|
|
1869
|
-
if (hasExpertToggle) nudgeControls.style.display = "none";
|
|
1870
|
-
}
|
|
1871
|
-
let dprCleanup = null;
|
|
1872
|
-
function watchDevicePixelRatio() {
|
|
1873
|
-
if (typeof runtimeGlobal.matchMedia !== "function") return;
|
|
1874
|
-
if (dprCleanup) dprCleanup();
|
|
1875
|
-
const dpr = runtimeGlobal.devicePixelRatio || 1;
|
|
1876
|
-
const mq = runtimeGlobal.matchMedia(`(resolution: ${dpr}dppx)`);
|
|
1877
|
-
const onChange = () => {
|
|
1878
|
-
if (isDestroyed) return;
|
|
1879
|
-
updateCanvasSize();
|
|
1880
|
-
scheduleRender();
|
|
1881
|
-
watchDevicePixelRatio();
|
|
1882
|
-
};
|
|
1883
|
-
if (typeof mq.addEventListener === "function") {
|
|
1884
|
-
mq.addEventListener("change", onChange);
|
|
1885
|
-
dprCleanup = () => mq.removeEventListener("change", onChange);
|
|
1886
|
-
} else if (typeof mq.addListener === "function") {
|
|
1887
|
-
mq.addListener(onChange);
|
|
1888
|
-
dprCleanup = () => mq.removeListener(onChange);
|
|
1889
|
-
} else {
|
|
1890
|
-
dprCleanup = null;
|
|
1891
|
-
}
|
|
1892
|
-
}
|
|
1893
|
-
function confirmEditor() {
|
|
1894
|
-
const output = cloneCorners(corners);
|
|
1895
|
-
if (typeof options.onConfirm === "function") {
|
|
1896
|
-
options.onConfirm(output);
|
|
1897
|
-
}
|
|
1898
|
-
return output;
|
|
1899
|
-
}
|
|
1900
|
-
function cancelEditor() {
|
|
1901
|
-
if (typeof options.onCancel === "function") {
|
|
1902
|
-
options.onCancel();
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
function publicReset() {
|
|
1906
|
-
corners = cloneCorners(initialCorners);
|
|
1907
|
-
emitChange();
|
|
1908
|
-
scheduleRender();
|
|
1909
|
-
}
|
|
1910
|
-
const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => {
|
|
1911
|
-
if (isDestroyed) return;
|
|
1912
|
-
const next = computeDisplaySize();
|
|
1913
|
-
if (next.width === lastDisplayWidth && next.height === lastDisplayHeight) {
|
|
1914
|
-
return;
|
|
1915
|
-
}
|
|
1916
|
-
updateCanvasSize();
|
|
1917
|
-
scheduleRender();
|
|
1918
|
-
}) : null;
|
|
1919
|
-
updateCanvasSize();
|
|
1920
|
-
buildToolbar();
|
|
1921
|
-
buildNudgeControls();
|
|
1922
|
-
watchDevicePixelRatio();
|
|
1923
|
-
if (resizeObserver) {
|
|
1924
|
-
resizeObserver.observe(container);
|
|
1925
|
-
}
|
|
1926
|
-
updateSelected();
|
|
1927
|
-
render();
|
|
1928
|
-
return {
|
|
1929
|
-
getCorners() {
|
|
1930
|
-
return cloneCorners(corners);
|
|
1931
|
-
},
|
|
1932
|
-
setCorners(nextCorners) {
|
|
1933
|
-
if (!nextCorners || !cornersValid(nextCorners)) {
|
|
1934
|
-
return false;
|
|
1935
|
-
}
|
|
1936
|
-
corners = cloneCorners(nextCorners);
|
|
1937
|
-
emitChange();
|
|
1938
|
-
scheduleRender();
|
|
1939
|
-
return true;
|
|
1940
|
-
},
|
|
1941
|
-
reset() {
|
|
1942
|
-
publicReset();
|
|
1943
|
-
},
|
|
1944
|
-
nudge(cornerKey, dx, dy, step = 1) {
|
|
1945
|
-
if (!cornerOrder.includes(cornerKey)) {
|
|
1946
|
-
return false;
|
|
1947
|
-
}
|
|
1948
|
-
return setCorner(cornerKey, {
|
|
1949
|
-
x: corners[cornerKey].x + dx * step,
|
|
1950
|
-
y: corners[cornerKey].y + dy * step
|
|
1951
|
-
});
|
|
1952
|
-
},
|
|
1953
|
-
/** Re-read CSS variables into the canvas layer after a runtime theme change. */
|
|
1954
|
-
refreshTheme(theme) {
|
|
1955
|
-
applyThemeOption(theme);
|
|
1956
|
-
resolveTheme();
|
|
1957
|
-
scheduleRender();
|
|
1958
|
-
},
|
|
1959
|
-
confirm() {
|
|
1960
|
-
return confirmEditor();
|
|
1961
|
-
},
|
|
1962
|
-
cancel() {
|
|
1963
|
-
cancelEditor();
|
|
1964
|
-
},
|
|
1965
|
-
destroy() {
|
|
1966
|
-
var _a2;
|
|
1967
|
-
if (isDestroyed) return;
|
|
1968
|
-
isDestroyed = true;
|
|
1969
|
-
if (rafId !== null && caf) {
|
|
1970
|
-
caf(rafId);
|
|
1971
|
-
rafId = null;
|
|
1972
|
-
}
|
|
1973
|
-
if (dprCleanup) {
|
|
1974
|
-
dprCleanup();
|
|
1975
|
-
dprCleanup = null;
|
|
1976
|
-
}
|
|
1977
|
-
if (resizeObserver) {
|
|
1978
|
-
resizeObserver.disconnect();
|
|
1979
|
-
}
|
|
1980
|
-
for (const key of cornerOrder) {
|
|
1981
|
-
const el = handleEls[key];
|
|
1982
|
-
if (el && el.parentNode) el.parentNode.removeChild(el);
|
|
1983
|
-
}
|
|
1984
|
-
if (toolbarEl && toolbarEl.parentNode) toolbarEl.parentNode.removeChild(toolbarEl);
|
|
1985
|
-
if (nudgeControls && nudgeControls.parentNode) nudgeControls.parentNode.removeChild(nudgeControls);
|
|
1986
|
-
if (editorCanvas.parentNode) editorCanvas.parentNode.removeChild(editorCanvas);
|
|
1987
|
-
if (addedRootClass) container.classList.remove("scanic-corner-editor");
|
|
1988
|
-
if ((_a2 = options.classNames) == null ? void 0 : _a2.root) container.classList.remove(options.classNames.root);
|
|
1989
|
-
if (changedContainerPosition) {
|
|
1990
|
-
container.style.position = restoreContainerStyle.position;
|
|
1991
|
-
}
|
|
1992
|
-
if (changedContainerMinHeight) {
|
|
1993
|
-
container.style.minHeight = restoreContainerStyle.minHeight;
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
async function initialize() {
|
|
1999
|
-
try {
|
|
2000
|
-
return await initializeWasm();
|
|
2001
|
-
} catch {
|
|
2002
|
-
return null;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
class Scanner {
|
|
2006
|
-
constructor(options = {}) {
|
|
2007
|
-
this.defaultOptions = {
|
|
2008
|
-
maxProcessingDimension: 800,
|
|
2009
|
-
mode: "detect",
|
|
2010
|
-
output: "canvas",
|
|
2011
|
-
...options
|
|
2012
|
-
};
|
|
2013
|
-
this.initialized = false;
|
|
2014
|
-
}
|
|
2015
|
-
/**
|
|
2016
|
-
* Warm up the scanner (load WASM, etc.)
|
|
2017
|
-
*/
|
|
2018
|
-
async initialize() {
|
|
2019
|
-
if (this.initialized) return;
|
|
2020
|
-
try {
|
|
2021
|
-
await initializeWasm();
|
|
2022
|
-
} catch {
|
|
2023
|
-
}
|
|
2024
|
-
this.initialized = true;
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* Scan an image for a document.
|
|
2028
|
-
* @param {HTMLImageElement|HTMLCanvasElement|ImageData} image
|
|
2029
|
-
* @param {Object} options Override default options
|
|
2030
|
-
*/
|
|
2031
|
-
async scan(image, options = {}) {
|
|
2032
|
-
if (!this.initialized) await this.initialize();
|
|
2033
|
-
const combinedOptions = { ...this.defaultOptions, ...options };
|
|
2034
|
-
return await scanDocument(image, combinedOptions);
|
|
2035
|
-
}
|
|
2036
|
-
/**
|
|
2037
|
-
* Extract a document from an image using manual corners.
|
|
2038
|
-
* @param {HTMLImageElement|HTMLCanvasElement|ImageData} image
|
|
2039
|
-
* @param {Object} corners
|
|
2040
|
-
* @param {Object} options
|
|
2041
|
-
*/
|
|
2042
|
-
async extract(image, corners, options = {}) {
|
|
2043
|
-
if (!this.initialized) await this.initialize();
|
|
2044
|
-
const combinedOptions = { ...this.defaultOptions, ...options };
|
|
2045
|
-
return await extractDocument(image, corners, combinedOptions);
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
async function prepareScaleAndGrayscale(image, maxDimension = 800) {
|
|
2049
|
-
let originalWidth, originalHeight;
|
|
2050
|
-
const isImageData = image && typeof image.width === "number" && typeof image.height === "number" && image.data;
|
|
2051
|
-
if (isImageData) {
|
|
2052
|
-
originalWidth = image.width;
|
|
2053
|
-
originalHeight = image.height;
|
|
2054
|
-
} else if (image) {
|
|
2055
|
-
originalWidth = image.width || image.naturalWidth;
|
|
2056
|
-
originalHeight = image.height || image.naturalHeight;
|
|
2057
|
-
} else {
|
|
2058
|
-
throw new Error("No image provided");
|
|
2059
|
-
}
|
|
2060
|
-
const maxCurrentDimension = Math.max(originalWidth, originalHeight);
|
|
2061
|
-
let targetWidth, targetHeight, scaleFactor;
|
|
2062
|
-
if (maxCurrentDimension <= maxDimension) {
|
|
2063
|
-
targetWidth = originalWidth;
|
|
2064
|
-
targetHeight = originalHeight;
|
|
2065
|
-
scaleFactor = 1;
|
|
2066
|
-
} else {
|
|
2067
|
-
const scale = maxDimension / maxCurrentDimension;
|
|
2068
|
-
targetWidth = Math.round(originalWidth * scale);
|
|
2069
|
-
targetHeight = Math.round(originalHeight * scale);
|
|
2070
|
-
scaleFactor = 1 / scale;
|
|
2071
|
-
}
|
|
2072
|
-
const useOffscreen = typeof OffscreenCanvas !== "undefined";
|
|
2073
|
-
const canvas = useOffscreen ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
|
|
2074
|
-
if (!useOffscreen) {
|
|
2075
|
-
canvas.width = targetWidth;
|
|
2076
|
-
canvas.height = targetHeight;
|
|
2077
|
-
}
|
|
2078
|
-
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
2079
|
-
ctx.imageSmoothingEnabled = true;
|
|
2080
|
-
ctx.imageSmoothingQuality = "medium";
|
|
2081
|
-
if (isImageData) {
|
|
2082
|
-
const tempCanvas = useOffscreen ? new OffscreenCanvas(originalWidth, originalHeight) : document.createElement("canvas");
|
|
2083
|
-
if (!useOffscreen) {
|
|
2084
|
-
tempCanvas.width = originalWidth;
|
|
2085
|
-
tempCanvas.height = originalHeight;
|
|
2086
|
-
}
|
|
2087
|
-
const tempCtx = tempCanvas.getContext("2d");
|
|
2088
|
-
tempCtx.putImageData(image, 0, 0);
|
|
2089
|
-
ctx.drawImage(tempCanvas, 0, 0, originalWidth, originalHeight, 0, 0, targetWidth, targetHeight);
|
|
2090
|
-
} else {
|
|
2091
|
-
ctx.drawImage(image, 0, 0, originalWidth, originalHeight, 0, 0, targetWidth, targetHeight);
|
|
2092
|
-
}
|
|
2093
|
-
const imageData = ctx.getImageData(0, 0, targetWidth, targetHeight);
|
|
2094
|
-
const data = imageData.data;
|
|
2095
|
-
const pixelCount = targetWidth * targetHeight;
|
|
2096
|
-
const grayscaleData = new Uint8ClampedArray(pixelCount);
|
|
2097
|
-
for (let i = 0, j = 0; i < data.length; i += 4, j++) {
|
|
2098
|
-
const gray = data[i] * 54 + data[i + 1] * 183 + data[i + 2] * 19 >> 8;
|
|
2099
|
-
grayscaleData[j] = gray;
|
|
2100
|
-
data[i] = data[i + 1] = data[i + 2] = gray;
|
|
2101
|
-
}
|
|
2102
|
-
return {
|
|
2103
|
-
grayscaleData,
|
|
2104
|
-
imageData,
|
|
2105
|
-
// Keep full RGBA for debug visualization
|
|
2106
|
-
scaleFactor,
|
|
2107
|
-
originalDimensions: { width: originalWidth, height: originalHeight },
|
|
2108
|
-
scaledDimensions: { width: targetWidth, height: targetHeight }
|
|
2109
|
-
};
|
|
2110
|
-
}
|
|
2111
|
-
function clamp01(value) {
|
|
2112
|
-
if (value <= 0) return 0;
|
|
2113
|
-
if (value >= 1) return 1;
|
|
2114
|
-
return value;
|
|
2115
|
-
}
|
|
2116
|
-
function pointDistance(a, b) {
|
|
2117
|
-
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
2118
|
-
}
|
|
2119
|
-
function polygonAreaFromCorners(corners) {
|
|
2120
|
-
if (!corners) return 0;
|
|
2121
|
-
const points = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft];
|
|
2122
|
-
let area = 0;
|
|
2123
|
-
for (let i = 0; i < points.length; i++) {
|
|
2124
|
-
const j = (i + 1) % points.length;
|
|
2125
|
-
area += points[i].x * points[j].y - points[j].x * points[i].y;
|
|
2126
|
-
}
|
|
2127
|
-
return Math.abs(area) / 2;
|
|
2128
|
-
}
|
|
2129
|
-
function sideLengthsFromCorners(corners) {
|
|
2130
|
-
return [
|
|
2131
|
-
pointDistance(corners.topLeft, corners.topRight),
|
|
2132
|
-
pointDistance(corners.topRight, corners.bottomRight),
|
|
2133
|
-
pointDistance(corners.bottomRight, corners.bottomLeft),
|
|
2134
|
-
pointDistance(corners.bottomLeft, corners.topLeft)
|
|
2135
|
-
];
|
|
2136
|
-
}
|
|
2137
|
-
function cornerAnglesDegrees(corners) {
|
|
2138
|
-
const points = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft];
|
|
2139
|
-
const angles = [];
|
|
2140
|
-
for (let i = 0; i < points.length; i++) {
|
|
2141
|
-
const prev = points[(i + points.length - 1) % points.length];
|
|
2142
|
-
const curr = points[i];
|
|
2143
|
-
const next = points[(i + 1) % points.length];
|
|
2144
|
-
const v1x = prev.x - curr.x;
|
|
2145
|
-
const v1y = prev.y - curr.y;
|
|
2146
|
-
const v2x = next.x - curr.x;
|
|
2147
|
-
const v2y = next.y - curr.y;
|
|
2148
|
-
const dot = v1x * v2x + v1y * v2y;
|
|
2149
|
-
const mag1 = Math.hypot(v1x, v1y);
|
|
2150
|
-
const mag2 = Math.hypot(v2x, v2y);
|
|
2151
|
-
const denom = Math.max(1e-6, mag1 * mag2);
|
|
2152
|
-
const cosTheta = Math.max(-1, Math.min(1, dot / denom));
|
|
2153
|
-
angles.push(Math.acos(cosTheta) * 180 / Math.PI);
|
|
2154
|
-
}
|
|
2155
|
-
return angles;
|
|
2156
|
-
}
|
|
2157
|
-
function computeRightAngleScore(corners) {
|
|
2158
|
-
const angles = cornerAnglesDegrees(corners);
|
|
2159
|
-
let total = 0;
|
|
2160
|
-
for (const angle of angles) {
|
|
2161
|
-
const deviation = Math.abs(angle - 90);
|
|
2162
|
-
total += clamp01(1 - deviation / 55);
|
|
2163
|
-
}
|
|
2164
|
-
return total / Math.max(1, angles.length);
|
|
2165
|
-
}
|
|
2166
|
-
function computeOppositeSideConsistency(corners) {
|
|
2167
|
-
const sides = sideLengthsFromCorners(corners);
|
|
2168
|
-
const widthConsistency = Math.min(sides[0], sides[2]) / Math.max(1e-6, Math.max(sides[0], sides[2]));
|
|
2169
|
-
const heightConsistency = Math.min(sides[1], sides[3]) / Math.max(1e-6, Math.max(sides[1], sides[3]));
|
|
2170
|
-
return (widthConsistency + heightConsistency) / 2;
|
|
2171
|
-
}
|
|
2172
|
-
function compareCandidates(a, b) {
|
|
2173
|
-
const validDelta = Number(b.isValid) - Number(a.isValid);
|
|
2174
|
-
if (validDelta !== 0) return validDelta;
|
|
2175
|
-
const confidenceDelta = b.confidence - a.confidence;
|
|
2176
|
-
const nearTie = Math.abs(confidenceDelta) < 0.015;
|
|
2177
|
-
if (!nearTie && confidenceDelta !== 0) return confidenceDelta;
|
|
2178
|
-
const complexityA = Math.abs((a.approxCount ?? 99) - 4);
|
|
2179
|
-
const complexityB = Math.abs((b.approxCount ?? 99) - 4);
|
|
2180
|
-
const complexityDelta = complexityA - complexityB;
|
|
2181
|
-
if (complexityDelta !== 0) return complexityDelta;
|
|
2182
|
-
const angleDelta = (b.rightAngleScore ?? 0) - (a.rightAngleScore ?? 0);
|
|
2183
|
-
if (Math.abs(angleDelta) > 1e-6) return angleDelta;
|
|
2184
|
-
const fitErrorA = Math.abs(1 - (a.contourFitRatio ?? 1));
|
|
2185
|
-
const fitErrorB = Math.abs(1 - (b.contourFitRatio ?? 1));
|
|
2186
|
-
const fitDelta = fitErrorA - fitErrorB;
|
|
2187
|
-
if (Math.abs(fitDelta) > 1e-6) return fitDelta;
|
|
2188
|
-
return confidenceDelta || b.score - a.score || b.coverageRatio - a.coverageRatio || b.area - a.area;
|
|
2189
|
-
}
|
|
2190
|
-
function cornersAreFiniteAndDistinct(corners, minDistance = 6) {
|
|
2191
|
-
const points = [corners == null ? void 0 : corners.topLeft, corners == null ? void 0 : corners.topRight, corners == null ? void 0 : corners.bottomRight, corners == null ? void 0 : corners.bottomLeft];
|
|
2192
|
-
if (points.some((p) => !p || !Number.isFinite(p.x) || !Number.isFinite(p.y))) {
|
|
2193
|
-
return false;
|
|
2194
|
-
}
|
|
2195
|
-
for (let i = 0; i < points.length; i++) {
|
|
2196
|
-
for (let j = i + 1; j < points.length; j++) {
|
|
2197
|
-
if (pointDistance(points[i], points[j]) < minDistance) {
|
|
2198
|
-
return false;
|
|
2199
|
-
}
|
|
2200
|
-
}
|
|
2201
|
-
}
|
|
2202
|
-
return true;
|
|
2203
|
-
}
|
|
2204
|
-
function isConvexQuadrilateral(corners) {
|
|
2205
|
-
const points = [corners.topLeft, corners.topRight, corners.bottomRight, corners.bottomLeft];
|
|
2206
|
-
const crossSigns = [];
|
|
2207
|
-
for (let i = 0; i < points.length; i++) {
|
|
2208
|
-
const p0 = points[i];
|
|
2209
|
-
const p1 = points[(i + 1) % points.length];
|
|
2210
|
-
const p2 = points[(i + 2) % points.length];
|
|
2211
|
-
const cross = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
|
|
2212
|
-
if (Math.abs(cross) < 1e-6) {
|
|
2213
|
-
continue;
|
|
2214
|
-
}
|
|
2215
|
-
crossSigns.push(Math.sign(cross));
|
|
2216
|
-
}
|
|
2217
|
-
if (crossSigns.length < 3) {
|
|
2218
|
-
return false;
|
|
2219
|
-
}
|
|
2220
|
-
const firstSign = crossSigns[0];
|
|
2221
|
-
return crossSigns.every((s) => s === firstSign);
|
|
2222
|
-
}
|
|
2223
|
-
function computeEdgeSupportScore(contour, edges, width, height) {
|
|
2224
|
-
if (!(contour == null ? void 0 : contour.points) || contour.points.length === 0) {
|
|
2225
|
-
return 0;
|
|
2226
|
-
}
|
|
2227
|
-
const sampleStep = Math.max(1, Math.floor(contour.points.length / 240));
|
|
2228
|
-
let samples = 0;
|
|
2229
|
-
let supported = 0;
|
|
2230
|
-
for (let i = 0; i < contour.points.length; i += sampleStep) {
|
|
2231
|
-
const p = contour.points[i];
|
|
2232
|
-
const x = Math.max(0, Math.min(width - 1, p.x | 0));
|
|
2233
|
-
const y = Math.max(0, Math.min(height - 1, p.y | 0));
|
|
2234
|
-
samples++;
|
|
2235
|
-
let localHit = false;
|
|
2236
|
-
for (let oy = -1; oy <= 1 && !localHit; oy++) {
|
|
2237
|
-
const ny = y + oy;
|
|
2238
|
-
if (ny < 0 || ny >= height) continue;
|
|
2239
|
-
for (let ox = -1; ox <= 1; ox++) {
|
|
2240
|
-
const nx = x + ox;
|
|
2241
|
-
if (nx < 0 || nx >= width) continue;
|
|
2242
|
-
if (edges[ny * width + nx] > 0) {
|
|
2243
|
-
localHit = true;
|
|
2244
|
-
break;
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
if (localHit) supported++;
|
|
2249
|
-
}
|
|
2250
|
-
if (samples === 0) return 0;
|
|
2251
|
-
return supported / samples;
|
|
2252
|
-
}
|
|
2253
|
-
function evaluateContourCandidate(contour, edges, width, height, options = {}) {
|
|
2254
|
-
const epsilon = options.epsilon || 0.02;
|
|
2255
|
-
const approx = approximatePolygon(contour.points, epsilon);
|
|
2256
|
-
const approxCount = approx.length;
|
|
2257
|
-
const corners = findCornerPoints(contour, { epsilon });
|
|
2258
|
-
if (!corners) {
|
|
2259
|
-
return {
|
|
2260
|
-
contour,
|
|
2261
|
-
corners: null,
|
|
2262
|
-
score: 0,
|
|
2263
|
-
confidence: 0,
|
|
2264
|
-
isValid: false,
|
|
2265
|
-
area: contour.area || 0,
|
|
2266
|
-
fillRatio: 0,
|
|
2267
|
-
coverageRatio: 0,
|
|
2268
|
-
cornersArea: 0,
|
|
2269
|
-
approxCount,
|
|
2270
|
-
convex: false,
|
|
2271
|
-
edgeSupport: 0,
|
|
2272
|
-
aspectRatio: Infinity,
|
|
2273
|
-
minSide: 0,
|
|
2274
|
-
rightAngleScore: 0,
|
|
2275
|
-
oppositeSideConsistency: 0,
|
|
2276
|
-
contourFitRatio: 0,
|
|
2277
|
-
contourFitScore: 0
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
|
-
const imageArea = width * height;
|
|
2281
|
-
const area = contour.area || 0;
|
|
2282
|
-
const box = contour.boundingBox || { minX: 0, minY: 0, maxX: 0, maxY: 0 };
|
|
2283
|
-
const boxArea = Math.max(1, (box.maxX - box.minX + 1) * (box.maxY - box.minY + 1));
|
|
2284
|
-
const fillRatio = area / boxArea;
|
|
2285
|
-
const cornersArea = polygonAreaFromCorners(corners);
|
|
2286
|
-
const coverageRatio = cornersArea / Math.max(1, imageArea);
|
|
2287
|
-
const sides = sideLengthsFromCorners(corners);
|
|
2288
|
-
const minSide = Math.min(...sides);
|
|
2289
|
-
const avgWidth = (sides[0] + sides[2]) / 2;
|
|
2290
|
-
const avgHeight = (sides[1] + sides[3]) / 2;
|
|
2291
|
-
const aspectRatio = avgWidth > avgHeight ? avgWidth / Math.max(1e-6, avgHeight) : avgHeight / Math.max(1e-6, avgWidth);
|
|
2292
|
-
const convex = isConvexQuadrilateral(corners);
|
|
2293
|
-
const edgeSupport = computeEdgeSupportScore(contour, edges, width, height);
|
|
2294
|
-
const rightAngleScore = computeRightAngleScore(corners);
|
|
2295
|
-
const oppositeSideConsistency = computeOppositeSideConsistency(corners);
|
|
2296
|
-
const contourFitRatio = area / Math.max(1, cornersArea);
|
|
2297
|
-
const contourFitScore = clamp01(1 - Math.abs(contourFitRatio - 1) / 0.55);
|
|
2298
|
-
const quadLikeness = approxCount === 4 ? 1 : approxCount === 5 ? 0.9 : approxCount === 6 ? 0.7 : approxCount <= 8 ? 0.5 : 0.28;
|
|
2299
|
-
const areaScore = clamp01(area / Math.max(1, imageArea * 0.4));
|
|
2300
|
-
const fillScore = clamp01((fillRatio - 0.08) / 0.72);
|
|
2301
|
-
const coverageScore = clamp01((coverageRatio - 0.03) / 0.82);
|
|
2302
|
-
const minSideRatio = options.minDocumentSideRatio !== void 0 ? options.minDocumentSideRatio : 0.06;
|
|
2303
|
-
const minCoverage = options.minDocumentCoverageRatio !== void 0 ? options.minDocumentCoverageRatio : 0.04;
|
|
2304
|
-
const maxAspect = options.maxDocumentAspectRatio !== void 0 ? options.maxDocumentAspectRatio : 8;
|
|
2305
|
-
const minFillRatio = options.minDocumentFillRatio !== void 0 ? options.minDocumentFillRatio : 0.07;
|
|
2306
|
-
const minContourFitRatio = options.minContourFitRatio !== void 0 ? options.minContourFitRatio : 0.11;
|
|
2307
|
-
const maxContourFitRatio = options.maxContourFitRatio !== void 0 ? options.maxContourFitRatio : 1.2;
|
|
2308
|
-
const minRightAngleScore = options.minRightAngleScore !== void 0 ? options.minRightAngleScore : 0.42;
|
|
2309
|
-
const minOppositeSideConsistency = options.minOppositeSideConsistency !== void 0 ? options.minOppositeSideConsistency : 0.3;
|
|
2310
|
-
const minSidePx = Math.min(width, height) * minSideRatio;
|
|
2311
|
-
const geometryValid = cornersAreFiniteAndDistinct(corners) && convex && minSide >= minSidePx && coverageRatio >= minCoverage && aspectRatio <= maxAspect && fillRatio >= minFillRatio && contourFitRatio >= minContourFitRatio && contourFitRatio <= maxContourFitRatio && rightAngleScore >= minRightAngleScore && oppositeSideConsistency >= minOppositeSideConsistency;
|
|
2312
|
-
const score = areaScore * 0.22 + fillScore * 0.14 + quadLikeness * 0.15 + (convex ? 1 : 0) * 0.08 + edgeSupport * 0.08 + coverageScore * 0.13 + rightAngleScore * 0.1 + oppositeSideConsistency * 0.05 + contourFitScore * 0.05;
|
|
2313
|
-
const confidence = geometryValid ? score : score * 0.33;
|
|
2314
|
-
return {
|
|
2315
|
-
contour,
|
|
2316
|
-
corners,
|
|
2317
|
-
score,
|
|
2318
|
-
confidence,
|
|
2319
|
-
isValid: geometryValid,
|
|
2320
|
-
area,
|
|
2321
|
-
fillRatio,
|
|
2322
|
-
coverageRatio,
|
|
2323
|
-
cornersArea,
|
|
2324
|
-
approxCount,
|
|
2325
|
-
convex,
|
|
2326
|
-
edgeSupport,
|
|
2327
|
-
aspectRatio,
|
|
2328
|
-
minSide,
|
|
2329
|
-
rightAngleScore,
|
|
2330
|
-
oppositeSideConsistency,
|
|
2331
|
-
contourFitRatio,
|
|
2332
|
-
contourFitScore
|
|
2333
|
-
};
|
|
2334
|
-
}
|
|
2335
|
-
function selectBestContourCandidate(contours, edges, width, height, options = {}) {
|
|
2336
|
-
const maxCandidateContours = options.maxCandidateContours || 12;
|
|
2337
|
-
const candidates = contours.slice(0, maxCandidateContours).map((contour, index) => ({
|
|
2338
|
-
rankByArea: index,
|
|
2339
|
-
...evaluateContourCandidate(contour, edges, width, height, options)
|
|
2340
|
-
})).sort(compareCandidates);
|
|
2341
|
-
return {
|
|
2342
|
-
best: candidates[0] || null,
|
|
2343
|
-
candidates
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
function shouldRunDetectionCascade(bestCandidate, options = {}) {
|
|
2347
|
-
if (options.enableDetectionCascade === false) return false;
|
|
2348
|
-
if (!bestCandidate || !bestCandidate.corners) return true;
|
|
2349
|
-
const minConfidenceForSinglePass = options.minCascadeTriggerConfidence !== void 0 ? options.minCascadeTriggerConfidence : 0.68;
|
|
2350
|
-
if (!bestCandidate.isValid) return true;
|
|
2351
|
-
if (bestCandidate.confidence < minConfidenceForSinglePass) return true;
|
|
2352
|
-
if (bestCandidate.approxCount > 5) return true;
|
|
2353
|
-
if (bestCandidate.rightAngleScore < 0.55) return true;
|
|
2354
|
-
if (bestCandidate.contourFitRatio < 0.14) return true;
|
|
2355
|
-
return false;
|
|
2356
|
-
}
|
|
2357
|
-
function buildDetectionPassProfiles(options = {}) {
|
|
2358
|
-
const baseKernel = options.dilationKernelSize || 3;
|
|
2359
|
-
const baseIterations = options.dilationIterations || 1;
|
|
2360
|
-
const baseApplyDilation = options.applyDilation !== void 0 ? options.applyDilation : true;
|
|
2361
|
-
const profiles = [
|
|
2362
|
-
{
|
|
2363
|
-
name: "default",
|
|
2364
|
-
lowThreshold: options.lowThreshold,
|
|
2365
|
-
highThreshold: options.highThreshold,
|
|
2366
|
-
dilationKernelSize: baseKernel,
|
|
2367
|
-
dilationIterations: baseIterations,
|
|
2368
|
-
applyDilation: baseApplyDilation
|
|
2369
|
-
}
|
|
2370
|
-
];
|
|
2371
|
-
if (options.enableDetectionCascade === false) {
|
|
2372
|
-
return profiles;
|
|
2373
|
-
}
|
|
2374
|
-
profiles.push({
|
|
2375
|
-
name: "connect-edges",
|
|
2376
|
-
lowThreshold: options.lowThreshold,
|
|
2377
|
-
highThreshold: options.highThreshold,
|
|
2378
|
-
dilationKernelSize: Math.max(baseKernel, 5),
|
|
2379
|
-
dilationIterations: Math.max(baseIterations, 2),
|
|
2380
|
-
applyDilation: true
|
|
2381
|
-
});
|
|
2382
|
-
profiles.push({
|
|
2383
|
-
name: "no-dilation",
|
|
2384
|
-
lowThreshold: options.lowThreshold,
|
|
2385
|
-
highThreshold: options.highThreshold,
|
|
2386
|
-
dilationKernelSize: baseKernel,
|
|
2387
|
-
dilationIterations: baseIterations,
|
|
2388
|
-
applyDilation: false
|
|
2389
|
-
});
|
|
2390
|
-
if (options.lowThreshold === void 0 && options.highThreshold === void 0) {
|
|
2391
|
-
profiles.push({
|
|
2392
|
-
name: "fixed-mid-thresholds",
|
|
2393
|
-
lowThreshold: 60,
|
|
2394
|
-
highThreshold: 180,
|
|
2395
|
-
dilationKernelSize: baseKernel,
|
|
2396
|
-
dilationIterations: baseIterations,
|
|
2397
|
-
applyDilation: baseApplyDilation
|
|
2398
|
-
});
|
|
2399
|
-
}
|
|
2400
|
-
const deduped = [];
|
|
2401
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2402
|
-
for (const profile of profiles) {
|
|
2403
|
-
const key = [
|
|
2404
|
-
profile.lowThreshold,
|
|
2405
|
-
profile.highThreshold,
|
|
2406
|
-
profile.dilationKernelSize,
|
|
2407
|
-
profile.dilationIterations,
|
|
2408
|
-
profile.applyDilation
|
|
2409
|
-
].join(":");
|
|
2410
|
-
if (seen.has(key)) continue;
|
|
2411
|
-
seen.add(key);
|
|
2412
|
-
deduped.push(profile);
|
|
2413
|
-
}
|
|
2414
|
-
return deduped;
|
|
2415
|
-
}
|
|
2416
|
-
async function detectDocumentInternal(grayscaleData, width, height, scaleFactor, options = {}) {
|
|
2417
|
-
const debugInfo = options.debug ? {} : { _timingsOnly: true };
|
|
2418
|
-
const timings = [];
|
|
2419
|
-
if (debugInfo && !debugInfo._timingsOnly) {
|
|
2420
|
-
debugInfo.preprocessing = {
|
|
2421
|
-
scaledDimensions: { width, height },
|
|
2422
|
-
scaleFactor,
|
|
2423
|
-
maxProcessingDimension: options.maxProcessingDimension || 800
|
|
2424
|
-
};
|
|
2425
|
-
}
|
|
2426
|
-
const passProfiles = buildDetectionPassProfiles(options);
|
|
2427
|
-
const passResults = [];
|
|
2428
|
-
const runDetectionPass = async (profile, passIndex) => {
|
|
2429
|
-
const passLabel = profile.name || `pass-${passIndex + 1}`;
|
|
2430
|
-
const passSuffix = passIndex === 0 ? "" : ` (${passLabel})`;
|
|
2431
|
-
const passDebug = options.debug ? {} : { _timingsOnly: true };
|
|
2432
|
-
const edges = await cannyEdgeDetector(grayscaleData, {
|
|
2433
|
-
width,
|
|
2434
|
-
height,
|
|
2435
|
-
lowThreshold: profile.lowThreshold,
|
|
2436
|
-
highThreshold: profile.highThreshold,
|
|
2437
|
-
dilationKernelSize: profile.dilationKernelSize,
|
|
2438
|
-
dilationIterations: profile.dilationIterations,
|
|
2439
|
-
applyDilation: profile.applyDilation,
|
|
2440
|
-
debug: passDebug,
|
|
2441
|
-
skipGrayscale: true,
|
|
2442
|
-
useWasmBlur: true,
|
|
2443
|
-
useWasmHysteresis: options.useWasmHysteresis,
|
|
2444
|
-
useWasmFullCanny: options.useWasmFullCanny
|
|
2445
|
-
});
|
|
2446
|
-
if (passDebug.timings) {
|
|
2447
|
-
passDebug.timings.forEach((timing) => {
|
|
2448
|
-
if (timing.step === "Edge Detection Total") return;
|
|
2449
|
-
timings.push({ step: `${timing.step}${passSuffix}`, ms: timing.ms });
|
|
2450
|
-
});
|
|
2451
|
-
}
|
|
2452
|
-
let t0 = performance.now();
|
|
2453
|
-
const contours = detectDocumentContour(edges, {
|
|
2454
|
-
minArea: (options.minArea || 1e3) / (scaleFactor * scaleFactor),
|
|
2455
|
-
width,
|
|
2456
|
-
height
|
|
2457
|
-
});
|
|
2458
|
-
timings.push({ step: `Find Contours${passSuffix}`, ms: (performance.now() - t0).toFixed(2) });
|
|
2459
|
-
t0 = performance.now();
|
|
2460
|
-
const { best: best2, candidates: candidates2 } = selectBestContourCandidate(contours, edges, width, height, options);
|
|
2461
|
-
timings.push({ step: `Corner Detection${passSuffix}`, ms: (performance.now() - t0).toFixed(2) });
|
|
2462
|
-
return {
|
|
2463
|
-
name: passLabel,
|
|
2464
|
-
params: {
|
|
2465
|
-
lowThreshold: profile.lowThreshold,
|
|
2466
|
-
highThreshold: profile.highThreshold,
|
|
2467
|
-
dilationKernelSize: profile.dilationKernelSize,
|
|
2468
|
-
dilationIterations: profile.dilationIterations,
|
|
2469
|
-
applyDilation: profile.applyDilation
|
|
2470
|
-
},
|
|
2471
|
-
contours,
|
|
2472
|
-
best: best2,
|
|
2473
|
-
candidates: candidates2
|
|
2474
|
-
};
|
|
2475
|
-
};
|
|
2476
|
-
const primaryPass = await runDetectionPass(passProfiles[0], 0);
|
|
2477
|
-
passResults.push(primaryPass);
|
|
2478
|
-
if (shouldRunDetectionCascade(primaryPass.best, options)) {
|
|
2479
|
-
for (let i = 1; i < passProfiles.length; i++) {
|
|
2480
|
-
passResults.push(await runDetectionPass(passProfiles[i], i));
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
const allCandidates = [];
|
|
2484
|
-
for (const pass of passResults) {
|
|
2485
|
-
for (const candidate of pass.candidates) {
|
|
2486
|
-
allCandidates.push({
|
|
2487
|
-
...candidate,
|
|
2488
|
-
passName: pass.name,
|
|
2489
|
-
passParams: pass.params
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
allCandidates.sort(compareCandidates);
|
|
2494
|
-
const best = allCandidates[0] || null;
|
|
2495
|
-
const candidates = allCandidates;
|
|
2496
|
-
if (!best || !best.corners) {
|
|
2497
|
-
console.log("No document detected");
|
|
2498
|
-
return {
|
|
2499
|
-
success: false,
|
|
2500
|
-
message: "No document detected",
|
|
2501
|
-
debug: debugInfo._timingsOnly ? null : debugInfo,
|
|
2502
|
-
timings
|
|
2503
|
-
};
|
|
2504
|
-
}
|
|
2505
|
-
if (debugInfo && !debugInfo._timingsOnly) {
|
|
2506
|
-
debugInfo.passes = passResults.map((pass) => ({
|
|
2507
|
-
name: pass.name,
|
|
2508
|
-
params: pass.params,
|
|
2509
|
-
contourCount: pass.contours.length,
|
|
2510
|
-
bestCandidate: pass.best ? {
|
|
2511
|
-
score: pass.best.score,
|
|
2512
|
-
confidence: pass.best.confidence,
|
|
2513
|
-
isValid: pass.best.isValid,
|
|
2514
|
-
approxCount: pass.best.approxCount,
|
|
2515
|
-
coverageRatio: pass.best.coverageRatio,
|
|
2516
|
-
fillRatio: pass.best.fillRatio,
|
|
2517
|
-
rightAngleScore: pass.best.rightAngleScore,
|
|
2518
|
-
contourFitRatio: pass.best.contourFitRatio
|
|
2519
|
-
} : null
|
|
2520
|
-
}));
|
|
2521
|
-
debugInfo.candidates = candidates.map((candidate) => ({
|
|
2522
|
-
passName: candidate.passName,
|
|
2523
|
-
rankByArea: candidate.rankByArea,
|
|
2524
|
-
area: candidate.area,
|
|
2525
|
-
fillRatio: candidate.fillRatio,
|
|
2526
|
-
coverageRatio: candidate.coverageRatio,
|
|
2527
|
-
cornersArea: candidate.cornersArea,
|
|
2528
|
-
approxCount: candidate.approxCount,
|
|
2529
|
-
convex: candidate.convex,
|
|
2530
|
-
edgeSupport: candidate.edgeSupport,
|
|
2531
|
-
aspectRatio: candidate.aspectRatio,
|
|
2532
|
-
minSide: candidate.minSide,
|
|
2533
|
-
rightAngleScore: candidate.rightAngleScore,
|
|
2534
|
-
oppositeSideConsistency: candidate.oppositeSideConsistency,
|
|
2535
|
-
contourFitRatio: candidate.contourFitRatio,
|
|
2536
|
-
contourFitScore: candidate.contourFitScore,
|
|
2537
|
-
score: candidate.score,
|
|
2538
|
-
confidence: candidate.confidence,
|
|
2539
|
-
isValid: candidate.isValid
|
|
2540
|
-
}));
|
|
2541
|
-
debugInfo.selectedCandidate = candidates[0] ? {
|
|
2542
|
-
passName: candidates[0].passName,
|
|
2543
|
-
rankByArea: candidates[0].rankByArea,
|
|
2544
|
-
area: candidates[0].area,
|
|
2545
|
-
fillRatio: candidates[0].fillRatio,
|
|
2546
|
-
coverageRatio: candidates[0].coverageRatio,
|
|
2547
|
-
approxCount: candidates[0].approxCount,
|
|
2548
|
-
edgeSupport: candidates[0].edgeSupport,
|
|
2549
|
-
aspectRatio: candidates[0].aspectRatio,
|
|
2550
|
-
minSide: candidates[0].minSide,
|
|
2551
|
-
rightAngleScore: candidates[0].rightAngleScore,
|
|
2552
|
-
oppositeSideConsistency: candidates[0].oppositeSideConsistency,
|
|
2553
|
-
contourFitRatio: candidates[0].contourFitRatio,
|
|
2554
|
-
score: candidates[0].score,
|
|
2555
|
-
confidence: candidates[0].confidence,
|
|
2556
|
-
isValid: candidates[0].isValid
|
|
2557
|
-
} : null;
|
|
2558
|
-
}
|
|
2559
|
-
const cornerPoints = best.corners;
|
|
2560
|
-
const documentContour = best.contour;
|
|
2561
|
-
let finalCorners = cornerPoints;
|
|
2562
|
-
if (scaleFactor !== 1) {
|
|
2563
|
-
finalCorners = {
|
|
2564
|
-
topLeft: { x: cornerPoints.topLeft.x * scaleFactor, y: cornerPoints.topLeft.y * scaleFactor },
|
|
2565
|
-
topRight: { x: cornerPoints.topRight.x * scaleFactor, y: cornerPoints.topRight.y * scaleFactor },
|
|
2566
|
-
bottomRight: { x: cornerPoints.bottomRight.x * scaleFactor, y: cornerPoints.bottomRight.y * scaleFactor },
|
|
2567
|
-
bottomLeft: { x: cornerPoints.bottomLeft.x * scaleFactor, y: cornerPoints.bottomLeft.y * scaleFactor }
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
return {
|
|
2571
|
-
success: true,
|
|
2572
|
-
contour: documentContour,
|
|
2573
|
-
corners: finalCorners,
|
|
2574
|
-
confidence: best.confidence,
|
|
2575
|
-
debug: debugInfo._timingsOnly ? null : debugInfo,
|
|
2576
|
-
timings
|
|
2577
|
-
};
|
|
2578
|
-
}
|
|
2579
|
-
function getPerspectiveTransform(srcPoints, dstPoints) {
|
|
2580
|
-
function buildMatrix(points) {
|
|
2581
|
-
const matrix2 = [];
|
|
2582
|
-
for (let i = 0; i < 4; i++) {
|
|
2583
|
-
const [x, y] = points[i];
|
|
2584
|
-
matrix2.push([x, y, 1, 0, 0, 0, -x * dstPoints[i][0], -y * dstPoints[i][0]]);
|
|
2585
|
-
matrix2.push([0, 0, 0, x, y, 1, -x * dstPoints[i][1], -y * dstPoints[i][1]]);
|
|
2586
|
-
}
|
|
2587
|
-
return matrix2;
|
|
2588
|
-
}
|
|
2589
|
-
const A = buildMatrix(srcPoints);
|
|
2590
|
-
const b = [
|
|
2591
|
-
dstPoints[0][0],
|
|
2592
|
-
dstPoints[0][1],
|
|
2593
|
-
dstPoints[1][0],
|
|
2594
|
-
dstPoints[1][1],
|
|
2595
|
-
dstPoints[2][0],
|
|
2596
|
-
dstPoints[2][1],
|
|
2597
|
-
dstPoints[3][0],
|
|
2598
|
-
dstPoints[3][1]
|
|
2599
|
-
];
|
|
2600
|
-
function solve(A2, b2) {
|
|
2601
|
-
const m = A2.length;
|
|
2602
|
-
const n = A2[0].length;
|
|
2603
|
-
const M = A2.map((row) => row.slice());
|
|
2604
|
-
const B = b2.slice();
|
|
2605
|
-
for (let i = 0; i < n; i++) {
|
|
2606
|
-
let maxRow = i;
|
|
2607
|
-
for (let k = i + 1; k < m; k++) {
|
|
2608
|
-
if (Math.abs(M[k][i]) > Math.abs(M[maxRow][i])) maxRow = k;
|
|
2609
|
-
}
|
|
2610
|
-
[M[i], M[maxRow]] = [M[maxRow], M[i]];
|
|
2611
|
-
[B[i], B[maxRow]] = [B[maxRow], B[i]];
|
|
2612
|
-
for (let k = i + 1; k < m; k++) {
|
|
2613
|
-
const c = M[k][i] / M[i][i];
|
|
2614
|
-
for (let j = i; j < n; j++) {
|
|
2615
|
-
M[k][j] -= c * M[i][j];
|
|
2616
|
-
}
|
|
2617
|
-
B[k] -= c * B[i];
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
const x = new Array(n);
|
|
2621
|
-
for (let i = n - 1; i >= 0; i--) {
|
|
2622
|
-
let sum = B[i];
|
|
2623
|
-
for (let j = i + 1; j < n; j++) {
|
|
2624
|
-
sum -= M[i][j] * x[j];
|
|
2625
|
-
}
|
|
2626
|
-
x[i] = sum / M[i][i];
|
|
2627
|
-
}
|
|
2628
|
-
return x;
|
|
2629
|
-
}
|
|
2630
|
-
const h = solve(A, b);
|
|
2631
|
-
const matrix = [
|
|
2632
|
-
[h[0], h[1], h[2]],
|
|
2633
|
-
[h[3], h[4], h[5]],
|
|
2634
|
-
[h[6], h[7], 1]
|
|
2635
|
-
];
|
|
2636
|
-
return matrix;
|
|
2637
|
-
}
|
|
2638
|
-
function unwarpImage(ctx, image, corners) {
|
|
2639
|
-
const { topLeft, topRight, bottomRight, bottomLeft } = corners;
|
|
2640
|
-
const widthA = Math.hypot(bottomRight.x - bottomLeft.x, bottomRight.y - bottomLeft.y);
|
|
2641
|
-
const widthB = Math.hypot(topRight.x - topLeft.x, topRight.y - topLeft.y);
|
|
2642
|
-
const maxWidth = Math.round(Math.max(widthA, widthB));
|
|
2643
|
-
const heightA = Math.hypot(topRight.x - bottomRight.x, topRight.y - bottomRight.y);
|
|
2644
|
-
const heightB = Math.hypot(topLeft.x - bottomLeft.x, topLeft.y - bottomLeft.y);
|
|
2645
|
-
const maxHeight = Math.round(Math.max(heightA, heightB));
|
|
2646
|
-
ctx.canvas.width = maxWidth;
|
|
2647
|
-
ctx.canvas.height = maxHeight;
|
|
2648
|
-
const srcPoints = [
|
|
2649
|
-
[topLeft.x, topLeft.y],
|
|
2650
|
-
[topRight.x, topRight.y],
|
|
2651
|
-
[bottomRight.x, bottomRight.y],
|
|
2652
|
-
[bottomLeft.x, bottomLeft.y]
|
|
2653
|
-
];
|
|
2654
|
-
const dstPoints = [
|
|
2655
|
-
[0, 0],
|
|
2656
|
-
[maxWidth - 1, 0],
|
|
2657
|
-
[maxWidth - 1, maxHeight - 1],
|
|
2658
|
-
[0, maxHeight - 1]
|
|
2659
|
-
];
|
|
2660
|
-
const perspectiveMatrix = getPerspectiveTransform(srcPoints, dstPoints);
|
|
2661
|
-
warpTransform(ctx, image, perspectiveMatrix, maxWidth, maxHeight);
|
|
2662
|
-
}
|
|
2663
|
-
function invert3x3(m) {
|
|
2664
|
-
const a = m[0][0], b = m[0][1], c = m[0][2];
|
|
2665
|
-
const d = m[1][0], e = m[1][1], f = m[1][2];
|
|
2666
|
-
const g = m[2][0], h = m[2][1], i = m[2][2];
|
|
2667
|
-
const A = e * i - f * h;
|
|
2668
|
-
const B = -(d * i - f * g);
|
|
2669
|
-
const C = d * h - e * g;
|
|
2670
|
-
const D = -(b * i - c * h);
|
|
2671
|
-
const E = a * i - c * g;
|
|
2672
|
-
const F = -(a * h - b * g);
|
|
2673
|
-
const G = b * f - c * e;
|
|
2674
|
-
const H = -(a * f - c * d);
|
|
2675
|
-
const I = a * e - b * d;
|
|
2676
|
-
const det = a * A + b * B + c * C;
|
|
2677
|
-
if (det === 0) throw new Error("Singular matrix");
|
|
2678
|
-
return [
|
|
2679
|
-
[A / det, D / det, G / det],
|
|
2680
|
-
[B / det, E / det, H / det],
|
|
2681
|
-
[C / det, F / det, I / det]
|
|
2682
|
-
];
|
|
2683
|
-
}
|
|
2684
|
-
function warpTransform(ctx, image, matrix, outWidth, outHeight) {
|
|
2685
|
-
const isImageData = image && typeof image.width === "number" && typeof image.height === "number" && image.data;
|
|
2686
|
-
const srcWidth = image.width || image.naturalWidth;
|
|
2687
|
-
const srcHeight = image.height || image.naturalHeight;
|
|
2688
|
-
const srcCanvas = document.createElement("canvas");
|
|
2689
|
-
srcCanvas.width = srcWidth;
|
|
2690
|
-
srcCanvas.height = srcHeight;
|
|
2691
|
-
const srcCtx = srcCanvas.getContext("2d", { willReadFrequently: true });
|
|
2692
|
-
if (isImageData) {
|
|
2693
|
-
srcCtx.putImageData(image, 0, 0);
|
|
2694
|
-
} else {
|
|
2695
|
-
srcCtx.drawImage(image, 0, 0, srcWidth, srcHeight);
|
|
2696
|
-
}
|
|
2697
|
-
const srcData = srcCtx.getImageData(0, 0, srcWidth, srcHeight).data;
|
|
2698
|
-
const inv = invert3x3(matrix);
|
|
2699
|
-
const i00 = inv[0][0], i01 = inv[0][1], i02 = inv[0][2];
|
|
2700
|
-
const i10 = inv[1][0], i11 = inv[1][1], i12 = inv[1][2];
|
|
2701
|
-
const i20 = inv[2][0], i21 = inv[2][1], i22 = inv[2][2];
|
|
2702
|
-
const outData = ctx.createImageData(outWidth, outHeight);
|
|
2703
|
-
const dst = outData.data;
|
|
2704
|
-
const maxSrcX = srcWidth - 1;
|
|
2705
|
-
const maxSrcY = srcHeight - 1;
|
|
2706
|
-
for (let oy = 0; oy < outHeight; oy++) {
|
|
2707
|
-
const iy1 = i01 * oy + i02;
|
|
2708
|
-
const iy2 = i11 * oy + i12;
|
|
2709
|
-
const iy3 = i21 * oy + i22;
|
|
2710
|
-
for (let ox = 0; ox < outWidth; ox++) {
|
|
2711
|
-
const w = i20 * ox + iy3;
|
|
2712
|
-
const invW = 1 / w;
|
|
2713
|
-
const sx = (i00 * ox + iy1) * invW;
|
|
2714
|
-
const sy = (i10 * ox + iy2) * invW;
|
|
2715
|
-
const csx = sx < 0 ? 0 : sx > maxSrcX ? maxSrcX : sx;
|
|
2716
|
-
const csy = sy < 0 ? 0 : sy > maxSrcY ? maxSrcY : sy;
|
|
2717
|
-
const x0 = csx | 0;
|
|
2718
|
-
const y0 = csy | 0;
|
|
2719
|
-
const x1 = x0 < maxSrcX ? x0 + 1 : x0;
|
|
2720
|
-
const y1 = y0 < maxSrcY ? y0 + 1 : y0;
|
|
2721
|
-
const fx = csx - x0;
|
|
2722
|
-
const fy = csy - y0;
|
|
2723
|
-
const fx1 = 1 - fx;
|
|
2724
|
-
const fy1 = 1 - fy;
|
|
2725
|
-
const w00 = fx1 * fy1;
|
|
2726
|
-
const w10 = fx * fy1;
|
|
2727
|
-
const w01 = fx1 * fy;
|
|
2728
|
-
const w11 = fx * fy;
|
|
2729
|
-
const idx00 = y0 * srcWidth + x0 << 2;
|
|
2730
|
-
const idx10 = y0 * srcWidth + x1 << 2;
|
|
2731
|
-
const idx01 = y1 * srcWidth + x0 << 2;
|
|
2732
|
-
const idx11 = y1 * srcWidth + x1 << 2;
|
|
2733
|
-
const di = oy * outWidth + ox << 2;
|
|
2734
|
-
dst[di] = srcData[idx00] * w00 + srcData[idx10] * w10 + srcData[idx01] * w01 + srcData[idx11] * w11 + 0.5 | 0;
|
|
2735
|
-
dst[di + 1] = srcData[idx00 + 1] * w00 + srcData[idx10 + 1] * w10 + srcData[idx01 + 1] * w01 + srcData[idx11 + 1] * w11 + 0.5 | 0;
|
|
2736
|
-
dst[di + 2] = srcData[idx00 + 2] * w00 + srcData[idx10 + 2] * w10 + srcData[idx01 + 2] * w01 + srcData[idx11 + 2] * w11 + 0.5 | 0;
|
|
2737
|
-
dst[di + 3] = 255;
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
ctx.putImageData(outData, 0, 0);
|
|
2741
|
-
}
|
|
2742
|
-
async function extractDocument(image, corners, options = {}) {
|
|
2743
|
-
const outputType = options.output || "canvas";
|
|
2744
|
-
if (!corners || !corners.topLeft || !corners.topRight || !corners.bottomRight || !corners.bottomLeft) {
|
|
2745
|
-
return {
|
|
2746
|
-
output: null,
|
|
2747
|
-
corners: null,
|
|
2748
|
-
success: false,
|
|
2749
|
-
message: "Invalid corner points provided"
|
|
2750
|
-
};
|
|
2751
|
-
}
|
|
2752
|
-
try {
|
|
2753
|
-
const resultCanvas = document.createElement("canvas");
|
|
2754
|
-
const ctx = resultCanvas.getContext("2d");
|
|
2755
|
-
unwarpImage(ctx, image, corners);
|
|
2756
|
-
let output;
|
|
2757
|
-
if (outputType === "canvas") {
|
|
2758
|
-
output = resultCanvas;
|
|
2759
|
-
} else if (outputType === "imagedata") {
|
|
2760
|
-
output = resultCanvas.getContext("2d").getImageData(0, 0, resultCanvas.width, resultCanvas.height);
|
|
2761
|
-
} else if (outputType === "dataurl") {
|
|
2762
|
-
output = resultCanvas.toDataURL();
|
|
2763
|
-
} else {
|
|
2764
|
-
output = resultCanvas;
|
|
2765
|
-
}
|
|
2766
|
-
return {
|
|
2767
|
-
output,
|
|
2768
|
-
corners,
|
|
2769
|
-
success: true,
|
|
2770
|
-
message: "Document extracted successfully"
|
|
2771
|
-
};
|
|
2772
|
-
} catch (error) {
|
|
2773
|
-
return {
|
|
2774
|
-
output: null,
|
|
2775
|
-
corners,
|
|
2776
|
-
success: false,
|
|
2777
|
-
message: `Extraction failed: ${error.message}`
|
|
2778
|
-
};
|
|
2779
|
-
}
|
|
2780
|
-
}
|
|
2781
|
-
async function scanDocument(image, options = {}) {
|
|
2782
|
-
const timings = [];
|
|
2783
|
-
const totalStart = performance.now();
|
|
2784
|
-
const mode = options.mode || "detect";
|
|
2785
|
-
const outputType = options.output || "canvas";
|
|
2786
|
-
!!options.debug;
|
|
2787
|
-
const maxProcessingDimension = options.maxProcessingDimension || 800;
|
|
2788
|
-
let t0 = performance.now();
|
|
2789
|
-
const { grayscaleData, imageData, scaleFactor, originalDimensions, scaledDimensions } = await prepareScaleAndGrayscale(image, maxProcessingDimension);
|
|
2790
|
-
timings.push({ step: "Image Prep + Scale + Gray", ms: (performance.now() - t0).toFixed(2) });
|
|
2791
|
-
const detection = await detectDocumentInternal(
|
|
2792
|
-
grayscaleData,
|
|
2793
|
-
scaledDimensions.width,
|
|
2794
|
-
scaledDimensions.height,
|
|
2795
|
-
scaleFactor,
|
|
2796
|
-
options
|
|
2797
|
-
);
|
|
2798
|
-
if (detection.timings) {
|
|
2799
|
-
detection.timings.forEach((t) => timings.push(t));
|
|
2800
|
-
}
|
|
2801
|
-
if (!detection.success) {
|
|
2802
|
-
const totalEnd2 = performance.now();
|
|
2803
|
-
timings.unshift({ step: "Total", ms: (totalEnd2 - totalStart).toFixed(2) });
|
|
2804
|
-
console.table(timings);
|
|
2805
|
-
return {
|
|
2806
|
-
output: null,
|
|
2807
|
-
corners: null,
|
|
2808
|
-
contour: null,
|
|
2809
|
-
confidence: detection.confidence || null,
|
|
2810
|
-
debug: detection.debug,
|
|
2811
|
-
success: false,
|
|
2812
|
-
message: detection.message || "No document detected",
|
|
2813
|
-
timings
|
|
2814
|
-
};
|
|
2815
|
-
}
|
|
2816
|
-
let resultCanvas;
|
|
2817
|
-
let output;
|
|
2818
|
-
if (mode === "detect") {
|
|
2819
|
-
output = null;
|
|
2820
|
-
} else if (mode === "extract") {
|
|
2821
|
-
t0 = performance.now();
|
|
2822
|
-
resultCanvas = document.createElement("canvas");
|
|
2823
|
-
const ctx = resultCanvas.getContext("2d");
|
|
2824
|
-
unwarpImage(ctx, image, detection.corners);
|
|
2825
|
-
timings.push({ step: "Perspective Transform", ms: (performance.now() - t0).toFixed(2) });
|
|
2826
|
-
}
|
|
2827
|
-
if (mode !== "detect" && resultCanvas) {
|
|
2828
|
-
t0 = performance.now();
|
|
2829
|
-
if (outputType === "canvas") {
|
|
2830
|
-
output = resultCanvas;
|
|
2831
|
-
} else if (outputType === "imagedata") {
|
|
2832
|
-
output = resultCanvas.getContext("2d").getImageData(0, 0, resultCanvas.width, resultCanvas.height);
|
|
2833
|
-
} else if (outputType === "dataurl") {
|
|
2834
|
-
output = resultCanvas.toDataURL();
|
|
2835
|
-
} else {
|
|
2836
|
-
output = resultCanvas;
|
|
2837
|
-
}
|
|
2838
|
-
timings.push({ step: "Output Conversion", ms: (performance.now() - t0).toFixed(2) });
|
|
2839
|
-
}
|
|
2840
|
-
const totalEnd = performance.now();
|
|
2841
|
-
timings.unshift({ step: "Total", ms: (totalEnd - totalStart).toFixed(2) });
|
|
2842
|
-
console.table(timings);
|
|
2843
|
-
return {
|
|
2844
|
-
output,
|
|
2845
|
-
corners: detection.corners,
|
|
2846
|
-
contour: detection.contour,
|
|
2847
|
-
confidence: detection.confidence || null,
|
|
2848
|
-
debug: detection.debug,
|
|
2849
|
-
success: true,
|
|
2850
|
-
message: "Document detected",
|
|
2851
|
-
timings
|
|
2852
|
-
};
|
|
2853
|
-
}
|
|
2854
|
-
export {
|
|
2855
|
-
Scanner,
|
|
2856
|
-
createCornerEditor,
|
|
2857
|
-
extractDocument,
|
|
2858
|
-
initialize,
|
|
2859
|
-
scanDocument
|
|
2860
|
-
};
|
|
1
|
+
var A,I=1e3,g=10;function B(A,B={}){const t=B.width||Math.sqrt(A.length),i=B.height||A.length/t,E=void 0!==B.mode?B.mode:1,e=void 0!==B.method?B.method:2,o=B.minArea||I,n=t+2,a=i+2,s=new Int32Array(n*a);for(let I=0;I<i;I++)for(let g=0;g<t;g++)A[I*t+g]>0&&(s[(I+1)*n+(g+1)]=1);const c=[];let r=2;for(let I=1;I<=i;I++)for(let A=1;A<=t;A++){const B=s[I*n+A],t=s[I*n+(A-1)];let i=null,o=!1,h=-1;if(1===B&&0===t?(o=!0,i={x:A,y:I},h=2):0===B&&t>=1&&-1!==t&&1===t&&(o=!1,i={x:A-1,y:I},h=6),i){if(0===E&&!o){s[i.y*n+i.x]=-1;continue}const A=r++,I=C(s,n,a,i,h,A);if(I&&I.length>0){let B=I;2===e&&(B=Q(I));const C=B.map(A=>({x:A.x-1,y:A.y-1}));if(C.length>=(2===e?4:g)){const I={id:A,points:C,isOuter:o};c.push(I)}}else 1===s[i.y*n+i.x]&&(s[i.y*n+i.x]=A)}}c.forEach(A=>{A.area=function(A){let I=0;const g=A.length;if(g<3)return 0;for(let B=0;B<g;B++){const C=(B+1)%g;I+=A[B].x*A[C].y,I-=A[C].x*A[B].y}return Math.abs(I)/2}(A.points),A.boundingBox=function(A){if(0===A.length)return{minX:0,minY:0,maxX:0,maxY:0};let I=A[0].x,g=A[0].y,B=A[0].x,C=A[0].y;for(let Q=1;Q<A.length;Q++){const t=A[Q];I=Math.min(I,t.x),g=Math.min(g,t.y),B=Math.max(B,t.x),C=Math.max(C,t.y)}return{minX:I,minY:g,maxX:B,maxY:C}}(A.points)});const h=c.filter(A=>A.area>=o);return h.sort((A,I)=>I.area-A.area),B.debug&&(B.debug.labels=s,B.debug.rawContours=c,B.debug.finalContours=h),h}function C(A,I,g,B,C,Q){const t=[],i=/* @__PURE__ */new Set;let E=B.x,e=B.y;const o=E,n=e;let a=-1;A[n*I+o]=Q;let s=0;const c=I*g,r=[0,1,1,1,0,-1,-1,-1],h=[-1,-1,0,1,1,1,0,-1];for(;s++<c;){let B;if(-1===a){let Q=!1;for(let t=0;t<8;t++){B=C+t&7;const i=E+r[B],o=e+h[B];if(i>=0&&i<I&&o>=0&&o<g&&A[o*I+i]>0){Q=!0;break}}if(!Q)return null}else B=a+2&7;let s=-1,c=-1;for(let C=0;C<8;C++){const Q=B+C&7,t=E+r[Q],i=e+h[Q];if(t>=0&&t<I&&i>=0&&i<g&&A[i*I+t]>0){s=t,c=i,a=Q+4&7;break}}if(-1===s){0===t.length&&t.push({x:E,y:e});break}const l=e*I+E;if(i.has(l))return t;t.push({x:E,y:e}),i.add(l);const y=c*I+s;if(1===A[y]&&(A[y]=Q),E=s,e=c,E===o&&e===n)break}return s>=c?null:t}function Q(A){const I=A.length;if(I<=2)return A;const g=[],B=A[I-1],C=A[0];let Q=B,t=C,i=A[1],E=t.x-Q.x,e=t.y-Q.y,o=i.x-t.x,n=i.y-t.y;E*n!==e*o&&g.push(t);for(let a=1;a<I-1;a++)Q=A[a-1],t=A[a],i=A[a+1],E=t.x-Q.x,e=t.y-Q.y,o=i.x-t.x,n=i.y-t.y,E*n!==e*o&&g.push(t);if(Q=A[I-2],t=B,i=C,E=t.x-Q.x,e=t.y-Q.y,o=i.x-t.x,n=i.y-t.y,E*n!==e*o&&g.push(t),0===g.length){if(1===I)return[A[0]];if(2===I)return A;let g=0,B=1;const Q=C.x,t=C.y;for(let C=1;C<I;C++){const I=A[C],i=I.x-Q,E=I.y-t,e=i*i+E*E;e>g&&(g=e,B=C)}return[C,A[B]]}return g}function t(A,I=1){if(A.length<=2)return A;let g=0,B=0;const C=A[0],Q=A[A.length-1];for(let t=1;t<A.length-1;t++){const I=i(A[t],C,Q);I>g&&(g=I,B=t)}if(g>I){const g=t(A.slice(0,B+1),I),C=t(A.slice(B),I);return g.slice(0,-1).concat(C)}return[C,Q]}function i(A,I,g){const B=g.x-I.x,C=g.y-I.y,Q=B*B+C*C;if(0===Q)return Math.sqrt(Math.pow(A.x-I.x,2)+Math.pow(A.y-I.y,2));const t=((A.x-I.x)*B+(A.y-I.y)*C)/Q;let i,E;t<0?(i=I.x,E=I.y):t>1?(i=g.x,E=g.y):(i=I.x+t*B,E=I.y+t*C);const e=A.x-i,o=A.y-E;return Math.sqrt(e*e+o*o)}function E(A,I=.02){return t(A,I*function(A){let I=0;const g=A.length;if(g<2)return 0;for(let B=0;B<g;B++){const C=(B+1)%g,Q=A[B].x-A[C].x,t=A[B].y-A[C].y;I+=Math.sqrt(Q*Q+t*t)}return I}(A))}function e(A,I){return Math.hypot(I.x-A.x,I.y-A.y)}function o(A){let I=0,g=0;for(const B of A)I+=B.x,g+=B.y;return{x:I/A.length,y:g/A.length}}function n(A,I={}){if(!A||!A.points||A.points.length<4)return null;const g=I.epsilon||.02,B=function(A,I){if(!A||A.length<4)return null;const g=[I,1.3*I,1.6*I,2*I],B=/* @__PURE__ */new Set;for(const C of g){const I=Math.min(.12,C),g=I.toFixed(4);if(B.has(g))continue;B.add(g);const Q=a(E(A,I));if(Q&&4===Q.length)return Q}return null}(A.points,g);let C;return B&&4===B.length?C=function(A){if(4!==A.length)return null;const I=o(A),g=[...A].sort((A,g)=>Math.atan2(A.y-I.y,A.x-I.x)-Math.atan2(g.y-I.y,g.x-I.x));let B=1/0,C=0;for(let t=0;t<4;t++){const A=g[t].x+g[t].y;A<B&&(B=A,C=t)}const Q=[g[C],g[(C+1)%4],g[(C+2)%4],g[(C+3)%4]];return{topLeft:Q[0],topRight:Q[1],bottomRight:Q[2],bottomLeft:Q[3]}}(B):(C=function(A){if(!A||A.length<4)return null;const I=o(A);let g=null,B=null,C=null,Q=null,t=0,i=0,E=0,n=0;for(const o of A){const A=e(o,I);o.x<I.x&&o.y<I.y?A>t&&(g=o,t=A):o.x>I.x&&o.y<I.y?A>i&&(B=o,i=A):o.x>I.x&&o.y>I.y?A>E&&(C=o,E=A):o.x<I.x&&o.y>I.y&&A>n&&(Q=o,n=A)}return g&&B&&C&&Q?{topLeft:g,topRight:B,bottomRight:C,bottomLeft:Q}:null}(A.points),C&&C.topLeft&&C.topRight&&C.bottomRight&&C.bottomLeft||(C=function(A){if(!A||0===A.length)return null;let I=A[0],g=A[0],B=A[0],C=A[0],Q=I.x+I.y,t=g.x-g.y,i=B.x+B.y,E=C.x-C.y;for(let e=1;e<A.length;e++){const o=A[e],n=o.x+o.y,a=o.x-o.y;n<Q&&(Q=n,I=o),n>i&&(i=n,B=o),a>t&&(t=a,g=o),a<E&&(E=a,C=o)}return{topLeft:I,topRight:g,bottomRight:B,bottomLeft:C}}(A.points))),C&&C.topLeft&&C.topRight&&C.bottomRight&&C.bottomLeft?C:null}function a(A){if(!A||A.length<4)return null;if(4===A.length)return A;if(A.length>8)return null;let I=function(A,I){if(!A||0===A.length)return[];const g=[];for(const B of A)(0===g.length||e(B,g[g.length-1])>I)&&g.push(B);g.length>2&&e(g[0],g[g.length-1])<=I&&g.pop();return g}(A,6);return 4===I.length?I:(I.length,null)}var s=null;function c(){return null!==s&&0!==s.byteLength||(s=new Uint8Array(A.memory.buffer)),s}var r=0;function h(A,I){const g=I(1*A.length,1)>>>0;return c().set(A,g/1),r=A.length,g}function l(A,I){return A>>>=0,c().subarray(A/1,A/1+I)}var y=null;function D(){return null!==y&&0!==y.byteLength||(y=new Float32Array(A.memory.buffer)),y}function w(I,g,B,C,Q){const t=function(A,I){const g=I(4*A.length,4)>>>0;return D().set(A,g/4),r=A.length,g}(I,A.__wbindgen_malloc),i=r,E=A.hysteresis_thresholding(t,i,g,B,C,Q);var e=l(E[0],E[1]).slice();return A.__wbindgen_free(E[0],1*E[1],1),e}var d=null;function f(I,g){const B=g(2*I.length,2)>>>0;return(null!==d&&0!==d.byteLength||(d=new Uint16Array(A.memory.buffer)),d).set(I,B/2),r=I.length,B}function u(I,g,B,C,Q){const t=f(I,A.__wbindgen_malloc),i=r,E=f(g,A.__wbindgen_malloc),e=r,o=A.non_maximum_suppression(t,i,E,e,B,C,Q);var n,a,s=(n=o[0],a=o[1],n>>>=0,D().subarray(n/4,n/4+a)).slice();return A.__wbindgen_free(o[0],4*o[1],4),s}async function m(I){if(void 0!==A)return A;if(void 0!==I&&Object.getPrototypeOf(I)===Object.prototype&&({module_or_path:I}=I),void 0===I)throw new Error("scanic: WASM module bytes must be passed to init().");const g=function(){const I={wbg:{}};return I.wbg.__wbindgen_init_externref_table=function(){const I=A.__wbindgen_export_0,g=I.grow(4);I.set(0,void 0),I.set(g+0,void 0),I.set(g+1,null),I.set(g+2,!0),I.set(g+3,!1)},I}();("string"==typeof I||"function"==typeof Request&&I instanceof Request||"function"==typeof URL&&I instanceof URL)&&(I=fetch(I));const{instance:B,module:C}=await async function(A,I){if("function"==typeof Response&&A instanceof Response){if("function"==typeof WebAssembly.instantiateStreaming)try{return await WebAssembly.instantiateStreaming(A,I)}catch(g){if("application/wasm"==A.headers.get("Content-Type"))throw g}const B=await A.arrayBuffer();return await WebAssembly.instantiate(B,I)}{const g=await WebAssembly.instantiate(A,I);return g instanceof WebAssembly.Instance?{instance:g,module:A}:g}}(await I,g);return function(I,g){return A=I.exports,m.__wbindgen_wasm_module=g,y=null,d=null,s=null,A.__wbindgen_start(),A}(B,C)}var N=null,R=!1,F=null;function p(A){if("function"==typeof atob){const I=atob(A),g=new Uint8Array(I.length);for(let A=0;A<I.length;A++)g[A]=I.charCodeAt(A);return g}return Uint8Array.from(Buffer.from(A,"base64"))}async function J(){if(!function(){if(null!==F)return F;let A=!1;if("object"==typeof WebAssembly&&"function"==typeof WebAssembly.validate){const I=new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11]),g=new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,111,3,2,1,0,10,6,1,4,0,208,111,11]);try{A=WebAssembly.validate(I)&&WebAssembly.validate(g)}catch{A=!1}}return F=A,A}())throw new Error("scanic: WebAssembly features required by the WASM module (reference-types, SIMD) are unavailable in this engine; using the JavaScript fallback.");return await m({module_or_path:p("AGFzbQEAAAABtQEWYAJ/fwBgAAJ/f2ACf38Bf2ADf39/AX9gAX8AYAN/f38AYAV/f39/fwBgBH9/f38Bf2AGf39/f319An9/YAAAYAF/AX9gB39/f39/f38AYAh/f39/f39/fwBgB39/f39/f30AYAZ/f39/f38AYAR/f39/AGALf39/f319f31/f38Cf39gB39/f39/f38Cf39gBn9/f39/fQJ/f2AFf39/f38Cf39gBH9/f38Cf39gAn9/An9/AicBA3diZx9fX3diaW5kZ2VuX2luaXRfZXh0ZXJucmVmX3RhYmxlAAkDOzoKCwwNBgQDAAIAAAICAAYADgUCBAQEAAUDAA8FEAcEERIICAITABQVBAQAAAAEBQMHAgAAAgICAAAABAkCcAETE28AgAEFAwEAEQYJAX8BQYCAwAALB4QCDQZtZW1vcnkCAARibHVyACESZWRnZV9tYXBfdG9fYmluYXJ5ACgXaHlzdGVyZXNpc190aHJlc2hvbGRpbmcAIh5oeXN0ZXJlc2lzX3RocmVzaG9sZGluZ19iaW5hcnkAIxNjYWxjdWxhdGVfZ3JhZGllbnRzACcGZGlsYXRlACUYY2FubnlfZWRnZV9kZXRlY3Rvcl9mdWxsAB0Xbm9uX21heGltdW1fc3VwcHJlc3Npb24AIBNfX3diaW5kZ2VuX2V4cG9ydF8wAQERX193YmluZGdlbl9tYWxsb2MAJA9fX3diaW5kZ2VuX2ZyZWUALxBfX3diaW5kZ2VuX3N0YXJ0AAAJGAEAQQELEi4XDCoZDTcyGjM4KRMOEDosKwwBAgrGiwI6ySUCCX8BfiMAQRBrIggkAAJAAkACQAJAAkAgAEH1AU8EQCAAQcz/e0sEQEEAIQAMBgsgAEELaiICQXhxIQVBvJvAACgCACIJRQ0EQR8hBkEAIAVrIQMgAEH0//8HTQRAIAVBJiACQQh2ZyIAa3ZBAXEgAEEBdGtBPmohBgsgBkECdEGgmMAAaigCACICRQRAQQAhAAwCCyAFQRkgBkEBdmtBACAGQR9HG3QhBEEAIQADQAJAIAIoAgRBeHEiByAFSQ0AIAcgBWsiByADTw0AIAIhASAHIgMNAEEAIQMgASEADAQLIAIoAhQiByAAIAcgAiAEQR12QQRxaigCECICRxsgACAHGyEAIARBAXQhBCACDQALDAELAkACQAJAAkACQEG4m8AAKAIAIgRBECAAQQtqQfgDcSAAQQtJGyIFQQN2IgB2IgFBA3EEQCABQX9zQQFxIABqIgdBA3QiAUGwmcAAaiIAIAFBuJnAAGooAgAiAigCCCIDRg0BIAMgADYCDCAAIAM2AggMAgsgBUHAm8AAKAIATQ0IIAENAkG8m8AAKAIAIgBFDQggAGhBAnRBoJjAAGooAgAiAigCBEF4cSAFayEDIAIhAQNAAkAgASgCECIADQAgASgCFCIADQAgAigCGCEGAkACQCACIAIoAgwiAEYEQCACQRRBECACKAIUIgAbaigCACIBDQFBACEADAILIAIoAggiASAANgIMIAAgATYCCAwBCyACQRRqIAJBEGogABshBANAIAQhByABIgBBFGogAEEQaiAAKAIUIgEbIQQgAEEUQRAgARtqKAIAIgENAAsgB0EANgIACyAGRQ0GAkAgAigCHEECdEGgmMAAaiIBKAIAIAJHBEAgAiAGKAIQRwRAIAYgADYCFCAADQIMCQsgBiAANgIQIAANAQwICyABIAA2AgAgAEUNBgsgACAGNgIYIAIoAhAiAQRAIAAgATYCECABIAA2AhgLIAIoAhQiAUUNBiAAIAE2AhQgASAANgIYDAYLIAAoAgRBeHEgBWsiASADIAEgA0kiARshAyAAIAIgARshAiAAIQEMAAsAC0G4m8AAIARBfiAHd3E2AgALIAJBCGohACACIAFBA3I2AgQgASACaiIBIAEoAgRBAXI2AgQMBwsCQEECIAB0IgJBACACa3IgASAAdHFoIgdBA3QiAUGwmcAAaiICIAFBuJnAAGooAgAiACgCCCIDRwRAIAMgAjYCDCACIAM2AggMAQtBuJvAACAEQX4gB3dxNgIACyAAIAVBA3I2AgQgACAFaiIGIAEgBWsiB0EBcjYCBCAAIAFqIAc2AgBBwJvAACgCACICBEBByJvAACgCACEBAkBBuJvAACgCACIEQQEgAkEDdnQiA3FFBEBBuJvAACADIARyNgIAIAJBeHFBsJnAAGoiAyEEDAELIAJBeHEiAkGwmcAAaiEEIAJBuJnAAGooAgAhAwsgBCABNgIIIAMgATYCDCABIAQ2AgwgASADNgIICyAAQQhqIQBByJvAACAGNgIAQcCbwAAgBzYCAAwGC0G8m8AAQbybwAAoAgBBfiACKAIcd3E2AgALAkACQCADQRBPBEAgAiAFQQNyNgIEIAIgBWoiByADQQFyNgIEIAMgB2ogAzYCAEHAm8AAKAIAIgFFDQFByJvAACgCACEAAkBBuJvAACgCACIEQQEgAUEDdnQiBnFFBEBBuJvAACAEIAZyNgIAIAFBeHFBsJnAAGoiBCEBDAELIAFBeHEiBEGwmcAAaiEBIARBuJnAAGooAgAhBAsgASAANgIIIAQgADYCDCAAIAE2AgwgACAENgIIDAELIAIgAyAFaiIAQQNyNgIEIAAgAmoiACAAKAIEQQFyNgIEDAELQcibwAAgBzYCAEHAm8AAIAM2AgALIAJBCGoiAEUNAwwECyAAIAFyRQRAQQAhAUECIAZ0IgBBACAAa3IgCXEiAEUNAyAAaEECdEGgmMAAaigCACEACyAARQ0BCwNAIAMgACgCBEF4cSICIAVrIgQgAyADIARLIgQbIAIgBUkiAhshAyABIAAgASAEGyACGyEBIAAoAhAiAgR/IAIFIAAoAhQLIgANAAsLIAFFDQAgBUHAm8AAKAIAIgBNIAMgACAFa09xDQAgASgCGCEGAkACQCABIAEoAgwiAEYEQCABQRRBECABKAIUIgAbaigCACICDQFBACEADAILIAEoAggiAiAANgIMIAAgAjYCCAwBCyABQRRqIAFBEGogABshBANAIAQhByACIgBBFGogAEEQaiAAKAIUIgIbIQQgAEEUQRAgAhtqKAIAIgINAAsgB0EANgIACwJAIAZFDQACQAJAIAEoAhxBAnRBoJjAAGoiAigCACABRwRAIAEgBigCEEcEQCAGIAA2AhQgAA0CDAQLIAYgADYCECAADQEMAwsgAiAANgIAIABFDQELIAAgBjYCGCABKAIQIgIEQCAAIAI2AhAgAiAANgIYCyABKAIUIgJFDQEgACACNgIUIAIgADYCGAwBC0G8m8AAQbybwAAoAgBBfiABKAIcd3E2AgALAkAgA0EQTwRAIAEgBUEDcjYCBCABIAVqIgAgA0EBcjYCBCAAIANqIAM2AgAgA0GAAk8EQCAAIAMQCwwCCwJAQbibwAAoAgAiAkEBIANBA3Z0IgRxRQRAQbibwAAgAiAEcjYCACADQfgBcUGwmcAAaiIDIQIMAQsgA0H4AXEiBEGwmcAAaiECIARBuJnAAGooAgAhAwsgAiAANgIIIAMgADYCDCAAIAI2AgwgACADNgIIDAELIAEgAyAFaiIAQQNyNgIEIAAgAWoiACAAKAIEQQFyNgIECyABQQhqIgANAQsCQAJAAkACQAJAIAVBwJvAACgCACIBSwRAIAVBxJvAACgCACIATwRAIAhBBGohAAJ/IAVBr4AEakGAgHxxIgFBEHYgAUH//wNxQQBHaiIBQAAiBEF/RgRAQQAhAUEADAELIAFBEHQiAkEQayACIARBEHQiAUEAIAJrRhsLIQIgAEEANgIIIAAgAjYCBCAAIAE2AgAgCCgCBCIBRQRAQQAhAAwICyAIKAIMIQdB0JvAACAIKAIIIgRB0JvAACgCAGoiADYCAEHUm8AAIABB1JvAACgCACICIAAgAksbNgIAAkACQEHMm8AAKAIAIgIEQEGgmcAAIQADQCABIAAoAgAiAyAAKAIEIgZqRg0CIAAoAggiAA0ACwwCC0Hcm8AAKAIAIgBBACAAIAFNG0UEQEHcm8AAIAE2AgALQeCbwABB/x82AgBBrJnAACAHNgIAQaSZwAAgBDYCAEGgmcAAIAE2AgBBvJnAAEGwmcAANgIAQcSZwABBuJnAADYCAEG4mcAAQbCZwAA2AgBBzJnAAEHAmcAANgIAQcCZwABBuJnAADYCAEHUmcAAQciZwAA2AgBByJnAAEHAmcAANgIAQdyZwABB0JnAADYCAEHQmcAAQciZwAA2AgBB5JnAAEHYmcAANgIAQdiZwABB0JnAADYCAEHsmcAAQeCZwAA2AgBB4JnAAEHYmcAANgIAQfSZwABB6JnAADYCAEHomcAAQeCZwAA2AgBB/JnAAEHwmcAANgIAQfCZwABB6JnAADYCAEH4mcAAQfCZwAA2AgBBhJrAAEH4mcAANgIAQYCawABB+JnAADYCAEGMmsAAQYCawAA2AgBBiJrAAEGAmsAANgIAQZSawABBiJrAADYCAEGQmsAAQYiawAA2AgBBnJrAAEGQmsAANgIAQZiawABBkJrAADYCAEGkmsAAQZiawAA2AgBBoJrAAEGYmsAANgIAQayawABBoJrAADYCAEGomsAAQaCawAA2AgBBtJrAAEGomsAANgIAQbCawABBqJrAADYCAEG8msAAQbCawAA2AgBBxJrAAEG4msAANgIAQbiawABBsJrAADYCAEHMmsAAQcCawAA2AgBBwJrAAEG4msAANgIAQdSawABByJrAADYCAEHImsAAQcCawAA2AgBB3JrAAEHQmsAANgIAQdCawABByJrAADYCAEHkmsAAQdiawAA2AgBB2JrAAEHQmsAANgIAQeyawABB4JrAADYCAEHgmsAAQdiawAA2AgBB9JrAAEHomsAANgIAQeiawABB4JrAADYCAEH8msAAQfCawAA2AgBB8JrAAEHomsAANgIAQYSbwABB+JrAADYCAEH4msAAQfCawAA2AgBBjJvAAEGAm8AANgIAQYCbwABB+JrAADYCAEGUm8AAQYibwAA2AgBBiJvAAEGAm8AANgIAQZybwABBkJvAADYCAEGQm8AAQYibwAA2AgBBpJvAAEGYm8AANgIAQZibwABBkJvAADYCAEGsm8AAQaCbwAA2AgBBoJvAAEGYm8AANgIAQbSbwABBqJvAADYCAEGom8AAQaCbwAA2AgBBzJvAACABQQ9qQXhxIgBBCGsiAjYCAEGwm8AAQaibwAA2AgBBxJvAACAEQShrIgQgASAAa2pBCGoiADYCACACIABBAXI2AgQgASAEakEoNgIEQdibwABBgICAATYCAAwICyACIANJIAEgAk1yDQAgACgCDCIDQQFxDQAgA0EBdiAHRg0DC0Hcm8AAQdybwAAoAgAiACABIAAgAUkbNgIAIAEgBGohA0GgmcAAIQACQAJAA0AgAyAAKAIAIgZHBEAgACgCCCIADQEMAgsLIAAoAgwiA0EBcQ0AIANBAXYgB0YNAQtBoJnAACEAA0ACQCACIAAoAgAiA08EQCACIAMgACgCBGoiBkkNAQsgACgCCCEADAELC0HMm8AAIAFBD2pBeHEiAEEIayIDNgIAQcSbwAAgBEEoayIJIAEgAGtqQQhqIgA2AgAgAyAAQQFyNgIEIAEgCWpBKDYCBEHYm8AAQYCAgAE2AgAgAiAGQSBrQXhxQQhrIgAgACACQRBqSRsiA0EbNgIEQaCZwAApAgAhCiADQRBqQaiZwAApAgA3AgAgA0EIaiIAIAo3AgBBrJnAACAHNgIAQaSZwAAgBDYCAEGgmcAAIAE2AgBBqJnAACAANgIAIANBHGohAANAIABBBzYCACAAQQRqIgAgBkkNAAsgAiADRg0HIAMgAygCBEF+cTYCBCACIAMgAmsiAEEBcjYCBCADIAA2AgAgAEGAAk8EQCACIAAQCwwICwJAQbibwAAoAgAiAUEBIABBA3Z0IgRxRQRAQbibwAAgASAEcjYCACAAQfgBcUGwmcAAaiIAIQEMAQsgAEH4AXEiAEGwmcAAaiEBIABBuJnAAGooAgAhAAsgASACNgIIIAAgAjYCDCACIAE2AgwgAiAANgIIDAcLIAAgATYCACAAIAAoAgQgBGo2AgQgAUEPakF4cUEIayIEIAVBA3I2AgQgBkEPakF4cUEIayIDIAQgBWoiAGshBSADQcybwAAoAgBGDQMgA0HIm8AAKAIARg0EIAMoAgQiAkEDcUEBRgRAIAMgAkF4cSIBEAogASAFaiEFIAEgA2oiAygCBCECCyADIAJBfnE2AgQgACAFQQFyNgIEIAAgBWogBTYCACAFQYACTwRAIAAgBRALDAYLAkBBuJvAACgCACIBQQEgBUEDdnQiAnFFBEBBuJvAACABIAJyNgIAIAVB+AFxQbCZwABqIgUhAwwBCyAFQfgBcSIBQbCZwABqIQMgAUG4mcAAaigCACEFCyADIAA2AgggBSAANgIMIAAgAzYCDCAAIAU2AggMBQtBxJvAACAAIAVrIgE2AgBBzJvAAEHMm8AAKAIAIgAgBWoiAjYCACACIAFBAXI2AgQgACAFQQNyNgIEIABBCGohAAwGC0HIm8AAKAIAIQACQCABIAVrIgJBD00EQEHIm8AAQQA2AgBBwJvAAEEANgIAIAAgAUEDcjYCBCAAIAFqIgEgASgCBEEBcjYCBAwBC0HAm8AAIAI2AgBByJvAACAAIAVqIgQ2AgAgBCACQQFyNgIEIAAgAWogAjYCACAAIAVBA3I2AgQLIABBCGohAAwFCyAAIAQgBmo2AgRBzJvAAEHMm8AAKAIAIgBBD2pBeHEiAUEIayICNgIAQcSbwABBxJvAACgCACAEaiIEIAAgAWtqQQhqIgE2AgAgAiABQQFyNgIEIAAgBGpBKDYCBEHYm8AAQYCAgAE2AgAMAwtBzJvAACAANgIAQcSbwABBxJvAACgCACAFaiIBNgIAIAAgAUEBcjYCBAwBC0HIm8AAIAA2AgBBwJvAAEHAm8AAKAIAIAVqIgE2AgAgACABQQFyNgIEIAAgAWogATYCAAsgBEEIaiEADAELQQAhAEHEm8AAKAIAIgEgBU0NAEHEm8AAIAEgBWsiATYCAEHMm8AAQcybwAAoAgAiACAFaiICNgIAIAIgAUEBcjYCBCAAIAVBA3I2AgQgAEEIaiEACyAIQRBqJAAgAAuGEgIlfwF7AkACQAJAAkAgAiADbCIOQQBIDQBBASERIA4EQEEBIQcgDkEBEDYiEUUNAQsgBEEBdiESAkACQAJAIANFDQAgAkUNBCACQQFrIQwgBEUEQEEAIQEgESEAA0BBACEIIA4gAiAKbGsiB0EAIAcgDk0bIgcgDCAHIAxJG0EBaiIHQRFPBEAgByAHQQ9xIgdBECAHGyIHayEIIAkgDiAJIA5LGyABaiILIAwgCyAMSRsgB2tBAWohCyAAIQcDQCAH/QwAAAAAAAAAAAAAAAAAAAAA/QsAACAHQRBqIQcgC0EQayILDQALCyAKQQFqIQogCSARaiELA0AgCCAJaiIHIA5PDQQgCCALakEAOgAAIAIgCEEBaiIIRw0ACyABIAJrIQEgAiAJaiEJIAAgAmohACADIApHDQALDAELIAxBAEgNBkEAIBJrIQoDQCACIBRsIRAgFEEBaiEUQQAhDyAKIQ0DQCAPQQFqIA0hByAEIQlBACEIA0AgByAMIAcgDEkbQQAgB0EAThsgEGoiEyABTw0FIAAgE2otAAAiEyAIQf8BcSIIIAggE0kbIQggB0EBaiEHIAlBAWsiCQ0ACyAPIBBqIgcgDk8NAyAHIBFqIAg6AAAgDUEBaiENIg8gAkcNAAsgAyAURw0ACwsgAyASayIAQQAgACADTRshDCACQQR2ISAgBEECSQ0EIAJFBEBBACEgDAULAkACQCADQQFrIgtBAE4EQEEAIBJrIQBBACENA0AgAiANbCEQIA1BAWohDUEAIQoDQCAKQQFqIAAhByAEIQlBACEIA0AgByALIAcgC0kbQQAgB0EAThsgAmwgCmoiDyAOTw0EIA8gEWotAAAiDyAIQf8BcSIIIAggD0kbIQggB0EBaiEHIAlBAWsiCQ0ACyAKIBBqIgcgBk8NBCAFIAdqIAg6AAAiCiACRw0ACyAAQQFqIQAgDSASRw0ACwwHCwwHCyAPIA5BzIzAABAYAAsgByAGQbyMwAAQGAALIAcgDkHcjMAAEBgACyATIAFB7IzAABAYAAsgByAOEC0ACyADIBJrIgBBACAAIANNGyEMCyAMIBJLBEBBACACIBJsIiEgAkFwcSIAaiIZayEaIAUgGWohGyAAQX9zIAIgAEEBciIBIAEgAkkbaiEWIAIgEWohHCAMIBJrISMgBSAhaiEkIBEgAkEBdGohHSARIAJBA2xqIRQgESACQQJ0IiVqIR4gBEEBayIBQXxxISYgAUEDcSEiIAJBEEkhJyAEQQJrQQNJISggGSEXIAAhASASIRMDQCACIBhsIRUCQCAnDQAgBEECTwRAIAUgAiATbGohKSARIBMgEmsgAmxqISpBACEPIBEhDSAcIQkgHSEQIBQhCyAeIQoDQCAqIA9BBHQiK2r9AAAAISxBASEHIChFBEBBACEHQQAhCANAICwgByAJav0AAAD9eSAHIBBq/QAAAP15IAcgC2r9AAAA/XkgByAKav0AAAD9eSEsIAcgJWohByAmIAhBBGoiCEcNAAsgCEEBaiEHCyAiBEAgDSACIAcgGGpsaiEHICIhCANAICwgB/0AAAD9eSEsIAIgB2ohByAIQQFrIggNAAsLICkgK2ogLP0LAAAgDUEQaiENIAlBEGohCSAQQRBqIRAgC0EQaiELIApBEGohCiAgIA9BAWoiD0cNAAsMAQsgAEUNACAFIBIgGGogAmxqIBEgFWogAPwKAAALAkAgACACRg0AAkACQAJAIAQEQCACIBNsIQ0gASELIAAhDwwBCyAAIQggFiAGIBUgGWoiByAGIAdLGyAHayIHIAcgFksbQQFqIgdBEE0NASAHQQ9xIglBECAJGyIKIBYgBiAXIAYgF0sbIBpqIgkgCSAWSxtBf3NqIQkgCCAHIApraiEIIBshBwNAIAf9DAAAAAAAAAAAAAAAAAAAAAD9CwAAIAdBEGohByAJQRBqIgkNAAsMAQsDQCALIBFqIRAgD0EBaiEKQQAhByAEIQlBACEIAkADQCAHIAtqIhUgDk8NASAHIBBqLQAAIhUgCEH/AXEiCCAIIBVJGyEIIAIgB2ohByAJQQFrIgkNAAsgDSAPaiIHIAZPDQMgBSAHaiAIOgAAIAtBAWohCyAKIg8gAk8NBAwBCwsgFSAOQayMwAAQGAALIB8gJGohCSAfICFqIQoDQCAIIApqIgcgBk8NASAIIAlqQQA6AAAgAiAIQQFqIghLDQALDAELIAcgBkGcjMAAEBgACyATQQFqIRMgAiAfaiEfIBogAmshGiACIBdqIRcgAiAbaiEbIAEgAmohASACIBxqIRwgAiAdaiEdIAIgFGohFCACIB5qIR4gGEEBaiIYICNHDQALCwJAAkACQCACRSADIAxNcg0AIARFBEAgAkEBayEKQQAgAiAMbCIQayEEIAUgEGohC0EAIQAgDCEBA0BBACEJIAYgACAMaiACbGsiBUEAIAUgBk0bIgUgCiAFIApJG0EBaiIFQRFPBEAgBUEPcSIHQRAgBxsiByAGIBAgBiAQSxsgBGoiCSAKIAkgCkkbQX9zaiEIIAUgB2shCSALIQcDQCAH/QwAAAAAAAAAAAAAAAAAAAAA/QsAACAHQRBqIQcgCEEQaiIIDQALCyABQQFqIQEgCSALaiEIIAkgEGohByACIAlrIQkDQCAGIAdNDQUgCEEAOgAAIAhBAWohCCAHQQFqIQcgCUEBayIJDQALIAQgAmshBCACIBBqIRAgAiALaiELIABBAWohACABIANHDQALDAELIANBAWsiC0EASA0DIAwgEmshAANAIAIgDGwhDyAMQQFqIQxBACEKA0AgCkEBaiAAIQcgBCEJQQAhCANAIAcgCyAHIAtJG0EAIAdBAE4bIAJsIApqIg0gDk8NBCANIBFqLQAAIg0gCEH/AXEiCCAIIA1JGyEIIAdBAWohByAJQQFrIgkNAAsgCiAPaiIHIAZPDQQgBSAHaiAIOgAAIgogAkcNAAsgAEEBaiEAIAMgDEcNAAsLIA4EQCARIA4QNAsPCyANIA5BjIzAABAYAAsgByAGQfyLwAAQGAALQbCKwABBHEHMisAAEBwAC68QAxh/AnsDfSAFIAZsIgtBAnQhCAJAAkAgC0H/////A0sgCEH8////B0tyDQACQCAIRQRAQQQhDUEEIQ8MAQtBBCEPIAhBBBA2Ig1FDQEgCyEXIAhBBBA2Ig9FDQILIAAgCzYCCCAAIA82AgQgACAXNgIAQQAhCCACQQRPBEAgAkECdiEMIA0hAANAIAACewJAAkACQAJAAkACQAJAAkAgAiAISwRAIAhBAWoiCSACTw0BIAhBAmoiDiACTw0CIAhBA2oiECACTw0DIAQgCE0NBCAEIAlNDQUgBCAOTQ0GIAQgEE0NByABIApqIgkuAQCy/RMgCUECai4BALL9IAEgCUEEai4BALL9IAIgCUEGai4BALL9IAMhICADIApqIgkuAQCy/RMgCUECai4BALL9IAEgCUEEai4BALL9IAIgCUEGai4BALL9IAMhISAHDQggIP3gASAh/eAB/eQBDAkLIAggAkHcjsAAEBgACyAIQQFqIAJB7I7AABAYAAsgCEECaiACQfyOwAAQGAALIAhBA2ogAkGMj8AAEBgACyAIIARBnI/AABAYAAsgCEEBaiAEQayPwAAQGAALIAhBAmogBEG8j8AAEBgACyAIQQNqIARBzI/AABAYAAsgICAg/eYBICEgIf3mAf3kAf3jAQv9CwIAIABBEGohACAKQQhqIQogCEEEaiEIIAxBAWsiDA0ACwsCQCACQXxxIgggAkYNACAIQX9zIgwgAiAIQQFyIgAgACACSRtqIgogCyAIIAggC0kbIg4gCGsiACAAIApLGyIJIAQgCCAEIAhLGyIQIAhrIgogCSAKSRtBAWohCQJAAkACQCAHRQRAIAlBBE0NASAJQQNxIgdBBCAHGyISIAIgCEEBaiIHIAIgB0sbIAxqIgcgACAAIAdLGyIAIAogACAKSRtBf3NqIQwgASACQQJ2IgBBA3QiB2ohCiADIAdqIQcgDSAAQQR0aiEAIAggCSASa2ohCANAIAAgCv0DAQD9+gH94AEgB/0DAQD9+gH94AH95AH9CwIAIApBCGohCiAHQQhqIQcgAEEQaiEAIAxBBGoiDA0ACwwBCyAJQQVPBEAgCUEDcSIHQQQgBxsiEiAIQX9zIAIgCEEBaiIHIAIgB0sbaiIHIAAgACAHSxsiACAKIAAgCkkbQX9zaiEMIAEgAkECdiIAQQN0IgdqIQogAyAHaiEHIA0gAEEEdGohACAIIAkgEmtqIQgDQCAAIAr9AwEA/foBIiAgIP3mASAH/QMBAP36ASIgICD95gH95AH94wH9CwIAIApBCGohCiAHQQhqIQcgAEEQaiEAIAxBBGoiDA0ACwsgASAIQQF0IgBqIQogDSAIQQJ0aiEHIAAgA2ohAANAIAggEEYNAiAIIA5HBEAgByAKLgEAsiIiICKUIAAuAQCyIiIgIpSSkTgCACAKQQJqIQogB0EEaiEHIABBAmohACAIQQFqIgggAkkNAQwFCwsgDiALQcyOwAAQGAALIAEgCEEBdCIAaiEKIA0gCEECdGohByAAIANqIQADQCAIIBBGDQEgCCAORg0CIAcgCi4BALKLIAAuAQCyi5I4AgAgCkECaiEKIAdBBGohByAAQQJqIQAgCEEBaiIIIAJJDQALDAILIBAgBEGsjsAAEBgACyAOIAtBvI7AABAYAAsCQAJAAkACQAJAAkACQAJAAkACQAJAAkAgBkEBayIaQQJJDQAgBUEBayIbQQJJDQAgBUEBaiEcIAVBf3MhHSABQQJqIQ4gA0ECaiEQIAVBAmshHiAPIAVBAnQiGUEEaiIAaiEPIAAgDWohEiAFQQF0IhUhEyAFIRhBAiEMQQEhBgNAIAwhCCAFIAZsIR9BASEJIA8hACASIQYgDiEDIBAhAUEAIQxBAiEHA0AgDCAYaiIRQQFqIhQgC08NAyAHIQogACAGKgIAIiJDAAAAAFwEfSACIBRNDQUgBCAUTQ0GAkAgASAVai4BACIHsosiIyADIBVqLgEAIhSyiyIkQ0GCGkCUXkUEQCAJIB9qIQkgJCAjQ0GCGkCUXkUEQCAUQQBKIAdBAEpxRSAHIBRxQQBOcUUEQCAMIBZqIgdBAmogC08NDSAMIBNqIgcgC08NDiAJIAVrQQFqIQcgCSAbaiEJDAMLIAwgFmoiByALTw0KIAwgE2oiB0ECaiALTw0LIAkgHWohByAJIBxqIQkMAgsgCyARTQ0NIBFBAmogC08NDiAJQQFrIQcgCUEBaiEJDAELIAwgFmoiCUEBaiIHIAtPDQ4gDCATaiIRQQFqIgkgC08NDwsgIkMAAAAAICIgDSAJQQJ0aioCAGAbQwAAAAAgIiANIAdBAnRqKgIAYBsFQwAAAAALOAIAIABBBGohACAGQQRqIQYgA0ECaiEDIAFBAmohASAKQQFqIQcgCiEJIB4gDEEBaiIMRw0ACyAPIBlqIQ8gEiAZaiESIA4gFWohDiAFIBNqIRMgBSAWaiEWIBAgFWohECAFIBhqIRggCCAIIBpJIgBqIQwgCCEGIAANAAsLIBcEQCANIBdBAnQQNAsPCyARQQFqIAtB/IzAABAYAAsgEUEBaiACQYyNwAAQGAALIBFBAWogBEGcjcAAEBgACyAHIAtBrI3AABAYAAsgB0ECaiALQbyNwAAQGAALIAdBAmogC0HMjcAAEBgACyAHIAtB3I3AABAYAAsgESALQeyNwAAQGAALIBFBAmogC0H8jcAAEBgACyAJQQFqIAtBjI7AABAYAAsgEUEBaiALQZyOwAAQGAALIA8gCBAtAAtBBCAIEC0AC/tEBD5/CH4JewR9IwBBMGsiDyQAAkACQAJAAkACQAJAIAMgBGwgAkYEQCAFQQFxRQ0BIAZDAAAAAF8EQCAFQQFrs0MAAAA/lEMAAIC/kkOamZk+lEPNzEw/kiEGCyAFQf////8DSyAFQQJ0IglB/P///wdLcg0CQQQhECAJQQQQNSIeRQ0CIA9BADYCLCAPIB42AiggDyAFNgIkIAlBBBA1IgpFDQUgD0EANgIUIA8gCjYCECAPIAU2AgxBACAFQQF2ayEVQwAAgL8gBiAGIAaSlJUhWUMAAAAAIQZBACEQA0AgDygCDCEWAn1DAAAAACFWQwAAAAAhWCMAQRBrIQcgWSAQIBVqIgogCmyylCJXvCILQR92IQ0CQAJ9IFcCfwJAAkACQAJAIAtB/////wdxIgpB0Ni6lQRPBEAgVyAKQYCAgPwHSw0IGiALQQBIIgtFIApBl+TFlQRLcQ0CIAtFDQEgB0MAAICAIFeVOAIIIAcqAggaIApBtOO/lgRNDQEMBwsgCkGY5MX1A00EQCAKQYCAgMgDTQ0DQQAhCiBXDAYLIApBkquU/ANNDQMLIFdDO6q4P5QgDUECdCoC9JdAkvwADAMLIFdDAAAAf5QMBQsgByBXQwAAAH+SOAIMIAcqAgwaIFdDAACAP5IMBAsgDUUgDWsLIgqyIlZDAHIxv5SSIlcgVkOOvr81lCJYkwshViBXIFYgViBWIFaUIlYgVkMVUjW7lEOPqio+kpSTIlaUQwAAAEAgVpOVIFiTkkMAAIA/kiFWIApFDQACQAJAAkAgCkH/AEwEQCAKQYJ/Tg0DIFZDAACADJQhViAKQZt+TQ0BIApB5gBqIQoMAwsgVkMAAAB/lCFWIApB/gFLDQEgCkH/AGshCgwCCyBWQwAAgAyUIVZBtn0gCiAKQbZ9TRtBzAFqIQoMAQsgVkMAAAB/lCFWQf0CIAogCkH9Ak8bQf4BayEKCyBWIApBF3RBgICA/ANqQYCAgPwHcb6UIVYLIFYLIVYgECAWRgRAIA9BDGoQFAsgDygCECARaiBWOAIAIBFBBGohESAGIFaSIQYgDyAQQQFqIhA2AhQgBSAQRw0ACwJ/IAVFBEBBACERQQAMAQtDAACAPyAGlSEGIA8oAhAhBUEAIRBBACERA0AgBiAFIBBqKgIAlEMAAIBHlEMAAAA/kvwBIQogDygCJCARRgRAIA9BJGoQFAsgDygCKCAQaiAKNgIAIA8gEUEBaiIRNgIsIAkgEEEEaiIQRw0ACyAPKAIoIR4gDygCJAshLSAPKAIMIgUEQCAPKAIQIAVBAnQQNAtBACEHIAJB/////wNLIAJBAnQiCkH8////B0tyDQMCfyAKRQRAQQQhBUEADAELQQQhByAKQQQQNiIFRQ0EIAILIS4CQCACRQRAQQEhEAwBCyACQQEQNiIQRQ0FCyAAIAI2AgggACAQNgIEIAAgAjYCACABIRUgBSEKIAMhACAeIQtBACEJAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBFBA2sOAwEACAALIARFDQogAEEEayIBQQAgACABTxshEiARRQRAIABBAnQhBwNAIAAgDGwiCSAAaiIBIAlJBEAgASEADAcLIAEgAksEQCABIQAMBwsgASACSw0IIAxBAWohDEEAIQkgCiEIIAchAQNAIAj9DAAAAAAAAAAAAAAAAAAAAAD9CwIAIAhBEGohCCABQRBrIQEgCUEEaiIJIBJNDQALIAFFIAAgCU1yRQRAIAhBACAB/AsACyAHIApqIQogBCAMRw0ACwwLCyAAQQFrIhNBAEgNA0EEIBFBAXYiAWshFkEAIAFrIRcDQCAAIBRsIgkgAGoiASAJSQRAIAEhAAwGCyABIAJLBEAgASEADAYLIAEgAksNByAUQQFqIRQgCSAVaiEaIAogCUECdGohGEEAIQggFiEJIBchDQNAIAkhDv0MAAAAAAAAAAAAAAAAAAAAACFNIAshAUEAIQcDQCAHIA1qIgkgEyAJIBNJGyIMQQAgCUEAThsiGyAATw0EIAlBAWoiGSATIBMgGUsbIgxBACAZQQBOGyIcIABPDQQgCUECaiIZIBMgEyAZSxsiDEEAIBlBAE4bIhkgAE8NBCAJQQNqIgkgEyAJIBNJGyIMQQAgCUEAThsiCSAATw0EIBogG2otAAD9ESAaIBxqLQAA/RwBIBkgGmotAAD9HAIgCSAaai0AAP0cAyAB/QkCAP21ASBN/a4BIU0gAUEEaiEBIBEgB0EBaiIHRw0ACyAYIAhBAnRqIE1BCP2tAf0LAgAgDkEEaiEJIA1BBGohDSAIQQRqIgggEk0NAAsgACAISwRAA0AgCEEBakIAIUUgDiEJIBEhByALIQwDQCAJIBMgCSATSRtBACAJQQBOGyINIABPDQYgCUEBaiEJIAw1AgAgDSAaajEAAH4gRXwhRSAMQQRqIQwgB0EBayIHDQALIBggCEECdGogRUIIiD4CACAOQQFqIQ4iCCAARw0ACwsgBCAURw0ACwwKCyAERQ0JAkACQAJAIAAOAgABAgtBAEEAQdSGwAAQGAALIAJFBEBBASEJDAoLIAJFBEBBASEJDAkLQQFBAUHkhsAAEBgACyALKAIIIhP9ESFQIAsoAgQiAf0RIVEgCygCACII/REhUiAAQQVrIgxBACAAIAxPGyEYIABBAWshFCABIAhqIRkgE60iRSABrSJIfCFGQQEgAGshGyAVQQFrIRYgAEECdCEaIApBBGohDCAAQQJrIRwgRf0SIU0gSP0SIU8gCK0iR/0SIU4gAEEGSSEfIAohCyAVIQ0CQAJAA0AgACAObCIIIABqIgkgCEkgAiAJSSIBcg0LIAENCiAKIAhBAnRqIiAgEyAIIBVqIhctAAFsIBkgFy0AACIJbGo2AgBBASEHAkACQAJAAkACQAJAAkAgH0UEQEECIQggDCEBA0AgCEEBayAATw0DIAAgCE0NBCAIQQFqIABPDQUgCEECaiAATw0CIAlB/wFxIR0gASAIIA1qIglBAWstAAAiIf0RIAktAAAiB/0cASAJQQFqLQAAIhL9HAIgCUECai0AACIJ/RwDIFH9tQEgHf0RICH9HAEgB/0cAiAS/RwDIFL9tQH9rgEgB/0RIBL9HAEgCf0cAiAXIBQgCEEDaiIHIAcgFEsbai0AAP0cAyBQ/bUB/a4BQQj9rQH9CwIAIAFBEGohASAIQQRqIQggByAYTQ0ACyAIQQFrIQcLIAcgFE8NBSAHQX9zIAAgB0EBaiIBIAAgAUsbaiIBIBwgB2siCCABIAhJGyIBIAAgB0EBayIIIAAgCEsbIAdrQQFqIgggASAISRsiASAHIAAgACAHSRsiEiAHayIIIAEgCEkbQQFqIgFBBE0NBCAHIBZqIQggCyAHQQJ0aiEJIAcgASABQQNxIgFBBCABG2siAWohBwNAIAkgTyAIQQFq/VwAACJT/YkB/akB/ckB/dUBIE4gCP1cAAAiVP2JAf2pAf3JAf3VAf3OASBNIAhBAmr9XAAAIlX9iQH9qQH9yQH91QH9zgFBCP3NASBPIFMgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VASBOIFQgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OASBNIFUgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OAUEI/c0B/Q0AAQIDCAkKCxAREhMYGRob/QsCACAIQQRqIQggCUEQaiEJIAFBBGsiAQ0ACwwECyAIQQJqIABB1IfAABAYAAsgCEEBayAAQaSHwAAQGAALIAggAEG0h8AAEBgACyAIQQFqIABBxIfAABAYAAsgB0EBayEIQQAgEmshHSALIAdBAnRqIQkDQCAAIAhNDQIgCCAdakF/Rg0EIAhBAmogAE8NBSAJIAggDWoiAUEBajEAACBIfiABMQAAIEd+fCABQQJqMQAAIEV+fEIIiD4CACAJQQRqIQkgGyAIQQFqIghqQX9HDQALCyAgIBRBAnRqIEYgFCAXajEAAH4gACAXakECazEAACBHfnxCCIg+AgAgACAWaiEWIAsgGmohCyAMIBpqIQwgACANaiENIA5BAWoiDiAERw0BDA0LCyAIIABB9IbAABAYAAsgEiAAQYSHwAAQGAALIAhBAmogAEGUh8AAEBgACyAMIABBxITAABAYAAsgDSAAQbSEwAAQGAALIAAgAk0NAQsgCSAAIAJBlITAABAbAAsgACACTQ0LIAAhAQsgCSABIAJBpITAABAbAAsgBEUgAEVyDQICQAJAAkAgAEEBayINQQBOBEAgDUEARyIUIABPBEAgACACSwRAQQEhBwwFC0EBIQcgACACTQ0CDAMLQQIgDSANQQJPGyEWIABBAUcEQEEDIA0gDUEDTxshEyAAQQJ0IRogCkEIaiEMA0AgACAIbCIJIABqIgcgCUkgAiAHSSIBcg0FIAENBCAKIAlBAnRqIgEgCSAVaiIOMQAAIkcgCzUCACJGIAs1AgQiSXx+IkogDiAUajEAACJLIAs1AgwiRX58IEcgCzUCCCJIfnwgCzUCECJHIA4gFmoxAAAiTH58QgiIPgIAIAEgSiBIIEt+fCBFIEx+fCBHIA4gE2oxAAB+fEIIiD4CBCAAQQJHBEBBBCEJIAwhAQNAIAlBA2siByANIAcgDUkbIgdBACAJQQJrIhdBAEobIhIgAE8NBSAJQQFrIgcgDSAHIA1JGyIHQQAgF0EBaiIYQQBOGyIZIABPDQUgCSANIAkgDUkbIgdBACAYQQFqIhhBAE4bIhsgAE8NBSABIA4gFyANIA0gF0sbajEAACBIfiAOIBlqMQAAIEV+fCAOIBJqMQAAIEl+fCAOIAlBBGsiByANIAcgDUkbajEAACBGfnwgDiAbajEAACBHfnxCCIg+AgAgCUEBaiEJIAFBBGohASAYQQFrIABHDQALCyAMIBpqIQwgCEEBaiIIIARHDQALDAcLIAIgBEEBayIAIAAgAksbQQFqIgFBBU8EQCALNQIAIAs1AgR8/RIhTSALNQIQ/RIhTyALNQII/RIhTiALNQIM/RIhUCAVIQggCiEAIAEgAUEDcSIBQQQgARtrIgkhAQNAIAAgTSAI/VwAACJR/YkB/akB/ckBIlL91QEgCCAUav1cAAAiU/2JAf2pAf3JASBQ/dUB/c4BIFIgTv3VAf3OASBPIAggFmr9XAAAIlL9iQH9qQH9yQH91QH9zgFBCP3NASBNIFEgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JASJR/dUBIFMgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JASBQ/dUB/c4BIFEgTv3VAf3OASBPIFIgTf0NAgMAAAAAAAAAAAAAAAAAAP2JAf2pAf3JAf3VAf3OAUEI/c0B/Q0AAQIDCAkKCxAREhMYGRob/QsCACAIQQRqIQggAEEQaiEAIAFBBGsiAQ0ACwsDQCAJQQFqIQcgAiAJTQ0EIAIgB0kNAyAKIAlBAnRqIAkgFWoiADEAACJFIAs1AgAgCzUCBHx+IAAgFGoxAAAgCzUCDH58IEUgCzUCCH58IAs1AhAgACAWajEAAH58QgiIPgIAIAciCSAERw0ACwwGCyAAIAJLBEAgACEHDAMLIAAgAksEQCAAIQcMAgsMDAsgByAAQYSIwAAQGAALIAkgByACQfSHwAAQGwALIAkgByACQeSHwAAQGwALIAggCSACQcSGwAAQGwALIAggCSACQbSGwAAQGwALIBAhCSACIQcgBCEYIB4hAUEAIQJBACEVAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBFBA0cEQCAERQ0SIANBBGsiEEEAIAMgEE8bIRRBACARQQF2ayEIIARBAWshDAwBCyADIAdNBEAgATUCCCFFIAE1AgQhRyABNQIAIUggA0UNAyBHIEh8IUYgByADayIAQQAgACAHTRsiACADQQFrIgEgACABSRtBAWoiAEEETQ0CIANBAnQhBCBF/RIhTSBG/RIhTyAJIQEgACAAQQNxIgBBBCAAG2siAiELIAUhAANAIAEgTSAAIARq/QACACJQ/ckB/dUBIE8gAP0AAgAiUf3JAf3VAf3OAUEY/c0BIk79DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgTv0dAEL/AVQbQn9CACBO/R0BQv8BVBv9HgH9UiBNIFD9ygH91QEgTyBR/coB/dUB/c4BQRj9zQEiTv0M/wAAAAAAAAD/AAAAAAAAAP0M//////////////////////0MAAAAAAAAAAAAAAAAAAAAACBO/R0AQv8BVBtCf0IAIE79HQFC/wFUG/0eAf1S/Q0ACBAYAAAAAAAAAAAAAAAA/VoAAAAgAUEEaiEBIABBEGohACALQQRrIgsNAAsMAgtBACADIAdB1ITAABAbAAsDQAJAAkACQAJAIAMgFWwiDiAVQQFqIhUgA2wiAEsgACAHS3JFBEAgCSAOaiENQQAhAEEAIQQCQANAAkAgACEKAkAgEUUEQP0MAAAAAAAAAAAAAAAAAAAAACJNIU8MAQsgDEEASA0BIAUgBEECdGohFiAIIQAgESELIAEhAv0MAAAAAAAAAAAAAAAAAAAAACJPIU0DQCACNQIA/RIiTiAWIAAgDCAAIAxJG0EAIABBAE4bIANsQQJ0aiIX/QYCCP3VASBN/c4BIU0gTiAX/QYCAP3VASBP/c4BIU8gAEEBaiEAIAJBBGohAiALQQFrIgsNAAsLIAMgBE0NBCAEIA1qQv8BIE/9HQBCGIgiRSBFQv8BWhs8AAAgBEEBciIAIANPDQUgACANakL/ASBP/R0BQhiIIkUgRUL/AVobPAAAIARBAnIiACADTw0GIAAgDWpC/wEgTf0dAEIYiCJFIEVC/wFaGzwAACAEQQNyIgAgA08NAiAAIA1qQv8BIE39HQFCGIgiRSBFQv8BWhs8AAAgCkEBaiEAIBQgBEEEaiIETw0BDAcLCwweCyAAIANBhITAABAYAAsgDiAAIAdBtIPAABAbAAsgBCADQdSDwAAQGAALIAAgA0Hkg8AAEBgACyAAIANB9IPAABAYAAsCQCADIARNDQAgEUUEQCAQIApBAnQiAGsiAkUNASAAIAlqIA5qQQRqQQAgAvwLAAwBCwJAIAxBAE4EQANAIARBAWpBACEAQgAhRSABIQIDQCAAIAhqIg4gDCAMIA5LG0EAIA5BAE4bIANsIARqIg4gB08NAyACNQIAIAUgDkECdGo1AgB+IEV8IUUgAkEEaiECIBEgAEEBaiIARw0ACyAEIA1qQv8BIEVCGIgiRSBFQv8BWhs8AAAiBCADRw0ADAMLAAsMGgsgDiAHQcSDwAAQGAALIAhBAWohCCAVIBhHDQALDBALQQAgA2shEiAFIAJBAnRqIQEgAyAHIAMgB0kbIAJqIQggBSACIANqQQJ0aiELIAMhBCAHIQAgCSEKA0AgACACRg0CIAAgCEYEQCACIBJrIAdBpIbAABAYAAsgAiAKakL/ASALNQIAIEV+IEYgATUCAH58QhiIIkkgSUL/AVobPAAAIABBAWshACALQQRqIQsgEkEBayESIApBAWohCiABQQRqIQEgAiAEQQFrIgRHDQALCyAYQQFrIgBBACAAIBhNGyIvQQJJDQ0CQCADQQRPBEAgRf0SIU0gR/0SIU8gSP0SIU4gBUEQaiIZIANBAnQiJGohHyAZIANBA3RqISAgAyAJaiElQXwgA2shHSADQQVrITAgCSADQQRqIjFqISFBfCADQQF0IjJrISYgAyEbQXwhJ0EEISggMiIcQQRqIjghKSAxIRFBASEqDAELIANFDQ4gA0EDdCERIANBAXQhDSADIAlqIQ4gA0ECdCEQQQAhCiAFIQFBASELA0AgCiANaiICIAMgCmoiBCISSSACIAdLcg0NIAcgCk0NDCAHIBJNDQkgAiAHTw0DIAogDmoiCEL/ASABIgAgEGoiATUCACBHfiAANQIAIEh+fCAAIBFqIgw1AgAgRX58QhiIIkYgRkL/AVobPAAAAkAgA0EBRg0AIApBAWogB08NDCAEQQFqIAdPDQkgAkEBaiAHTw0GIAhBAWpC/wEgAUEEajUCACBHfiAAQQRqNQIAIEh+fCAMQQRqNQIAIEV+fEIYiCJGIEZC/wFaGzwAACADQQJGDQAgCkECaiAHTw0LIARBAmogB08NCCACQQJqIAdPDQUgCEECakL/ASABQQhqNQIAIEd+IABBCGo1AgAgSH58IAxBCGo1AgAgRX58QhiIIkYgRkL/AVobPAAACyADIApqIQogC0EBaiILIC9JDQALDA4LA0AgKiIBQQFqIiogA2wiACABIANsIgJJIAAgB0tyDQ0gAyAzbCIiQQRqITkgIiAyaiE6ICIgOGohOyADICJqITwgIiAxaiE9IAIgCWohIyAFIABBAnRqIT4gBSACQQJ0aiE/IAUgAUEBayADbEECdGohQEEEIQtBACEIICEhDCAZIRAgHyENICAhASAnIRUgKCEXICYhDiApIRMgHSEWIBEhGiAwIRRBACEAAkACQAJAAkACQAJAA0AgCyECIAghQSAUITQgGiE1IBYhQiATITYgDiFDIBchNyAVIUQgASEKIA0hEiAQIQQgDCErIAAgA08NASAAICNqQv8BIE8gPyAAQQJ0Igtq/QACACJQ/ckB/dUBIE4gCyBAav0AAgAiUf3JAf3VAf3OASBNIAsgPmr9AAIAIlL9yQH91QH9zgEiU/0dAEIYiCJGIEZC/wFaGzwAACAAQQFyIgsgA08NAiALICNqQv8BIFP9HQFCGIgiRiBGQv8BWhs8AAAgAEECciILIANPDQMgCyAjakL/ASBPIFD9ygH91QEgTiBR/coB/dUB/c4BIE0gUv3KAf3VAf3OASJQ/R0AQhiIIkYgRkL/AVobPAAAIABBA3IiACADTw0EIAAgI2pC/wEgUP0dAUIYiCJGIEZC/wFaGzwAACAMQQRqIQwgBEEQaiEQIA1BEGohDSABQRBqIQEgFUEEayEVIBdBBGohFyAOQQRrIQ4gE0EEaiETIBZBBGshFiAaQQRqIRogFEEEayEUIAhBAWohCCACIgBBBGoiCyADTQ0ACyAAIANPDQUgMCBBQQJ0IgBrIghBACAAayIBIDxrIAcgACA9aiIMIAcgDEsbakEEayIMIAggDEkbIgggASA6ayAHIAAgO2oiDCAHIAxLG2pBBGsiDCAIIAxJGyIIIAEgImsgByAAIDlqIgAgACAHSRtqQQRrIgAgACAISxtBAWoiAUEETQ0EIAFBA3EiAEEEIAAbIgggNCAHIDUgByA1SxsgQmoiACAAIDRLGyIAIAcgNiAHIDZLGyBDaiIMIAAgDEkbIgAgByA3IAcgN0sbIERqIgwgACAMSRtBf3NqIQAgAiABIAhraiECA0AgKyBPIBL9AAIAIlH9yQH91QEgTiAE/QACACJS/ckB/dUB/c4BIE0gCv0AAgAiU/3JAf3VAf3OAUEY/c0BIlD9DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgUP0dAEL/AVQbQn9CACBQ/R0BQv8BVBv9HgH9UiBPIFH9ygH91QEgTiBS/coB/dUB/c4BIE0gU/3KAf3VAf3OAUEY/c0BIlD9DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgUP0dAEL/AVQbQn9CACBQ/R0BQv8BVBv9HgH9Uv0NAAgQGAAAAAAAAAAAAAAAAP1aAAAAICtBBGohKyAEQRBqIQQgEkEQaiESIApBEGohCiAAQQRqIgANAAsMBAsgACADQdSFwAAQGAALIAsgA0HkhcAAEBgACyALIANB9IXAABAYAAsgACADQYSGwAAQGAALIAUgAiAsakECdGohACAFIAIgG2pBAnRqIQEgBSACIBxqQQJ0aiELA0AgAiAsaiIKIAdPDQ0gAiAbaiISIAdPDQogAiAcaiIKIAdPDQcgAiAlakL/ASABNQIAIEd+IAA1AgAgSH58IAs1AgAgRX58QhiIIkYgRkL/AVobPAAAIABBBGohACABQQRqIQEgC0EEaiELIAJBAWoiAiADRw0ACwsgAyAsaiEsIAMgG2ohGyADIBxqIRwgAyAlaiElIAMgIWohISAZICRqIRkgHyAkaiEfICAgJGohICAnIANrIScgAyAoaiEoICYgA2shJiADIClqISkgHSADayEdIAMgEWohESAzQQFqITMgKiAvSQ0ACwwNCyAHIAdBlIbAABAYAAsgA0EBdCAKaiEKDAILIANBAXQgCmpBAmohCgwBCyADQQF0IApqQQFqIQoLIAogB0HEhcAAEBgACyAEQQJqIRIMAQsgBEEBaiESCyASIAdBtIXAABAYAAsgCkECaiEKDAELIApBAWohCgsgCiAHQaSFwAAQGAALIAMgCmohAiADQQF0IApqIQALIAIgACAHQZSFwAAQGwALIBhBAkkNACAHIAMgGGwiAE8gACAYQQFrIANsIghPcUUEQCAIIAAgB0HkhMAAEBsACyADRQ0AQQAhBCBFIEd8IUUgByAIayIAQQAgACAHTRsiACAHIBhBAmsgA2wiDGsiAUEAIAEgB00bIgEgACABSRsiACADQQFrIgEgACABSRsiAUEBaiIKQQVPBEAgCCAJaiEAIApBA3EiAkEEIAIbIgQgAUF/c2ohCyAFIAhBAnRqIQIgBSAMQQJ0aiEBIAogBGshBCBI/RIhTSBF/RIhTwNAIAAgTyAC/QACACJQ/ckB/dUBIE0gAf0AAgAiUf3JAf3VAf3OAUEY/c0BIk79DP8AAAAAAAAA/wAAAAAAAAD9DP/////////////////////9DAAAAAAAAAAAAAAAAAAAAAAgTv0dAEL/AVQbQn9CACBO/R0BQv8BVBv9HgH9UiBPIFD9ygH91QEgTSBR/coB/dUB/c4BQRj9zQEiTv0M/wAAAAAAAAD/AAAAAAAAAP0M//////////////////////0MAAAAAAAAAAAAAAAAAAAAACBO/R0AQv8BVBtCf0IAIE79HQFC/wFUG/0eAf1S/Q0ACBAYAAAAAAAAAAAAAAAA/VoAAAAgAEEEaiEAIAFBEGohASACQRBqIQIgC0EEaiILDQALCyAFIARBAnQiACAMQQJ0amohASAFIAhBAnQgAGpqIQsgAyAEayEKIAQgDGohAiAEIAhqIQACQAJAA0AgAiAHTw0CIAAgB08NASAAIAlqQv8BIEUgCzUCAH4gATUCACBIfnxCGIgiRyBHQv8BWhs8AAAgAUEEaiEBIAJBAWohAiALQQRqIQsgAEEBaiEAIApBAWsiCg0ACwwCCyAAIAdBhIXAABAYAAsgAiAHQfSEwAAQGAALIC4EQCAFIC5BAnQQNAsgLQRAIB4gLUECdBA0CyAPQTBqJAAPCyAPQQA2AhwgD0EBNgIQIA9BiInAADYCDCAPQgQ3AhQgD0EMakGQicAAECYACyAPQQA2AhwgD0EBNgIQIA9BwIjAADYCDCAPQgQ3AhQgD0EMakHIiMAAECYACyAQIAkQLQALIAcgChAtAAtBASACEC0AC0EEIAkQLQALQYCAwABBHEGkg8AAEBwAC7QFARV/IANBAXQiCyAEbCIIQQF0IQYCQCAIQQBIIAZB/v///wdLcg0AAn8gBkUEQEECIQlBAAwBC0ECIQogBkECEDYiCUUNASAICyEKIAAgCDYCCCAAIAk2AgQgACAKNgIAAkACQAJAAkACQAJAAkACQAJAAkAgBEEBayISQQJJIANBAWtBAklyRQRAIAEgA2ohDSALQQJqIQogASALaiEOIANBAmshEyADQQJ0IhQgCWpBBGohCSADIQYgCyEPQQEhEANAIBBBAWohECAJIQAgCiEEQQAhBwNAIAcgEWoiBSACTw0DIAVBAWogAk8NBCAFQQJqIAJPDQUgBiAHaiIFIAJPDQYgBUECaiACTw0HIAcgD2oiBSACTw0IIAVBAWogAk8NCSAFQQJqIAJPDQogBCAITw0LIAEgB2oiBUEBai0AACEVIAcgDmoiDEEBai0AACEWIAAgDEECai0AACIXIAVBAmotAAAiGCAHIA1qIhlBAmotAAAgGS0AAGtBAXRqIAUtAAAiBSAMLQAAIgxqa2o7AQAgBEEBaiAITw0MIABBAmogDCAFIBhqayAXaiAWIBVrQQF0ajsBACAAQQRqIQAgBEECaiEEIBMgB0EBaiIHRw0ACyADIA1qIQ0gAyARaiERIAMgBmohBiAJIBRqIQkgCiALaiEKIAMgDmohDiABIANqIQEgAyAPaiEPIBAgEkcNAAsLDwsgBSACQdyKwAAQGAALIAVBAWogAkHsisAAEBgACyAFQQJqIAJB/IrAABAYAAsgBSACQYyLwAAQGAALIAVBAmogAkGci8AAEBgACyAFIAJBrIvAABAYAAsgBUEBaiACQbyLwAAQGAALIAVBAmogAkHMi8AAEBgACyAEIAhB3IvAABAYAAsgBEEBaiAIQeyLwAAQGAALIAogBhAtAAuUBgEFfyAAQQhrIgEgAEEEaygCACIDQXhxIgBqIQICQAJAIANBAXENACADQQJxRQ0BIAEoAgAiAyAAaiEAIAEgA2siAUHIm8AAKAIARgRAIAIoAgRBA3FBA0cNAUHAm8AAIAA2AgAgAiACKAIEQX5xNgIEIAEgAEEBcjYCBCACIAA2AgAPCyABIAMQCgsCQAJAAkACQAJAIAIoAgQiA0ECcUUEQCACQcybwAAoAgBGDQIgAkHIm8AAKAIARg0DIAIgA0F4cSICEAogASAAIAJqIgBBAXI2AgQgACABaiAANgIAIAFByJvAACgCAEcNAUHAm8AAIAA2AgAPCyACIANBfnE2AgQgASAAQQFyNgIEIAAgAWogADYCAAsgAEGAAkkNAiABIAAQC0EAIQFB4JvAAEHgm8AAKAIAQQFrIgA2AgAgAA0EQaiZwAAoAgAiAARAA0AgAUEBaiEBIAAoAggiAA0ACwtB4JvAAEH/HyABIAFB/x9NGzYCAA8LQcybwAAgATYCAEHEm8AAQcSbwAAoAgAgAGoiADYCACABIABBAXI2AgRByJvAACgCACABRgRAQcCbwABBADYCAEHIm8AAQQA2AgALIABB2JvAACgCACIDTQ0DQcybwAAoAgAiAkUNA0EAIQBBxJvAACgCACIEQSlJDQJBoJnAACEBA0AgAiABKAIAIgVPBEAgAiAFIAEoAgRqSQ0ECyABKAIIIQEMAAsAC0HIm8AAIAE2AgBBwJvAAEHAm8AAKAIAIABqIgA2AgAgASAAQQFyNgIEIAAgAWogADYCAA8LAkBBuJvAACgCACICQQEgAEEDdnQiA3FFBEBBuJvAACACIANyNgIAIABB+AFxQbCZwABqIgAhAgwBCyAAQfgBcSIAQbCZwABqIQIgAEG4mcAAaigCACEACyACIAE2AgggACABNgIMIAEgAjYCDCABIAA2AggPC0GomcAAKAIAIgEEQANAIABBAWohACABKAIIIgENAAsLQeCbwABB/x8gACAAQf8fTRs2AgAgAyAETw0AQdibwABBfzYCAAsLuAQBCH8jAEEQayIDJAAgAyABNgIEIAMgADYCACADQqCAgIAONwIIAn8CQAJAAkAgAigCECIJBEAgAigCFCIADQEMAgsgAigCDCIARQ0BIAIoAggiASAAQQN0IgBqIQQgAEEIa0EDdkEBaiEGIAIoAgAhAANAAkAgAEEEaigCACIFRQ0AIAMoAgAgACgCACAFIAMoAgQoAgwRAwBFDQBBAQwFC0EBIAEoAgAgAyABQQRqKAIAEQIADQQaIABBCGohACAEIAFBCGoiAUcNAAsMAgsgAEEYbCEKIABBAWtB/////wFxQQFqIQYgAigCCCEEIAIoAgAhAANAAkAgAEEEaigCACIBRQ0AIAMoAgAgACgCACABIAMoAgQoAgwRAwBFDQBBAQwEC0EAIQdBACEIAkACQAJAIAUgCWoiAUEIai8BAEEBaw4CAQIACyABQQpqLwEAIQgMAQsgBCABQQxqKAIAQQN0ai8BBCEICwJAAkACQCABLwEAQQFrDgIBAgALIAFBAmovAQAhBwwBCyAEIAFBBGooAgBBA3RqLwEEIQcLIAMgBzsBDiADIAg7AQwgAyABQRRqKAIANgIIQQEgBCABQRBqKAIAQQN0aiIBKAIAIAMgASgCBBECAA0DGiAAQQhqIQAgBUEYaiIFIApHDQALDAELCwJAIAYgAigCBE8NACADKAIAIAIoAgAgBkEDdGoiACgCACAAKAIEIAMoAgQoAgwRAwBFDQBBAQwBC0EACyADQRBqJAALjwQBAn8gACABaiECAkACQCAAKAIEIgNBAXENACADQQJxRQ0BIAAoAgAiAyABaiEBIAAgA2siAEHIm8AAKAIARgRAIAIoAgRBA3FBA0cNAUHAm8AAIAE2AgAgAiACKAIEQX5xNgIEIAAgAUEBcjYCBCACIAE2AgAMAgsgACADEAoLAkACQAJAIAIoAgQiA0ECcUUEQCACQcybwAAoAgBGDQIgAkHIm8AAKAIARg0DIAIgA0F4cSICEAogACABIAJqIgFBAXI2AgQgACABaiABNgIAIABByJvAACgCAEcNAUHAm8AAIAE2AgAPCyACIANBfnE2AgQgACABQQFyNgIEIAAgAWogATYCAAsgAUGAAk8EQCAAIAEQCw8LAkBBuJvAACgCACICQQEgAUEDdnQiA3FFBEBBuJvAACACIANyNgIAIAFB+AFxQbCZwABqIgEhAgwBCyABQfgBcSIBQbCZwABqIQIgAUG4mcAAaigCACEBCyACIAA2AgggASAANgIMIAAgAjYCDCAAIAE2AggPC0HMm8AAIAA2AgBBxJvAAEHEm8AAKAIAIAFqIgE2AgAgACABQQFyNgIEIABByJvAACgCAEcNAUHAm8AAQQA2AgBByJvAAEEANgIADwtByJvAACAANgIAQcCbwABBwJvAACgCACABaiIBNgIAIAAgAUEBcjYCBCAAIAFqIAE2AgALC+cCAQV/AkAgAUHN/3tBECAAIABBEE0bIgBrTw0AIABBECABQQtqQXhxIAFBC0kbIgRqQQxqEAEiAkUNACACQQhrIQECQCAAQQFrIgMgAnFFBEAgASEADAELIAJBBGsiBSgCACIGQXhxIAIgA2pBACAAa3FBCGsiAiAAQQAgAiABa0EQTRtqIgAgAWsiAmshAyAGQQNxBEAgACADIAAoAgRBAXFyQQJyNgIEIAAgA2oiAyADKAIEQQFyNgIEIAUgAiAFKAIAQQFxckECcjYCACABIAJqIgMgAygCBEEBcjYCBCABIAIQCAwBCyABKAIAIQEgACADNgIEIAAgASACajYCAAsCQCAAKAIEIgFBA3FFDQAgAUF4cSICIARBEGpNDQAgACAEIAFBAXFyQQJyNgIEIAAgBGoiASACIARrIgRBA3I2AgQgACACaiICIAIoAgRBAXI2AgQgASAEEAgLIABBCGohAwsgAwuCAwEEfyAAKAIMIQICQAJAAkAgAUGAAk8EQCAAKAIYIQMCQAJAIAAgAkYEQCAAQRRBECAAKAIUIgIbaigCACIBDQFBACECDAILIAAoAggiASACNgIMIAIgATYCCAwBCyAAQRRqIABBEGogAhshBANAIAQhBSABIgJBFGogAkEQaiACKAIUIgEbIQQgAkEUQRAgARtqKAIAIgENAAsgBUEANgIACyADRQ0CAkAgACgCHEECdEGgmMAAaiIBKAIAIABHBEAgAygCECAARg0BIAMgAjYCFCACDQMMBAsgASACNgIAIAJFDQQMAgsgAyACNgIQIAINAQwCCyAAKAIIIgAgAkcEQCAAIAI2AgwgAiAANgIIDwtBuJvAAEG4m8AAKAIAQX4gAUEDdndxNgIADwsgAiADNgIYIAAoAhAiAQRAIAIgATYCECABIAI2AhgLIAAoAhQiAEUNACACIAA2AhQgACACNgIYDwsPC0G8m8AAQbybwAAoAgBBfiAAKAIcd3E2AgALxAIBBH8gAEIANwIQIAACf0EAIAFBgAJJDQAaQR8gAUH///8HSw0AGiABQSYgAUEIdmciA2t2QQFxIANBAXRrQT5qCyICNgIcIAJBAnRBoJjAAGohBEEBIAJ0IgNBvJvAACgCAHFFBEAgBCAANgIAIAAgBDYCGCAAIAA2AgwgACAANgIIQbybwABBvJvAACgCACADcjYCAA8LAkACQCABIAQoAgAiAygCBEF4cUYEQCADIQIMAQsgAUEZIAJBAXZrQQAgAkEfRxt0IQUDQCADIAVBHXZBBHFqIgQoAhAiAkUNAiAFQQF0IQUgAiEDIAIoAgRBeHEgAUcNAAsLIAIoAggiASAANgIMIAIgADYCCCAAQQA2AhggACACNgIMIAAgATYCCA8LIARBEGogADYCACAAIAM2AhggACAANgIMIAAgADYCCAv7BQIKfwF+IwBBEGsiCCQAQQohAiAAKAIAIgQhAyAEQegHTwRAIAQhAANAIAhBBmogAmoiBkEEayAAIABBkM4AbiIDQZDOAGxrIgdB//8DcUHkAG4iBUEBdC8AqJRAOwAAIAZBAmsgByAFQeQAbGtB//8DcUEBdC8AqJRAOwAAIAJBBGshAiAAQf+s4gRLIAMhAA0ACwsCQCADQQlNBEAgAyEADAELIAJBAmsiAiAIQQZqaiADIANB//8DcUHkAG4iAEHkAGxrQf//A3FBAXQvAKiUQDsAAAtBACAEIAAbRQRAIAJBAWsiAiAIQQZqaiAAQQF0LQCplEA6AAALAn8gCEEGaiACaiEKQQogAmshBkEAIQRBAUErQYCAxAAgASgCCCICQYCAgAFxIgAbIQtBACACQYCAgARxGyEHAkAgAEEVdiAGaiIAIAEvAQwiA0kEQAJAAkAgAkGAgIAIcUUEQCADIABrIQNBACEAAkACQAJAIAJBHXZBA3FBAWsOAwABAAILIAMhAAwBCyADQf7/A3FBAXYhAAsgAkH///8AcSEJIAEoAgQhBSABKAIAIQEDQCAEQf//A3EgAEH//wNxTw0CQQEhAiAEQQFqIQQgASAJIAUoAhARAgBFDQALDAQLIAEgASkCCCIMp0GAgID/eXFBsICAgAJyNgIIQQEhAiABKAIAIgUgASgCBCIJIAsgBxAeDQMgAyAAa0H//wNxIQADQCAEQf//A3EgAE8NAiAEQQFqIQQgBUEwIAkoAhARAgBFDQALDAMLQQEhAiABIAUgCyAHEB4NAiABIAogBiAFKAIMEQMADQJBACEEIAMgAGtB//8DcSEAA0AgBEH//wNxIgMgAEkhAiAAIANNDQMgBEEBaiEEIAEgCSAFKAIQEQIARQ0ACwwCCyAFIAogBiAJKAIMEQMADQEgASAMNwIIQQAMAgtBASECIAEoAgAiACABKAIEIgEgCyAHEB4NACAAIAogBiABKAIMEQMAIQILIAILIAhBEGokAAuIAgEGfyAAKAIIIgQhAgJ/QQEgAUGAAUkNABpBAiABQYAQSQ0AGkEDQQQgAUGAgARJGwsiBiAAKAIAIARrSwR/IAAgBCAGEBIgACgCCAUgAgsgACgCBGohAgJAIAFBgAFPBEAgAUE/cUGAf3IhBSABQQZ2IQMgAUGAEEkEQCACIAU6AAEgAiADQcABcjoAAAwCCyABQQx2IQcgA0E/cUGAf3IhAyABQf//A00EQCACIAU6AAIgAiADOgABIAIgB0HgAXI6AAAMAgsgAiAFOgADIAIgAzoAAiACIAdBP3FBgH9yOgABIAIgAUESdkFwcjoAAAwBCyACIAE6AAALIAAgBCAGajYCCEEAC58CAgN/AX4jAEFAaiICJAAgASgCAEGAgICAeEYEQCABKAIMIQMgAkEkaiIEQQA2AgAgAkKAgICAEDcCHCACQTBqIAMoAgAiA0EIaikCADcDACACQThqIANBEGopAgA3AwAgAiADKQIANwMoIAJBHGpBjJLAACACQShqEAcaIAJBGGogBCgCACIDNgIAIAIgAikCHCIFNwMQIAFBCGogAzYCACABIAU3AgALIAEpAgAhBSABQoCAgIAQNwIAIAJBCGoiAyABQQhqIgEoAgA2AgAgAUEANgIAIAIgBTcDAEEMQQQQNSIBRQRAQQRBDBA5AAsgASACKQMANwIAIAFBCGogAygCADYCACAAQeyTwAA2AgQgACABNgIAIAJBQGskAAuUAgECfyMAQSBrIgUkAEHwm8AAQfCbwAAoAgAiBkEBajYCAAJAAn9BACAGQQBIDQAaQQFB7JvAAC0AAA0AGkHsm8AAQQE6AABB6JvAAEHom8AAKAIAQQFqNgIAQQILQf8BcSIGQQJHBEAgBkEBcUUNASAFQQhqIAAgASgCGBEAAAwBC0H0m8AAKAIAIgZBAEgNAEH0m8AAIAZBAWo2AgBB+JvAACgCAARAIAUgACABKAIUEQAAIAUgBDoAHSAFIAM6ABwgBSACNgIYIAUgBSkDADcCEEH4m8AAKAIAIAVBEGpB/JvAACgCACgCFBEAAAtB9JvAAEH0m8AAKAIAQQFrNgIAQeybwABBADoAACADRQ0AAAsAC8EBAgN/AX4jAEEwayICJAAgASgCAEGAgICAeEYEQCABKAIMIQMgAkEUaiIEQQA2AgAgAkKAgICAEDcCDCACQSBqIAMoAgAiA0EIaikCADcDACACQShqIANBEGopAgA3AwAgAiADKQIANwMYIAJBDGpBjJLAACACQRhqEAcaIAJBCGogBCgCACIDNgIAIAIgAikCDCIFNwMAIAFBCGogAzYCACABIAU3AgALIABB7JPAADYCBCAAIAE2AgAgAkEwaiQAC6gBAgJ/AX5BASEHQQQhBgJAIAQgBWpBAWtBACAEa3GtIAOtfiIIQiCIUEUEQEEAIQMMAQsgCKciA0GAgICAeCAEa0sEQEEAIQMMAQsCQAJAAn8gAQRAIAIgASAFbCAEIAMQMQwBCyADRQRAIAQhBgwCCyADIAQQNQsiBg0AIAAgBDYCBAwBCyAAIAY2AgRBACEHC0EIIQYLIAAgBmogAzYCACAAIAc2AgALhwEBAX8jAEEQayIDJAAgAiABIAJqIgFLBEBBAEEAEC0ACyADQQRqIAAoAgAiAiAAKAIEQQggASACQQF0IgIgASACSxsiASABQQhNGyIBQQFBARARIAMoAgRBAUYEQCADKAIIIAMoAgwQLQALIAMoAgghAiAAIAE2AgAgACACNgIEIANBEGokAAt5AQF/IwBBIGsiAiQAAn8gACgCAEGAgICAeEcEQCABIAAoAgQgACgCCBAwDAELIAJBEGogACgCDCgCACIAQQhqKQIANwMAIAJBGGogAEEQaikCADcDACACIAApAgA3AwggASgCACABKAIEIAJBCGoQBwsgAkEgaiQAC2kBA38jAEEQayIBJAAgAUEEaiAAKAIAIgIgACgCBEEEIAJBAXQiAiACQQRNGyICQQRBBBARIAEoAgRBAUYEQCABKAIIIAEoAgwQLQALIAEoAgghAyAAIAI2AgAgACADNgIEIAFBEGokAAtpAQN/IwBBEGsiASQAIAFBBGogACgCACICIAAoAgRBBCACQQF0IgIgAkEETRsiAkEEQQgQESABKAIEQQFGBEAgASgCCCABKAIMEC0ACyABKAIIIQMgACACNgIAIAAgAzYCBCABQRBqJAALaQEDfyMAQRBrIgEkACABQQRqIAAoAgAiAiAAKAIEQQQgAkEBdCICIAJBBE0bIgJBAkECEBEgASgCBEEBRgRAIAEoAgggASgCDBAtAAsgASgCCCEDIAAgAjYCACAAIAM2AgQgAUEQaiQACxIAIwBBMGsiACQAIABBMGokAAtoAgF/AX4jAEEwayIDJAAgAyABNgIEIAMgADYCACADQQI2AgwgA0Hkl8AANgIIIANCAjcCFCADQoCAgIAwIgQgA62ENwMoIAMgBCADQQRqrYQ3AyAgAyADQSBqNgIQIANBCGogAhAmAAtHAQF/IAAoAgAgACgCCCIDayACSQRAIAAgAyACEBIgACgCCCEDCyACBEAgACgCBCADaiABIAL8CgAACyAAIAIgA2o2AghBAAtEAQJ/IAEoAgQhAiABKAIAIQNBCEEEEDUiAUUEQEEEQQgQOQALIAEgAjYCBCABIAM2AgAgAEHcksAANgIEIAAgATYCAAvGAgACQCAAIAJNBEAgACABTSABIAJLcg0BIwBBMGsiAiQAIAIgATYCBCACIAA2AgAgAkECNgIMIAJB2JbAADYCCCACQgI3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IAAAABwAEAAMAAAAEgAAABcAAAAcABAADAAAABcAAAAZAAAAHAAQAAwAAAAUAAAAGQAAABwAEAAMAAAAUQAAAB4AAAAcABAADAAAAFIAAAAeAAAATGF6eSBpbnN0YW5jZSBoYXMgcHJldmlvdXNseSBiZWVuIHBvaXNvbmVkAAA8CBAAKgAAAEgBEABaAAAACAMAABkAAAByZWVudHJhbnQgaW5pdAAAgAgQAA4AAABIARAAWgAAAHoCAAANAAAAfP2LMlfmV/kC30S/40jnr21dy9YsUOtjeEGmV3Ebi7ltZW1vcnkgYWxsb2NhdGlvbiBvZiAgYnl0ZXMgZmFpbGVkAADICBAAFQAAAN0IEAANAAAALwEQABgAAABkAQAACQAAAAQAAAAMAAAABAAAAAUAAAAGAAAABwAAAAAAAAAIAAAABAAAAAgAAAAJAAAACgAAAAsAAAAMAAAAEAAAAAQAAAANAAAADgAAAA8AAAAQAAAAAAAAAAgAAAAEAAAAEQAAAGFzc2VydGlvbiBmYWlsZWQ6IHBzaXplID49IHNpemUgKyBtaW5fb3ZlcmhlYWQAAAQBEAAqAAAAsQQAAAkAAABhc3NlcnRpb24gZmFpbGVkOiBwc2l6ZSA8PSBzaXplICsgbWF4X292ZXJoZWFkAAAEARAAKgAAALcEAAANAAAABAAAAAwAAAAEAAAAEgAAAGNhcGFjaXR5IG92ZXJmbG93AAAA/AkQABEAAADjABAAIAAAABwAAAAFAAAAMDAwMTAyMDMwNDA1MDYwNzA4MDkxMDExMTIxMzE0MTUxNjE3MTgxOTIwMjEyMjIzMjQyNTI2MjcyODI5MzAzMTMyMzMzNDM1MzYzNzM4Mzk0MDQxNDI0MzQ0NDU0NjQ3NDg0OTUwNTE1MjUzNTQ1NTU2NTc1ODU5NjA2MTYyNjM2NDY1NjY2NzY4Njk3MDcxNzI3Mzc0NzU3Njc3Nzg3OTgwODE4MjgzODQ4NTg2ODc4ODg5OTA5MTkyOTM5NDk1OTY5Nzk4OTlyYW5nZSBlbmQgaW5kZXggIG91dCBvZiByYW5nZSBmb3Igc2xpY2Ugb2YgbGVuZ3RoIAAA8AoQABAAAAAACxAAIgAAAHNsaWNlIGluZGV4IHN0YXJ0cyBhdCAgYnV0IGVuZHMgYXQgADQLEAAWAAAASgsQAA0AAAByYW5nZSBzdGFydCBpbmRleCAAAGgLEAASAAAAAAsQACIAAABhdHRlbXB0IHRvIGRpdmlkZSBieSB6ZXJvAAAAjAsQABkAAABpbmRleCBvdXQgb2YgYm91bmRzOiB0aGUgbGVuIGlzICBidXQgdGhlIGluZGV4IGlzIAAAsAsQACAAAADQCxAAEgAAAAAAAD8AAAC/AEGUmMAACwEBAHwJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVydXN0Yx0xLjkyLjAgKGRlZDVjMDZjZiAyMDI1LTEyLTA4KQZ3YWxydXMGMC4yMy4zDHdhc20tYmluZGdlbhMwLjIuMTAwICgyNDA1ZWMyYjQpAHQPdGFyZ2V0X2ZlYXR1cmVzBysPbXV0YWJsZS1nbG9iYWxzKxNub250cmFwcGluZy1mcHRvaW50KwdzaW1kMTI4KwtidWxrLW1lbW9yeSsIc2lnbi1leHQrD3JlZmVyZW5jZS10eXBlcysKbXVsdGl2YWx1ZQ==")})}function G(){return N||(N=J().catch(A=>{throw N=null,A})),N}function k(A,I,g,B=5,C=0){0===C&&(C=.3*(.5*(B-1)-1)+.8);const Q=Math.floor(B/2),t=function(A,I){const g=new Float32Array(A),B=Math.floor(A/2);let C=0;for(let Q=0;Q<A;Q++){const A=Q-B;g[Q]=Math.exp(-A*A/(2*I*I)),C+=g[Q]}for(let Q=0;Q<A;Q++)g[Q]/=C;return g}(B,C),i=new Uint8ClampedArray(I*g),E=new Uint8ClampedArray(I*g);for(let e=0;e<g;e++){const g=e*I;for(let B=0;B<I;B++){let C=0;for(let i=-Q;i<=Q;i++){C+=A[g+Math.min(I-1,Math.max(0,B+i))]*t[Q+i]}i[g+B]=C}}for(let e=0;e<I;e++)for(let A=0;A<g;A++){let B=0;for(let C=-Q;C<=Q;C++){B+=i[Math.min(g-1,Math.max(0,A+C))*I+e]*t[Q+C]}E[A*I+e]=Math.round(B)}return E}function S(A,I,g,B,C){const Q=new Uint8Array(I*g),t=[];for(let e=1;e<g-1;e++)for(let g=1;g<I-1;g++){const i=e*I+g,E=A[i];E>=C?(Q[i]=2,t.push({x:g,y:e})):Q[i]=E>=B?0:1}for(let e=0;e<I;e++)Q[e]=1,Q[(g-1)*I+e]=1;for(let e=1;e<g-1;e++)Q[e*I]=1,Q[e*I+I-1]=1;const i=[-1,0,1,-1,1,-1,0,1],E=[-1,-1,-1,0,0,1,1,1];for(;t.length>0;){const{x:A,y:g}=t.pop();for(let B=0;B<8;B++){const C=A+i[B],e=g+E[B],o=e*I+C;0===Q[o]&&(Q[o]=2,t.push({x:C,y:e}))}}return Q}async function M(I,g={}){const B=!(!g.debug||g.debug._timingsOnly),C=[],Q=performance.now();let t,i,E;if(g.skipGrayscale||!1)t=g.width,i=g.height,E=I,B&&(g.debug.grayscale=E);else{t=I.width,i=I.height;let A=performance.now();E=function(A){const{width:I,height:g,data:B}=A,C=new Uint8ClampedArray(I*g);for(let Q=0,t=0;Q<B.length;Q+=4,t++)C[t]=54*B[Q]+183*B[Q+1]+19*B[Q+2]>>8;return C}(I);let Q=performance.now();C.push({step:"Grayscale",ms:(Q-A).toFixed(2)}),B&&(g.debug.grayscale=E)}let e=void 0!==g.lowThreshold?g.lowThreshold:null,o=void 0!==g.highThreshold?g.highThreshold:null;if(null===e||null===o){const A=1024,I=new Uint32Array(A);let g=0;for(let e=1;e<i-1;e++)for(let B=1;B<t-1;B++){const C=e*t+B,Q=-E[C-t-1]+E[C-t+1]-2*E[C-1]+2*E[C+1]-E[C+t-1]+E[C+t+1],i=-E[C-t-1]-2*E[C-t]-E[C-t+1]+E[C+t-1]+2*E[C+t]+E[C+t+1];let o=Math.abs(Q)+Math.abs(i);o>=A&&(o=A-1),o>0&&(I[o]++,g++)}const B=Math.round(.7*g);let C=0,Q=75;for(let t=1;t<A;t++)if(C+=I[t],C>=B){Q=t;break}null===e&&(e=Math.max(5,Math.round(.5*Q))),null===o&&(o=Math.min(500,Math.round(1.5*Q)))}const n=g.kernelSize||5,a=g.sigma||0,s=void 0!==g.L2gradient&&g.L2gradient,c=void 0===g.applyDilation||g.applyDilation,y=g.dilationKernelSize||5,D=void 0!==g.useWasmHysteresis&&g.useWasmHysteresis,d=void 0!==g.useWasmFullCanny&&g.useWasmFullCanny;if(e>=o&&([e,o]=[o,e]),d)try{await G();const I=performance.now(),D=new Uint8ClampedArray(function(I,g,B,C,Q,t,i,E,e,o){const n=h(I,A.__wbindgen_malloc),a=r,s=A.canny_edge_detector_full(n,a,g,B,C,Q,t,i,E,e,o);var c=l(s[0],s[1]).slice();return A.__wbindgen_free(s[0],1*s[1],1),c}(E,t,i,e,o,n,a,s,c,y)),w=(performance.now()-I).toFixed(2);C.push({step:"Edge Processing (WASM)",ms:w}),B&&(g.debug.finalEdges=D),g.debug?g.debug.timings=C:g.debug={timings:C};const d=performance.now();return C.unshift({step:"Edge Detection Total",ms:(d-Q).toFixed(2)}),D}catch(Y){if(!R){R=!0;Y instanceof Error?Y.message:String(Y)}}let f,m,N,F,p,J;f=performance.now();try{await G(),N=function(I,g,B,C,Q){const t=h(I,A.__wbindgen_malloc),i=r,E=A.blur(t,i,g,B,C,Q);var e=l(E[0],E[1]).slice();return A.__wbindgen_free(E[0],1*E[1],1),e}(E,t,i,n,a)}catch(q){N=k(E,t,i,n,a)}m=performance.now(),C.push({step:"Gaussian Blur",ms:(m-f).toFixed(2)}),B&&(g.debug.blurred=N),f=performance.now();{const A=function(A,I,g){const B=new Int16Array(I*g),C=new Int16Array(I*g);for(let Q=1;Q<g-1;Q++){const g=Q*I,t=(Q-1)*I,i=(Q+1)*I;for(let Q=1;Q<I-1;Q++){const I=g+Q,E=A[t+Q-1],e=A[t+Q],o=A[t+Q+1],n=A[g+Q-1],a=A[g+Q+1],s=A[i+Q-1],c=A[i+Q],r=A[i+Q+1],h=o-E+2*(a-n)+(r-s),l=s+2*c+r-(E+2*e+o);B[I]=h,C[I]=l}}return{dx:B,dy:C}}(N,t,i);F=A.dx,p=A.dy}m=performance.now(),C.push({step:"Gradients",ms:(m-f).toFixed(2)}),f=performance.now();try{await G(),J=await u(F,p,t,i,s)}catch(q){J=function(A,I,g,B,C){const Q=new Float32Array(g*B),t=new Float32Array(g*B);for(let i=0;i<A.length;i++){const g=A[i],B=I[i];Q[i]=C?Math.sqrt(g*g+B*B):Math.abs(g)+Math.abs(B)}for(let i=1;i<B-1;i++)for(let B=1;B<g-1;B++){const C=i*g+B,E=Q[C];if(0===E){t[C]=0;continue}const e=A[C],o=I[C];let n=0,a=0;const s=Math.abs(e),c=Math.abs(o);if(c>2.4142*s)n=Q[C-g],a=Q[C+g];else if(s>2.4142*c)n=Q[C-1],a=Q[C+1];else{const A=(e^o)<0?-1:1;o>0?(n=Q[(i-1)*g+(B-A)],a=Q[(i+1)*g+(B+A)]):(n=Q[(i+1)*g+(B-A)],a=Q[(i-1)*g+(B+A)]),e>0&&o>0||e<0&&o<0?(n=Q[(i-1)*g+(B+1)],a=Q[(i+1)*g+(B-1)]):(n=Q[(i-1)*g+(B-1)],a=Q[(i+1)*g+(B+1)])}t[C]=E>=n&&E>=a?E:0}return t}(F,p,t,i,s)}m=performance.now(),C.push({step:"Non-Max Suppression",ms:(m-f).toFixed(2)}),f=performance.now();const M=s?e*e:e,b=s?o*o:o;let L;if(D)try{await G(),L=w(J,t,i,M,b)}catch(q){L=S(J,t,i,M,b)}else L=S(J,t,i,M,b);m=performance.now(),C.push({step:"Hysteresis",ms:(m-f).toFixed(2)}),f=performance.now();const x=new Uint8ClampedArray(t*i);for(let A=0;A<L.length;A++)x[A]=2===L[A]?255:0;m=performance.now(),C.push({step:"Binary Image",ms:(m-f).toFixed(2)}),f=performance.now();let U=x;if(c)try{await G(),U=function(I,g,B,C){const Q=h(I,A.__wbindgen_malloc),t=r,i=A.dilate(Q,t,g,B,C);var E=l(i[0],i[1]).slice();return A.__wbindgen_free(i[0],1*i[1],1),E}(x,t,i,y)}catch(q){U=function(A,I,g,B=5){const C=Math.floor(B/2),Q=new Uint8ClampedArray(I*g),t=new Uint8ClampedArray(I*g);for(let i=0;i<g;i++){const g=i*I;for(let B=0;B<I;B++){let t=0;for(let Q=-C;Q<=C;Q++){const C=B+Q;if(C>=0&&C<I){const I=A[g+C];I>t&&(t=I)}}Q[g+B]=t}}for(let i=0;i<I;i++)for(let A=0;A<g;A++){let B=0;for(let t=-C;t<=C;t++){const C=A+t;if(C>=0&&C<g){const A=Q[C*I+i];A>B&&(B=A)}}t[A*I+i]=B}return t}(x,t,i,y)}if(m=performance.now(),C.push({step:"Dilation",ms:(m-f).toFixed(2)}),B){g.debug.dx=F,g.debug.dy=p;const A=new Float32Array(t*i);for(let I=0;I<F.length;I++){const g=F[I],B=p[I];A[I]=s?Math.sqrt(g*g+B*B):Math.abs(g)+Math.abs(B)}g.debug.magnitude=A,g.debug.suppressed=J,g.debug.edgeMap=L,g.debug.cannyEdges=x,g.debug.finalEdges=U}g.debug?g.debug.timings=C:g.debug||(g.debug={timings:C});const H=performance.now();return C.unshift({step:"Edge Detection Total",ms:(H-Q).toFixed(2)}),U}function b(A){return{topLeft:{x:A.topLeft.x,y:A.topLeft.y},topRight:{x:A.topRight.x,y:A.topRight.y},bottomRight:{x:A.bottomRight.x,y:A.bottomRight.y},bottomLeft:{x:A.bottomLeft.x,y:A.bottomLeft.y}}}function L(A,I){return Math.hypot(A.x-I.x,A.y-I.y)}function x(A,I=4){const g=[A?.topLeft,A?.topRight,A?.bottomRight,A?.bottomLeft];if(g.some(A=>!A||!Number.isFinite(A.x)||!Number.isFinite(A.y)))return!1;for(let B=0;B<g.length;B++)for(let A=B+1;A<g.length;A++)if(L(g[B],g[A])<I)return!1;return!0}function U(A){const I=[A.topLeft,A.topRight,A.bottomRight,A.bottomLeft],g=[];for(let C=0;C<I.length;C++){const A=I[C],B=I[(C+1)%I.length],Q=I[(C+2)%I.length],t=(B.x-A.x)*(Q.y-B.y)-(B.y-A.y)*(Q.x-B.x);Math.abs(t)<1e-6||g.push(Math.sign(t))}if(g.length<3)return!1;const B=g[0];return g.every(A=>A===B)}var H="scanic-corner-editor-styles",Y='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 4 3 10 9 10"/><path d="M3.5 14a8.5 8.5 0 1 0 2-7.4L3 10"/></svg>',q='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',v='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',K='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>';function T(A={}){const I=A.container;if(!I||"function"!=typeof I.appendChild)throw new Error("createCornerEditor requires a valid container element");const g=I.ownerDocument||("undefined"!=typeof document?document:null),B="undefined"!=typeof window?window:globalThis;!1!==A.injectStyles&&function(A){if(!A||"function"!=typeof A.getElementById)return;if(A.getElementById(H))return;const I=A.createElement("style");I.id=H,I.textContent="\n.scanic-corner-editor {\n --scanic-accent: #6366f1;\n --scanic-mask: rgba(15, 23, 42, 0.45);\n --scanic-edge-color: var(--scanic-accent);\n --scanic-edge-width: 2.5;\n --scanic-handle-size: 20px;\n --scanic-handle-hit: 44px;\n --scanic-handle-color: #ffffff;\n --scanic-handle-ring: 2px;\n --scanic-handle-ring-color: var(--scanic-accent);\n --scanic-handle-shadow: 0 1px 4px rgba(15, 23, 42, 0.45);\n --scanic-handle-active-shadow: 0 8px 22px rgba(15, 23, 42, 0.5);\n --scanic-handle-active-scale: 1.28;\n --scanic-surface: rgba(15, 23, 42, 0.92);\n --scanic-surface-fg: #e2e8f0;\n --scanic-surface-radius: 12px;\n}\n.scanic-handle {\n position: absolute;\n box-sizing: border-box;\n width: var(--scanic-handle-size);\n height: var(--scanic-handle-size);\n margin: 0;\n padding: 0;\n border: var(--scanic-handle-ring) solid var(--scanic-handle-ring-color);\n border-radius: 50%;\n background: var(--scanic-handle-color);\n box-shadow: var(--scanic-handle-shadow);\n transform: translate(-50%, -50%);\n cursor: grab;\n touch-action: none;\n z-index: 2;\n transition: transform 0.12s ease, box-shadow 0.12s ease,\n background 0.12s ease, border-color 0.12s ease;\n}\n/* Enlarges the pointer/touch target without changing the visual size. */\n.scanic-handle::after {\n content: '';\n position: absolute;\n left: 50%;\n top: 50%;\n width: var(--scanic-handle-hit);\n height: var(--scanic-handle-hit);\n transform: translate(-50%, -50%);\n border-radius: 50%;\n}\n/* Soft accent halo that blooms when a handle is grabbed — the \"depth\" layer. */\n.scanic-handle::before {\n content: '';\n position: absolute;\n left: 50%;\n top: 50%;\n width: 0;\n height: 0;\n border-radius: 50%;\n background: var(--scanic-accent);\n opacity: 0;\n transform: translate(-50%, -50%);\n transition: opacity 0.15s ease, width 0.15s ease, height 0.15s ease;\n pointer-events: none;\n}\n.scanic-handle:hover {\n transform: translate(-50%, -50%) scale(1.12);\n}\n.scanic-handle:focus-visible {\n outline: none;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--scanic-accent) 45%, transparent),\n var(--scanic-handle-shadow);\n}\n/* The currently selected corner — the target of nudges / keyboard. */\n.scanic-handle.is-selected {\n border-color: var(--scanic-accent);\n transform: translate(-50%, -50%) scale(1.12);\n}\n.scanic-handle.is-selected::before {\n opacity: 0.16;\n width: calc(var(--scanic-handle-size) * 2);\n height: calc(var(--scanic-handle-size) * 2);\n}\n.scanic-handle.is-active {\n cursor: grabbing;\n background: var(--scanic-accent);\n border-color: #ffffff;\n transform: translate(-50%, -50%) scale(var(--scanic-handle-active-scale));\n box-shadow: var(--scanic-handle-active-shadow);\n}\n.scanic-handle.is-active::before {\n opacity: 0.22;\n width: calc(var(--scanic-handle-size) * 2.8);\n height: calc(var(--scanic-handle-size) * 2.8);\n}\n.scanic-toolbar,\n.scanic-nudges {\n position: absolute;\n z-index: 3;\n display: flex;\n gap: 4px;\n padding: 5px;\n background: var(--scanic-surface);\n border-radius: var(--scanic-surface-radius);\n -webkit-backdrop-filter: blur(8px);\n backdrop-filter: blur(8px);\n box-shadow: 0 6px 20px rgba(15, 23, 42, 0.35);\n}\n.scanic-toolbar {\n left: 50%;\n bottom: 12px;\n transform: translateX(-50%);\n}\n.scanic-nudges {\n top: 10px;\n right: 10px;\n display: grid;\n grid-template-columns: repeat(4, auto);\n gap: 3px;\n}\n.scanic-toolbar button,\n.scanic-nudges button {\n display: grid;\n place-items: center;\n margin: 0;\n padding: 0;\n font-family: inherit;\n font-size: 15px;\n font-weight: 600;\n line-height: 1;\n color: var(--scanic-surface-fg);\n background: transparent;\n border: 0;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.12s ease, color 0.12s ease, filter 0.12s ease;\n}\n.scanic-toolbar button {\n width: 34px;\n height: 34px;\n}\n.scanic-nudges button {\n width: 30px;\n height: 30px;\n}\n.scanic-toolbar button svg {\n width: 18px;\n height: 18px;\n display: block;\n}\n.scanic-toolbar button:hover,\n.scanic-nudges button:hover {\n background: rgba(255, 255, 255, 0.14);\n}\n.scanic-toolbar button:focus-visible,\n.scanic-nudges button:focus-visible {\n outline: 2px solid var(--scanic-accent);\n outline-offset: 1px;\n}\n.scanic-toolbar .scanic-btn-apply {\n background: var(--scanic-accent);\n color: #ffffff;\n}\n.scanic-toolbar .scanic-btn-apply:hover {\n background: var(--scanic-accent);\n filter: brightness(1.1);\n}\n.scanic-toolbar .scanic-btn-expert.is-on {\n background: color-mix(in srgb, var(--scanic-accent) 32%, transparent);\n color: #ffffff;\n}\n",(A.head||A.documentElement).appendChild(I)}(g);const{canvas:C,width:Q,height:t}=function(A){if(!A)throw new Error("No image provided");const I=A&&"number"==typeof A.width&&"number"==typeof A.height&&A.data;let g=0,B=0;if(I?(g=A.width,B=A.height):(g=A.width||A.naturalWidth,B=A.height||A.naturalHeight),!g||!B)throw new Error("Image must be loaded before creating the corner editor");const C=document.createElement("canvas");C.width=g,C.height=B;const Q=C.getContext("2d",{willReadFrequently:!0});if(!Q)throw new Error("Failed to create 2D canvas context for source image");return I?Q.putImageData(A,0,0):Q.drawImage(A,0,0,g,B),{canvas:C,width:g,height:B}}(A.image),i=!I.classList.contains("scanic-corner-editor");I.classList.add("scanic-corner-editor"),A.classNames?.root&&I.classList.add(A.classNames.root);const E={position:I.style.position,minHeight:I.style.minHeight};let e=!1,o=!1;"static"===getComputedStyle(I).position&&(I.style.position="relative",e=!0),L(A.theme);const n=g.createElement("canvas");n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.display="block",n.style.touchAction="none",n.style.userSelect="none",n.style.webkitUserSelect="none",n.style.cursor="default",I.appendChild(n);const a=n.getContext("2d");if(!a)throw new Error("Failed to create 2D canvas context for corner editor");const s={enabled:!1!==A.magnifier?.enabled,size:A.magnifier?.size||120,zoom:A.magnifier?.zoom||2,margin:A.magnifier?.margin||8,borderColor:A.magnifier?.borderColor||"#ffffff",borderWidth:A.magnifier?.borderWidth||2,crosshairColor:A.magnifier?.crosshairColor||"#ffffff",crosshairSize:A.magnifier?.crosshairSize||18},c={enabled:!!A.nudges?.enabled,steps:(A.nudges?.steps&&A.nudges.steps.length?A.nudges.steps:[1,10]).map(A=>Math.max(1,Math.round(A)))},r={enabled:!1!==A.toolbar?.enabled,reset:!1!==A.toolbar?.reset,cancel:!1!==A.toolbar?.cancel,apply:!1!==A.toolbar?.apply,labels:{reset:A.toolbar?.labels?.reset||"Reset",cancel:A.toolbar?.labels?.cancel||"Cancel",apply:A.toolbar?.labels?.apply||"Apply"}},h=!1!==A.keyboard,l=function(A,I,g=.08){const B=Math.max(8,Math.min(A,I)*g);return{topLeft:{x:B,y:B},topRight:{x:A-B,y:B},bottomRight:{x:A-B,y:I-B},bottomLeft:{x:B,y:I-B}}}(Q,t),y=A.corners?b(A.corners):l;let D=x(y)&&U(y)?y:l;const w=b(D);let d=!1,f=null,u="topLeft",m=null,N=null;const R=Math.max(24,A.handleHitArea||44);A.handleHitArea&&I.style.setProperty("--scanic-handle-hit",R+"px");const F=["topLeft","topRight","bottomRight","bottomLeft"],p={topLeft:"Top-left corner",topRight:"Top-right corner",bottomRight:"Bottom-right corner",bottomLeft:"Bottom-left corner"};let J={scale:1,offsetX:0,offsetY:0,width:1,height:1};const G={mask:"rgba(15,23,42,0.45)",edgeColor:"#6366f1",edgeWidth:2.5},k={};for(const b of F){const B=g.createElement("button");B.type="button",B.className="scanic-handle"+(A.classNames?.handle?" "+A.classNames.handle:""),B.dataset.corner=b,B.setAttribute("aria-label",p[b]),h||(B.tabIndex=-1),B.addEventListener("pointerdown",A=>CA(b,A)),B.addEventListener("pointermove",A=>QA(b,A)),B.addEventListener("pointerup",A=>tA(b,A)),B.addEventListener("pointercancel",A=>tA(b,A)),B.addEventListener("focus",()=>{u=b,z()}),h&&B.addEventListener("keydown",A=>iA(b,A)),I.appendChild(B),k[b]=B}function S(A,I){const g=n.style.color;n.style.color=`var(${A}, ${I})`;const B=getComputedStyle(n).color||I;return n.style.color=g,B}function M(){G.mask=S("--scanic-mask","rgba(15, 23, 42, 0.45)"),G.edgeColor=S("--scanic-edge-color","#6366f1");const A=getComputedStyle(I).getPropertyValue("--scanic-edge-width"),g=parseFloat(A);G.edgeWidth=Number.isFinite(g)&&g>0?g:2.5}function L(A){if(!A||"object"!=typeof A)return;const g=new Set(["handleSize","handleHit"]);for(const[B,C]of Object.entries({accent:"--scanic-accent",mask:"--scanic-mask",edgeColor:"--scanic-edge-color",edgeWidth:"--scanic-edge-width",handleSize:"--scanic-handle-size",handleHit:"--scanic-handle-hit",handleColor:"--scanic-handle-color",handleRingColor:"--scanic-handle-ring-color",surface:"--scanic-surface",surfaceColor:"--scanic-surface-fg",radius:"--scanic-surface-radius"})){const Q=A[B];if(null==Q)continue;const t="number"==typeof Q&&g.has(B)?`${Q}px`:String(Q);I.style.setProperty(C,t)}}function T(){"function"==typeof A.onChange&&A.onChange(b(D))}let W=0,P=0;function Z(){const A=Math.max(1,Math.round(I.clientWidth));let g=Math.round(I.clientHeight);if(g<80){const C=t/Math.max(1,Q),i=.7*(B.innerHeight||800);g=Math.max(240,Math.round(Math.min(A*C,i))),I.style.minHeight=g+"px",o=!0}return{width:A,height:Math.max(1,g)}}function X(){const{width:A,height:I}=Z(),g=B.devicePixelRatio||1;W=A,P=I,n.style.width=A+"px",n.style.height=I+"px";const C=Math.round(A*g),i=Math.round(I*g);n.width!==C&&(n.width=C),n.height!==i&&(n.height=i),a.setTransform(g,0,0,g,0,0);const E=Math.max(1,A-24),e=Math.max(1,I-24),o=Math.min(E/Q,e/t),s=Q*o,c=t*o;J={scale:o,offsetX:Math.round((A-s)/2),offsetY:Math.round((I-c)/2),width:A,height:I},M()}function V(A){return{x:J.offsetX+A.x*J.scale,y:J.offsetY+A.y*J.scale}}function j(A){return x(A)&&U(A)}function O(A){const I=n.getBoundingClientRect();return{x:A.clientX-I.left,y:A.clientY-I.top}}function z(){for(const A of F)k[A].classList.toggle("is-selected",A===u)}function _(){d||(a.clearRect(0,0,J.width,J.height),a.drawImage(C,0,0,Q,t,J.offsetX,J.offsetY,Q*J.scale,t*J.scale),function(){const A=F.map(A=>V(D[A]));a.save(),a.fillStyle=G.mask,a.beginPath(),a.rect(0,0,J.width,J.height),a.moveTo(A[0].x,A[0].y);for(let I=1;I<A.length;I++)a.lineTo(A[I].x,A[I].y);a.closePath(),a.fill("evenodd"),a.restore(),a.save(),a.strokeStyle=G.edgeColor,a.lineWidth=G.edgeWidth,a.lineJoin="round",a.beginPath(),a.moveTo(A[0].x,A[0].y);for(let I=1;I<A.length;I++)a.lineTo(A[I].x,A[I].y);a.closePath(),a.stroke(),a.restore()}(),function(){if(!s.enabled||!f||!N)return;const A=D[f],I=s.size,g=I/2,B=Math.max(1.1,s.zoom);let i=N.x+g+s.margin,E=N.y-g-s.margin;i+g>J.width&&(i=N.x-g-s.margin),E-g<0&&(E=N.y+g+s.margin),i=Math.max(g+2,Math.min(J.width-g-2,i)),E=Math.max(g+2,Math.min(J.height-g-2,E));const e=I/B,o=Math.max(0,Math.min(Q-e,A.x-e/2)),n=Math.max(0,Math.min(t-e,A.y-e/2));a.save(),a.beginPath(),a.arc(i,E,g,0,2*Math.PI),a.clip(),a.drawImage(C,o,n,e,e,i-g,E-g,I,I),a.restore(),a.save(),a.beginPath(),a.arc(i,E,g,0,2*Math.PI),a.lineWidth=s.borderWidth,a.strokeStyle=s.borderColor,a.stroke();const c=s.crosshairSize/2;a.strokeStyle=s.crosshairColor,a.lineWidth=1.5,a.beginPath(),a.moveTo(i-c,E),a.lineTo(i+c,E),a.moveTo(i,E-c),a.lineTo(i,E+c),a.stroke(),a.restore()}(),function(){for(const A of F){const I=V(D[A]),g=k[A];g.style.left=I.x+"px",g.style.top=I.y+"px"}}())}const $="function"==typeof B.requestAnimationFrame?B.requestAnimationFrame.bind(B):null,AA="function"==typeof B.cancelAnimationFrame?B.cancelAnimationFrame.bind(B):null;let IA=null;function gA(){d||($?null===IA&&(IA=$(()=>{IA=null,_()})):_())}function BA(A,I){const g=b(D);return g[A]={x:Math.max(0,Math.min(Q,I.x)),y:Math.max(0,Math.min(t,I.y))},!!j(g)&&(D=g,u=A,z(),T(),gA(),!0)}function CA(A,I){if(!d){if("function"==typeof I.preventDefault&&I.preventDefault(),f=A,u=A,z(),m=I.pointerId,N=O(I),k[A].classList.add("is-active"),h&&"function"==typeof k[A].focus)try{k[A].focus({preventScroll:!0})}catch(g){k[A].focus()}if(k[A].setPointerCapture&&null!=I.pointerId)try{k[A].setPointerCapture(I.pointerId)}catch(g){}gA()}}function QA(A,I){d||f!==A||null!==m&&I.pointerId!==m||(N=O(I),BA(A,function(A,I){const g=(A-J.offsetX)/J.scale,B=(I-J.offsetY)/J.scale;return{x:Math.max(0,Math.min(Q,g)),y:Math.max(0,Math.min(t,B))}}(N.x,N.y)))}function tA(A,I){if(f===A&&(null===m||I.pointerId===m)){if(k[A].releasePointerCapture&&null!=m)try{k[A].releasePointerCapture(m)}catch(g){}k[A].classList.remove("is-active"),f=null,m=null,N=null,gA()}}function iA(A,I){if(d||!h)return;if("Enter"===I.key)return I.preventDefault(),void rA();if("Escape"===I.key)return I.preventDefault(),void hA();let g=0,B=0;if("ArrowLeft"===I.key)g=-1;else if("ArrowRight"===I.key)g=1;else if("ArrowUp"===I.key)B=-1;else{if("ArrowDown"!==I.key)return;B=1}I.preventDefault();const C=I.shiftKey?c.steps[c.steps.length-1]||10:1;BA(A,{x:D[A].x+g*C,y:D[A].y+B*C})}let EA=null,eA=null,oA=null,nA=!1;const aA=r.enabled&&c.enabled;function sA({html:A,text:I,title:B,className:C,onClick:Q}){const t=g.createElement("button");return t.type="button",null!=A?t.innerHTML=A:t.textContent=I,B&&(t.title=B,t.setAttribute("aria-label",B)),C&&(t.className=C),t.addEventListener("click",Q),t}let cA=null;function rA(){const I=b(D);return"function"==typeof A.onConfirm&&A.onConfirm(I),I}function hA(){"function"==typeof A.onCancel&&A.onCancel()}function lA(){D=b(w),T(),gA()}const yA="undefined"!=typeof ResizeObserver?new ResizeObserver(()=>{if(d)return;const A=Z();A.width===W&&A.height===P||(X(),gA())}):null;return X(),r.enabled&&(r.reset||r.cancel||r.apply||aA)&&(EA=g.createElement("div"),EA.className="scanic-toolbar"+(A.classNames?.toolbar?" "+A.classNames.toolbar:""),r.reset&&EA.appendChild(sA({html:Y,title:r.labels.reset,className:"scanic-btn-reset",onClick:()=>lA()})),aA&&(oA=sA({html:K,title:"Precision nudge (expert)",className:"scanic-btn-expert",onClick:()=>{return nA=A=!nA,eA&&(eA.style.display=A?"":"none"),void(oA&&(oA.classList.toggle("is-on",A),oA.setAttribute("aria-pressed",String(A))));var A}}),oA.setAttribute("aria-pressed","false"),EA.appendChild(oA)),r.cancel&&EA.appendChild(sA({html:q,title:r.labels.cancel,className:"scanic-btn-cancel",onClick:()=>hA()})),r.apply&&EA.appendChild(sA({html:v,title:r.labels.apply,className:"scanic-btn-apply",onClick:()=>rA()})),I.appendChild(EA)),function(){if(!c.enabled)return;eA=g.createElement("div"),eA.className="scanic-nudges"+(A.classNames?.nudges?" "+A.classNames.nudges:"");const B=(A,I,g,B,C)=>sA({text:A,title:I,className:"scanic-btn-nudge",onClick:()=>{const A=D[u];BA(u,{x:A.x+g*C,y:A.y+B*C})}});for(const A of c.steps)eA.appendChild(B("←",`Move left ${A}px`,-1,0,A)),eA.appendChild(B("↑",`Move up ${A}px`,0,-1,A)),eA.appendChild(B("↓",`Move down ${A}px`,0,1,A)),eA.appendChild(B("→",`Move right ${A}px`,1,0,A));I.appendChild(eA),aA&&(eA.style.display="none")}(),function A(){if("function"!=typeof B.matchMedia)return;cA&&cA();const I=B.devicePixelRatio||1,g=B.matchMedia(`(resolution: ${I}dppx)`),C=()=>{d||(X(),gA(),A())};"function"==typeof g.addEventListener?(g.addEventListener("change",C),cA=()=>g.removeEventListener("change",C)):"function"==typeof g.addListener?(g.addListener(C),cA=()=>g.removeListener(C)):cA=null}(),yA&&yA.observe(I),z(),_(),{getCorners:()=>b(D),setCorners:A=>!(!A||!j(A))&&(D=b(A),T(),gA(),!0),reset(){lA()},nudge:(A,I,g,B=1)=>!!F.includes(A)&&BA(A,{x:D[A].x+I*B,y:D[A].y+g*B}),refreshTheme(A){L(A),M(),gA()},confirm:()=>rA(),cancel(){hA()},destroy(){if(!d){d=!0,null!==IA&&AA&&(AA(IA),IA=null),cA&&(cA(),cA=null),yA&&yA.disconnect();for(const A of F){const I=k[A];I&&I.parentNode&&I.parentNode.removeChild(I)}EA&&EA.parentNode&&EA.parentNode.removeChild(EA),eA&&eA.parentNode&&eA.parentNode.removeChild(eA),n.parentNode&&n.parentNode.removeChild(n),i&&I.classList.remove("scanic-corner-editor"),A.classNames?.root&&I.classList.remove(A.classNames.root),e&&(I.style.position=E.position),o&&(I.style.minHeight=E.minHeight)}}}}async function W(){try{return await G()}catch{return null}}var P=class{constructor(A={}){this.defaultOptions={maxProcessingDimension:800,mode:"detect",output:"canvas",...A},this.initialized=!1}async initialize(){if(!this.initialized){if("ml"===this.defaultOptions.detector)try{const{initializeMl:A}=await import("./mlDetector-DV0KqH2X.js");await A(this.defaultOptions.ml||{})}catch{}else try{await G()}catch{}this.initialized=!0}}async scan(A,I={}){return this.initialized||await this.initialize(),await IA(A,{...this.defaultOptions,...I})}async extract(A,I,g={}){return this.initialized||await this.initialize(),await AA(A,I,{...this.defaultOptions,...g})}};function Z(A){return A<=0?0:A>=1?1:A}function X(A,I){return Math.hypot(A.x-I.x,A.y-I.y)}function V(A){return[X(A.topLeft,A.topRight),X(A.topRight,A.bottomRight),X(A.bottomRight,A.bottomLeft),X(A.bottomLeft,A.topLeft)]}function j(A){const I=function(A){const I=[A.topLeft,A.topRight,A.bottomRight,A.bottomLeft],g=[];for(let B=0;B<I.length;B++){const A=I[(B+I.length-1)%I.length],C=I[B],Q=I[(B+1)%I.length],t=A.x-C.x,i=A.y-C.y,E=Q.x-C.x,e=Q.y-C.y,o=t*E+i*e,n=Math.max(1e-6,Math.hypot(t,i)*Math.hypot(E,e)),a=Math.max(-1,Math.min(1,o/n));g.push(180*Math.acos(a)/Math.PI)}return g}(A);let g=0;for(const B of I){g+=Z(1-Math.abs(B-90)/55)}return g/Math.max(1,I.length)}function O(A,I){const g=Number(I.isValid)-Number(A.isValid);if(0!==g)return g;const B=I.confidence-A.confidence;if(!(Math.abs(B)<.015)&&0!==B)return B;const C=Math.abs((A.approxCount??99)-4)-Math.abs((I.approxCount??99)-4);if(0!==C)return C;const Q=(I.rightAngleScore??0)-(A.rightAngleScore??0);if(Math.abs(Q)>1e-6)return Q;const t=Math.abs(1-(A.contourFitRatio??1))-Math.abs(1-(I.contourFitRatio??1));return Math.abs(t)>1e-6?t:B||I.score-A.score||I.coverageRatio-A.coverageRatio||I.area-A.area}function z(A,I,g,B,C={}){const Q=C.epsilon||.02,t=E(A.points,Q).length,i=n(A,{epsilon:Q});if(!i)return{contour:A,corners:null,score:0,confidence:0,isValid:!1,area:A.area||0,fillRatio:0,coverageRatio:0,cornersArea:0,approxCount:t,convex:!1,edgeSupport:0,aspectRatio:1/0,minSide:0,rightAngleScore:0,oppositeSideConsistency:0,contourFitRatio:0,contourFitScore:0};const e=g*B,o=A.area||0,a=A.boundingBox||{minX:0,minY:0,maxX:0,maxY:0},s=o/Math.max(1,(a.maxX-a.minX+1)*(a.maxY-a.minY+1)),c=function(A){if(!A)return 0;const I=[A.topLeft,A.topRight,A.bottomRight,A.bottomLeft];let g=0;for(let B=0;B<I.length;B++){const A=(B+1)%I.length;g+=I[B].x*I[A].y-I[A].x*I[B].y}return Math.abs(g)/2}(i),r=c/Math.max(1,e),h=V(i),l=Math.min(...h),y=(h[0]+h[2])/2,D=(h[1]+h[3])/2,w=y>D?y/Math.max(1e-6,D):D/Math.max(1e-6,y),d=function(A){const I=[A.topLeft,A.topRight,A.bottomRight,A.bottomLeft],g=[];for(let C=0;C<I.length;C++){const A=I[C],B=I[(C+1)%I.length],Q=I[(C+2)%I.length],t=(B.x-A.x)*(Q.y-B.y)-(B.y-A.y)*(Q.x-B.x);Math.abs(t)<1e-6||g.push(Math.sign(t))}if(g.length<3)return!1;const B=g[0];return g.every(A=>A===B)}(i),f=function(A,I,g,B){if(!A?.points||0===A.points.length)return 0;const C=Math.max(1,Math.floor(A.points.length/240));let Q=0,t=0;for(let i=0;i<A.points.length;i+=C){const C=A.points[i],E=Math.max(0,Math.min(g-1,0|C.x)),e=Math.max(0,Math.min(B-1,0|C.y));Q++;let o=!1;for(let A=-1;A<=1&&!o;A++){const C=e+A;if(!(C<0||C>=B))for(let A=-1;A<=1;A++){const B=E+A;if(!(B<0||B>=g)&&I[C*g+B]>0){o=!0;break}}}o&&t++}return 0===Q?0:t/Q}(A,I,g,B),u=j(i),m=function(A){const I=V(A);return(Math.min(I[0],I[2])/Math.max(1e-6,Math.max(I[0],I[2]))+Math.min(I[1],I[3])/Math.max(1e-6,Math.max(I[1],I[3])))/2}(i),N=o/Math.max(1,c),R=Z(1-Math.abs(N-1)/.55),F=4===t?1:5===t?.9:6===t?.7:t<=8?.5:.28,p=Z(o/Math.max(1,.4*e)),J=Z((s-.08)/.72),G=Z((r-.03)/.82),k=void 0!==C.minDocumentSideRatio?C.minDocumentSideRatio:.06,S=void 0!==C.minDocumentCoverageRatio?C.minDocumentCoverageRatio:.04,M=void 0!==C.maxDocumentAspectRatio?C.maxDocumentAspectRatio:8,b=void 0!==C.minDocumentFillRatio?C.minDocumentFillRatio:.07,L=void 0!==C.minContourFitRatio?C.minContourFitRatio:.11,x=void 0!==C.maxContourFitRatio?C.maxContourFitRatio:1.2,U=void 0!==C.minRightAngleScore?C.minRightAngleScore:.42,H=void 0!==C.minOppositeSideConsistency?C.minOppositeSideConsistency:.3,Y=Math.min(g,B)*k,q=function(A,I=6){const g=[A?.topLeft,A?.topRight,A?.bottomRight,A?.bottomLeft];if(g.some(A=>!A||!Number.isFinite(A.x)||!Number.isFinite(A.y)))return!1;for(let B=0;B<g.length;B++)for(let A=B+1;A<g.length;A++)if(X(g[B],g[A])<I)return!1;return!0}(i)&&d&&l>=Y&&r>=S&&w<=M&&s>=b&&N>=L&&N<=x&&u>=U&&m>=H,v=.22*p+.14*J+.15*F+.08*(d?1:0)+.08*f+.13*G+.1*u+.05*m+.05*R;return{contour:A,corners:i,score:v,confidence:q?v:.33*v,isValid:q,area:o,fillRatio:s,coverageRatio:r,cornersArea:c,approxCount:t,convex:d,edgeSupport:f,aspectRatio:w,minSide:l,rightAngleScore:u,oppositeSideConsistency:m,contourFitRatio:N,contourFitScore:R}}async function _(A,I,g,C,Q={}){const t=Q.debug?{}:{_timingsOnly:!0},i=[];t&&!t._timingsOnly&&(t.preprocessing={scaledDimensions:{width:I,height:g},scaleFactor:C,maxProcessingDimension:Q.maxProcessingDimension||800});const E=function(A={}){const I=A.dilationKernelSize||3,g=A.dilationIterations||1,B=void 0===A.applyDilation||A.applyDilation,C=[{name:"default",lowThreshold:A.lowThreshold,highThreshold:A.highThreshold,dilationKernelSize:I,dilationIterations:g,applyDilation:B}];if(!1===A.enableDetectionCascade)return C;C.push({name:"connect-edges",lowThreshold:A.lowThreshold,highThreshold:A.highThreshold,dilationKernelSize:Math.max(I,5),dilationIterations:Math.max(g,2),applyDilation:!0}),C.push({name:"no-dilation",lowThreshold:A.lowThreshold,highThreshold:A.highThreshold,dilationKernelSize:I,dilationIterations:g,applyDilation:!1}),void 0===A.lowThreshold&&void 0===A.highThreshold&&C.push({name:"fixed-mid-thresholds",lowThreshold:60,highThreshold:180,dilationKernelSize:I,dilationIterations:g,applyDilation:B});const Q=[],t=/* @__PURE__ */new Set;for(const i of C){const A=[i.lowThreshold,i.highThreshold,i.dilationKernelSize,i.dilationIterations,i.applyDilation].join(":");t.has(A)||(t.add(A),Q.push(i))}return Q}(Q),e=[],o=async(t,E)=>{const e=t.name||`pass-${E+1}`,o=0===E?"":` (${e})`,n=Q.debug?{}:{_timingsOnly:!0},a=await M(A,{width:I,height:g,lowThreshold:t.lowThreshold,highThreshold:t.highThreshold,dilationKernelSize:t.dilationKernelSize,dilationIterations:t.dilationIterations,applyDilation:t.applyDilation,debug:n,skipGrayscale:!0,useWasmBlur:!0,useWasmHysteresis:Q.useWasmHysteresis,useWasmFullCanny:Q.useWasmFullCanny});n.timings&&n.timings.forEach(A=>{"Edge Detection Total"!==A.step&&i.push({step:`${A.step}${o}`,ms:A.ms})});let s=performance.now();const c=B(a,{minArea:(Q.minArea||1e3)/(C*C),width:I,height:g});i.push({step:`Find Contours${o}`,ms:(performance.now()-s).toFixed(2)}),s=performance.now();const{best:r,candidates:h}=function(A,I,g,B,C={}){const Q=C.maxCandidateContours||12,t=A.slice(0,Q).map((A,Q)=>({rankByArea:Q,...z(A,I,g,B,C)})).sort(O);return{best:t[0]||null,candidates:t}}(c,a,I,g,Q);return i.push({step:`Corner Detection${o}`,ms:(performance.now()-s).toFixed(2)}),{name:e,params:{lowThreshold:t.lowThreshold,highThreshold:t.highThreshold,dilationKernelSize:t.dilationKernelSize,dilationIterations:t.dilationIterations,applyDilation:t.applyDilation},contours:c,best:r,candidates:h}},n=await o(E[0],0);if(e.push(n),function(A,I={}){if(!1===I.enableDetectionCascade)return!1;if(!A||!A.corners)return!0;const g=void 0!==I.minCascadeTriggerConfidence?I.minCascadeTriggerConfidence:.68;return!A.isValid||A.confidence<g||A.approxCount>5||A.rightAngleScore<.55||A.contourFitRatio<.14}(n.best,Q))for(let B=1;B<E.length;B++)e.push(await o(E[B],B));const a=[];for(const B of e)for(const A of B.candidates)a.push({...A,passName:B.name,passParams:B.params});a.sort(O);const s=a[0]||null,c=a;if(!s||!s.corners)return{success:!1,message:"No document detected",debug:t._timingsOnly?null:t,timings:i};t&&!t._timingsOnly&&(t.passes=e.map(A=>({name:A.name,params:A.params,contourCount:A.contours.length,bestCandidate:A.best?{score:A.best.score,confidence:A.best.confidence,isValid:A.best.isValid,approxCount:A.best.approxCount,coverageRatio:A.best.coverageRatio,fillRatio:A.best.fillRatio,rightAngleScore:A.best.rightAngleScore,contourFitRatio:A.best.contourFitRatio}:null})),t.candidates=c.map(A=>({passName:A.passName,rankByArea:A.rankByArea,area:A.area,fillRatio:A.fillRatio,coverageRatio:A.coverageRatio,cornersArea:A.cornersArea,approxCount:A.approxCount,convex:A.convex,edgeSupport:A.edgeSupport,aspectRatio:A.aspectRatio,minSide:A.minSide,rightAngleScore:A.rightAngleScore,oppositeSideConsistency:A.oppositeSideConsistency,contourFitRatio:A.contourFitRatio,contourFitScore:A.contourFitScore,score:A.score,confidence:A.confidence,isValid:A.isValid})),t.selectedCandidate=c[0]?{passName:c[0].passName,rankByArea:c[0].rankByArea,area:c[0].area,fillRatio:c[0].fillRatio,coverageRatio:c[0].coverageRatio,approxCount:c[0].approxCount,edgeSupport:c[0].edgeSupport,aspectRatio:c[0].aspectRatio,minSide:c[0].minSide,rightAngleScore:c[0].rightAngleScore,oppositeSideConsistency:c[0].oppositeSideConsistency,contourFitRatio:c[0].contourFitRatio,score:c[0].score,confidence:c[0].confidence,isValid:c[0].isValid}:null);const r=s.corners,h=s.contour;let l=r;return 1!==C&&(l={topLeft:{x:r.topLeft.x*C,y:r.topLeft.y*C},topRight:{x:r.topRight.x*C,y:r.topRight.y*C},bottomRight:{x:r.bottomRight.x*C,y:r.bottomRight.y*C},bottomLeft:{x:r.bottomLeft.x*C,y:r.bottomLeft.y*C}}),{success:!0,contour:h,corners:l,confidence:s.confidence,debug:t._timingsOnly?null:t,timings:i}}function $(A,I,g){const{topLeft:B,topRight:C,bottomRight:Q,bottomLeft:t}=g,i=Math.hypot(Q.x-t.x,Q.y-t.y),E=Math.hypot(C.x-B.x,C.y-B.y),e=Math.round(Math.max(i,E)),o=Math.hypot(C.x-Q.x,C.y-Q.y),n=Math.hypot(B.x-t.x,B.y-t.y),a=Math.round(Math.max(o,n));A.canvas.width=e,A.canvas.height=a,function(A,I,g,B,C){const Q=I&&"number"==typeof I.width&&"number"==typeof I.height&&I.data,t=I.width||I.naturalWidth,i=I.height||I.naturalHeight,E=document.createElement("canvas");E.width=t,E.height=i;const e=E.getContext("2d",{willReadFrequently:!0});Q?e.putImageData(I,0,0):e.drawImage(I,0,0,t,i);const o=e.getImageData(0,0,t,i).data,n=function(A){const I=A[0][0],g=A[0][1],B=A[0][2],C=A[1][0],Q=A[1][1],t=A[1][2],i=A[2][0],E=A[2][1],e=A[2][2],o=Q*e-t*E,n=-(C*e-t*i),a=C*E-Q*i,s=-(g*e-B*E),c=I*e-B*i,r=-(I*E-g*i),h=g*t-B*Q,l=-(I*t-B*C),y=I*Q-g*C,D=I*o+g*n+B*a;if(0===D)throw new Error("Singular matrix");return[[o/D,s/D,h/D],[n/D,c/D,l/D],[a/D,r/D,y/D]]}(g),a=n[0][0],s=n[0][1],c=n[0][2],r=n[1][0],h=n[1][1],l=n[1][2],y=n[2][0],D=n[2][1],w=n[2][2],d=A.createImageData(B,C),f=d.data,u=t-1,m=i-1;for(let N=0;N<C;N++){const A=s*N+c,I=h*N+l,g=D*N+w;for(let C=0;C<B;C++){const Q=1/(y*C+g),i=(a*C+A)*Q,E=(r*C+I)*Q,e=i<0?0:i>u?u:i,n=E<0?0:E>m?m:E,s=0|e,c=0|n,h=s<u?s+1:s,l=c<m?c+1:c,D=e-s,w=n-c,d=1-D,R=1-w,F=d*R,p=D*R,J=d*w,G=D*w,k=c*t+s<<2,S=c*t+h<<2,M=l*t+s<<2,b=l*t+h<<2,L=N*B+C<<2;f[L]=o[k]*F+o[S]*p+o[M]*J+o[b]*G+.5|0,f[L+1]=o[k+1]*F+o[S+1]*p+o[M+1]*J+o[b+1]*G+.5|0,f[L+2]=o[k+2]*F+o[S+2]*p+o[M+2]*J+o[b+2]*G+.5|0,f[L+3]=255}}A.putImageData(d,0,0)}(A,I,function(A,I){const g=function(A,I){const g=A.length,B=A[0].length,C=A.map(A=>A.slice()),Q=I.slice();for(let i=0;i<B;i++){let A=i;for(let I=i+1;I<g;I++)Math.abs(C[I][i])>Math.abs(C[A][i])&&(A=I);[C[i],C[A]]=[C[A],C[i]],[Q[i],Q[A]]=[Q[A],Q[i]];for(let I=i+1;I<g;I++){const A=C[I][i]/C[i][i];for(let g=i;g<B;g++)C[I][g]-=A*C[i][g];Q[I]-=A*Q[i]}}const t=new Array(B);for(let i=B-1;i>=0;i--){let A=Q[i];for(let I=i+1;I<B;I++)A-=C[i][I]*t[I];t[i]=A/C[i][i]}return t}(function(A){const g=[];for(let B=0;B<4;B++){const[C,Q]=A[B];g.push([C,Q,1,0,0,0,-C*I[B][0],-Q*I[B][0]]),g.push([0,0,0,C,Q,1,-C*I[B][1],-Q*I[B][1]])}return g}(A),[I[0][0],I[0][1],I[1][0],I[1][1],I[2][0],I[2][1],I[3][0],I[3][1]]);return[[g[0],g[1],g[2]],[g[3],g[4],g[5]],[g[6],g[7],1]]}([[B.x,B.y],[C.x,C.y],[Q.x,Q.y],[t.x,t.y]],[[0,0],[e-1,0],[e-1,a-1],[0,a-1]]),e,a)}async function AA(A,I,g={}){const B=g.output||"canvas";if(!(I&&I.topLeft&&I.topRight&&I.bottomRight&&I.bottomLeft))return{output:null,corners:null,success:!1,message:"Invalid corner points provided"};try{const g=document.createElement("canvas");let C;return $(g.getContext("2d"),A,I),C="canvas"===B?g:"imagedata"===B?g.getContext("2d").getImageData(0,0,g.width,g.height):"dataurl"===B?g.toDataURL():g,{output:C,corners:I,success:!0,message:"Document extracted successfully"}}catch(C){return{output:null,corners:I,success:!1,message:`Extraction failed: ${C.message}`}}}async function IA(A,I={}){const g=[],B=performance.now(),C=I.mode||"detect",Q=I.output||"canvas";I.debug;const t=I.maxProcessingDimension||800;let i,E,e,o;if("ml"===(I.detector||"classical")){E=performance.now();const{detectDocumentMl:B}=await import("./mlDetector-DV0KqH2X.js");i=await B(A,I.ml||{}),g.push({step:"ML Detection Total",ms:(performance.now()-E).toFixed(2)}),i.timings&&i.timings.forEach(A=>g.push(A))}else{E=performance.now();const{grayscaleData:B,scaleFactor:C,scaledDimensions:Q}=await async function(A,I=800){let g,B;const C=A&&"number"==typeof A.width&&"number"==typeof A.height&&A.data;if(C)g=A.width,B=A.height;else{if(!A)throw new Error("No image provided");g=A.width||A.naturalWidth,B=A.height||A.naturalHeight}const Q=Math.max(g,B);let t,i,E;if(Q<=I)t=g,i=B,E=1;else{const A=I/Q;t=Math.round(g*A),i=Math.round(B*A),E=1/A}const e="undefined"!=typeof OffscreenCanvas,o=e?new OffscreenCanvas(t,i):document.createElement("canvas");e||(o.width=t,o.height=i);const n=o.getContext("2d",{willReadFrequently:!0});if(n.imageSmoothingEnabled=!0,n.imageSmoothingQuality="medium",C){const I=e?new OffscreenCanvas(g,B):document.createElement("canvas");e||(I.width=g,I.height=B),I.getContext("2d").putImageData(A,0,0),n.drawImage(I,0,0,g,B,0,0,t,i)}else n.drawImage(A,0,0,g,B,0,0,t,i);const a=n.getImageData(0,0,t,i),s=a.data,c=new Uint8ClampedArray(t*i);for(let r=0,h=0;r<s.length;r+=4,h++){const A=54*s[r]+183*s[r+1]+19*s[r+2]>>8;c[h]=A,s[r]=s[r+1]=s[r+2]=A}return{grayscaleData:c,imageData:a,scaleFactor:E,originalDimensions:{width:g,height:B},scaledDimensions:{width:t,height:i}}}(A,t);g.push({step:"Image Prep + Scale + Gray",ms:(performance.now()-E).toFixed(2)}),i=await _(B,Q.width,Q.height,C,I),i.timings&&i.timings.forEach(A=>g.push(A))}if(!i.success){const A=performance.now();return g.unshift({step:"Total",ms:(A-B).toFixed(2)}),{output:null,corners:null,contour:null,confidence:i.confidence||null,score:i.score??null,debug:i.debug,success:!1,message:i.message||"No document detected",timings:g}}"detect"===C?o=null:"extract"===C&&(E=performance.now(),e=document.createElement("canvas"),$(e.getContext("2d"),A,i.corners),g.push({step:"Perspective Transform",ms:(performance.now()-E).toFixed(2)})),"detect"!==C&&e&&(E=performance.now(),o="canvas"===Q?e:"imagedata"===Q?e.getContext("2d").getImageData(0,0,e.width,e.height):"dataurl"===Q?e.toDataURL():e,g.push({step:"Output Conversion",ms:(performance.now()-E).toFixed(2)}));const n=performance.now();return g.unshift({step:"Total",ms:(n-B).toFixed(2)}),{output:o,corners:i.corners,contour:i.contour??null,confidence:i.confidence||null,score:i.score??null,debug:i.debug,success:!0,message:"Document detected",timings:g}}export{P as Scanner,T as createCornerEditor,AA as extractDocument,W as initialize,IA as scanDocument};
|