@poe-platform/safe-bash 0.1.834 → 0.1.836

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.
@@ -1,5 +1,215 @@
1
1
  import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
2
2
 
3
+ // packages/safe-bash-command-imagemagick/src/connected-components.ts
4
+ import { yieldTurn as yieldTurn2 } from "../safe-bash-contracts/yield.js";
5
+
6
+ // packages/safe-bash-command-imagemagick/src/raster-backing.ts
7
+ import { yieldTurn } from "../safe-bash-contracts/yield.js";
8
+ var RasterBackingCache = class {
9
+ constructor(storage, signal) {
10
+ this.storage = storage;
11
+ this.signal = signal;
12
+ }
13
+ storage;
14
+ signal;
15
+ pages = /* @__PURE__ */ new Map();
16
+ work = 0;
17
+ async allocate(length) {
18
+ const region = { position: this.storage.allocate(length), length }, zero = new Uint8Array(Math.min(4096, length));
19
+ for (let offset = 0; offset < length; offset += zero.length) {
20
+ if (offset % 65536 === 0) await yieldTurn(this.signal);
21
+ await this.storage.write(region.position + offset, zero.subarray(0, Math.min(zero.length, length - offset)), { signal: this.signal });
22
+ }
23
+ return region;
24
+ }
25
+ async page(region, offset) {
26
+ this.signal.throwIfAborted();
27
+ if (++this.work % 16384 === 0) await yieldTurn(this.signal);
28
+ const start = Math.floor(offset / 4096) * 4096, key = region.position + ":" + start;
29
+ let page = this.pages.get(key);
30
+ if (page) {
31
+ this.pages.delete(key);
32
+ this.pages.set(key, page);
33
+ return page;
34
+ }
35
+ if (this.pages.size === 32) {
36
+ const oldest = this.pages.entries().next().value;
37
+ if (oldest[1].dirty) await this.storage.write(oldest[1].position, oldest[1].bytes, { signal: this.signal });
38
+ this.pages.delete(oldest[0]);
39
+ }
40
+ const length = Math.min(4096, region.length - start), borrowed = await this.storage.read(region.position + start, length, { signal: this.signal });
41
+ if (borrowed.length !== length) throw new Error("Truncated raster backing");
42
+ const bytes = new Uint8Array(borrowed);
43
+ page = { position: region.position + start, bytes, view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), dirty: false };
44
+ this.pages.set(key, page);
45
+ return page;
46
+ }
47
+ async byte(region, offset) {
48
+ return (await this.page(region, offset)).bytes[offset % 4096];
49
+ }
50
+ async mark(region, offset) {
51
+ const page = await this.page(region, offset);
52
+ page.bytes[offset % 4096] = 1;
53
+ page.dirty = true;
54
+ }
55
+ async uint32(region, offset) {
56
+ return (await this.page(region, offset)).view.getUint32(offset % 4096, true);
57
+ }
58
+ async setUint32(region, offset, value) {
59
+ const page = await this.page(region, offset);
60
+ page.view.setUint32(offset % 4096, value, true);
61
+ page.dirty = true;
62
+ }
63
+ async float32(region, offset) {
64
+ return (await this.page(region, offset)).view.getFloat32(offset % 4096, true);
65
+ }
66
+ async setFloat32(region, offset, value) {
67
+ const page = await this.page(region, offset);
68
+ page.view.setFloat32(offset % 4096, value, true);
69
+ page.dirty = true;
70
+ }
71
+ async flush() {
72
+ for (const page of this.pages.values()) {
73
+ this.signal.throwIfAborted();
74
+ if (page.dirty) {
75
+ await this.storage.write(page.position, page.bytes, { signal: this.signal });
76
+ page.dirty = false;
77
+ }
78
+ }
79
+ }
80
+ };
81
+
82
+ // packages/safe-bash-command-imagemagick/src/connected-components.ts
83
+ function* labelSteps(width, height, connectivity) {
84
+ const components = [];
85
+ for (let seed = 0; seed < width * height; seed++) {
86
+ if (seed % 1024 === 0) yield;
87
+ if (yield { region: "labels", index: seed }) continue;
88
+ if (components.length === 65536) throw new Error("connected-components: too many objects (maximum 65536)");
89
+ const id = components.length, color = yield { region: "pixels", index: seed };
90
+ const component = { id, area: 0, x: 0, y: 0, left: width, top: height, right: 0, bottom: 0, color: [0, 0, 0, 0] };
91
+ let head = 0, tail = 1;
92
+ yield { region: "labels", index: seed, value: id + 1 };
93
+ yield { region: "queue", index: 0, value: seed };
94
+ while (head < tail) {
95
+ if (head % 1024 === 0) yield;
96
+ const current = yield { region: "queue", index: head++ }, x = current % width, y = Math.floor(current / width);
97
+ const pixel = yield { region: "pixels", index: current };
98
+ component.area++;
99
+ component.x += x;
100
+ component.y += y;
101
+ component.left = Math.min(component.left, x);
102
+ component.right = Math.max(component.right, x);
103
+ component.top = Math.min(component.top, y);
104
+ component.bottom = Math.max(component.bottom, y);
105
+ for (let channel = 0; channel < 4; channel++) component.color[channel] += pixel >>> channel * 8 & 255;
106
+ for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
107
+ if (!dx && !dy || connectivity === 4 && dx && dy || x + dx < 0 || x + dx >= width || y + dy < 0 || y + dy >= height) continue;
108
+ const next = (y + dy) * width + x + dx;
109
+ if (yield { region: "labels", index: next }) continue;
110
+ const neighbor = yield { region: "pixels", index: next };
111
+ if (neighbor !== color && (neighbor >>> 24 !== 0 || color >>> 24 !== 0)) continue;
112
+ yield { region: "labels", index: next, value: id + 1 };
113
+ yield { region: "queue", index: tail++, value: next };
114
+ }
115
+ }
116
+ components.push(component);
117
+ }
118
+ return components;
119
+ }
120
+ function* reportSteps(components) {
121
+ let sorted = components.slice();
122
+ for (let span = 1; span < sorted.length; span *= 2) {
123
+ const next = [];
124
+ for (let start = 0; start < sorted.length; start += span * 2) {
125
+ let left = start, right = Math.min(start + span, sorted.length);
126
+ const middle = right, end = Math.min(start + span * 2, sorted.length);
127
+ while (left < middle || right < end) {
128
+ if (next.length % 1024 === 0) yield;
129
+ next.push(right === end || left < middle && sorted[left].area >= sorted[right].area ? sorted[left++] : sorted[right++]);
130
+ }
131
+ }
132
+ sorted = next;
133
+ }
134
+ let text = "Objects (id: bounding-box centroid area mean-color):\n";
135
+ for (const c of sorted) {
136
+ if (c.id % 1024 === 0) yield;
137
+ const channels = c.color.slice(0, 3).map((value) => Number((value / c.area).toPrecision(6)));
138
+ const color = c.color[3] < c.area * 255 ? `srgba(${channels.join(",")},${Number((c.color[3] / c.area / 255).toPrecision(6))})` : `srgb(${channels.join(",")})`;
139
+ text += ` ${c.id}: ${c.right - c.left + 1}x${c.bottom - c.top + 1}+${c.left}+${c.top} ${(c.x / c.area).toFixed(1)},${(c.y / c.area).toFixed(1)} ${c.area} ${color}
140
+ `;
141
+ }
142
+ return text;
143
+ }
144
+ function* connectedComponentsSteps(image, connectivity, verbose) {
145
+ const count = image.width * image.height, labels = new Uint32Array(count), queue = new Uint32Array(count), pixels = new DataView(image.data.buffer, image.data.byteOffset, image.data.byteLength);
146
+ const steps = labelSteps(image.width, image.height, connectivity);
147
+ let next = steps.next();
148
+ while (!next.done) {
149
+ const request = next.value;
150
+ if (!request) {
151
+ yield;
152
+ next = steps.next(0);
153
+ continue;
154
+ }
155
+ const { region, index, value } = request;
156
+ if (region === "pixels") next = steps.next(pixels.getUint32(index * 4, true));
157
+ else {
158
+ const data2 = region === "labels" ? labels : queue;
159
+ if (value !== void 0) data2[index] = value;
160
+ next = steps.next(data2[index]);
161
+ }
162
+ }
163
+ const text = verbose ? yield* reportSteps(next.value) : "", data = new Uint8Array(count * 4);
164
+ for (let i = 0; i < count; i++) {
165
+ if (i % 1024 === 0) yield;
166
+ const value = Math.round((labels[i] - 1) / 257);
167
+ data.set([value, value, value, 255], i * 4);
168
+ }
169
+ return { image: { ...image, data, hasAlpha: false, space: "srgb" }, text };
170
+ }
171
+ async function connectedComponentsStored(image, storage, connectivity, verbose, signal) {
172
+ const size = image.width * image.height * 4, cache = new RasterBackingCache(storage, signal);
173
+ const regions = { pixels: { position: image.position, length: size }, labels: await cache.allocate(size), queue: await cache.allocate(size) };
174
+ const steps = labelSteps(image.width, image.height, connectivity);
175
+ let next = steps.next();
176
+ while (!next.done) {
177
+ const request = next.value;
178
+ if (!request) {
179
+ await yieldTurn2(signal);
180
+ next = steps.next(0);
181
+ continue;
182
+ }
183
+ const region = regions[request.region], offset = request.index * 4;
184
+ if (request.value !== void 0) {
185
+ await cache.setUint32(region, offset, request.value);
186
+ next = steps.next(0);
187
+ } else next = steps.next(await cache.uint32(region, offset));
188
+ }
189
+ let text = "";
190
+ if (verbose) {
191
+ const report = reportSteps(next.value);
192
+ let part = report.next();
193
+ while (!part.done) {
194
+ await yieldTurn2(signal);
195
+ part = report.next();
196
+ }
197
+ text = part.value;
198
+ }
199
+ await cache.flush();
200
+ const position = storage.allocate(size);
201
+ for (let offset = 0; offset < size; offset += 4096) {
202
+ await yieldTurn2(signal);
203
+ const bytes = new Uint8Array(await storage.read(regions.labels.position + offset, Math.min(4096, size - offset), { signal })), view = new DataView(bytes.buffer);
204
+ for (let i = 0; i < bytes.length; i += 4) {
205
+ const value = Math.round((view.getUint32(i, true) - 1) / 257);
206
+ bytes.set([value, value, value, 255], i);
207
+ }
208
+ await storage.write(position + offset, bytes, { signal });
209
+ }
210
+ return { image: { ...image, position, hasAlpha: false, space: "srgb" }, text };
211
+ }
212
+
3
213
  // packages/safe-bash-command-imagemagick/src/sequence-kernel.ts
4
214
  var byte = (value) => Math.max(0, Math.min(255, Math.round(value)));
5
215
  function* clutPixelSteps(size, lut, mask) {
@@ -250,7 +460,7 @@ async function withImageInputs(input, stdinBytes, signal, run) {
250
460
 
251
461
  // packages/safe-bash-command-imagemagick/src/text-output.ts
252
462
  import { IntegerTable } from "@poe-platform/safe-fs/storage";
253
- import { yieldTurn } from "../safe-bash-contracts/yield.js";
463
+ import { yieldTurn as yieldTurn3 } from "../safe-bash-contracts/yield.js";
254
464
  var hex = (value) => value.toString(16).toUpperCase().padStart(2, "0");
255
465
  function pixelEnumerationLine(x, y, r, g, b, a) {
256
466
  return `${x},${y}: (${r},${g},${b},${a}) #${hex(r)}${hex(g)}${hex(b)}${a < 255 ? hex(a) : ""} srgba(${r},${g},${b},${(a / 255).toFixed(3)})`;
@@ -264,7 +474,7 @@ async function* storedPixelEnumeration(image, storage, signal) {
264
474
  `;
265
475
  const size = image.width * image.height * 4;
266
476
  for (let start = 0; start < size; start += 16384) {
267
- await yieldTurn(signal);
477
+ await yieldTurn3(signal);
268
478
  const bytes = await storage.read(image.position + start, Math.min(16384, size - start));
269
479
  for (let offset = 0; offset < bytes.length; offset += 4) {
270
480
  const pixel = (start + offset) / 4;
@@ -276,7 +486,7 @@ async function* storedHistogram(image, storage, signal) {
276
486
  const counts = new IntegerTable(storage, 512), order = new IntegerTable(storage, 128), size = image.width * image.height * 4;
277
487
  let unique = 0;
278
488
  for (let start = 0; start < size; start += 16384) {
279
- await yieldTurn(signal);
489
+ await yieldTurn3(signal);
280
490
  const bytes = await storage.read(image.position + start, Math.min(16384, size - start));
281
491
  for (let offset = 0; offset < bytes.length; offset += 4) {
282
492
  const key = BigInt((bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3]) >>> 0), count = await counts.get(key);
@@ -285,7 +495,7 @@ async function* storedHistogram(image, storage, signal) {
285
495
  }
286
496
  }
287
497
  for (let index = 0; index < unique; index++) {
288
- if (index % 128 === 0) await yieldTurn(signal);
498
+ if (index % 128 === 0) await yieldTurn3(signal);
289
499
  const key = await order.get(BigInt(index));
290
500
  yield histogramLine(Number(key), Number(await counts.get(key))) + "\n";
291
501
  }
@@ -327,7 +537,7 @@ async function* encodeText(parts, signal) {
327
537
 
328
538
  // packages/safe-bash-command-imagemagick/src/stored-stack.ts
329
539
  import { IntegerTable as IntegerTable2 } from "@poe-platform/safe-fs/storage";
330
- import { yieldTurn as yieldTurn2 } from "../safe-bash-contracts/yield.js";
540
+ import { yieldTurn as yieldTurn4 } from "../safe-bash-contracts/yield.js";
331
541
  var magickInput = /* @__PURE__ */ Symbol("magickInput");
332
542
  var StoredImageStack = class {
333
543
  constructor(storage, signal, parent) {
@@ -359,7 +569,7 @@ var StoredImageStack = class {
359
569
  if (source && !delays) {
360
570
  delays = { position: this.storage.allocate(source.length * 9), length: source.length };
361
571
  for (let start = 0; start < source.length; start += 512) {
362
- await yieldTurn2(this.signal);
572
+ await yieldTurn4(this.signal);
363
573
  const count = Math.min(512, source.length - start), bytes = new Uint8Array(count * 9), view = new DataView(bytes.buffer);
364
574
  for (let i = 0; i < count; i++) {
365
575
  const value = storedDelay ? await storedDelay.at(start + i, { signal: this.signal }) : delay[start + i];
@@ -376,7 +586,7 @@ var StoredImageStack = class {
376
586
  const text = JSON.stringify(record), encoder = new TextEncoder(), position = this.storage.allocate(8);
377
587
  let size = 0;
378
588
  for (let offset = 0; offset < text.length; ) {
379
- await yieldTurn2(this.signal);
589
+ await yieldTurn4(this.signal);
380
590
  const bytes = new Uint8Array(4096), { read, written } = encoder.encodeInto(text.slice(offset, offset + 4097), bytes);
381
591
  await this.storage.write(this.storage.allocate(written), bytes.subarray(0, written));
382
592
  offset += read;
@@ -399,7 +609,7 @@ var StoredImageStack = class {
399
609
  const decoder = new TextDecoder();
400
610
  let text = "";
401
611
  for (let offset = 0; offset < size; offset += 4096) {
402
- await yieldTurn2(this.signal);
612
+ await yieldTurn4(this.signal);
403
613
  text += decoder.decode(await this.storage.read(position + 8 + offset, Math.min(4096, size - offset)), { stream: true });
404
614
  }
405
615
  const record = JSON.parse(text + decoder.decode());
@@ -448,12 +658,12 @@ var StoredImageStack = class {
448
658
  const removed = new IntegerTable2(this.storage, 128);
449
659
  let work = 0;
450
660
  for (const index of indices) if (index >= 0 && index < this.length) {
451
- if (++work % 128 === 0) await yieldTurn2(this.signal);
661
+ if (++work % 128 === 0) await yieldTurn4(this.signal);
452
662
  await removed.set(BigInt(index), 1n);
453
663
  }
454
664
  let next = 0;
455
665
  for (let index = 0; index < this.length; index++) {
456
- if (index % 128 === 0) await yieldTurn2(this.signal);
666
+ if (index % 128 === 0) await yieldTurn4(this.signal);
457
667
  if (await removed.get(BigInt(index))) continue;
458
668
  if (next !== index) await this.indices.set(BigInt(next), await this.indices.get(BigInt(index)));
459
669
  next++;
@@ -463,89 +673,11 @@ var StoredImageStack = class {
463
673
  };
464
674
 
465
675
  // packages/safe-bash-command-imagemagick/src/remap.ts
466
- import { yieldTurn as yieldTurn4 } from "../safe-bash-contracts/yield.js";
467
-
468
- // packages/safe-bash-command-imagemagick/src/raster-backing.ts
469
- import { yieldTurn as yieldTurn3 } from "../safe-bash-contracts/yield.js";
470
- var RasterBackingCache = class {
471
- constructor(storage, signal) {
472
- this.storage = storage;
473
- this.signal = signal;
474
- }
475
- storage;
476
- signal;
477
- pages = /* @__PURE__ */ new Map();
478
- work = 0;
479
- async allocate(length) {
480
- const region = { position: this.storage.allocate(length), length }, zero = new Uint8Array(Math.min(4096, length));
481
- for (let offset = 0; offset < length; offset += zero.length) {
482
- if (offset % 65536 === 0) await yieldTurn3(this.signal);
483
- await this.storage.write(region.position + offset, zero.subarray(0, Math.min(zero.length, length - offset)), { signal: this.signal });
484
- }
485
- return region;
486
- }
487
- async page(region, offset) {
488
- this.signal.throwIfAborted();
489
- if (++this.work % 16384 === 0) await yieldTurn3(this.signal);
490
- const start = Math.floor(offset / 4096) * 4096, key = region.position + ":" + start;
491
- let page = this.pages.get(key);
492
- if (page) {
493
- this.pages.delete(key);
494
- this.pages.set(key, page);
495
- return page;
496
- }
497
- if (this.pages.size === 32) {
498
- const oldest = this.pages.entries().next().value;
499
- if (oldest[1].dirty) await this.storage.write(oldest[1].position, oldest[1].bytes, { signal: this.signal });
500
- this.pages.delete(oldest[0]);
501
- }
502
- const length = Math.min(4096, region.length - start), borrowed = await this.storage.read(region.position + start, length, { signal: this.signal });
503
- if (borrowed.length !== length) throw new Error("Truncated raster backing");
504
- const bytes = new Uint8Array(borrowed);
505
- page = { position: region.position + start, bytes, view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), dirty: false };
506
- this.pages.set(key, page);
507
- return page;
508
- }
509
- async byte(region, offset) {
510
- return (await this.page(region, offset)).bytes[offset % 4096];
511
- }
512
- async mark(region, offset) {
513
- const page = await this.page(region, offset);
514
- page.bytes[offset % 4096] = 1;
515
- page.dirty = true;
516
- }
517
- async uint32(region, offset) {
518
- return (await this.page(region, offset)).view.getUint32(offset % 4096, true);
519
- }
520
- async setUint32(region, offset, value) {
521
- const page = await this.page(region, offset);
522
- page.view.setUint32(offset % 4096, value, true);
523
- page.dirty = true;
524
- }
525
- async float32(region, offset) {
526
- return (await this.page(region, offset)).view.getFloat32(offset % 4096, true);
527
- }
528
- async setFloat32(region, offset, value) {
529
- const page = await this.page(region, offset);
530
- page.view.setFloat32(offset % 4096, value, true);
531
- page.dirty = true;
532
- }
533
- async flush() {
534
- for (const page of this.pages.values()) {
535
- this.signal.throwIfAborted();
536
- if (page.dirty) {
537
- await this.storage.write(page.position, page.bytes, { signal: this.signal });
538
- page.dirty = false;
539
- }
540
- }
541
- }
542
- };
543
-
544
- // packages/safe-bash-command-imagemagick/src/remap.ts
676
+ import { yieldTurn as yieldTurn5 } from "../safe-bash-contracts/yield.js";
545
677
  async function remapStoredImage(image, paletteImage, storage, dither, signal) {
546
678
  const palette = [], seen = /* @__PURE__ */ new Set();
547
679
  for (let offset = 0; offset < paletteImage.width * paletteImage.height * 4 && palette.length < 256; offset += 4096) {
548
- await yieldTurn4(signal);
680
+ await yieldTurn5(signal);
549
681
  const length = Math.min(4096, paletteImage.width * paletteImage.height * 4 - offset), bytes = await storage.read(paletteImage.position + offset, length, { signal });
550
682
  if (bytes.length !== length) throw new Error("Truncated remap palette");
551
683
  for (let i = 0; i < length && palette.length < 256; i += 4) {
@@ -560,7 +692,7 @@ async function remapStoredImage(image, paletteImage, storage, dither, signal) {
560
692
  const count = image.width * image.height, cache = new RasterBackingCache(storage, signal);
561
693
  const errors = dither ? { position: storage.allocate(count * 12), length: count * 12 } : void 0;
562
694
  if (errors) for (let start = 0; start < count; start += 1024) {
563
- await yieldTurn4(signal);
695
+ await yieldTurn5(signal);
564
696
  const length = Math.min(1024, count - start), pixels = await storage.read(image.position + start * 4, length * 4, { signal });
565
697
  if (pixels.length !== length * 4) throw new Error("Truncated remap pixels");
566
698
  const bytes = new Uint8Array(length * 12), view = new DataView(bytes.buffer);
@@ -569,7 +701,7 @@ async function remapStoredImage(image, paletteImage, storage, dither, signal) {
569
701
  }
570
702
  const position = storage.allocate(count * 4);
571
703
  for (let start = 0; start < count; start += 1024) {
572
- await yieldTurn4(signal);
704
+ await yieldTurn5(signal);
573
705
  const length = Math.min(1024, count - start), pixels = new Uint8Array(await storage.read(image.position + start * 4, length * 4, { signal }));
574
706
  if (pixels.length !== length * 4) throw new Error("Truncated remap pixels");
575
707
  for (let i = 0; i < length; i++) {
@@ -609,12 +741,15 @@ async function remapStoredImage(image, paletteImage, storage, dither, signal) {
609
741
  }
610
742
 
611
743
  // packages/safe-bash-command-imagemagick/src/floodfill.ts
612
- import { yieldTurn as yieldTurn5 } from "../safe-bash-contracts/yield.js";
613
- async function floodfillStoredImage(image, storage, seedX, seedY, target, replacement, fuzz, signal) {
744
+ import { yieldTurn as yieldTurn6 } from "../safe-bash-contracts/yield.js";
745
+ function matchesAlphaFloodfill(r, g, b, a, refR, refG, refB, refA, fuzz) {
746
+ return (a - refA) ** 2 + ((r - refR) ** 2 + (g - refG) ** 2 + (b - refB) ** 2) * (a / 255) * (refA / 255) / 3 <= 2 * fuzz ** 2;
747
+ }
748
+ async function floodfillStoredImage(image, storage, seedX, seedY, target, replacement, fuzz, signal, alphaOnly = false) {
614
749
  const count = image.width * image.height, source = { position: image.position, length: count * 4 }, cache = new RasterBackingCache(storage, signal);
615
750
  const start = seedY * image.width + seedX, seed = await cache.uint32(source, start * 4);
616
751
  const r = target ? target.r : seed & 255, g = target ? target.g : seed >>> 8 & 255, b = target ? target.b : seed >>> 16 & 255;
617
- const matches = (pixel) => Math.max(Math.abs((pixel & 255) - r), Math.abs((pixel >>> 8 & 255) - g), Math.abs((pixel >>> 16 & 255) - b)) <= fuzz;
752
+ const matches = (pixel) => alphaOnly ? matchesAlphaFloodfill(pixel & 255, pixel >>> 8 & 255, pixel >>> 16 & 255, pixel >>> 24, r, g, b, seed >>> 24, fuzz) : Math.max(Math.abs((pixel & 255) - r), Math.abs((pixel >>> 8 & 255) - g), Math.abs((pixel >>> 16 & 255) - b)) <= fuzz;
618
753
  if (!matches(seed)) return image;
619
754
  const visited = await cache.allocate(count), stack = await cache.allocate(count * 4);
620
755
  let tail = 1;
@@ -631,14 +766,16 @@ async function floodfillStoredImage(image, storage, seedX, seedY, target, replac
631
766
  await cache.flush();
632
767
  const position = storage.allocate(count * 4);
633
768
  for (let start2 = 0; start2 < count; start2 += 1024) {
634
- await yieldTurn5(signal);
769
+ await yieldTurn6(signal);
635
770
  const length = Math.min(1024, count - start2), pixels = new Uint8Array(await storage.read(image.position + start2 * 4, length * 4, { signal }));
636
771
  const flags = await storage.read(visited.position + start2, length, { signal });
637
772
  if (pixels.length !== length * 4 || flags.length !== length) throw new Error("Truncated floodfill pixels");
638
773
  for (let i = 0; i < length; i++) if (flags[i]) {
639
- pixels[i * 4] = replacement.r;
640
- pixels[i * 4 + 1] = replacement.g;
641
- pixels[i * 4 + 2] = replacement.b;
774
+ if (!alphaOnly) {
775
+ pixels[i * 4] = replacement.r;
776
+ pixels[i * 4 + 1] = replacement.g;
777
+ pixels[i * 4 + 2] = replacement.b;
778
+ }
642
779
  pixels[i * 4 + 3] = replacement.a;
643
780
  }
644
781
  await storage.write(position + start2 * 4, pixels, { signal });
@@ -816,7 +953,7 @@ function* convolvePixelSteps(image, kernel, bias) {
816
953
  // packages/safe-bash-command-imagemagick/src/encoded-snapshots.ts
817
954
  import { encodeStoredImage } from "../image-ast/portable.js";
818
955
  import { IntegerTable as IntegerTable3 } from "@poe-platform/safe-fs/storage";
819
- import { yieldTurn as yieldTurn6 } from "../safe-bash-contracts/yield.js";
956
+ import { yieldTurn as yieldTurn7 } from "../safe-bash-contracts/yield.js";
820
957
  var EncodedSnapshots = class {
821
958
  constructor(storage, signal) {
822
959
  this.storage = storage;
@@ -846,7 +983,7 @@ var EncodedSnapshots = class {
846
983
  };
847
984
  try {
848
985
  while (true) {
849
- await yieldTurn6(this.signal);
986
+ await yieldTurn7(this.signal);
850
987
  const next = await encoder.next();
851
988
  if (next.done) {
852
989
  if (!next.value) throw new Error("Image encoding did not finish");
@@ -858,7 +995,7 @@ var EncodedSnapshots = class {
858
995
  new DataView(header.buffer).setFloat64(0, bytes.length, true);
859
996
  await this.storage.write(position, header);
860
997
  for (let start = 0; start < bytes.length; start += 4096) {
861
- await yieldTurn6(this.signal);
998
+ await yieldTurn7(this.signal);
862
999
  await this.storage.write(position + 8 + start, bytes.subarray(start, start + 4096));
863
1000
  }
864
1001
  await this.buckets.set(hash, BigInt(position));
@@ -897,7 +1034,7 @@ var EncodedSnapshots = class {
897
1034
  const decoder = new TextDecoder();
898
1035
  let text = "";
899
1036
  for (let start = 0; start < size; start += 4096) {
900
- await yieldTurn6(this.signal);
1037
+ await yieldTurn7(this.signal);
901
1038
  text += decoder.decode(await this.storage.read(position + 8 + start, Math.min(4096, size - start)), { stream: true });
902
1039
  }
903
1040
  const record = JSON.parse(text + decoder.decode());
@@ -907,7 +1044,7 @@ var EncodedSnapshots = class {
907
1044
  async find(path, hash) {
908
1045
  let position = Number(await this.buckets.get(hash) ?? -1n);
909
1046
  while (position >= 0) {
910
- await yieldTurn6(this.signal);
1047
+ await yieldTurn7(this.signal);
911
1048
  const record = await this.record(position);
912
1049
  if (record.path === path) return record;
913
1050
  position = record.next ?? -1;
@@ -971,7 +1108,7 @@ import { FsError as FsError2 } from "@poe-platform/safe-fs/contracts";
971
1108
  import { resolvePath as resolvePath2 } from "../safe-bash-contracts/path.js";
972
1109
  import { writeBytes } from "../safe-bash-contracts/io.js";
973
1110
  import { writeFileOutput } from "../safe-bash-contracts/filesystem-output-budget.js";
974
- import { yieldTurn as yieldTurn7 } from "../safe-bash-contracts/yield.js";
1111
+ import { yieldTurn as yieldTurn8 } from "../safe-bash-contracts/yield.js";
975
1112
  var CompareInputFailure = class extends Error {
976
1113
  constructor(reason) {
977
1114
  super("Comparison input failed");
@@ -996,7 +1133,7 @@ async function withCompareFiles(input, stdinBytes, signal, run) {
996
1133
  const { data, data16: ignored, ...metadata } = image, position = storage.allocate(data.length);
997
1134
  for (let offset = 0; offset < data.length; offset += 16384) {
998
1135
  if (offset % 1048576 === 0)
999
- await yieldTurn7(signal);
1136
+ await yieldTurn8(signal);
1000
1137
  await storage.write(position + offset, data.subarray(offset, offset + 16384));
1001
1138
  }
1002
1139
  return { ...metadata, position };
@@ -1037,7 +1174,7 @@ async function withCompareFiles(input, stdinBytes, signal, run) {
1037
1174
  }
1038
1175
  const encoded = snapshot ? (async function* () {
1039
1176
  for (let position = 0; position < snapshot.source.size; position += 16384) {
1040
- await yieldTurn7(signal);
1177
+ await yieldTurn8(signal);
1041
1178
  yield new Uint8Array(await snapshot.source.read(position, Math.min(16384, snapshot.source.size - position), io));
1042
1179
  }
1043
1180
  })() : encodeStoredImage2(image, storage, signal, encoding), chunks = [];
@@ -1106,7 +1243,7 @@ async function withCompareFiles(input, stdinBytes, signal, run) {
1106
1243
  try {
1107
1244
  let next = iterator.next();
1108
1245
  while (!next.done) {
1109
- await yieldTurn7(signal);
1246
+ await yieldTurn8(signal);
1110
1247
  let configured = next.value, image;
1111
1248
  try {
1112
1249
  image = bytes ? await retain(decodeImage(bytes, configured)) : await decodeFileImage(checked, storage, fs, cwd, signal, configured);
@@ -1438,7 +1575,7 @@ async function withIdentifyFiles(input, stdinBytes, signal, run) {
1438
1575
  // packages/safe-bash-command-imagemagick/src/index.ts
1439
1576
  import { FsError as FsError4 } from "../safe-bash-contracts/errors.js";
1440
1577
  import { resolvePath as resolvePath3 } from "../safe-bash-contracts/path.js";
1441
- import { yieldTurn as yieldTurn8, drainCooperativeSteps as drainSteps } from "../safe-bash-contracts/yield.js";
1578
+ import { yieldTurn as yieldTurn9, drainCooperativeSteps as drainSteps } from "../safe-bash-contracts/yield.js";
1442
1579
  import { InputByteBudget } from "../safe-bash-contracts/io.js";
1443
1580
  import { writeFileOutput as writeFileOutput2 } from "../safe-bash-contracts/filesystem-output-budget.js";
1444
1581
  import {
@@ -1836,6 +1973,7 @@ function createDefaultState() {
1836
1973
  quality: 92,
1837
1974
  density: 72,
1838
1975
  fuzz: 10,
1976
+ alphaFuzz: 0,
1839
1977
  kernel: "lanczos3",
1840
1978
  compose: "over",
1841
1979
  composeRaw: "Over",
@@ -1955,7 +2093,7 @@ function* createRoseImageSteps() {
1955
2093
  data
1956
2094
  };
1957
2095
  }
1958
- function* applyMagickFloodfillSteps(img, geomStr, targetColor, replacement, fuzz, signal) {
2096
+ function* applyMagickFloodfillSteps(img, geomStr, targetColor, replacement, fuzz, signal, alphaOnly = false) {
1959
2097
  let cooperativeWork = 0;
1960
2098
  const g = parseMagickGeometry(geomStr);
1961
2099
  const w = img.width;
@@ -1969,6 +2107,7 @@ function* applyMagickFloodfillSteps(img, geomStr, targetColor, replacement, fuzz
1969
2107
  const refB = targetColor ? targetColor.b : out[seedOff + 2];
1970
2108
  const matchesRef = (pIdx) => {
1971
2109
  const off = pIdx * 4;
2110
+ if (alphaOnly) return matchesAlphaFloodfill(out[off], out[off + 1], out[off + 2], out[off + 3], refR, refG, refB, img.data[seedOff + 3], fuzz);
1972
2111
  return Math.max(
1973
2112
  Math.abs(out[off] - refR),
1974
2113
  Math.abs(out[off + 1] - refG),
@@ -1988,9 +2127,11 @@ function* applyMagickFloodfillSteps(img, geomStr, targetColor, replacement, fuzz
1988
2127
  yield;
1989
2128
  const cur = queue[head++];
1990
2129
  const off = cur * 4;
1991
- out[off] = replacement.r;
1992
- out[off + 1] = replacement.g;
1993
- out[off + 2] = replacement.b;
2130
+ if (!alphaOnly) {
2131
+ out[off] = replacement.r;
2132
+ out[off + 1] = replacement.g;
2133
+ out[off + 2] = replacement.b;
2134
+ }
1994
2135
  out[off + 3] = replacement.a;
1995
2136
  const cx = cur % w;
1996
2137
  const cy = Math.floor(cur / w);
@@ -4095,7 +4236,7 @@ async function decodeStoredMagickSvg(svg, density, backend, signal) {
4095
4236
  const encoder = new TextEncoder(), base = backend.storage.allocate(0);
4096
4237
  let size = 0;
4097
4238
  for (let offset = 0; offset < svg.length; ) {
4098
- await yieldTurn8(signal);
4239
+ await yieldTurn9(signal);
4099
4240
  const bytes = new Uint8Array(4096), { read, written } = encoder.encodeInto(svg.slice(offset, offset + 4097), bytes);
4100
4241
  await backend.storage.write(backend.storage.allocate(written), bytes.subarray(0, written));
4101
4242
  offset += read;
@@ -4207,14 +4348,14 @@ function* magickDrawSteps(img, drawCmd, state) {
4207
4348
  stroke = rgbaToCss(parseColor(tokens[i++] ?? "#000000"));
4208
4349
  } else if (cmd === "stroke-width" || cmd === "strokewidth") {
4209
4350
  strokeWidth = Math.max(0, num());
4210
- } else if (cmd === "color") {
4351
+ } else if (cmd === "color" || cmd === "alpha") {
4211
4352
  const x = num(), y = num(), method = tokens[i++]?.toLowerCase();
4212
- if (method !== "floodfill" || !Number.isFinite(x) || !Number.isFinite(y)) throw new Error("Color drawing requires finite coordinates and the floodfill method");
4353
+ if (method !== "floodfill" || !Number.isFinite(x) || !Number.isFinite(y)) throw new Error(`${cmd} drawing requires finite coordinates and the floodfill method`);
4213
4354
  if (svgElements.length > 0) {
4214
4355
  yield { kind: "svg", svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${img.width}" height="${img.height}">${svgElements.join("")}</svg>` };
4215
4356
  svgElements.length = 0;
4216
4357
  }
4217
- yield { kind: "floodfill", x: Math.max(0, Math.min(img.width - 1, Math.round(x))), y: Math.max(0, Math.min(img.height - 1, Math.round(y))), color: parseColor(fill) };
4358
+ yield { kind: "floodfill", x: Math.max(0, Math.min(img.width - 1, Math.round(x))), y: Math.max(0, Math.min(img.height - 1, Math.round(y))), color: parseColor(fill), alphaOnly: cmd === "alpha" };
4218
4359
  } else if (cmd === "rectangle") {
4219
4360
  const x0 = num();
4220
4361
  const y0 = num();
@@ -4764,7 +4905,7 @@ async function drainIdentifySteps(steps, signal) {
4764
4905
  while (!next.done) {
4765
4906
  try {
4766
4907
  if (next.value) await next.value.run();
4767
- else await yieldTurn8(signal);
4908
+ else await yieldTurn9(signal);
4768
4909
  next = steps.next();
4769
4910
  } catch (error) {
4770
4911
  next = steps.throw(error);
@@ -4961,9 +5102,15 @@ function* applyMagickLookup(image, lookup) {
4961
5102
  }
4962
5103
  return { ...image, data };
4963
5104
  }
5105
+ function parseConnectedComponents(tokens, state, start) {
5106
+ const value = tokens[start + 1];
5107
+ if (tokens[start] !== "-connected-components") throw new Error(`Unsupported option: ${tokens[start]}`);
5108
+ if (value !== "4" && value !== "8") throw new Error("connected-components: connectivity must be 4 or 8");
5109
+ if (state.alphaFuzz !== 0) throw new Error("connected-components: nonzero fuzz is unsupported");
5110
+ return Number(value);
5111
+ }
4964
5112
  function parseMagickAnalysisOperation(tokens, state, start) {
4965
5113
  const token = tokens[start];
4966
- if (token === "-connected-components") throw new Error("Unsupported option: -connected-components");
4967
5114
  if (!["-auto-level", "-auto-gamma", "-normalize", "-contrast-stretch", "-linear-stretch", "-equalize"].includes(token ?? "")) return;
4968
5115
  let end = start;
4969
5116
  const argument = token === "-normalize" ? "2%x1%" : token === "-contrast-stretch" || token === "-linear-stretch" ? tokens[++end] ?? "0%x0%" : "";
@@ -5252,7 +5399,7 @@ async function transformStoredMagickPixels(image, backend, operation, signal) {
5252
5399
  const size = image.width * image.height * 4, position = backend.storage.allocate(size);
5253
5400
  let result = image;
5254
5401
  for (let offset = 0; offset < size; offset += 16384) {
5255
- if (offset % 1048576 === 0) await yieldTurn8(signal);
5402
+ if (offset % 1048576 === 0) await yieldTurn9(signal);
5256
5403
  const data = await backend.storage.read(image.position + offset, Math.min(16384, size - offset));
5257
5404
  const transformed = await drainSteps(operation.apply({ ...image, width: data.length / 4, height: 1, data }), signal);
5258
5405
  const { data: pixels, data16: ignored, ...metadata } = transformed;
@@ -5300,6 +5447,7 @@ function applyMagickReadSetting(tokens, state, start) {
5300
5447
  state.density = Math.max(1, Math.round(g.width ?? 72));
5301
5448
  } else if (t === "-fuzz") {
5302
5449
  const rawFuzz = tokens[++i] ?? "10";
5450
+ state.alphaFuzz = rawFuzz.endsWith("%") ? parseFloat(rawFuzz) * 255 / 100 : parseFloat(rawFuzz) / 257;
5303
5451
  state.fuzz = rawFuzz.endsWith("%") ? Math.round(parseFloat(rawFuzz) / 100 * 255) : Math.round(parseFloat(rawFuzz));
5304
5452
  } else if (t === "-filter") {
5305
5453
  state.kernel = parseKernel(tokens[++i] ?? "lanczos");
@@ -5309,18 +5457,24 @@ function applyMagickReadSetting(tokens, state, start) {
5309
5457
  state.compose = parseCompose(cRaw);
5310
5458
  } else if (t === "-define") {
5311
5459
  const defStr = tokens[++i] ?? "";
5460
+ if (defStr.toLowerCase().startsWith("connected-components:")) {
5461
+ const [key, value, extra] = defStr.toLowerCase().split("=");
5462
+ if (extra !== void 0 || key !== "connected-components:verbose" || value !== "true" && value !== "false") throw new Error(`Unsupported connected-components define: ${defStr}`);
5463
+ state.connectedVerbose = value === "true";
5464
+ }
5312
5465
  const m = /^compose:args=(.*)$/i.exec(defStr);
5313
5466
  if (m)
5314
5467
  state.composeArgs = m[1];
5315
5468
  } else if (t === "-set") {
5316
5469
  const k = (tokens[++i] ?? "").toLowerCase();
5317
5470
  const v = tokens[++i] ?? "";
5471
+ if (k.startsWith("option:connected-components:") || k.startsWith("connected-components:")) throw new Error(`Unsupported connected-components setting: ${k}`);
5318
5472
  if (k === "option:compose:args")
5319
5473
  state.composeArgs = v;
5320
5474
  } else return void 0;
5321
5475
  return i;
5322
5476
  }
5323
- function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], stdinBytes, signal) {
5477
+ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], stdinBytes, signal, analysis = { text: "" }) {
5324
5478
  let cooperativeWork = 63;
5325
5479
  let stack = [];
5326
5480
  let operandsOnly = false;
@@ -5354,7 +5508,7 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
5354
5508
  const endIdx = depth === 0 ? j - 1 : j;
5355
5509
  const subTokens = tokens.slice(i + 1, endIdx);
5356
5510
  const subState = { ...state };
5357
- const subResult = yield* evaluatePipelineTokensSteps(subTokens, files, subState, stack, stdinBytes, signal);
5511
+ const subResult = yield* evaluatePipelineTokensSteps(subTokens, files, subState, stack, stdinBytes, signal, analysis);
5358
5512
  stack.push(...subResult);
5359
5513
  i = j;
5360
5514
  continue;
@@ -5365,6 +5519,13 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
5365
5519
  const analysisOperation = setting === void 0 && !pixelOperation ? parseMagickAnalysisOperation(tokens, state, i) : void 0;
5366
5520
  if (setting !== void 0) {
5367
5521
  i = setting;
5522
+ } else if (t === "-connected-components" || t === "+connected-components") {
5523
+ const connectivity = parseConnectedComponents(tokens, state, i++);
5524
+ stack = yield* mapSteps(stack, function* (image) {
5525
+ const result = yield* connectedComponentsSteps(image, connectivity, state.connectedVerbose ?? false);
5526
+ analysis.text += result.text;
5527
+ return result.image;
5528
+ });
5368
5529
  } else if (analysisOperation) {
5369
5530
  i = analysisOperation.end;
5370
5531
  stack = yield* mapSteps(stack, function* (image) {
@@ -5709,7 +5870,7 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
5709
5870
  continue;
5710
5871
  }
5711
5872
  if (step.kind === "shape") im = yield* applyMagickRasterSteps(im, drawShapePixelSteps(im, step), signal);
5712
- else if (step.kind === "floodfill") im = yield* applyMagickFloodfillSteps(im, `+${step.x}+${step.y}`, void 0, step.color, state.fuzz, signal);
5873
+ else if (step.kind === "floodfill") im = yield* applyMagickFloodfillSteps(im, `+${step.x}+${step.y}`, void 0, step.color, step.alphaOnly ? state.alphaFuzz : state.fuzz, signal, step.alphaOnly);
5713
5874
  else {
5714
5875
  const overlay = decodeImage3(new TextEncoder().encode(step.svg), { density: state.density });
5715
5876
  im = yield* applyMagickRasterSteps(im, drawOverlayPixelSteps(im, overlay), signal, im, [im, overlay]);
@@ -5879,6 +6040,11 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
5879
6040
  return stack;
5880
6041
  }
5881
6042
  function* runConvertCliSteps(argv, files, stdinBytes, signal) {
6043
+ const analysis = { text: "" };
6044
+ const result = yield* runConvertCliResultSteps(argv, files, stdinBytes, signal, analysis);
6045
+ return { ...result, stdout: analysis.text + result.stdout };
6046
+ }
6047
+ function* runConvertCliResultSteps(argv, files, stdinBytes, signal, analysis = { text: "" }) {
5882
6048
  const listRes = tryHandleMagickListOption(argv);
5883
6049
  if (listRes) return listRes;
5884
6050
  let cooperativeWork = 63;
@@ -5901,7 +6067,7 @@ function* runConvertCliSteps(argv, files, stdinBytes, signal) {
5901
6067
  const pipelineTokens = argv.slice(0, -1);
5902
6068
  const state = createDefaultState();
5903
6069
  try {
5904
- const stack = yield* evaluatePipelineTokensSteps(pipelineTokens, files, state, [], stdinBytes, signal);
6070
+ const stack = yield* evaluatePipelineTokensSteps(pipelineTokens, files, state, [], stdinBytes, signal, analysis);
5905
6071
  if (stack.length === 0) {
5906
6072
  return {
5907
6073
  exitCode: 1,
@@ -5912,6 +6078,7 @@ function* runConvertCliSteps(argv, files, stdinBytes, signal) {
5912
6078
  }
5913
6079
  const finalImg = stack[stack.length - 1];
5914
6080
  const outLower = outSpec.toLowerCase();
6081
+ if (outLower === "null:") return { exitCode: 0, stdout: "", stderr: "" };
5915
6082
  if (outLower === "info:" || outLower === "info:-") {
5916
6083
  const formatted = [];
5917
6084
  for (const [idx, im] of stack.entries()) {
@@ -6020,7 +6187,7 @@ async function transformStoredMagickRaster(image, backend, steps, signal, dimens
6020
6187
  signal.throwIfAborted();
6021
6188
  const request = next.value;
6022
6189
  if (!request) {
6023
- await yieldTurn8(signal);
6190
+ await yieldTurn9(signal);
6024
6191
  await checkpoint?.();
6025
6192
  next = steps.next();
6026
6193
  } else if (request.kind === "write") {
@@ -6051,7 +6218,7 @@ async function morphStoredMagickImage(image, backend, plan, signal) {
6051
6218
  const position = backend.storage.allocate(size);
6052
6219
  const a = images[step.a], b = images[step.b];
6053
6220
  for (let offset = 0; offset < size; offset += 16384) {
6054
- await yieldTurn8(signal);
6221
+ await yieldTurn9(signal);
6055
6222
  const length = Math.min(16384, size - offset);
6056
6223
  const left = new Uint8Array(await backend.storage.read(a.position + offset, length));
6057
6224
  const bytes = await drainSteps(subtractMagickPixelSteps(left, await backend.storage.read(b.position + offset, length)), signal);
@@ -6100,7 +6267,7 @@ async function transformStoredMagickCoordinates(image, backend, token, geometry,
6100
6267
  let work = 0;
6101
6268
  for (const y of ys) for (let row = 0; row < y.length; row++) for (const x of xs) {
6102
6269
  for (let offset = 0; offset < x.length; offset += 4096) {
6103
- if (work++ % 64 === 0) await yieldTurn8(signal);
6270
+ if (work++ % 64 === 0) await yieldTurn9(signal);
6104
6271
  const length = Math.min(4096, x.length - offset) * 4;
6105
6272
  const bytes = await backend.storage.read(image.position + ((y.source + row) * w + x.source + offset) * 4, length);
6106
6273
  await backend.storage.write(position + ((y.target + row) * width + x.target + offset) * 4, bytes);
@@ -6112,12 +6279,12 @@ async function compositeStoredMagick(base, overlay, backend, signal, left, top,
6112
6279
  let hasAlpha = base.hasAlpha || !generic && !magickCompositeMode(mode).delegated;
6113
6280
  const position = inPlace ? base.position : backend.storage.allocate(base.width * base.height * 4);
6114
6281
  for (let start = 0; !inPlace && start < base.width * base.height * 4; start += 16384) {
6115
- await yieldTurn8(signal);
6282
+ await yieldTurn9(signal);
6116
6283
  await backend.storage.write(position + start, await backend.storage.read(base.position + start, Math.min(16384, base.width * base.height * 4 - start)));
6117
6284
  }
6118
6285
  for (let y = Math.max(0, top); y < Math.min(base.height, top + overlay.height); y++) {
6119
6286
  for (let x = Math.max(0, left); x < Math.min(base.width, left + overlay.width); x += 4096) {
6120
- await yieldTurn8(signal);
6287
+ await yieldTurn9(signal);
6121
6288
  const count = Math.min(4096, Math.min(base.width, left + overlay.width) - x), destination = (y * base.width + x) * 4;
6122
6289
  const dst = { ...base, width: count, height: 1, data: new Uint8Array(await backend.storage.read(position + destination, count * 4)) };
6123
6290
  const src = { ...overlay, width: count, height: 1, data: new Uint8Array(await backend.storage.read(overlay.position + ((y - top) * overlay.width + x - left) * 4, count * 4)) };
@@ -6142,6 +6309,11 @@ async function compositeStoredMagick(base, overlay, backend, signal, left, top,
6142
6309
  return { ...base, position, hasAlpha };
6143
6310
  }
6144
6311
  async function tryConvertFiles(argv, input, stdinBytes, signal) {
6312
+ const analysis = { text: "" };
6313
+ const result = await tryConvertFilesResult(argv, input, stdinBytes, signal, analysis);
6314
+ return result ? { ...result, stdout: analysis.text + result.stdout } : void 0;
6315
+ }
6316
+ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis) {
6145
6317
  if (!input.filesystem.capabilities || !input.filesystem.open || !input.filesystem.removeFileConditional) return;
6146
6318
  const outSpec = argv.at(-1);
6147
6319
  const optionArgs = argv.slice(0, argv.indexOf("--") < 0 ? argv.length : argv.indexOf("--"));
@@ -6150,7 +6322,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6150
6322
  return drainSteps(runConvertCliSteps(argv, /* @__PURE__ */ new Map(), stdinBytes, signal), signal);
6151
6323
  }
6152
6324
  const output = inferOutputFormat(outSpec, "png");
6153
- await yieldTurn8(signal);
6325
+ await yieldTurn9(signal);
6154
6326
  let state = createDefaultState();
6155
6327
  const tokens = argv.slice(0, -1), scopes = [];
6156
6328
  const steps = [];
@@ -6167,7 +6339,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6167
6339
  };
6168
6340
  try {
6169
6341
  for (let i = 0; i < tokens.length; i++) {
6170
- if (i && i % 64 === 0) await yieldTurn8(signal);
6342
+ if (i && i % 64 === 0) await yieldTurn9(signal);
6171
6343
  const token = tokens[i];
6172
6344
  if (token === "") continue;
6173
6345
  if (!operandsOnly && token === "--") {
@@ -6210,19 +6382,19 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6210
6382
  if (token.endsWith("clone") || token.endsWith("duplicate")) {
6211
6383
  const source = stack.length ? stack : parents.at(-1) ?? stack, length = source.length;
6212
6384
  for (let repeat = 0; repeat < count; repeat++) for (const index of parseMagickIndexSpec(spec, length)) {
6213
- await yieldTurn8(signal);
6385
+ await yieldTurn9(signal);
6214
6386
  await stack.push(await source.get(index));
6215
6387
  }
6216
6388
  } else if (token.endsWith("delete")) await stack.remove(parseMagickIndexSpec(spec, stack.length));
6217
6389
  else if (token === "-reverse") {
6218
6390
  for (let n = 0; n < Math.floor(stack.length / 2); n++) {
6219
- await yieldTurn8(signal);
6391
+ await yieldTurn9(signal);
6220
6392
  await stack.swap(n, stack.length - 1 - n);
6221
6393
  }
6222
6394
  } else if (token.endsWith("insert")) {
6223
6395
  const raw = token === "+insert" ? 0 : Number(spec), target = raw < 0 ? Math.max(0, stack.length + raw) : Math.min(stack.length - 1, Math.max(0, raw));
6224
6396
  for (let n = stack.length - 1; n > target; n--) {
6225
- await yieldTurn8(signal);
6397
+ await yieldTurn9(signal);
6226
6398
  await stack.swap(n, n - 1);
6227
6399
  }
6228
6400
  } else {
@@ -6271,7 +6443,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6271
6443
  const a = await stack.get(index), b = await stack.get(index + 1);
6272
6444
  await result.push(a);
6273
6445
  for (let step = 1; step <= count; step++) {
6274
- await yieldTurn8(signal);
6446
+ await yieldTurn9(signal);
6275
6447
  const fraction = step / (count + 1), width = Math.max(1, Math.round(a.width * (1 - fraction) + b.width * fraction)), height = Math.max(1, Math.round(a.height * (1 - fraction) + b.height * fraction));
6276
6448
  const first = await resize(a, width, height), last = await resize(b, width, height);
6277
6449
  await result.push(await transformStoredMagickRaster(first, backend, interpolatePixelSteps(width * height * 4, fraction), signal, first, [first, last]));
@@ -6311,6 +6483,17 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6311
6483
  steps.push(async (image, backend) => image ? transformStoredMagickPixels(image, backend, pixelOperation, signal) : void 0);
6312
6484
  continue;
6313
6485
  }
6486
+ if (!operandsOnly && (token === "-connected-components" || token === "+connected-components")) {
6487
+ const connectivity = parseConnectedComponents(tokens, state, i++), verbose = state.connectedVerbose ?? false;
6488
+ steps.push(async (image, backend) => {
6489
+ if (!image) return;
6490
+ const result = await connectedComponentsStored(image, backend.storage, connectivity, verbose, signal);
6491
+ if (input.stdout) await writeIdentifyText(input.stdout, result.text, signal);
6492
+ else analysis.text += result.text;
6493
+ return result.image;
6494
+ });
6495
+ continue;
6496
+ }
6314
6497
  const analysisOperation = operandsOnly ? void 0 : parseMagickAnalysisOperation(tokens, state, i);
6315
6498
  if (analysisOperation) {
6316
6499
  i = analysisOperation.end;
@@ -6318,7 +6501,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6318
6501
  if (!image) return;
6319
6502
  const histogram = Array.from({ length: 4 }, () => new Float64Array(256)), size = image.width * image.height * 4;
6320
6503
  for (let offset = 0; offset < size; offset += 16384) {
6321
- if (offset % 1048576 === 0) await yieldTurn8(signal);
6504
+ if (offset % 1048576 === 0) await yieldTurn9(signal);
6322
6505
  await drainSteps(accumulateMagickHistogram(await backend.storage.read(image.position + offset, Math.min(16384, size - offset)), histogram), signal);
6323
6506
  }
6324
6507
  const lookup = analysisOperation.lookup(histogram, image);
@@ -6352,7 +6535,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6352
6535
  if (append && stack.length === 1) return stack;
6353
6536
  let width = 0, height = 0;
6354
6537
  for (let n = 0; n < stack.length; n++) {
6355
- await yieldTurn8(signal);
6538
+ await yieldTurn9(signal);
6356
6539
  const frame = await stack.get(n);
6357
6540
  width = append && !vertical ? width + frame.width : Math.max(width, frame.width);
6358
6541
  height = append && vertical ? height + frame.height : Math.max(height, frame.height);
@@ -6420,7 +6603,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6420
6603
  const output2 = inferOutputFormat(path, image.format);
6421
6604
  if (stack.length > 1 && (hasSceneOutputPattern(output2.path) || !adjoin)) {
6422
6605
  for (let index = 0; index < stack.length; index++) {
6423
- await yieldTurn8(signal);
6606
+ await yieldTurn9(signal);
6424
6607
  await backend.stage(await stack.get(index), formatSceneOutputPath(output2.path, index), { format: output2.format, quality });
6425
6608
  }
6426
6609
  } else await backend.stage(image, output2.path, { format: output2.format, quality });
@@ -6466,11 +6649,11 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6466
6649
  if (!image) return;
6467
6650
  for (const step of magickDrawSteps(image, drawing, settings)) {
6468
6651
  if (!step) {
6469
- await yieldTurn8(signal);
6652
+ await yieldTurn9(signal);
6470
6653
  continue;
6471
6654
  }
6472
6655
  if (step.kind === "shape") image = await transformStoredMagickRaster(image, backend, drawShapePixelSteps(image, step), signal);
6473
- else if (step.kind === "floodfill") image = await floodfillStoredImage(image, backend.storage, step.x, step.y, void 0, step.color, settings.fuzz, signal);
6656
+ else if (step.kind === "floodfill") image = await floodfillStoredImage(image, backend.storage, step.x, step.y, void 0, step.color, step.alphaOnly ? settings.alphaFuzz : settings.fuzz, signal, step.alphaOnly);
6474
6657
  else {
6475
6658
  const overlay = await decodeStoredMagickSvg(step.svg, settings.density, backend, signal);
6476
6659
  image = await transformStoredMagickRaster(image, backend, drawOverlayPixelSteps(image, overlay), signal, image, [image, overlay]);
@@ -6675,13 +6858,17 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6675
6858
  return { exitCode: 1, stdout: "", stderr: `magick: no images defined '${outSpec}'
6676
6859
  ` };
6677
6860
  }
6678
- await yieldTurn8(signal);
6861
+ await yieldTurn9(signal);
6862
+ if (outSpec.toLowerCase() === "null:") {
6863
+ await backend.publishPending();
6864
+ return { exitCode: 0, stdout: "", stderr: "" };
6865
+ }
6679
6866
  const lower = outSpec.toLowerCase(), info = lower === "info:" || lower === "info:-";
6680
6867
  if (info || lower.startsWith("txt:") || lower.startsWith("histogram:")) {
6681
6868
  async function* information() {
6682
6869
  let last = "";
6683
6870
  for (let index = 0; index < stack.length; index++) {
6684
- await yieldTurn8(signal);
6871
+ await yieldTurn9(signal);
6685
6872
  const frame = await stack.get(index);
6686
6873
  if (index && !state.formatStr) yield "\n";
6687
6874
  const text = state.formatStr ? await formatRetainedIdentify(state.formatStr, frame, { ...frame[magickInput], sceneIdx: index, sceneCount: stack.length, quality: state.quality }, async () => ({ image: frame, storage: backend.storage }), signal) : `${frame.width}x${frame.height} sRGB 8-bit`;
@@ -6707,7 +6894,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6707
6894
  }
6708
6895
  if (stack.length > 1 && (hasSceneOutputPattern(output.path) || !state.adjoin)) {
6709
6896
  for (let index = 0; index < stack.length; index++) {
6710
- await yieldTurn8(signal);
6897
+ await yieldTurn9(signal);
6711
6898
  await backend.stage(await stack.get(index), formatSceneOutputPath(output.path, index), { format: output.format, quality: state.quality });
6712
6899
  }
6713
6900
  await backend.publishPending();
@@ -6723,7 +6910,7 @@ async function tryConvertFiles(argv, input, stdinBytes, signal) {
6723
6910
  frame = await transformStoredImage(frame, backend.storage, { kind: "resize", ...resize }, signal);
6724
6911
  }
6725
6912
  for (let offset = 0; offset < size; offset += 16384) {
6726
- await yieldTurn8(signal);
6913
+ await yieldTurn9(signal);
6727
6914
  await backend.storage.write(position + index * size + offset, await backend.storage.read(frame.position + offset, Math.min(16384, size - offset)));
6728
6915
  }
6729
6916
  }
@@ -6971,13 +7158,13 @@ async function probeImageArguments(argv, files, stdinBytes, active) {
6971
7158
  await withImageInputs(files, stdinBytes, active, async (read) => {
6972
7159
  const inspect = async (source) => {
6973
7160
  for (let position = 0; position < source.size; position += 16384) {
6974
- await yieldTurn8(active);
7161
+ await yieldTurn9(active);
6975
7162
  await source.read(position, Math.min(16384, source.size - position), { signal: active });
6976
7163
  }
6977
7164
  };
6978
7165
  let operandsOnly = false;
6979
7166
  for (let index = 0; index < argv.length; index++) {
6980
- await yieldTurn8(active);
7167
+ await yieldTurn9(active);
6981
7168
  const token = argv[index];
6982
7169
  if (!operandsOnly && token === "--") {
6983
7170
  operandsOnly = true;
@@ -7037,7 +7224,7 @@ async function parseStoredCompareInput(token, state, backend, signal, readOption
7037
7224
  const [first, last] = baseToken.slice(baseToken.indexOf(":") + 1).split("-"), a = parseColor(checker ? "white" : first || "#ffffff"), b = parseColor(checker ? "black" : last || "#000000");
7038
7225
  const cx = (width - 1) / 2, cy = (height - 1) / 2, radius = Math.max(1, Math.hypot(cx, cy));
7039
7226
  for (let start = 0; start < width * height; start += 4096) {
7040
- if (start % 262144 === 0) await yieldTurn8(signal);
7227
+ if (start % 262144 === 0) await yieldTurn9(signal);
7041
7228
  const count = Math.min(4096, width * height - start), bytes = new Uint8Array(count * 4);
7042
7229
  for (let i = 0; i < count; i++) {
7043
7230
  const x = (start + i) % width, y = Math.floor((start + i) / width), offset = i * 4;
@@ -7099,7 +7286,7 @@ async function parseStoredCompareInput(token, state, backend, signal, readOption
7099
7286
  const width = state.sizeWidth, height = state.sizeHeight, position = backend.storage.allocate(width * height * 4);
7100
7287
  let cachedPosition = -1, cached = new Uint8Array();
7101
7288
  for (let start = 0; start < width * height; start += 4096) {
7102
- if (start % 262144 === 0) await yieldTurn8(signal);
7289
+ if (start % 262144 === 0) await yieldTurn9(signal);
7103
7290
  const count = Math.min(4096, width * height - start), bytes = new Uint8Array(count * 4);
7104
7291
  for (let offset = 0; offset < count; ) {
7105
7292
  const x = (start + offset) % width, y = Math.floor((start + offset) / width), px = x % pattern.width, py = y % pattern.height, blockX = Math.floor(px / 4096) * 4096;
@@ -7660,7 +7847,7 @@ async function runMontageFiles(argv, input, stdinBytes, signal) {
7660
7847
  } };
7661
7848
  };
7662
7849
  for (let index = 0; index < paths.length; index++) {
7663
- await yieldTurn8(signal);
7850
+ await yieldTurn9(signal);
7664
7851
  const token = paths[index];
7665
7852
  let path2 = parseInputToken(token.toLowerCase().startsWith("tile:") ? token.slice(5) : token).baseToken;
7666
7853
  const colon = path2.indexOf(":");
@@ -7668,7 +7855,7 @@ async function runMontageFiles(argv, input, stdinBytes, signal) {
7668
7855
  await backend.read(path2, async (source2) => {
7669
7856
  const position = backend.storage.allocate(source2.size);
7670
7857
  for (let start = 0; start < source2.size; start += 16384) {
7671
- await yieldTurn8(signal);
7858
+ await yieldTurn9(signal);
7672
7859
  await backend.storage.write(position + start, await source2.read(start, Math.min(16384, source2.size - start), { signal }));
7673
7860
  }
7674
7861
  await sources.set(BigInt(index * 5), BigInt(position));
@@ -7698,7 +7885,7 @@ async function runMontageFiles(argv, input, stdinBytes, signal) {
7698
7885
  await sources.set(BigInt(index * 5 + 3), BigInt(height));
7699
7886
  let directPng = metadata.format === "png" && !metadata.isProgressive && prefix[24] === 8 && (prefix[25] === 2 || prefix[25] === 6), idat = false;
7700
7887
  if (directPng) for (let offset = 8; offset + 8 <= source.size; ) {
7701
- await yieldTurn8(signal);
7888
+ await yieldTurn9(signal);
7702
7889
  const header = await source.read(offset, 8), size = new DataView(header.buffer, header.byteOffset, 4).getUint32(0, false);
7703
7890
  if (offset + 8 + size > source.size) break;
7704
7891
  const name = String.fromCharCode(...header.subarray(4));
@@ -7752,7 +7939,7 @@ async function runMontageFiles(argv, input, stdinBytes, signal) {
7752
7939
  let canvas = await createStoredCanvas(Math.max(1, cols * slotW), Math.max(1, rows * slotH), state.background, backend, signal);
7753
7940
  let anyTransparent = state.background.a < 255 || borderW > 0 && state.borderColor.a < 255;
7754
7941
  for (let index = 0; index < n; index++) {
7755
- await yieldTurn8(signal);
7942
+ await yieldTurn9(signal);
7756
7943
  const col = index % cols, row = Math.floor(index / cols);
7757
7944
  if (row >= rows) break;
7758
7945
  let image, directPng = false;
@@ -7775,13 +7962,13 @@ async function runMontageFiles(argv, input, stdinBytes, signal) {
7775
7962
  if (fast && borderW > 0 && state.borderColor.a > 0) {
7776
7963
  const border = await createStoredCanvas(width, height, state.borderColor, backend, signal), empty = new Uint8Array(16384);
7777
7964
  for (let row2 = borderW; row2 < height - borderW; row2++) for (let start = borderW; start < width - borderW; start += 4096) {
7778
- await yieldTurn8(signal);
7965
+ await yieldTurn9(signal);
7779
7966
  await backend.storage.write(border.position + (row2 * width + start) * 4, empty.subarray(0, Math.min(4096, width - borderW - start) * 4));
7780
7967
  }
7781
7968
  canvas = await compositeStoredMagick(canvas, border, backend, signal, x, y, "over", void 0, false, true, true);
7782
7969
  }
7783
7970
  if (!anyTransparent) for (let start = 0; start < image.width * image.height * 4; start += 16384) {
7784
- await yieldTurn8(signal);
7971
+ await yieldTurn9(signal);
7785
7972
  const bytes = await backend.storage.read(image.position + start, Math.min(16384, image.width * image.height * 4 - start));
7786
7973
  for (let k = 3; k < bytes.length; k += 4) if (bytes[k] < 255) {
7787
7974
  anyTransparent = true;
@@ -7867,7 +8054,7 @@ async function runBufferedImageFiles(argv, input, runner, stdinBytes, signal) {
7867
8054
  const hasOutputOperand = runner !== runIdentifyCli && !(runner === runMagickCli && argv[0] === "identify");
7868
8055
  let operandsOnly = false;
7869
8056
  for (const [index, token] of argv.entries()) {
7870
- if (++cooperativeWork % 64 === 0) await yieldTurn8(context.signal);
8057
+ if (++cooperativeWork % 64 === 0) await yieldTurn9(context.signal);
7871
8058
  if (!operandsOnly && token === "--") {
7872
8059
  operandsOnly = true;
7873
8060
  continue;
@@ -7902,7 +8089,7 @@ async function runBufferedImageFiles(argv, input, runner, stdinBytes, signal) {
7902
8089
  const chunks = [];
7903
8090
  let total = 0;
7904
8091
  for await (const chunk of readBytes2(context.stdin, signal)) {
7905
- if (++cooperativeWork % 64 === 0) await yieldTurn8(context.signal);
8092
+ if (++cooperativeWork % 64 === 0) await yieldTurn9(context.signal);
7906
8093
  chargeInput(chunk.byteLength);
7907
8094
  chunks.push(chunk);
7908
8095
  total += chunk.byteLength;
@@ -7910,7 +8097,7 @@ async function runBufferedImageFiles(argv, input, runner, stdinBytes, signal) {
7910
8097
  stdinBytes = new Uint8Array(total);
7911
8098
  let off = 0;
7912
8099
  for (const chunk of chunks) {
7913
- if (++cooperativeWork % 64 === 0) await yieldTurn8(context.signal);
8100
+ if (++cooperativeWork % 64 === 0) await yieldTurn9(context.signal);
7914
8101
  stdinBytes.set(chunk, off);
7915
8102
  off += chunk.byteLength;
7916
8103
  }
@@ -7930,7 +8117,7 @@ async function runBufferedImageFiles(argv, input, runner, stdinBytes, signal) {
7930
8117
  delivered = { ...result, stdout: "" };
7931
8118
  }
7932
8119
  for (const [key, val] of vfsFiles.entries()) {
7933
- if (++cooperativeWork % 64 === 0) await yieldTurn8(context.signal);
8120
+ if (++cooperativeWork % 64 === 0) await yieldTurn9(context.signal);
7934
8121
  if (existingSnap.get(key) !== val) {
7935
8122
  const abs = resolvePath3(context.cwd, key);
7936
8123
  await writeFileOutput2(context, val, (data) => context.fs.writeFile(abs, data, { signal }));