@workglow/util 0.2.23 → 0.2.24

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,6 +1,3 @@
1
- // src/media/imageCacheCodec.ts
2
- import { registerPortCodec } from "@workglow/util";
3
-
4
1
  // src/media/imageRasterCodecRegistry.ts
5
2
  var GLOBAL_CODEC_KEY = Symbol.for("@workglow/util/media/imageRasterCodec");
6
3
  var _g = globalThis;
@@ -86,7 +83,203 @@ async function decodeDataUriToNodeImageValue(dataUri) {
86
83
  }
87
84
  }
88
85
 
86
+ // src/media/cpuImage.ts
87
+ var FORMAT_TO_MIME = {
88
+ png: "image/png",
89
+ jpeg: "image/jpeg",
90
+ webp: "image/webp"
91
+ };
92
+
93
+ class CpuImage {
94
+ bin;
95
+ backend = "cpu";
96
+ constructor(bin) {
97
+ this.bin = bin;
98
+ }
99
+ get width() {
100
+ if (!this.bin)
101
+ throw new Error("CpuImage.width on a disposed image");
102
+ return this.bin.width;
103
+ }
104
+ get height() {
105
+ if (!this.bin)
106
+ throw new Error("CpuImage.height on a disposed image");
107
+ return this.bin.height;
108
+ }
109
+ get channels() {
110
+ if (!this.bin)
111
+ throw new Error("CpuImage.channels on a disposed image");
112
+ return this.bin.channels;
113
+ }
114
+ getBinary() {
115
+ if (!this.bin)
116
+ throw new Error("CpuImage.getBinary on a disposed image");
117
+ return this.bin;
118
+ }
119
+ static async from(value) {
120
+ if (isBrowserImageValue(value)) {
121
+ if (typeof OffscreenCanvas === "undefined") {
122
+ throw new Error("CpuImage.from(BrowserImageValue) requires OffscreenCanvas");
123
+ }
124
+ const off = new OffscreenCanvas(value.width, value.height);
125
+ const ctx = off.getContext("2d");
126
+ if (!ctx)
127
+ throw new Error("CpuImage.from: could not acquire 2D context");
128
+ ctx.drawImage(value.bitmap, 0, 0);
129
+ const id = ctx.getImageData(0, 0, value.width, value.height);
130
+ return new CpuImage({ data: id.data, width: value.width, height: value.height, channels: 4 });
131
+ }
132
+ if (isNodeImageValue(value)) {
133
+ const bin = await decodeNodeImageValue(value);
134
+ return new CpuImage(bin);
135
+ }
136
+ throw new Error("CpuImage.from: unrecognized ImageValue shape");
137
+ }
138
+ static fromRaw(bin) {
139
+ return new CpuImage(bin);
140
+ }
141
+ async toImageValue(previewScale) {
142
+ if (!this.bin)
143
+ throw new Error("CpuImage.toImageValue on a disposed image");
144
+ if (typeof OffscreenCanvas !== "undefined" && typeof createImageBitmap === "function") {
145
+ const off = new OffscreenCanvas(this.bin.width, this.bin.height);
146
+ const ctx = off.getContext("2d");
147
+ if (!ctx)
148
+ throw new Error("CpuImage.toImageValue could not acquire a 2D context");
149
+ const rgba2 = expandToRgba(this.bin);
150
+ ctx.putImageData(new ImageData(new Uint8ClampedArray(rgba2.buffer, rgba2.byteOffset, rgba2.byteLength), this.bin.width, this.bin.height), 0, 0);
151
+ const bitmap = await createImageBitmap(off);
152
+ const out2 = {
153
+ bitmap,
154
+ width: this.bin.width,
155
+ height: this.bin.height,
156
+ previewScale
157
+ };
158
+ this.bin = null;
159
+ return out2;
160
+ }
161
+ const rgba = expandToRgba(this.bin);
162
+ const buffer = Buffer.from(rgba.buffer, rgba.byteOffset, rgba.byteLength);
163
+ const out = {
164
+ buffer,
165
+ format: "raw-rgba",
166
+ width: this.bin.width,
167
+ height: this.bin.height,
168
+ previewScale
169
+ };
170
+ this.bin = null;
171
+ return out;
172
+ }
173
+ async encode(format, _quality) {
174
+ if (!this.bin)
175
+ throw new Error("CpuImage.encode on a disposed image");
176
+ const codec = getImageRasterCodec();
177
+ const dataUri = await codec.encodeDataUri(this.bin, FORMAT_TO_MIME[format]);
178
+ return dataUriToBytes(dataUri);
179
+ }
180
+ dispose() {
181
+ this.bin = null;
182
+ }
183
+ }
184
+ function expandToRgba(bin) {
185
+ if (bin.channels === 4)
186
+ return bin.data;
187
+ const px = bin.width * bin.height;
188
+ const out = new Uint8ClampedArray(px * 4);
189
+ if (bin.channels === 3) {
190
+ for (let i = 0;i < px; i++) {
191
+ out[i * 4 + 0] = bin.data[i * 3 + 0] ?? 0;
192
+ out[i * 4 + 1] = bin.data[i * 3 + 1] ?? 0;
193
+ out[i * 4 + 2] = bin.data[i * 3 + 2] ?? 0;
194
+ out[i * 4 + 3] = 255;
195
+ }
196
+ } else if (bin.channels === 1) {
197
+ for (let i = 0;i < px; i++) {
198
+ const g = bin.data[i] ?? 0;
199
+ out[i * 4 + 0] = g;
200
+ out[i * 4 + 1] = g;
201
+ out[i * 4 + 2] = g;
202
+ out[i * 4 + 3] = 255;
203
+ }
204
+ }
205
+ return out;
206
+ }
207
+ function dataUriToBytes(dataUri) {
208
+ const comma = dataUri.indexOf(",");
209
+ const b64 = dataUri.slice(comma + 1);
210
+ const bin = atob(b64);
211
+ const bytes = new Uint8Array(bin.length);
212
+ for (let i = 0;i < bin.length; i++)
213
+ bytes[i] = bin.charCodeAt(i);
214
+ return bytes;
215
+ }
216
+ async function decodeNodeImageValue(value) {
217
+ if (value.format === "raw-rgba") {
218
+ const data = new Uint8ClampedArray(value.buffer.buffer, value.buffer.byteOffset, value.buffer.byteLength);
219
+ return { data, width: value.width, height: value.height, channels: 4 };
220
+ }
221
+ const codec = getImageRasterCodec();
222
+ const dataUri = `data:image/${value.format};base64,${value.buffer.toString("base64")}`;
223
+ const decoded = await codec.decodeDataUri(dataUri);
224
+ return {
225
+ data: decoded.data,
226
+ width: decoded.width,
227
+ height: decoded.height,
228
+ channels: decoded.channels
229
+ };
230
+ }
231
+
232
+ // src/media/gpuDevice.browser.ts
233
+ var cached = null;
234
+ async function getGpuDevice() {
235
+ if (cached)
236
+ return cached;
237
+ cached = (async () => {
238
+ if (typeof navigator === "undefined" || !("gpu" in navigator))
239
+ return null;
240
+ const adapter = await navigator.gpu.requestAdapter();
241
+ if (!adapter)
242
+ return null;
243
+ const device = await adapter.requestDevice();
244
+ device.lost.then(() => {
245
+ cached = null;
246
+ });
247
+ return device;
248
+ })();
249
+ return cached;
250
+ }
251
+ function resetGpuDeviceForTests() {
252
+ cached = null;
253
+ }
254
+
255
+ // src/media/gpuImage.ts
256
+ var GLOBAL_FACTORY_KEY = Symbol.for("@workglow/util/media/gpuImageFactory");
257
+ var _g2 = globalThis;
258
+ if (!_g2[GLOBAL_FACTORY_KEY]) {
259
+ _g2[GLOBAL_FACTORY_KEY] = {};
260
+ }
261
+ var factory = _g2[GLOBAL_FACTORY_KEY];
262
+ function registerGpuImageFactory(key, fn) {
263
+ factory[key] = fn;
264
+ }
265
+ function getGpuImageFactory(key) {
266
+ const fn = factory[key];
267
+ return typeof fn === "function" ? fn : undefined;
268
+ }
269
+ var GpuImage = new Proxy({}, {
270
+ get(_t, prop) {
271
+ if (typeof prop !== "string" || prop === "then")
272
+ return;
273
+ const fn = factory[prop];
274
+ if (typeof fn !== "function") {
275
+ throw new Error(`GpuImage.${prop} is not registered. Import the platform entry point.`);
276
+ }
277
+ return fn;
278
+ }
279
+ });
280
+
89
281
  // src/media/imageCacheCodec.ts
282
+ import { registerPortCodec } from "@workglow/util";
90
283
  function isImageValueWire(v) {
91
284
  if (v === null || typeof v !== "object")
92
285
  return false;
@@ -191,17 +384,17 @@ class Container {
191
384
  factories = new Map;
192
385
  singletons = new Set;
193
386
  resolving = [];
194
- register(token, factory, singleton = true) {
195
- this.factories.set(token, factory);
387
+ register(token, factory2, singleton = true) {
388
+ this.factories.set(token, factory2);
196
389
  if (singleton) {
197
390
  this.singletons.add(token);
198
391
  }
199
392
  }
200
- registerIfAbsent(token, factory, singleton = true) {
393
+ registerIfAbsent(token, factory2, singleton = true) {
201
394
  if (this.factories.has(token) || this.services.has(token)) {
202
395
  return;
203
396
  }
204
- this.register(token, factory, singleton);
397
+ this.register(token, factory2, singleton);
205
398
  }
206
399
  registerInstance(token, instance) {
207
400
  this.services.set(token, instance);
@@ -211,8 +404,8 @@ class Container {
211
404
  if (this.services.has(token)) {
212
405
  return this.services.get(token);
213
406
  }
214
- const factory = this.factories.get(token);
215
- if (!factory) {
407
+ const factory2 = this.factories.get(token);
408
+ if (!factory2) {
216
409
  throw new Error(`Service not registered: ${String(token)}`);
217
410
  }
218
411
  if (this.resolving.includes(token)) {
@@ -221,7 +414,7 @@ class Container {
221
414
  }
222
415
  this.resolving.push(token);
223
416
  try {
224
- const instance = factory();
417
+ const instance = factory2();
225
418
  if (this.singletons.has(token)) {
226
419
  this.services.set(token, instance);
227
420
  }
@@ -270,8 +463,8 @@ class Container {
270
463
  }
271
464
  createChildContainer() {
272
465
  const child = new Container;
273
- this.factories.forEach((factory, token) => {
274
- child.factories.set(token, factory);
466
+ this.factories.forEach((factory2, token) => {
467
+ child.factories.set(token, factory2);
275
468
  if (this.singletons.has(token)) {
276
469
  child.singletons.add(token);
277
470
  }
@@ -286,11 +479,11 @@ class Container {
286
479
  }
287
480
  }
288
481
  var GLOBAL_CONTAINER_KEY = Symbol.for("@workglow/util/di/globalContainer");
289
- var _g2 = globalThis;
290
- if (!_g2[GLOBAL_CONTAINER_KEY]) {
291
- _g2[GLOBAL_CONTAINER_KEY] = new Container;
482
+ var _g3 = globalThis;
483
+ if (!_g3[GLOBAL_CONTAINER_KEY]) {
484
+ _g3[GLOBAL_CONTAINER_KEY] = new Container;
292
485
  }
293
- var globalContainer = _g2[GLOBAL_CONTAINER_KEY];
486
+ var globalContainer = _g3[GLOBAL_CONTAINER_KEY];
294
487
 
295
488
  // src/di/ServiceRegistry.ts
296
489
  function createServiceToken(id) {
@@ -302,11 +495,11 @@ class ServiceRegistry {
302
495
  constructor(container = globalContainer) {
303
496
  this.container = container;
304
497
  }
305
- register(token, factory, singleton = true) {
306
- this.container.register(token.id, factory, singleton);
498
+ register(token, factory2, singleton = true) {
499
+ this.container.register(token.id, factory2, singleton);
307
500
  }
308
- registerIfAbsent(token, factory, singleton = true) {
309
- this.container.registerIfAbsent(token.id, factory, singleton);
501
+ registerIfAbsent(token, factory2, singleton = true) {
502
+ this.container.registerIfAbsent(token.id, factory2, singleton);
310
503
  }
311
504
  registerInstance(token, instance) {
312
505
  this.container.registerInstance(token.id, instance);
@@ -347,474 +540,14 @@ async function resolveImage(id, _format, _registry) {
347
540
  }
348
541
  registerInputResolver("image", resolveImage);
349
542
 
350
- // src/media/color.ts
351
- var HEX_PATTERN = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
352
- var CSS_RGB_CHANNEL = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
353
- var CSS_RGB_ALPHA = "(?:0(?:\\.\\d+)?|1(?:\\.0+)?)";
354
- var CSS_RGB_PATTERN = new RegExp(`^rgba?\\(\\s*(${CSS_RGB_CHANNEL})\\s*,\\s*(${CSS_RGB_CHANNEL})\\s*,\\s*(${CSS_RGB_CHANNEL})\\s*(?:,\\s*(${CSS_RGB_ALPHA}))?\\s*\\)$`);
355
- function parseHexColor(hex) {
356
- if (typeof hex !== "string" || !HEX_PATTERN.test(hex)) {
357
- throw new Error(`Invalid hex color: ${String(hex)}`);
358
- }
359
- const body = hex.slice(1);
360
- const double = (nibble) => parseInt(nibble + nibble, 16);
361
- if (body.length === 3) {
362
- return { r: double(body[0]), g: double(body[1]), b: double(body[2]), a: 255 };
363
- }
364
- if (body.length === 4) {
365
- return {
366
- r: double(body[0]),
367
- g: double(body[1]),
368
- b: double(body[2]),
369
- a: double(body[3])
370
- };
371
- }
372
- if (body.length === 6) {
373
- return {
374
- r: parseInt(body.slice(0, 2), 16),
375
- g: parseInt(body.slice(2, 4), 16),
376
- b: parseInt(body.slice(4, 6), 16),
377
- a: 255
378
- };
379
- }
380
- return {
381
- r: parseInt(body.slice(0, 2), 16),
382
- g: parseInt(body.slice(2, 4), 16),
383
- b: parseInt(body.slice(4, 6), 16),
384
- a: parseInt(body.slice(6, 8), 16)
385
- };
386
- }
387
- var CHANNEL_MIN = 0;
388
- var CHANNEL_MAX = 255;
389
- function assertChannel(name, value) {
390
- if (!Number.isInteger(value) || value < CHANNEL_MIN || value > CHANNEL_MAX) {
391
- throw new Error(`Color channel ${name} out of range (0-255 integer): ${value}`);
392
- }
393
- }
394
- function byteToHex(value) {
395
- return value.toString(16).padStart(2, "0");
396
- }
397
- function toHexColor(c) {
398
- assertChannel("r", c.r);
399
- assertChannel("g", c.g);
400
- assertChannel("b", c.b);
401
- assertChannel("a", c.a);
402
- const head = `#${byteToHex(c.r)}${byteToHex(c.g)}${byteToHex(c.b)}`;
403
- return c.a === 255 ? head : `${head}${byteToHex(c.a)}`;
404
- }
405
- function isInRangeByte(value) {
406
- return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255;
407
- }
408
- function isColorObject(value) {
409
- if (value === null || typeof value !== "object" || Array.isArray(value))
410
- return false;
411
- const candidate = value;
412
- if (!isInRangeByte(candidate.r))
413
- return false;
414
- if (!isInRangeByte(candidate.g))
415
- return false;
416
- if (!isInRangeByte(candidate.b))
417
- return false;
418
- if (candidate.a !== undefined && !isInRangeByte(candidate.a))
419
- return false;
420
- return true;
421
- }
422
- function isHexColor(value) {
423
- return typeof value === "string" && HEX_PATTERN.test(value);
424
- }
425
- function parseCssRgbColor(value) {
426
- const match = CSS_RGB_PATTERN.exec(value);
427
- if (!match) {
428
- throw new Error(`Invalid CSS rgb color: ${String(value)}`);
429
- }
430
- const r = Number.parseInt(match[1] ?? "", 10);
431
- const g = Number.parseInt(match[2] ?? "", 10);
432
- const b = Number.parseInt(match[3] ?? "", 10);
433
- const alpha = match[4] === undefined ? 1 : Number.parseFloat(match[4]);
434
- assertChannel("r", r);
435
- assertChannel("g", g);
436
- assertChannel("b", b);
437
- if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
438
- throw new Error(`Color alpha out of range (0-1 number): ${match[4]}`);
439
- }
440
- return { r, g, b, a: Math.round(alpha * 255) };
441
- }
442
- function resolveColor(value) {
443
- if (typeof value === "string") {
444
- if (isHexColor(value))
445
- return parseHexColor(value);
446
- return parseCssRgbColor(value);
447
- }
448
- if (!isColorObject(value)) {
449
- throw new Error(`Invalid color value: ${JSON.stringify(value)}`);
450
- }
451
- return {
452
- r: value.r,
453
- g: value.g,
454
- b: value.b,
455
- a: value.a ?? 255
456
- };
457
- }
458
- // src/media/cpuImage.ts
459
- var FORMAT_TO_MIME = {
460
- png: "image/png",
461
- jpeg: "image/jpeg",
462
- webp: "image/webp"
463
- };
464
-
465
- class CpuImage {
466
- bin;
467
- backend = "cpu";
468
- constructor(bin) {
469
- this.bin = bin;
470
- }
471
- get width() {
472
- if (!this.bin)
473
- throw new Error("CpuImage.width on a disposed image");
474
- return this.bin.width;
475
- }
476
- get height() {
477
- if (!this.bin)
478
- throw new Error("CpuImage.height on a disposed image");
479
- return this.bin.height;
480
- }
481
- get channels() {
482
- if (!this.bin)
483
- throw new Error("CpuImage.channels on a disposed image");
484
- return this.bin.channels;
485
- }
486
- getBinary() {
487
- if (!this.bin)
488
- throw new Error("CpuImage.getBinary on a disposed image");
489
- return this.bin;
490
- }
491
- static async from(value) {
492
- if (isBrowserImageValue(value)) {
493
- if (typeof OffscreenCanvas === "undefined") {
494
- throw new Error("CpuImage.from(BrowserImageValue) requires OffscreenCanvas");
495
- }
496
- const off = new OffscreenCanvas(value.width, value.height);
497
- const ctx = off.getContext("2d");
498
- if (!ctx)
499
- throw new Error("CpuImage.from: could not acquire 2D context");
500
- ctx.drawImage(value.bitmap, 0, 0);
501
- const id = ctx.getImageData(0, 0, value.width, value.height);
502
- return new CpuImage({ data: id.data, width: value.width, height: value.height, channels: 4 });
503
- }
504
- if (isNodeImageValue(value)) {
505
- const bin = await decodeNodeImageValue(value);
506
- return new CpuImage(bin);
507
- }
508
- throw new Error("CpuImage.from: unrecognized ImageValue shape");
509
- }
510
- static fromRaw(bin) {
511
- return new CpuImage(bin);
512
- }
513
- async toImageValue(previewScale) {
514
- if (!this.bin)
515
- throw new Error("CpuImage.toImageValue on a disposed image");
516
- if (typeof OffscreenCanvas !== "undefined" && typeof createImageBitmap === "function") {
517
- const off = new OffscreenCanvas(this.bin.width, this.bin.height);
518
- const ctx = off.getContext("2d");
519
- if (!ctx)
520
- throw new Error("CpuImage.toImageValue could not acquire a 2D context");
521
- const rgba2 = expandToRgba(this.bin);
522
- ctx.putImageData(new ImageData(new Uint8ClampedArray(rgba2.buffer, rgba2.byteOffset, rgba2.byteLength), this.bin.width, this.bin.height), 0, 0);
523
- const bitmap = await createImageBitmap(off);
524
- const out2 = {
525
- bitmap,
526
- width: this.bin.width,
527
- height: this.bin.height,
528
- previewScale
529
- };
530
- this.bin = null;
531
- return out2;
532
- }
533
- const rgba = expandToRgba(this.bin);
534
- const buffer = Buffer.from(rgba.buffer, rgba.byteOffset, rgba.byteLength);
535
- const out = {
536
- buffer,
537
- format: "raw-rgba",
538
- width: this.bin.width,
539
- height: this.bin.height,
540
- previewScale
541
- };
542
- this.bin = null;
543
- return out;
544
- }
545
- async encode(format, _quality) {
546
- if (!this.bin)
547
- throw new Error("CpuImage.encode on a disposed image");
548
- const codec = getImageRasterCodec();
549
- const dataUri = await codec.encodeDataUri(this.bin, FORMAT_TO_MIME[format]);
550
- return dataUriToBytes(dataUri);
551
- }
552
- dispose() {
553
- this.bin = null;
554
- }
555
- }
556
- function expandToRgba(bin) {
557
- if (bin.channels === 4)
558
- return bin.data;
559
- const px = bin.width * bin.height;
560
- const out = new Uint8ClampedArray(px * 4);
561
- if (bin.channels === 3) {
562
- for (let i = 0;i < px; i++) {
563
- out[i * 4 + 0] = bin.data[i * 3 + 0] ?? 0;
564
- out[i * 4 + 1] = bin.data[i * 3 + 1] ?? 0;
565
- out[i * 4 + 2] = bin.data[i * 3 + 2] ?? 0;
566
- out[i * 4 + 3] = 255;
567
- }
568
- } else if (bin.channels === 1) {
569
- for (let i = 0;i < px; i++) {
570
- const g = bin.data[i] ?? 0;
571
- out[i * 4 + 0] = g;
572
- out[i * 4 + 1] = g;
573
- out[i * 4 + 2] = g;
574
- out[i * 4 + 3] = 255;
575
- }
576
- }
577
- return out;
578
- }
579
- function dataUriToBytes(dataUri) {
580
- const comma = dataUri.indexOf(",");
581
- const b64 = dataUri.slice(comma + 1);
582
- const bin = atob(b64);
583
- const bytes = new Uint8Array(bin.length);
584
- for (let i = 0;i < bin.length; i++)
585
- bytes[i] = bin.charCodeAt(i);
586
- return bytes;
587
- }
588
- async function decodeNodeImageValue(value) {
589
- if (value.format === "raw-rgba") {
590
- const data = new Uint8ClampedArray(value.buffer.buffer, value.buffer.byteOffset, value.buffer.byteLength);
591
- return { data, width: value.width, height: value.height, channels: 4 };
592
- }
593
- const codec = getImageRasterCodec();
594
- const dataUri = `data:image/${value.format};base64,${value.buffer.toString("base64")}`;
595
- const decoded = await codec.decodeDataUri(dataUri);
596
- return {
597
- data: decoded.data,
598
- width: decoded.width,
599
- height: decoded.height,
600
- channels: decoded.channels
601
- };
602
- }
603
- // src/media/encode.ts
604
- async function rawPixelBufferToBytes(bin, mimeType) {
605
- const dataUri = await getImageRasterCodec().encodeDataUri(bin, mimeType);
606
- const b64 = dataUri.slice(dataUri.indexOf(",") + 1);
607
- const decoded = atob(b64);
608
- const bytes = new Uint8Array(decoded.length);
609
- for (let i = 0;i < decoded.length; i++)
610
- bytes[i] = decoded.charCodeAt(i);
611
- return bytes;
612
- }
613
- async function rawPixelBufferToDataUri(bin, mimeType = "image/png") {
614
- return getImageRasterCodec().encodeDataUri(bin, mimeType);
615
- }
616
- async function rawPixelBufferToBlob(bin, mimeType = "image/png") {
617
- const bytes = await rawPixelBufferToBytes(bin, mimeType);
618
- return new Blob([bytes.buffer], { type: mimeType });
619
- }
620
- // src/media/filterRegistry.ts
621
- var GLOBAL_REGISTRY_KEY = Symbol.for("@workglow/util/media/filterRegistry");
622
- var _g3 = globalThis;
623
- function getRegistry() {
624
- let reg = _g3[GLOBAL_REGISTRY_KEY];
625
- if (!reg) {
626
- reg = new Map;
627
- _g3[GLOBAL_REGISTRY_KEY] = reg;
628
- }
629
- return reg;
630
- }
631
- var key = (backend, filter) => `${backend}:${filter}`;
632
- function registerFilterOp(backend, filter, fn) {
633
- getRegistry().set(key(backend, filter), fn);
634
- }
635
- function applyFilter(image, filter, params) {
636
- const fn = getRegistry().get(key(image.backend, filter));
637
- if (!fn) {
638
- throw new Error(`applyFilter("${filter}") on backend "${image.backend}": no implementation registered. ` + `Task-layer fallback should have routed this to "cpu" first; this means even the cpu arm is missing. ` + `Ensure @workglow/tasks has been imported so its filter-arm side effects run.`);
639
- }
640
- return fn(image, params);
641
- }
642
- function hasFilterOp(backend, filter) {
643
- return getRegistry().has(key(backend, filter));
644
- }
645
- function _resetFilterRegistryForTests() {
646
- getRegistry().clear();
647
- }
648
- // src/media/gpuDevice.browser.ts
649
- var cached = null;
650
- async function getGpuDevice() {
651
- if (cached)
652
- return cached;
653
- cached = (async () => {
654
- if (typeof navigator === "undefined" || !("gpu" in navigator))
655
- return null;
656
- const adapter = await navigator.gpu.requestAdapter();
657
- if (!adapter)
658
- return null;
659
- const device = await adapter.requestDevice();
660
- device.lost.then(() => {
661
- cached = null;
662
- });
663
- return device;
664
- })();
665
- return cached;
666
- }
667
- function resetGpuDeviceForTests() {
668
- cached = null;
669
- }
670
- // src/media/gpuImage.ts
671
- var GLOBAL_FACTORY_KEY = Symbol.for("@workglow/util/media/gpuImageFactory");
672
- var _g4 = globalThis;
673
- if (!_g4[GLOBAL_FACTORY_KEY]) {
674
- _g4[GLOBAL_FACTORY_KEY] = {};
675
- }
676
- var factory = _g4[GLOBAL_FACTORY_KEY];
677
- function registerGpuImageFactory(key2, fn) {
678
- factory[key2] = fn;
679
- }
680
- function getGpuImageFactory(key2) {
681
- const fn = factory[key2];
682
- return typeof fn === "function" ? fn : undefined;
683
- }
684
- var GpuImage = new Proxy({}, {
685
- get(_t, prop) {
686
- if (typeof prop !== "string" || prop === "then")
687
- return;
688
- const fn = factory[prop];
689
- if (typeof fn !== "function") {
690
- throw new Error(`GpuImage.${prop} is not registered. Import the platform entry point.`);
691
- }
692
- return fn;
693
- }
694
- });
695
- // src/media/imageValueSchema.ts
696
- function ImageValueSchema(annotations = {}) {
697
- return {
698
- type: ["string", "object"],
699
- properties: {},
700
- title: "Image",
701
- description: "Image (hydrated to ImageValue at task entry)",
702
- ...annotations,
703
- format: "image"
704
- };
705
- }
706
- // src/media/MediaRawImage.ts
707
- class MediaRawImage {
708
- data;
709
- width;
710
- height;
711
- channels;
712
- constructor(data, width, height, channels) {
713
- this.data = data;
714
- this.width = width;
715
- this.height = height;
716
- this.channels = channels;
717
- }
718
- }
719
- function isMediaRawImageShape(value) {
720
- if (!value || typeof value !== "object")
721
- return false;
722
- const v = value;
723
- return v.data instanceof Uint8ClampedArray && typeof v.width === "number" && typeof v.height === "number" && typeof v.channels === "number";
724
- }
725
- // src/media/previewBudget.ts
726
- var GLOBAL_RESIZE_KEY = Symbol.for("@workglow/util/media/previewResizeFn");
727
- var GLOBAL_BUDGET_KEY = Symbol.for("@workglow/util/media/previewBudget");
728
- var _g5 = globalThis;
729
- var DEFAULT_BUDGET = 512;
730
- if (typeof _g5[GLOBAL_BUDGET_KEY] !== "number") {
731
- _g5[GLOBAL_BUDGET_KEY] = DEFAULT_BUDGET;
732
- }
733
- function registerPreviewResizeFn(fn) {
734
- _g5[GLOBAL_RESIZE_KEY] = fn;
735
- }
736
- function getPreviewResizeFn() {
737
- return _g5[GLOBAL_RESIZE_KEY];
738
- }
739
- function getPreviewBudget() {
740
- return _g5[GLOBAL_BUDGET_KEY];
741
- }
742
- function setPreviewBudget(px) {
743
- if (!Number.isFinite(px) || px <= 0) {
744
- throw new Error(`setPreviewBudget: invalid value ${px}; expected a positive finite number`);
745
- }
746
- _g5[GLOBAL_BUDGET_KEY] = Math.floor(px);
747
- }
748
- async function previewSource(image) {
749
- const budget = getPreviewBudget();
750
- const long = Math.max(image.width, image.height);
751
- if (long <= budget)
752
- return image;
753
- const ratio = budget / long;
754
- const resize = getPreviewResizeFn();
755
- if (!resize)
756
- return image;
757
- const targetW = Math.max(1, Math.round(image.width * ratio));
758
- const targetH = Math.max(1, Math.round(image.height * ratio));
759
- const result = await resize(image, targetW, targetH);
760
- const composedScale = image.previewScale * ratio;
761
- return {
762
- ...result,
763
- previewScale: composedScale
764
- };
765
- }
766
- // src/media/shaderRegistry.browser.ts
767
- var VERTEX_PRELUDE = `
768
- @group(0) @binding(0) var src: texture_2d<f32>;
769
- @group(0) @binding(1) var src_sampler: sampler;
770
-
771
- struct VsOut {
772
- @builtin(position) pos: vec4f,
773
- @location(0) uv: vec2f,
774
- };
775
-
776
- @vertex
777
- fn vs(@builtin(vertex_index) vid: u32) -> VsOut {
778
- let xy = vec2f(f32((vid << 1u) & 2u), f32(vid & 2u));
779
- var out: VsOut;
780
- out.pos = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
781
- out.uv = vec2f(xy.x, 1.0 - xy.y);
782
- return out;
783
- }`;
784
- var PASSTHROUGH_SHADER_SRC = `${VERTEX_PRELUDE}
785
- @fragment
786
- fn fs(in: VsOut) -> @location(0) vec4f {
787
- return textureSample(src, src_sampler, in.uv);
788
- }
789
- `;
790
- function createShaderCache(device) {
791
- const map = new Map;
792
- return {
793
- get(source) {
794
- let mod = map.get(source);
795
- if (!mod) {
796
- mod = device.createShaderModule({ code: source });
797
- map.set(source, mod);
798
- }
799
- return mod;
800
- }
801
- };
802
- }
803
- var singleton = null;
804
- function getShaderCache(device) {
805
- if (!singleton || singleton.device !== device) {
806
- singleton = { device, cache: createShaderCache(device) };
807
- }
808
- return singleton.cache;
809
- }
810
- // src/media/texturePool.browser.ts
811
- var DEFAULT_CAPACITY_PER_SIZE = 8;
812
- var TEXTURE_USAGE = 4 | 16 | 1 | 2;
813
- function createTexturePool(device, opts = {}) {
814
- const capacity = opts.capacityPerSize ?? DEFAULT_CAPACITY_PER_SIZE;
815
- const buckets = new Map;
816
- const owners = new WeakMap;
817
- const sizeClassKey = (w, h, f) => `${w}x${h}:${f}`;
543
+ // src/media/texturePool.browser.ts
544
+ var DEFAULT_CAPACITY_PER_SIZE = 8;
545
+ var TEXTURE_USAGE = 4 | 16 | 1 | 2;
546
+ function createTexturePool(device, opts = {}) {
547
+ const capacity = opts.capacityPerSize ?? DEFAULT_CAPACITY_PER_SIZE;
548
+ const buckets = new Map;
549
+ const owners = new WeakMap;
550
+ const sizeClassKey = (w, h, f) => `${w}x${h}:${f}`;
818
551
  return {
819
552
  acquire(width, height, format) {
820
553
  const k = sizeClassKey(width, height, format);
@@ -860,22 +593,68 @@ function createTexturePool(device, opts = {}) {
860
593
  entry.texture.destroy();
861
594
  }
862
595
  }
863
- buckets.clear();
596
+ buckets.clear();
597
+ }
598
+ };
599
+ }
600
+ var singleton = null;
601
+ function getTexturePool(device) {
602
+ if (!singleton || singleton.device !== device) {
603
+ singleton?.pool.drain();
604
+ singleton = { device, pool: createTexturePool(device) };
605
+ }
606
+ return singleton.pool;
607
+ }
608
+ function resetTexturePoolForTests() {
609
+ singleton?.pool.drain();
610
+ singleton = null;
611
+ }
612
+
613
+ // src/media/shaderRegistry.browser.ts
614
+ var VERTEX_PRELUDE = `
615
+ @group(0) @binding(0) var src: texture_2d<f32>;
616
+ @group(0) @binding(1) var src_sampler: sampler;
617
+
618
+ struct VsOut {
619
+ @builtin(position) pos: vec4f,
620
+ @location(0) uv: vec2f,
621
+ };
622
+
623
+ @vertex
624
+ fn vs(@builtin(vertex_index) vid: u32) -> VsOut {
625
+ let xy = vec2f(f32((vid << 1u) & 2u), f32(vid & 2u));
626
+ var out: VsOut;
627
+ out.pos = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
628
+ out.uv = vec2f(xy.x, 1.0 - xy.y);
629
+ return out;
630
+ }`;
631
+ var PASSTHROUGH_SHADER_SRC = `${VERTEX_PRELUDE}
632
+ @fragment
633
+ fn fs(in: VsOut) -> @location(0) vec4f {
634
+ return textureSample(src, src_sampler, in.uv);
635
+ }
636
+ `;
637
+ function createShaderCache(device) {
638
+ const map = new Map;
639
+ return {
640
+ get(source) {
641
+ let mod = map.get(source);
642
+ if (!mod) {
643
+ mod = device.createShaderModule({ code: source });
644
+ map.set(source, mod);
645
+ }
646
+ return mod;
864
647
  }
865
648
  };
866
649
  }
867
650
  var singleton2 = null;
868
- function getTexturePool(device) {
651
+ function getShaderCache(device) {
869
652
  if (!singleton2 || singleton2.device !== device) {
870
- singleton2?.pool.drain();
871
- singleton2 = { device, pool: createTexturePool(device) };
653
+ singleton2 = { device, cache: createShaderCache(device) };
872
654
  }
873
- return singleton2.pool;
874
- }
875
- function resetTexturePoolForTests() {
876
- singleton2?.pool.drain();
877
- singleton2 = null;
655
+ return singleton2.cache;
878
656
  }
657
+
879
658
  // src/media/webGpuImage.browser.ts
880
659
  var TEX_FORMAT = "rgba8unorm";
881
660
 
@@ -1032,14 +811,240 @@ class WebGpuImage {
1032
811
  }
1033
812
  }
1034
813
  registerGpuImageFactory("from", WebGpuImage.from.bind(WebGpuImage));
814
+
815
+ // src/media/color.ts
816
+ var HEX_PATTERN = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
817
+ var CSS_RGB_CHANNEL = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
818
+ var CSS_RGB_ALPHA = "(?:0(?:\\.\\d+)?|1(?:\\.0+)?)";
819
+ var CSS_RGB_PATTERN = new RegExp(`^rgba?\\(\\s*(${CSS_RGB_CHANNEL})\\s*,\\s*(${CSS_RGB_CHANNEL})\\s*,\\s*(${CSS_RGB_CHANNEL})\\s*(?:,\\s*(${CSS_RGB_ALPHA}))?\\s*\\)$`);
820
+ function parseHexColor(hex) {
821
+ if (typeof hex !== "string" || !HEX_PATTERN.test(hex)) {
822
+ throw new Error(`Invalid hex color: ${String(hex)}`);
823
+ }
824
+ const body = hex.slice(1);
825
+ const double = (nibble) => parseInt(nibble + nibble, 16);
826
+ if (body.length === 3) {
827
+ return { r: double(body[0]), g: double(body[1]), b: double(body[2]), a: 255 };
828
+ }
829
+ if (body.length === 4) {
830
+ return {
831
+ r: double(body[0]),
832
+ g: double(body[1]),
833
+ b: double(body[2]),
834
+ a: double(body[3])
835
+ };
836
+ }
837
+ if (body.length === 6) {
838
+ return {
839
+ r: parseInt(body.slice(0, 2), 16),
840
+ g: parseInt(body.slice(2, 4), 16),
841
+ b: parseInt(body.slice(4, 6), 16),
842
+ a: 255
843
+ };
844
+ }
845
+ return {
846
+ r: parseInt(body.slice(0, 2), 16),
847
+ g: parseInt(body.slice(2, 4), 16),
848
+ b: parseInt(body.slice(4, 6), 16),
849
+ a: parseInt(body.slice(6, 8), 16)
850
+ };
851
+ }
852
+ var CHANNEL_MIN = 0;
853
+ var CHANNEL_MAX = 255;
854
+ function assertChannel(name, value) {
855
+ if (!Number.isInteger(value) || value < CHANNEL_MIN || value > CHANNEL_MAX) {
856
+ throw new Error(`Color channel ${name} out of range (0-255 integer): ${value}`);
857
+ }
858
+ }
859
+ function byteToHex(value) {
860
+ return value.toString(16).padStart(2, "0");
861
+ }
862
+ function toHexColor(c) {
863
+ assertChannel("r", c.r);
864
+ assertChannel("g", c.g);
865
+ assertChannel("b", c.b);
866
+ assertChannel("a", c.a);
867
+ const head = `#${byteToHex(c.r)}${byteToHex(c.g)}${byteToHex(c.b)}`;
868
+ return c.a === 255 ? head : `${head}${byteToHex(c.a)}`;
869
+ }
870
+ function isInRangeByte(value) {
871
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255;
872
+ }
873
+ function isColorObject(value) {
874
+ if (value === null || typeof value !== "object" || Array.isArray(value))
875
+ return false;
876
+ const candidate = value;
877
+ if (!isInRangeByte(candidate.r))
878
+ return false;
879
+ if (!isInRangeByte(candidate.g))
880
+ return false;
881
+ if (!isInRangeByte(candidate.b))
882
+ return false;
883
+ if (candidate.a !== undefined && !isInRangeByte(candidate.a))
884
+ return false;
885
+ return true;
886
+ }
887
+ function isHexColor(value) {
888
+ return typeof value === "string" && HEX_PATTERN.test(value);
889
+ }
890
+ function parseCssRgbColor(value) {
891
+ const match = CSS_RGB_PATTERN.exec(value);
892
+ if (!match) {
893
+ throw new Error(`Invalid CSS rgb color: ${String(value)}`);
894
+ }
895
+ const r = Number.parseInt(match[1] ?? "", 10);
896
+ const g = Number.parseInt(match[2] ?? "", 10);
897
+ const b = Number.parseInt(match[3] ?? "", 10);
898
+ const alpha = match[4] === undefined ? 1 : Number.parseFloat(match[4]);
899
+ assertChannel("r", r);
900
+ assertChannel("g", g);
901
+ assertChannel("b", b);
902
+ if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
903
+ throw new Error(`Color alpha out of range (0-1 number): ${match[4]}`);
904
+ }
905
+ return { r, g, b, a: Math.round(alpha * 255) };
906
+ }
907
+ function resolveColor(value) {
908
+ if (typeof value === "string") {
909
+ if (isHexColor(value))
910
+ return parseHexColor(value);
911
+ return parseCssRgbColor(value);
912
+ }
913
+ if (!isColorObject(value)) {
914
+ throw new Error(`Invalid color value: ${JSON.stringify(value)}`);
915
+ }
916
+ return {
917
+ r: value.r,
918
+ g: value.g,
919
+ b: value.b,
920
+ a: value.a ?? 255
921
+ };
922
+ }
923
+ // src/media/encode.ts
924
+ async function rawPixelBufferToBytes(bin, mimeType) {
925
+ const dataUri = await getImageRasterCodec().encodeDataUri(bin, mimeType);
926
+ const b64 = dataUri.slice(dataUri.indexOf(",") + 1);
927
+ const decoded = atob(b64);
928
+ const bytes = new Uint8Array(decoded.length);
929
+ for (let i = 0;i < decoded.length; i++)
930
+ bytes[i] = decoded.charCodeAt(i);
931
+ return bytes;
932
+ }
933
+ async function rawPixelBufferToDataUri(bin, mimeType = "image/png") {
934
+ return getImageRasterCodec().encodeDataUri(bin, mimeType);
935
+ }
936
+ async function rawPixelBufferToBlob(bin, mimeType = "image/png") {
937
+ const bytes = await rawPixelBufferToBytes(bin, mimeType);
938
+ return new Blob([bytes.buffer], { type: mimeType });
939
+ }
940
+ // src/media/filterRegistry.ts
941
+ var GLOBAL_REGISTRY_KEY = Symbol.for("@workglow/util/media/filterRegistry");
942
+ var _g4 = globalThis;
943
+ function getRegistry() {
944
+ let reg = _g4[GLOBAL_REGISTRY_KEY];
945
+ if (!reg) {
946
+ reg = new Map;
947
+ _g4[GLOBAL_REGISTRY_KEY] = reg;
948
+ }
949
+ return reg;
950
+ }
951
+ var key = (backend, filter) => `${backend}:${filter}`;
952
+ function registerFilterOp(backend, filter, fn) {
953
+ getRegistry().set(key(backend, filter), fn);
954
+ }
955
+ function applyFilter(image, filter, params) {
956
+ const fn = getRegistry().get(key(image.backend, filter));
957
+ if (!fn) {
958
+ throw new Error(`applyFilter("${filter}") on backend "${image.backend}": no implementation registered. ` + `Task-layer fallback should have routed this to "cpu" first; this means even the cpu arm is missing. ` + `Ensure @workglow/tasks has been imported so its filter-arm side effects run.`);
959
+ }
960
+ return fn(image, params);
961
+ }
962
+ function hasFilterOp(backend, filter) {
963
+ return getRegistry().has(key(backend, filter));
964
+ }
965
+ function _resetFilterRegistryForTests() {
966
+ getRegistry().clear();
967
+ }
968
+ // src/media/imageValueSchema.ts
969
+ function ImageValueSchema(annotations = {}) {
970
+ return {
971
+ type: ["string", "object"],
972
+ properties: {},
973
+ title: "Image",
974
+ description: "Image (hydrated to ImageValue at task entry)",
975
+ ...annotations,
976
+ format: "image"
977
+ };
978
+ }
979
+ // src/media/MediaRawImage.ts
980
+ class MediaRawImage {
981
+ data;
982
+ width;
983
+ height;
984
+ channels;
985
+ constructor(data, width, height, channels) {
986
+ this.data = data;
987
+ this.width = width;
988
+ this.height = height;
989
+ this.channels = channels;
990
+ }
991
+ }
992
+ function isMediaRawImageShape(value) {
993
+ if (!value || typeof value !== "object")
994
+ return false;
995
+ const v = value;
996
+ return v.data instanceof Uint8ClampedArray && typeof v.width === "number" && typeof v.height === "number" && typeof v.channels === "number";
997
+ }
998
+ // src/media/previewBudget.ts
999
+ var GLOBAL_RESIZE_KEY = Symbol.for("@workglow/util/media/previewResizeFn");
1000
+ var GLOBAL_BUDGET_KEY = Symbol.for("@workglow/util/media/previewBudget");
1001
+ var _g5 = globalThis;
1002
+ var DEFAULT_BUDGET = 512;
1003
+ if (typeof _g5[GLOBAL_BUDGET_KEY] !== "number") {
1004
+ _g5[GLOBAL_BUDGET_KEY] = DEFAULT_BUDGET;
1005
+ }
1006
+ function registerPreviewResizeFn(fn) {
1007
+ _g5[GLOBAL_RESIZE_KEY] = fn;
1008
+ }
1009
+ function getPreviewResizeFn() {
1010
+ return _g5[GLOBAL_RESIZE_KEY];
1011
+ }
1012
+ function getPreviewBudget() {
1013
+ return _g5[GLOBAL_BUDGET_KEY];
1014
+ }
1015
+ function setPreviewBudget(px) {
1016
+ if (!Number.isFinite(px) || px <= 0) {
1017
+ throw new Error(`setPreviewBudget: invalid value ${px}; expected a positive finite number`);
1018
+ }
1019
+ _g5[GLOBAL_BUDGET_KEY] = Math.floor(px);
1020
+ }
1021
+ async function previewSource(image) {
1022
+ const budget = getPreviewBudget();
1023
+ const long = Math.max(image.width, image.height);
1024
+ if (long <= budget)
1025
+ return image;
1026
+ const ratio = budget / long;
1027
+ const resize = getPreviewResizeFn();
1028
+ if (!resize)
1029
+ return image;
1030
+ const targetW = Math.max(1, Math.round(image.width * ratio));
1031
+ const targetH = Math.max(1, Math.round(image.height * ratio));
1032
+ const result = await resize(image, targetW, targetH);
1033
+ const composedScale = image.previewScale * ratio;
1034
+ return {
1035
+ ...result,
1036
+ previewScale: composedScale
1037
+ };
1038
+ }
1039
+
1035
1040
  // src/media-browser.ts
1036
- async function probeImageDimensions() {
1041
+ async function probeImageDimensions(_) {
1037
1042
  throw new Error("probeImageDimensions: not available in browser runtime");
1038
1043
  }
1039
- async function decodeBufferToRaw() {
1044
+ async function decodeBufferToRaw(_) {
1040
1045
  throw new Error("decodeBufferToRaw: not available in browser runtime");
1041
1046
  }
1042
- async function encodeRawPixels() {
1047
+ async function encodeRawPixels(_, _options) {
1043
1048
  throw new Error("encodeRawPixels: not available in browser runtime");
1044
1049
  }
1045
1050
  async function _preferGpu(value) {
@@ -1093,4 +1098,4 @@ export {
1093
1098
  CpuImage
1094
1099
  };
1095
1100
 
1096
- //# debugId=ECB4E78593EFEFAB64756E2164756E21
1101
+ //# debugId=DBD4B12E61C4A72264756E2164756E21