@pixerate/editor 0.272.1 → 0.274.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.
Files changed (42) hide show
  1. package/dist/chunk-2A5IKZMU.js +557 -0
  2. package/dist/chunk-YH6SKC7P.cjs +557 -0
  3. package/dist/paint-editor/collaboration.d.cts +6 -1
  4. package/dist/paint-editor/collaboration.d.ts +6 -1
  5. package/dist/paint-editor/controller.d.cts +117 -0
  6. package/dist/paint-editor/controller.d.ts +117 -0
  7. package/dist/paint-editor/document.d.cts +1 -1
  8. package/dist/paint-editor/document.d.ts +1 -1
  9. package/dist/paint-editor/evolution-tools.d.cts +48 -0
  10. package/dist/paint-editor/evolution-tools.d.ts +48 -0
  11. package/dist/paint-editor/evolution.d.cts +175 -0
  12. package/dist/paint-editor/evolution.d.ts +175 -0
  13. package/dist/paint-editor/index.cjs +8500 -7258
  14. package/dist/paint-editor/index.d.cts +3 -0
  15. package/dist/paint-editor/index.d.ts +3 -0
  16. package/dist/paint-editor/index.js +8481 -7239
  17. package/dist/paint-editor/presence.d.cts +5 -1
  18. package/dist/paint-editor/presence.d.ts +5 -1
  19. package/dist/paint-editor/ui.d.cts +7 -2
  20. package/dist/paint-editor/ui.d.ts +7 -2
  21. package/dist/raster/fill.d.cts +85 -0
  22. package/dist/raster/fill.d.ts +85 -0
  23. package/dist/raster/index.cjs +9 -1
  24. package/dist/raster/index.d.cts +1 -0
  25. package/dist/raster/index.d.ts +1 -0
  26. package/dist/raster/index.js +8 -0
  27. package/package.json +1 -1
  28. package/skills/README.md +1 -0
  29. package/skills/pixerate-brushes/SKILL.md +3 -0
  30. package/skills/pixerate-paint-editor/SKILL.md +55 -96
  31. package/skills/pixerate-paint-editor/references/brush-facets.md +3 -0
  32. package/skills/pixerate-paint-editor/references/evolution.md +27 -0
  33. package/skills/pixerate-paint-editor/references/mutation.md +25 -0
  34. package/skills/pixerate-paint-editor/references/oils.md +8 -1
  35. package/skills/pixerate-paint-editor/references/smart-inks.md +51 -0
  36. package/skills/pixerate-paint-mutation/SKILL.md +76 -0
  37. package/skills/pixerate-paint-mutation/evals/decline-unanswered.json +58 -0
  38. package/skills/pixerate-paint-mutation/evals/dry-ink-answer.json +74 -0
  39. package/skills/pixerate-paint-mutation/evals/wet-wash-answer.json +76 -0
  40. package/skills/pixerate-paint-oil-impasto/SKILL.md +2 -1
  41. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +16 -5
  42. package/skills/pixerate-paint-sketch-to-finish/evals/flats-under-ink.json +2 -3
@@ -0,0 +1,557 @@
1
+ // src/raster/fill.ts
2
+ var INF = 1e12;
3
+ function squaredDistanceTransform(binary, w, h) {
4
+ const n = w * h;
5
+ const dist = new Float64Array(n);
6
+ let hasZero = false;
7
+ for (let i = 0; i < n; i++) {
8
+ if (binary[i] === 0) {
9
+ dist[i] = 0;
10
+ hasZero = true;
11
+ } else {
12
+ dist[i] = INF;
13
+ }
14
+ }
15
+ if (!hasZero) return dist;
16
+ const maxDim = Math.max(w, h);
17
+ const f = new Float64Array(maxDim);
18
+ const d = new Float64Array(maxDim);
19
+ const v = new Int32Array(maxDim);
20
+ const z = new Float64Array(maxDim + 1);
21
+ for (let x = 0; x < w; x++) {
22
+ for (let y = 0; y < h; y++) f[y] = dist[y * w + x];
23
+ edt1d(f, d, v, z, h);
24
+ for (let y = 0; y < h; y++) dist[y * w + x] = d[y];
25
+ }
26
+ for (let y = 0; y < h; y++) {
27
+ const row = y * w;
28
+ for (let x = 0; x < w; x++) f[x] = dist[row + x];
29
+ edt1d(f, d, v, z, w);
30
+ for (let x = 0; x < w; x++) dist[row + x] = d[x];
31
+ }
32
+ return dist;
33
+ }
34
+ function edt1d(f, d, v, z, n) {
35
+ let k = 0;
36
+ v[0] = 0;
37
+ z[0] = -INF;
38
+ z[1] = INF;
39
+ for (let q = 1; q < n; q++) {
40
+ const fq = f[q] + q * q;
41
+ let s = (fq - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
42
+ while (s <= z[k]) {
43
+ k--;
44
+ s = (fq - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
45
+ }
46
+ k++;
47
+ v[k] = q;
48
+ z[k] = s;
49
+ z[k + 1] = INF;
50
+ }
51
+ k = 0;
52
+ for (let q = 0; q < n; q++) {
53
+ while (z[k + 1] < q) k++;
54
+ const dx = q - v[k];
55
+ d[q] = dx * dx + f[v[k]];
56
+ }
57
+ }
58
+ function pixelMatches(data, p, sr, sg, sb, sa, tol) {
59
+ const a = data[p + 3];
60
+ if (sa === 0 && a === 0) return true;
61
+ return Math.abs(data[p] - sr) <= tol && Math.abs(data[p + 1] - sg) <= tol && Math.abs(data[p + 2] - sb) <= tol && Math.abs(a - sa) <= tol;
62
+ }
63
+ function floodOneSeed(reference, w, h, sx, sy, tol, closeGap, expand, queue) {
64
+ const n = w * h;
65
+ const marks = new Uint8Array(n);
66
+ if (sx < 0 || sy < 0 || sx >= w || sy >= h) return marks;
67
+ const sp = (sy * w + sx) * 4;
68
+ const sr = reference[sp];
69
+ const sg = reference[sp + 1];
70
+ const sb = reference[sp + 2];
71
+ const sa = reference[sp + 3];
72
+ const open = new Uint8Array(n);
73
+ for (let i = 0; i < n; i++) {
74
+ if (pixelMatches(reference, i * 4, sr, sg, sb, sa, tol)) open[i] = 1;
75
+ }
76
+ const start = sy * w + sx;
77
+ open[start] = 1;
78
+ if (closeGap <= 0) {
79
+ let head = 0;
80
+ let tail = 0;
81
+ marks[start] = 1;
82
+ queue[tail++] = start;
83
+ while (head < tail) {
84
+ const cur = queue[head++];
85
+ const cx = cur % w;
86
+ const cy = (cur - cx) / w;
87
+ if (cx > 0) {
88
+ const nb = cur - 1;
89
+ if (open[nb] === 1 && marks[nb] === 0) {
90
+ marks[nb] = 1;
91
+ queue[tail++] = nb;
92
+ }
93
+ }
94
+ if (cx + 1 < w) {
95
+ const nb = cur + 1;
96
+ if (open[nb] === 1 && marks[nb] === 0) {
97
+ marks[nb] = 1;
98
+ queue[tail++] = nb;
99
+ }
100
+ }
101
+ if (cy > 0) {
102
+ const nb = cur - w;
103
+ if (open[nb] === 1 && marks[nb] === 0) {
104
+ marks[nb] = 1;
105
+ queue[tail++] = nb;
106
+ }
107
+ }
108
+ if (cy + 1 < h) {
109
+ const nb = cur + w;
110
+ if (open[nb] === 1 && marks[nb] === 0) {
111
+ marks[nb] = 1;
112
+ queue[tail++] = nb;
113
+ }
114
+ }
115
+ }
116
+ } else {
117
+ const distSq = squaredDistanceTransform(open, w, h);
118
+ const r = (closeGap + 1) * 0.5;
119
+ const rSq = r * r;
120
+ const maxClimb = Math.ceil(r) + 2;
121
+ let coreSeed = start;
122
+ let head = 0;
123
+ let tail = 0;
124
+ marks[start] = 1;
125
+ queue[tail++] = start;
126
+ for (let step = 0; step < maxClimb && distSq[coreSeed] <= rSq; step++) {
127
+ const cx = coreSeed % w;
128
+ const cy = (coreSeed - cx) / w;
129
+ let best = coreSeed;
130
+ let bestD = distSq[coreSeed];
131
+ if (cx > 0 && open[coreSeed - 1] === 1 && distSq[coreSeed - 1] > bestD) {
132
+ best = coreSeed - 1;
133
+ bestD = distSq[best];
134
+ }
135
+ if (cx + 1 < w && open[coreSeed + 1] === 1 && distSq[coreSeed + 1] > bestD) {
136
+ best = coreSeed + 1;
137
+ bestD = distSq[best];
138
+ }
139
+ if (cy > 0 && open[coreSeed - w] === 1 && distSq[coreSeed - w] > bestD) {
140
+ best = coreSeed - w;
141
+ bestD = distSq[best];
142
+ }
143
+ if (cy + 1 < h && open[coreSeed + w] === 1 && distSq[coreSeed + w] > bestD) {
144
+ best = coreSeed + w;
145
+ bestD = distSq[best];
146
+ }
147
+ if (best === coreSeed) break;
148
+ coreSeed = best;
149
+ if (marks[coreSeed] === 0) {
150
+ marks[coreSeed] = 1;
151
+ queue[tail++] = coreSeed;
152
+ }
153
+ }
154
+ if (distSq[coreSeed] > rSq) {
155
+ let scan = 0;
156
+ while (scan < tail) {
157
+ const cur = queue[scan++];
158
+ if (distSq[cur] <= rSq) continue;
159
+ const cx = cur % w;
160
+ const cy = (cur - cx) / w;
161
+ if (cx > 0) {
162
+ const nb = cur - 1;
163
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] > rSq) {
164
+ marks[nb] = 1;
165
+ queue[tail++] = nb;
166
+ }
167
+ }
168
+ if (cx + 1 < w) {
169
+ const nb = cur + 1;
170
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] > rSq) {
171
+ marks[nb] = 1;
172
+ queue[tail++] = nb;
173
+ }
174
+ }
175
+ if (cy > 0) {
176
+ const nb = cur - w;
177
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] > rSq) {
178
+ marks[nb] = 1;
179
+ queue[tail++] = nb;
180
+ }
181
+ }
182
+ if (cy + 1 < h) {
183
+ const nb = cur + w;
184
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] > rSq) {
185
+ marks[nb] = 1;
186
+ queue[tail++] = nb;
187
+ }
188
+ }
189
+ }
190
+ while (head < tail) {
191
+ const cur = queue[head++];
192
+ const curD = distSq[cur];
193
+ const cx = cur % w;
194
+ const cy = (cur - cx) / w;
195
+ if (cx > 0) {
196
+ const nb = cur - 1;
197
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
198
+ marks[nb] = 1;
199
+ queue[tail++] = nb;
200
+ }
201
+ }
202
+ if (cx + 1 < w) {
203
+ const nb = cur + 1;
204
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
205
+ marks[nb] = 1;
206
+ queue[tail++] = nb;
207
+ }
208
+ }
209
+ if (cy > 0) {
210
+ const nb = cur - w;
211
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
212
+ marks[nb] = 1;
213
+ queue[tail++] = nb;
214
+ }
215
+ }
216
+ if (cy + 1 < h) {
217
+ const nb = cur + w;
218
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
219
+ marks[nb] = 1;
220
+ queue[tail++] = nb;
221
+ }
222
+ }
223
+ }
224
+ } else {
225
+ const maxLocal = Math.max(64, closeGap * closeGap * 8);
226
+ while (head < tail && tail < maxLocal) {
227
+ const cur = queue[head++];
228
+ const cx = cur % w;
229
+ const cy = (cur - cx) / w;
230
+ if (cx > 0) {
231
+ const nb = cur - 1;
232
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq) {
233
+ marks[nb] = 1;
234
+ queue[tail++] = nb;
235
+ }
236
+ }
237
+ if (cx + 1 < w) {
238
+ const nb = cur + 1;
239
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq) {
240
+ marks[nb] = 1;
241
+ queue[tail++] = nb;
242
+ }
243
+ }
244
+ if (cy > 0) {
245
+ const nb = cur - w;
246
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq) {
247
+ marks[nb] = 1;
248
+ queue[tail++] = nb;
249
+ }
250
+ }
251
+ if (cy + 1 < h) {
252
+ const nb = cur + w;
253
+ if (open[nb] === 1 && marks[nb] === 0 && distSq[nb] <= rSq) {
254
+ marks[nb] = 1;
255
+ queue[tail++] = nb;
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ if (expand !== 0) {
262
+ let any = false;
263
+ for (let i = 0; i < n; i++) {
264
+ if (marks[i] === 1) {
265
+ any = true;
266
+ break;
267
+ }
268
+ }
269
+ if (any) {
270
+ const expSq = expand * expand;
271
+ if (expand > 0) {
272
+ const inv = new Uint8Array(n);
273
+ for (let i = 0; i < n; i++) inv[i] = marks[i] === 1 ? 0 : 1;
274
+ const dSq = squaredDistanceTransform(inv, w, h);
275
+ for (let i = 0; i < n; i++) marks[i] = dSq[i] <= expSq ? 1 : 0;
276
+ } else {
277
+ const dSq = squaredDistanceTransform(marks, w, h);
278
+ for (let i = 0; i < n; i++) marks[i] = dSq[i] > expSq ? 1 : 0;
279
+ }
280
+ }
281
+ }
282
+ return marks;
283
+ }
284
+ function summarizeMarks(marks, w, h) {
285
+ let minX = w;
286
+ let minY = h;
287
+ let maxX = -1;
288
+ let maxY = -1;
289
+ let pixelCount = 0;
290
+ let leaked = false;
291
+ for (let y = 0; y < h; y++) {
292
+ const row = y * w;
293
+ for (let x = 0; x < w; x++) {
294
+ if (marks[row + x] === 1) {
295
+ pixelCount++;
296
+ if (x < minX) minX = x;
297
+ if (y < minY) minY = y;
298
+ if (x > maxX) maxX = x;
299
+ if (y > maxY) maxY = y;
300
+ if (x === 0 || y === 0 || x === w - 1 || y === h - 1) leaked = true;
301
+ }
302
+ }
303
+ }
304
+ const bounds = pixelCount > 0 ? { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 } : null;
305
+ return { bounds, pixelCount, leaked };
306
+ }
307
+ function fillRegionMask(reference, width, height, options) {
308
+ const n = width * height;
309
+ const tol = Math.max(0, Math.min(255, options.tolerance ?? 32));
310
+ const closeGap = Math.max(0, Math.min(32, options.closeGap ?? 0));
311
+ const expand = Math.max(-16, Math.min(16, options.expand ?? 0));
312
+ const antialias = options.antialias ?? true;
313
+ const combined = new Uint8Array(n);
314
+ const queue = new Int32Array(n);
315
+ const regions = [];
316
+ for (const pt of options.points) {
317
+ const sx = Math.floor(pt.x);
318
+ const sy = Math.floor(pt.y);
319
+ const seedMarks = floodOneSeed(reference, width, height, sx, sy, tol, closeGap, expand, queue);
320
+ const summary = summarizeMarks(seedMarks, width, height);
321
+ regions.push({
322
+ point: { x: pt.x, y: pt.y },
323
+ bounds: summary.bounds,
324
+ pixelCount: summary.pixelCount,
325
+ leaked: summary.leaked
326
+ });
327
+ for (let i = 0; i < n; i++) {
328
+ if (seedMarks[i] === 1) combined[i] = 1;
329
+ }
330
+ }
331
+ const total = summarizeMarks(combined, width, height);
332
+ const mask = new Float32Array(n);
333
+ if (total.bounds) {
334
+ const x0 = total.bounds.x;
335
+ const y0 = total.bounds.y;
336
+ const x1 = x0 + total.bounds.width;
337
+ const y1 = y0 + total.bounds.height;
338
+ for (let y = y0; y < y1; y++) {
339
+ const row = y * width;
340
+ for (let x = x0; x < x1; x++) {
341
+ const idx = row + x;
342
+ if (combined[idx] !== 1) continue;
343
+ if (!antialias) {
344
+ mask[idx] = 1;
345
+ continue;
346
+ }
347
+ let sum = 0;
348
+ let edge = false;
349
+ for (let dy = -1; dy <= 1; dy++) {
350
+ const ny = y + dy;
351
+ for (let dx = -1; dx <= 1; dx++) {
352
+ const nx = x + dx;
353
+ const v = nx < 0 || ny < 0 || nx >= width || ny >= height ? 1 : combined[ny * width + nx];
354
+ if (v === 0) edge = true;
355
+ sum += v;
356
+ }
357
+ }
358
+ mask[idx] = edge ? 0.5 + 0.5 * (sum / 9) : 1;
359
+ }
360
+ }
361
+ }
362
+ return {
363
+ mask,
364
+ bounds: total.bounds,
365
+ pixelCount: total.pixelCount,
366
+ leaked: total.leaked,
367
+ regions
368
+ };
369
+ }
370
+ function findUnfilledRegionsMask(reference, width, height, options = {}) {
371
+ const n = width * height;
372
+ if (n <= 0) return [];
373
+ const tol = Math.max(0, Math.min(255, options.tolerance ?? 32));
374
+ const closeGap = Math.max(0, Math.min(32, options.closeGap ?? 0));
375
+ const minArea = Math.max(1, Math.floor(options.minArea ?? 16));
376
+ const includeEdge = options.includeEdge ?? false;
377
+ const maxRegions = Math.max(1, Math.floor(options.maxRegions ?? 100));
378
+ const target = options.target;
379
+ let hasTransparent = false;
380
+ for (let i = 0; i < n; i++) {
381
+ if (reference[i * 4 + 3] <= tol) {
382
+ hasTransparent = true;
383
+ break;
384
+ }
385
+ }
386
+ const br = reference[0];
387
+ const bg = reference[1];
388
+ const bb = reference[2];
389
+ const ba = reference[3];
390
+ const openRef = new Uint8Array(n);
391
+ for (let i = 0; i < n; i++) {
392
+ const p = i * 4;
393
+ const isRefOpen = hasTransparent ? reference[p + 3] <= tol : pixelMatches(reference, p, br, bg, bb, ba, tol);
394
+ if (isRefOpen) openRef[i] = 1;
395
+ }
396
+ const unfilled = new Uint8Array(n);
397
+ for (let i = 0; i < n; i++) {
398
+ if (openRef[i] === 1 && (!target || target[i * 4 + 3] <= tol)) {
399
+ unfilled[i] = 1;
400
+ }
401
+ }
402
+ const distSq = squaredDistanceTransform(openRef, width, height);
403
+ const r = closeGap > 0 ? (closeGap + 1) * 0.5 : 0;
404
+ const rSq = r * r;
405
+ const labels = new Int32Array(n);
406
+ const queue = new Int32Array(n);
407
+ let nextLabel = 0;
408
+ let coreTail = 0;
409
+ for (let i = 0; i < n; i++) {
410
+ if (unfilled[i] !== 1 || labels[i] !== 0 || distSq[i] <= rSq) continue;
411
+ const label = ++nextLabel;
412
+ labels[i] = label;
413
+ let head = coreTail;
414
+ queue[coreTail++] = i;
415
+ while (head < coreTail) {
416
+ const cur = queue[head++];
417
+ const cx = cur % width;
418
+ const cy = (cur - cx) / width;
419
+ if (cx > 0) {
420
+ const nb = cur - 1;
421
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] > rSq) {
422
+ labels[nb] = label;
423
+ queue[coreTail++] = nb;
424
+ }
425
+ }
426
+ if (cx + 1 < width) {
427
+ const nb = cur + 1;
428
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] > rSq) {
429
+ labels[nb] = label;
430
+ queue[coreTail++] = nb;
431
+ }
432
+ }
433
+ if (cy > 0) {
434
+ const nb = cur - width;
435
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] > rSq) {
436
+ labels[nb] = label;
437
+ queue[coreTail++] = nb;
438
+ }
439
+ }
440
+ if (cy + 1 < height) {
441
+ const nb = cur + width;
442
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] > rSq) {
443
+ labels[nb] = label;
444
+ queue[coreTail++] = nb;
445
+ }
446
+ }
447
+ }
448
+ }
449
+ if (closeGap > 0 && nextLabel > 0) {
450
+ let head = 0;
451
+ let tail = coreTail;
452
+ while (head < tail) {
453
+ const cur = queue[head++];
454
+ const label = labels[cur];
455
+ const curD = distSq[cur];
456
+ const cx = cur % width;
457
+ const cy = (cur - cx) / width;
458
+ if (cx > 0) {
459
+ const nb = cur - 1;
460
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
461
+ labels[nb] = label;
462
+ queue[tail++] = nb;
463
+ }
464
+ }
465
+ if (cx + 1 < width) {
466
+ const nb = cur + 1;
467
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
468
+ labels[nb] = label;
469
+ queue[tail++] = nb;
470
+ }
471
+ }
472
+ if (cy > 0) {
473
+ const nb = cur - width;
474
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
475
+ labels[nb] = label;
476
+ queue[tail++] = nb;
477
+ }
478
+ }
479
+ if (cy + 1 < height) {
480
+ const nb = cur + width;
481
+ if (unfilled[nb] === 1 && labels[nb] === 0 && distSq[nb] <= rSq && distSq[nb] <= curD) {
482
+ labels[nb] = label;
483
+ queue[tail++] = nb;
484
+ }
485
+ }
486
+ }
487
+ }
488
+ if (nextLabel === 0) return [];
489
+ const counts = new Int32Array(nextLabel + 1);
490
+ const sumX = new Float64Array(nextLabel + 1);
491
+ const sumY = new Float64Array(nextLabel + 1);
492
+ const minX = new Int32Array(nextLabel + 1).fill(width);
493
+ const minY = new Int32Array(nextLabel + 1).fill(height);
494
+ const maxX = new Int32Array(nextLabel + 1).fill(-1);
495
+ const maxY = new Int32Array(nextLabel + 1).fill(-1);
496
+ const edge = new Uint8Array(nextLabel + 1);
497
+ const bestIdx = new Int32Array(nextLabel + 1).fill(-1);
498
+ const bestDist = new Float64Array(nextLabel + 1).fill(-1);
499
+ for (let y = 0; y < height; y++) {
500
+ const row = y * width;
501
+ for (let x = 0; x < width; x++) {
502
+ const idx = row + x;
503
+ const l = labels[idx];
504
+ if (l === 0) continue;
505
+ counts[l]++;
506
+ sumX[l] += x;
507
+ sumY[l] += y;
508
+ if (x < minX[l]) minX[l] = x;
509
+ if (y < minY[l]) minY[l] = y;
510
+ if (x > maxX[l]) maxX[l] = x;
511
+ if (y > maxY[l]) maxY[l] = y;
512
+ if (x === 0 || y === 0 || x === width - 1 || y === height - 1) edge[l] = 1;
513
+ const d = distSq[idx];
514
+ if (d > bestDist[l]) {
515
+ bestDist[l] = d;
516
+ bestIdx[l] = idx;
517
+ }
518
+ }
519
+ }
520
+ const out = [];
521
+ for (let l = 1; l <= nextLabel; l++) {
522
+ const count = counts[l];
523
+ if (count < minArea) continue;
524
+ const touchesEdge = edge[l] === 1;
525
+ if (!includeEdge && touchesEdge) continue;
526
+ const cx = Math.round(sumX[l] / count);
527
+ const cy = Math.round(sumY[l] / count);
528
+ const cIdx = cy * width + cx;
529
+ let px = cx;
530
+ let py = cy;
531
+ if (cx < 0 || cy < 0 || cx >= width || cy >= height || labels[cIdx] !== l || distSq[cIdx] <= rSq) {
532
+ const bi = bestIdx[l];
533
+ px = bi % width;
534
+ py = (bi - px) / width;
535
+ }
536
+ out.push({
537
+ point: { x: px, y: py },
538
+ centroid: { x: cx, y: cy },
539
+ bounds: {
540
+ x: minX[l],
541
+ y: minY[l],
542
+ width: maxX[l] - minX[l] + 1,
543
+ height: maxY[l] - minY[l] + 1
544
+ },
545
+ pixelCount: count,
546
+ touchesEdge
547
+ });
548
+ }
549
+ out.sort((a, b) => b.pixelCount - a.pixelCount || a.bounds.y - b.bounds.y || a.bounds.x - b.bounds.x);
550
+ return out.slice(0, maxRegions);
551
+ }
552
+
553
+ export {
554
+ squaredDistanceTransform,
555
+ fillRegionMask,
556
+ findUnfilledRegionsMask
557
+ };