@workglow/util 0.2.17 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/browser.js +33 -2
  2. package/dist/browser.js.map +5 -4
  3. package/dist/bun.js +33 -2
  4. package/dist/bun.js.map +5 -4
  5. package/dist/di/Container.d.ts +0 -3
  6. package/dist/di/Container.d.ts.map +1 -1
  7. package/dist/di/PortCodecRegistry.d.ts +14 -0
  8. package/dist/di/PortCodecRegistry.d.ts.map +1 -0
  9. package/dist/di/index.d.ts +1 -0
  10. package/dist/di/index.d.ts.map +1 -1
  11. package/dist/media/color.d.ts +3 -3
  12. package/dist/media/color.d.ts.map +1 -1
  13. package/dist/media/cpuImage.d.ts +29 -0
  14. package/dist/media/cpuImage.d.ts.map +1 -0
  15. package/dist/media/encode.d.ts +11 -0
  16. package/dist/media/encode.d.ts.map +1 -0
  17. package/dist/media/filterRegistry.d.ts +13 -0
  18. package/dist/media/filterRegistry.d.ts.map +1 -0
  19. package/dist/media/gpuDevice.browser.d.ts +8 -0
  20. package/dist/media/gpuDevice.browser.d.ts.map +1 -0
  21. package/dist/media/gpuImage.d.ts +50 -0
  22. package/dist/media/gpuImage.d.ts.map +1 -0
  23. package/dist/media/gpuImageSchema.d.ts +8 -0
  24. package/dist/media/gpuImageSchema.d.ts.map +1 -0
  25. package/dist/media/imageCacheCodec.d.ts +9 -0
  26. package/dist/media/imageCacheCodec.d.ts.map +1 -0
  27. package/dist/media/imageHydrationResolver.d.ts +2 -0
  28. package/dist/media/imageHydrationResolver.d.ts.map +1 -0
  29. package/dist/media/imageRasterCodecRegistry.d.ts.map +1 -1
  30. package/dist/media/previewBudget.d.ts +23 -0
  31. package/dist/media/previewBudget.d.ts.map +1 -0
  32. package/dist/media/shaderRegistry.browser.d.ts +13 -0
  33. package/dist/media/shaderRegistry.browser.d.ts.map +1 -0
  34. package/dist/media/sharpImage.bun.d.ts +7 -0
  35. package/dist/media/sharpImage.bun.d.ts.map +1 -0
  36. package/dist/media/sharpImage.node.d.ts +93 -0
  37. package/dist/media/sharpImage.node.d.ts.map +1 -0
  38. package/dist/media/texturePool.browser.d.ts +17 -0
  39. package/dist/media/texturePool.browser.d.ts.map +1 -0
  40. package/dist/media/webGpuImage.browser.d.ts +40 -0
  41. package/dist/media/webGpuImage.browser.d.ts.map +1 -0
  42. package/dist/media-browser.d.ts +18 -3
  43. package/dist/media-browser.d.ts.map +1 -1
  44. package/dist/media-browser.js +850 -404
  45. package/dist/media-browser.js.map +21 -7
  46. package/dist/media-node.d.ts +21 -2
  47. package/dist/media-node.d.ts.map +1 -1
  48. package/dist/media-node.js +695 -312
  49. package/dist/media-node.js.map +20 -6
  50. package/dist/node.js +33 -2
  51. package/dist/node.js.map +5 -4
  52. package/dist/worker-browser.js +33 -2
  53. package/dist/worker-browser.js.map +5 -4
  54. package/dist/worker-bun.js +33 -2
  55. package/dist/worker-bun.js.map +5 -4
  56. package/dist/worker-node.js +33 -2
  57. package/dist/worker-node.js.map +5 -4
  58. package/package.json +4 -1
  59. package/dist/media/Image.browser.d.ts +0 -24
  60. package/dist/media/Image.browser.d.ts.map +0 -1
  61. package/dist/media/Image.d.ts +0 -100
  62. package/dist/media/Image.d.ts.map +0 -1
@@ -1,16 +1,345 @@
1
- // src/media/imageTypes.ts
2
- function parseDataUri(dataUri) {
3
- const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
4
- if (!match) {
5
- throw new Error("Invalid base64 data URI");
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/media/imageCacheCodec.ts
5
+ import { registerPortCodec } from "@workglow/util";
6
+
7
+ // src/media/gpuImage.ts
8
+ var GLOBAL_FACTORY_KEY = Symbol.for("@workglow/util/media/gpuImageFactory");
9
+ var _g = globalThis;
10
+ if (!_g[GLOBAL_FACTORY_KEY]) {
11
+ _g[GLOBAL_FACTORY_KEY] = {};
12
+ }
13
+ var factory = _g[GLOBAL_FACTORY_KEY];
14
+ function registerGpuImageFactory(key, fn) {
15
+ factory[key] = fn;
16
+ }
17
+ function getGpuImageFactory(key) {
18
+ const fn = factory[key];
19
+ return typeof fn === "function" ? fn : undefined;
20
+ }
21
+ var GpuImage = new Proxy({}, {
22
+ get(_t, prop) {
23
+ if (typeof prop !== "string" || prop === "then")
24
+ return;
25
+ const fn = factory[prop];
26
+ if (typeof fn !== "function") {
27
+ throw new Error(`GpuImage.${prop} is not registered. Import the platform entry point.`);
28
+ }
29
+ return fn;
6
30
  }
7
- return {
8
- mimeType: match[1],
9
- base64: match[2]
10
- };
31
+ });
32
+
33
+ // src/media/imageRasterCodecRegistry.ts
34
+ var GLOBAL_CODEC_KEY = Symbol.for("@workglow/util/media/imageRasterCodec");
35
+ var _g2 = globalThis;
36
+ if (!_g2[GLOBAL_CODEC_KEY]) {
37
+ _g2[GLOBAL_CODEC_KEY] = { value: null };
38
+ }
39
+ var slot = _g2[GLOBAL_CODEC_KEY];
40
+ function registerImageRasterCodec(next) {
41
+ slot.value = next;
42
+ }
43
+ function getImageRasterCodec() {
44
+ if (!slot.value) {
45
+ throw new Error("Image raster codec is not registered. Ensure you import @workglow/tasks from the browser or Node entry (dist/browser.js or dist/node.js), or call registerImageRasterCodec() during startup.");
46
+ }
47
+ return slot.value;
48
+ }
49
+
50
+ // src/media/cpuImage.ts
51
+ var FORMAT_TO_MIME = {
52
+ png: "image/png",
53
+ jpeg: "image/jpeg",
54
+ webp: "image/webp"
55
+ };
56
+ function dataUriToBytes(dataUri) {
57
+ const comma = dataUri.indexOf(",");
58
+ const b64 = dataUri.slice(comma + 1);
59
+ const bin = atob(b64);
60
+ const bytes = new Uint8Array(bin.length);
61
+ for (let i = 0;i < bin.length; i++)
62
+ bytes[i] = bin.charCodeAt(i);
63
+ return bytes;
11
64
  }
65
+ function expandToRgba(bin) {
66
+ if (bin.channels === 4)
67
+ return bin.data;
68
+ const px = bin.width * bin.height;
69
+ const out = new Uint8ClampedArray(px * 4);
70
+ if (bin.channels === 3) {
71
+ for (let i = 0;i < px; i++) {
72
+ out[i * 4 + 0] = bin.data[i * 3 + 0] ?? 0;
73
+ out[i * 4 + 1] = bin.data[i * 3 + 1] ?? 0;
74
+ out[i * 4 + 2] = bin.data[i * 3 + 2] ?? 0;
75
+ out[i * 4 + 3] = 255;
76
+ }
77
+ } else if (bin.channels === 1) {
78
+ for (let i = 0;i < px; i++) {
79
+ const g = bin.data[i] ?? 0;
80
+ out[i * 4 + 0] = g;
81
+ out[i * 4 + 1] = g;
82
+ out[i * 4 + 2] = g;
83
+ out[i * 4 + 3] = 255;
84
+ }
85
+ }
86
+ return out;
87
+ }
88
+
89
+ class CpuImage {
90
+ bin;
91
+ backend = "cpu";
92
+ _previewScale;
93
+ constructor(bin, previewScale = 1) {
94
+ this.bin = bin;
95
+ this._previewScale = previewScale;
96
+ }
97
+ get width() {
98
+ return this.bin.width;
99
+ }
100
+ get height() {
101
+ return this.bin.height;
102
+ }
103
+ get channels() {
104
+ return this.bin.channels;
105
+ }
106
+ get previewScale() {
107
+ return this._previewScale;
108
+ }
109
+ _setPreviewScale(scale) {
110
+ this._previewScale = scale;
111
+ return this;
112
+ }
113
+ async materialize() {
114
+ return this.bin;
115
+ }
116
+ getBinary() {
117
+ return this.bin;
118
+ }
119
+ async toCanvas(canvas) {
120
+ if (typeof ImageData === "undefined") {
121
+ throw new Error("CpuImage.toCanvas requires a browser environment with ImageData");
122
+ }
123
+ const rgba = expandToRgba(this.bin);
124
+ const id = new ImageData(new Uint8ClampedArray(rgba.buffer, rgba.byteOffset, rgba.byteLength), this.bin.width, this.bin.height);
125
+ if (canvas.width !== this.bin.width)
126
+ canvas.width = this.bin.width;
127
+ if (canvas.height !== this.bin.height)
128
+ canvas.height = this.bin.height;
129
+ const ctx = canvas.getContext("2d");
130
+ if (!ctx)
131
+ throw new Error("CpuImage.toCanvas could not acquire a 2D context");
132
+ ctx.putImageData(id, 0, 0);
133
+ }
134
+ async encode(format, _quality) {
135
+ const codec = getImageRasterCodec();
136
+ const dataUri = await codec.encodeDataUri(this.bin, FORMAT_TO_MIME[format]);
137
+ return dataUriToBytes(dataUri);
138
+ }
139
+ retain(_n = 1) {
140
+ return this;
141
+ }
142
+ release() {}
143
+ static fromImageBinary(bin, previewScale = 1) {
144
+ return new CpuImage(bin, previewScale);
145
+ }
146
+ }
147
+ registerGpuImageFactory("fromImageBinary", CpuImage.fromImageBinary);
148
+
149
+ // src/media/imageCacheCodec.ts
150
+ registerPortCodec("image", {
151
+ async serialize(value) {
152
+ if (typeof value.materialize !== "function") {
153
+ return value;
154
+ }
155
+ const bin = await value.materialize();
156
+ return {
157
+ kind: "image-binary",
158
+ width: bin.width,
159
+ height: bin.height,
160
+ channels: bin.channels,
161
+ data: bin.data
162
+ };
163
+ },
164
+ async deserialize(cached) {
165
+ if (cached.kind !== "image-binary") {
166
+ return cached;
167
+ }
168
+ return CpuImage.fromImageBinary({
169
+ data: cached.data,
170
+ width: cached.width,
171
+ height: cached.height,
172
+ channels: cached.channels
173
+ });
174
+ }
175
+ });
176
+
177
+ // src/di/Container.ts
178
+ class Container {
179
+ services = new Map;
180
+ factories = new Map;
181
+ singletons = new Set;
182
+ resolving = [];
183
+ register(token, factory2, singleton = true) {
184
+ this.factories.set(token, factory2);
185
+ if (singleton) {
186
+ this.singletons.add(token);
187
+ }
188
+ }
189
+ registerIfAbsent(token, factory2, singleton = true) {
190
+ if (this.factories.has(token) || this.services.has(token)) {
191
+ return;
192
+ }
193
+ this.register(token, factory2, singleton);
194
+ }
195
+ registerInstance(token, instance) {
196
+ this.services.set(token, instance);
197
+ this.singletons.add(token);
198
+ }
199
+ get(token) {
200
+ if (this.services.has(token)) {
201
+ return this.services.get(token);
202
+ }
203
+ const factory2 = this.factories.get(token);
204
+ if (!factory2) {
205
+ throw new Error(`Service not registered: ${String(token)}`);
206
+ }
207
+ if (this.resolving.includes(token)) {
208
+ const cycle = [...this.resolving.slice(this.resolving.indexOf(token)), token];
209
+ throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
210
+ }
211
+ this.resolving.push(token);
212
+ try {
213
+ const instance = factory2();
214
+ if (this.singletons.has(token)) {
215
+ this.services.set(token, instance);
216
+ }
217
+ return instance;
218
+ } finally {
219
+ this.resolving.pop();
220
+ }
221
+ }
222
+ has(token) {
223
+ return this.services.has(token) || this.factories.has(token);
224
+ }
225
+ remove(token) {
226
+ this.services.delete(token);
227
+ this.factories.delete(token);
228
+ this.singletons.delete(token);
229
+ }
230
+ async dispose() {
231
+ const errors = [];
232
+ try {
233
+ for (const service of this.services.values()) {
234
+ if (service == null)
235
+ continue;
236
+ try {
237
+ if (typeof service[Symbol.asyncDispose] === "function") {
238
+ await service[Symbol.asyncDispose]();
239
+ } else if (typeof service[Symbol.dispose] === "function") {
240
+ service[Symbol.dispose]();
241
+ } else if (typeof service.dispose === "function") {
242
+ await service.dispose();
243
+ }
244
+ } catch (err) {
245
+ errors.push(err);
246
+ }
247
+ }
248
+ } finally {
249
+ this.services.clear();
250
+ this.factories.clear();
251
+ this.singletons.clear();
252
+ }
253
+ if (errors.length > 0) {
254
+ throw new AggregateError(errors, "One or more services failed to dispose");
255
+ }
256
+ }
257
+ async[Symbol.asyncDispose]() {
258
+ await this.dispose();
259
+ }
260
+ createChildContainer() {
261
+ const child = new Container;
262
+ this.factories.forEach((factory2, token) => {
263
+ child.factories.set(token, factory2);
264
+ if (this.singletons.has(token)) {
265
+ child.singletons.add(token);
266
+ }
267
+ });
268
+ this.services.forEach((service, token) => {
269
+ if (this.singletons.has(token)) {
270
+ child.services.set(token, service);
271
+ child.singletons.add(token);
272
+ }
273
+ });
274
+ return child;
275
+ }
276
+ }
277
+ var GLOBAL_CONTAINER_KEY = Symbol.for("@workglow/util/di/globalContainer");
278
+ var _g3 = globalThis;
279
+ if (!_g3[GLOBAL_CONTAINER_KEY]) {
280
+ _g3[GLOBAL_CONTAINER_KEY] = new Container;
281
+ }
282
+ var globalContainer = _g3[GLOBAL_CONTAINER_KEY];
283
+
284
+ // src/di/ServiceRegistry.ts
285
+ function createServiceToken(id) {
286
+ return { id, _type: null };
287
+ }
288
+
289
+ class ServiceRegistry {
290
+ container;
291
+ constructor(container = globalContainer) {
292
+ this.container = container;
293
+ }
294
+ register(token, factory2, singleton = true) {
295
+ this.container.register(token.id, factory2, singleton);
296
+ }
297
+ registerIfAbsent(token, factory2, singleton = true) {
298
+ this.container.registerIfAbsent(token.id, factory2, singleton);
299
+ }
300
+ registerInstance(token, instance) {
301
+ this.container.registerInstance(token.id, instance);
302
+ }
303
+ get(token) {
304
+ return this.container.get(token.id);
305
+ }
306
+ has(token) {
307
+ return this.container.has(token.id);
308
+ }
309
+ async dispose() {
310
+ await this.container.dispose();
311
+ }
312
+ }
313
+ var globalServiceRegistry = new ServiceRegistry(globalContainer);
314
+
315
+ // src/di/InputResolverRegistry.ts
316
+ var INPUT_RESOLVERS = createServiceToken("task.input.resolvers");
317
+ globalServiceRegistry.registerIfAbsent(INPUT_RESOLVERS, () => new Map, true);
318
+ function getInputResolvers() {
319
+ return globalServiceRegistry.get(INPUT_RESOLVERS);
320
+ }
321
+ function registerInputResolver(formatPrefix, resolver) {
322
+ const resolvers = getInputResolvers();
323
+ resolvers.set(formatPrefix, resolver);
324
+ }
325
+
326
+ // src/media/imageHydrationResolver.ts
327
+ async function resolveImageString(id, _format, _registry) {
328
+ if (typeof id !== "string")
329
+ return id;
330
+ if (id.startsWith("data:")) {
331
+ return GpuImage.fromDataUri(id);
332
+ }
333
+ const preview = id.length > 32 ? `${id.slice(0, 32)}...` : id;
334
+ throw new Error(`format:"image" resolver received an unsupported string "${preview}". ` + `Only data: URIs are handled. For other schemes register a sub-resolver, ` + `e.g. registerInputResolver("image:http", fn).`);
335
+ }
336
+ registerInputResolver("image", resolveImageString);
337
+
12
338
  // src/media/color.ts
13
339
  var HEX_PATTERN = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
340
+ var CSS_RGB_CHANNEL = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
341
+ var CSS_RGB_ALPHA = "(?:0(?:\\.\\d+)?|1(?:\\.0+)?)";
342
+ 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*\\)$`);
14
343
  function parseHexColor(hex) {
15
344
  if (typeof hex !== "string" || !HEX_PATTERN.test(hex)) {
16
345
  throw new Error(`Invalid hex color: ${String(hex)}`);
@@ -81,9 +410,29 @@ function isColorObject(value) {
81
410
  function isHexColor(value) {
82
411
  return typeof value === "string" && HEX_PATTERN.test(value);
83
412
  }
413
+ function parseCssRgbColor(value) {
414
+ const match = CSS_RGB_PATTERN.exec(value);
415
+ if (!match) {
416
+ throw new Error(`Invalid CSS rgb color: ${String(value)}`);
417
+ }
418
+ const r = Number.parseInt(match[1] ?? "", 10);
419
+ const g = Number.parseInt(match[2] ?? "", 10);
420
+ const b = Number.parseInt(match[3] ?? "", 10);
421
+ const alpha = match[4] === undefined ? 1 : Number.parseFloat(match[4]);
422
+ assertChannel("r", r);
423
+ assertChannel("g", g);
424
+ assertChannel("b", b);
425
+ if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
426
+ throw new Error(`Color alpha out of range (0-1 number): ${match[4]}`);
427
+ }
428
+ return { r, g, b, a: Math.round(alpha * 255) };
429
+ }
84
430
  function resolveColor(value) {
85
- if (typeof value === "string")
86
- return parseHexColor(value);
431
+ if (typeof value === "string") {
432
+ if (isHexColor(value))
433
+ return parseHexColor(value);
434
+ return parseCssRgbColor(value);
435
+ }
87
436
  if (!isColorObject(value)) {
88
437
  throw new Error(`Invalid color value: ${JSON.stringify(value)}`);
89
438
  }
@@ -94,16 +443,79 @@ function resolveColor(value) {
94
443
  a: value.a ?? 255
95
444
  };
96
445
  }
97
- // src/media/imageRasterCodecRegistry.ts
98
- var codec = null;
99
- function registerImageRasterCodec(next) {
100
- codec = next;
446
+ // src/media/encode.ts
447
+ async function encodeImageBinaryBytes(bin, mimeType) {
448
+ const dataUri = await getImageRasterCodec().encodeDataUri(bin, mimeType);
449
+ const b64 = dataUri.slice(dataUri.indexOf(",") + 1);
450
+ const decoded = atob(b64);
451
+ const bytes = new Uint8Array(decoded.length);
452
+ for (let i = 0;i < decoded.length; i++)
453
+ bytes[i] = decoded.charCodeAt(i);
454
+ return bytes;
101
455
  }
102
- function getImageRasterCodec() {
103
- if (!codec) {
104
- throw new Error("Image raster codec is not registered. Ensure you import @workglow/tasks from the browser or Node entry (dist/browser.js or dist/node.js), or call registerImageRasterCodec() during startup.");
456
+ async function encodeImageBinaryToPng(bin) {
457
+ return encodeImageBinaryBytes(bin, "image/png");
458
+ }
459
+ async function imageBinaryToBase64Png(bin) {
460
+ const dataUri = await getImageRasterCodec().encodeDataUri(bin, "image/png");
461
+ return dataUri.slice(dataUri.indexOf(",") + 1);
462
+ }
463
+ async function imageBinaryToDataUri(bin, mimeType = "image/png") {
464
+ return getImageRasterCodec().encodeDataUri(bin, mimeType);
465
+ }
466
+ async function imageBinaryToBlob(bin, mimeType = "image/png") {
467
+ const bytes = await encodeImageBinaryBytes(bin, mimeType);
468
+ return new Blob([bytes.buffer], { type: mimeType });
469
+ }
470
+ // src/media/filterRegistry.ts
471
+ var GLOBAL_REGISTRY_KEY = Symbol.for("@workglow/util/media/filterRegistry");
472
+ var _g4 = globalThis;
473
+ function getRegistry() {
474
+ let reg = _g4[GLOBAL_REGISTRY_KEY];
475
+ if (!reg) {
476
+ reg = new Map;
477
+ _g4[GLOBAL_REGISTRY_KEY] = reg;
478
+ }
479
+ return reg;
480
+ }
481
+ var key = (backend, filter) => `${backend}:${filter}`;
482
+ function registerFilterOp(backend, filter, fn) {
483
+ getRegistry().set(key(backend, filter), fn);
484
+ }
485
+ function applyFilter(image, filter, params) {
486
+ const fn = getRegistry().get(key(image.backend, filter));
487
+ if (!fn) {
488
+ 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.`);
489
+ }
490
+ return fn(image, params);
491
+ }
492
+ function hasFilterOp(backend, filter) {
493
+ return getRegistry().has(key(backend, filter));
494
+ }
495
+ function _resetFilterRegistryForTests() {
496
+ getRegistry().clear();
497
+ }
498
+ // src/media/gpuImageSchema.ts
499
+ function GpuImageSchema(annotations = {}) {
500
+ return {
501
+ type: ["string", "object"],
502
+ properties: {},
503
+ title: "Image",
504
+ description: "Image (hydrated to GpuImage by the runner)",
505
+ ...annotations,
506
+ format: "image"
507
+ };
508
+ }
509
+ // src/media/imageTypes.ts
510
+ function parseDataUri(dataUri) {
511
+ const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
512
+ if (!match) {
513
+ throw new Error("Invalid base64 data URI");
105
514
  }
106
- return codec;
515
+ return {
516
+ mimeType: match[1],
517
+ base64: match[2]
518
+ };
107
519
  }
108
520
  // src/media/MediaRawImage.ts
109
521
  class MediaRawImage {
@@ -124,328 +536,299 @@ function isMediaRawImageShape(value) {
124
536
  const v = value;
125
537
  return v.data instanceof Uint8ClampedArray && typeof v.width === "number" && typeof v.height === "number" && typeof v.channels === "number";
126
538
  }
127
- // src/media/Image.ts
128
- var IMAGE_BRAND = Symbol.for("@workglow/util/media/Image");
129
- function parseDataUriMimeType(dataUri) {
130
- const match = dataUri.match(/^data:([^;,]+)/);
131
- const raw = match?.[1]?.trim();
132
- return raw ? raw.toLowerCase() : "image/png";
539
+ // src/media/previewBudget.ts
540
+ var GLOBAL_RESIZE_KEY = Symbol.for("@workglow/util/media/previewResizeFn");
541
+ var GLOBAL_BUDGET_KEY = Symbol.for("@workglow/util/media/previewBudget");
542
+ var _g5 = globalThis;
543
+ var DEFAULT_BUDGET = 512;
544
+ if (typeof _g5[GLOBAL_BUDGET_KEY] !== "number") {
545
+ _g5[GLOBAL_BUDGET_KEY] = DEFAULT_BUDGET;
133
546
  }
134
- function dataUriToBlob(dataUri) {
135
- const { mimeType, base64 } = parseDataUri(dataUri);
136
- const binary = atob(base64);
137
- const bytes = new Uint8Array(binary.length);
138
- for (let i = 0;i < binary.length; i++) {
139
- bytes[i] = binary.charCodeAt(i);
140
- }
141
- return new Blob([bytes], { type: mimeType });
547
+ function registerPreviewResizeFn(fn) {
548
+ _g5[GLOBAL_RESIZE_KEY] = fn;
142
549
  }
143
- function toImageBinary(value) {
144
- const ch = value.channels;
145
- if (ch !== 1 && ch !== 3 && ch !== 4) {
146
- throw new Error(`Image: unsupported channel count ${ch}`);
147
- }
148
- const data = coerceToUint8ClampedArray(value.data);
149
- return {
150
- data,
151
- width: value.width,
152
- height: value.height,
153
- channels: ch,
154
- rawChannels: value.rawChannels
155
- };
550
+ function getPreviewResizeFn() {
551
+ return _g5[GLOBAL_RESIZE_KEY];
156
552
  }
157
- function coerceToUint8ClampedArray(data) {
158
- if (data instanceof Uint8ClampedArray) {
159
- return data;
160
- }
161
- if (ArrayBuffer.isView(data)) {
162
- return new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
163
- }
164
- if (Array.isArray(data)) {
165
- return Uint8ClampedArray.from(data);
166
- }
167
- if (data && typeof data === "object") {
168
- const obj = data;
169
- const keys = Object.keys(obj);
170
- if (keys.length > 0 && keys.every((k) => /^\d+$/.test(k))) {
171
- const arr = new Uint8ClampedArray(keys.length);
172
- for (let i = 0;i < keys.length; i++) {
173
- arr[i] = Number(obj[String(i)]);
174
- }
175
- return arr;
176
- }
553
+ function getPreviewBudget() {
554
+ return _g5[GLOBAL_BUDGET_KEY];
555
+ }
556
+ function setPreviewBudget(px) {
557
+ if (!Number.isFinite(px) || px <= 0) {
558
+ throw new Error(`setPreviewBudget: invalid value ${px}; expected a positive finite number`);
177
559
  }
178
- throw new Error("Image: pixel data is not array-like");
560
+ _g5[GLOBAL_BUDGET_KEY] = Math.floor(px);
561
+ }
562
+ function previewSource(image) {
563
+ if (image.backend !== "webgpu")
564
+ return image;
565
+ const budget = getPreviewBudget();
566
+ const long = Math.max(image.width, image.height);
567
+ if (long <= budget)
568
+ return image;
569
+ const ratio = budget / long;
570
+ const resize = getPreviewResizeFn();
571
+ if (!resize)
572
+ return image;
573
+ const result = resize(image, Math.round(image.width * ratio), Math.round(image.height * ratio));
574
+ const composed = image.previewScale * ratio;
575
+ return result._setPreviewScale(composed);
179
576
  }
577
+ // src/media/shaderRegistry.browser.ts
578
+ var VERTEX_PRELUDE = `
579
+ @group(0) @binding(0) var src: texture_2d<f32>;
580
+ @group(0) @binding(1) var src_sampler: sampler;
180
581
 
181
- class Image {
182
- [IMAGE_BRAND] = true;
183
- source;
184
- pixelsCache;
185
- dataUriCache = new Map;
186
- blobCache = new Map;
187
- constructor(source) {
188
- this.source = source;
189
- }
190
- static fromDataUri(dataUri) {
191
- if (!dataUri.startsWith("data:")) {
192
- throw new Error("Image.fromDataUri: input must start with 'data:'");
193
- }
194
- return new Image({ kind: "dataUri", dataUri, mimeType: parseDataUriMimeType(dataUri) });
195
- }
196
- static fromPixels(pixels) {
197
- return new Image({ kind: "pixels", pixels });
198
- }
199
- static fromBlob(blob) {
200
- return new Image({ kind: "blob", blob });
201
- }
202
- static from(value) {
203
- if (Image.is(value)) {
204
- return value;
205
- }
206
- if (typeof value === "string" && value.startsWith("data:")) {
207
- return Image.fromDataUri(value);
208
- }
209
- if (typeof Blob !== "undefined" && value instanceof Blob) {
210
- return Image.fromBlob(value);
211
- }
212
- if (typeof ImageBitmap !== "undefined" && value instanceof ImageBitmap) {
213
- return new Image({ kind: "bitmap", bitmap: value });
214
- }
215
- if (typeof VideoFrame !== "undefined" && value instanceof VideoFrame) {
216
- return new Image({ kind: "videoFrame", frame: value });
217
- }
218
- if (typeof OffscreenCanvas !== "undefined" && value instanceof OffscreenCanvas) {
219
- return new Image({ kind: "offscreenCanvas", canvas: value });
220
- }
221
- if (value && typeof value === "object" && "data" in value && "width" in value && "height" in value && "channels" in value) {
222
- return Image.fromPixels(toImageBinary(value));
223
- }
224
- throw new Error(`Image.from: unsupported image value of type ${typeof value}`);
225
- }
226
- static is(value) {
227
- return typeof value === "object" && value !== null && value[IMAGE_BRAND] === true;
228
- }
229
- static fromJSON(value) {
230
- if (Image.is(value)) {
231
- return value;
232
- }
233
- if (typeof value === "string" && value.startsWith("data:")) {
234
- return Image.fromDataUri(value);
235
- }
236
- if (value && typeof value === "object" && value.unsynced === true) {
237
- const kind = value.kind;
238
- throw new Error(`Image.fromJSON: cannot reconstruct image from "unsynced" sentinel (kind=${String(kind)}); pixels were not materialized before serialization. Call await image.getPixels() before JSON.stringify.`);
239
- }
240
- if (value && typeof value === "object" && "data" in value && typeof value.width === "number" && typeof value.height === "number" && typeof value.channels === "number") {
241
- const v = value;
242
- return Image.fromPixels(toImageBinary({
243
- data: v.data,
244
- width: v.width,
245
- height: v.height,
246
- channels: v.channels
247
- }));
248
- }
249
- throw new Error("Image.fromJSON: value does not match any known Image shape");
250
- }
251
- get kind() {
252
- return this.source.kind;
253
- }
254
- get mimeType() {
255
- if (this.source.kind === "dataUri")
256
- return this.source.mimeType;
257
- if (this.source.kind === "blob")
258
- return this.source.blob.type || undefined;
259
- return;
260
- }
261
- get width() {
262
- if (this.source.kind === "pixels")
263
- return this.source.pixels.width;
264
- if (this.source.kind === "bitmap")
265
- return this.source.bitmap.width;
266
- if (this.source.kind === "offscreenCanvas")
267
- return this.source.canvas.width;
268
- if (this.source.kind === "videoFrame")
269
- return this.source.frame.displayWidth;
270
- return this.pixelsCache?.width;
271
- }
272
- get height() {
273
- if (this.source.kind === "pixels")
274
- return this.source.pixels.height;
275
- if (this.source.kind === "bitmap")
276
- return this.source.bitmap.height;
277
- if (this.source.kind === "offscreenCanvas")
278
- return this.source.canvas.height;
279
- if (this.source.kind === "videoFrame")
280
- return this.source.frame.displayHeight;
281
- return this.pixelsCache?.height;
282
- }
283
- get channels() {
284
- if (this.source.kind === "pixels")
285
- return this.source.pixels.channels;
286
- return this.pixelsCache?.channels;
287
- }
288
- async getPixels() {
289
- if (this.pixelsCache)
290
- return this.pixelsCache;
291
- if (this.source.kind === "pixels") {
292
- this.pixelsCache = this.source.pixels;
293
- return this.pixelsCache;
294
- }
295
- if (this.source.kind === "dataUri") {
296
- this.pixelsCache = await getImageRasterCodec().decodeDataUri(this.source.dataUri);
297
- return this.pixelsCache;
298
- }
299
- if (this.source.kind === "blob") {
300
- const dataUri = await blobToDataUri(this.source.blob);
301
- this.pixelsCache = await getImageRasterCodec().decodeDataUri(dataUri);
302
- return this.pixelsCache;
582
+ struct VsOut {
583
+ @builtin(position) pos: vec4f,
584
+ @location(0) uv: vec2f,
585
+ };
586
+
587
+ @vertex
588
+ fn vs(@builtin(vertex_index) vid: u32) -> VsOut {
589
+ let xy = vec2f(f32((vid << 1u) & 2u), f32(vid & 2u));
590
+ var out: VsOut;
591
+ out.pos = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
592
+ out.uv = vec2f(xy.x, 1.0 - xy.y);
593
+ return out;
594
+ }`;
595
+ var PASSTHROUGH_SHADER_SRC = `${VERTEX_PRELUDE}
596
+ @fragment
597
+ fn fs(in: VsOut) -> @location(0) vec4f {
598
+ return textureSample(src, src_sampler, in.uv);
599
+ }
600
+ `;
601
+ function createShaderCache(device) {
602
+ const map = new Map;
603
+ return {
604
+ get(source) {
605
+ let mod = map.get(source);
606
+ if (!mod) {
607
+ mod = device.createShaderModule({ code: source });
608
+ map.set(source, mod);
609
+ }
610
+ return mod;
303
611
  }
304
- throw new Error(`Image.getPixels: browser-only source '${this.source.kind}' requires Image.browser augmentation`);
612
+ };
613
+ }
614
+ var singleton = null;
615
+ function getShaderCache(device) {
616
+ if (!singleton || singleton.device !== device) {
617
+ singleton = { device, cache: createShaderCache(device) };
305
618
  }
306
- async getDataUri(mimeType = "image/png") {
307
- if (this.source.kind === "dataUri") {
308
- if (mimeType === this.source.mimeType) {
309
- return this.source.dataUri;
619
+ return singleton.cache;
620
+ }
621
+ // src/media/texturePool.browser.ts
622
+ var DEFAULT_CAPACITY_PER_SIZE = 8;
623
+ var TEXTURE_USAGE = 4 | 16 | 1 | 2;
624
+ function createTexturePool(device, opts = {}) {
625
+ const capacity = opts.capacityPerSize ?? DEFAULT_CAPACITY_PER_SIZE;
626
+ const buckets = new Map;
627
+ const owners = new WeakMap;
628
+ const sizeClassKey = (w, h, f) => `${w}x${h}:${f}`;
629
+ return {
630
+ acquire(width, height, format) {
631
+ const k = sizeClassKey(width, height, format);
632
+ const bucket = buckets.get(k);
633
+ if (bucket && bucket.length > 0) {
634
+ const reused = bucket.pop();
635
+ reused.inPool = false;
636
+ return reused.texture;
310
637
  }
311
- }
312
- const cached = this.dataUriCache.get(mimeType);
313
- if (cached)
314
- return cached;
315
- const pixels = await this.getPixels();
316
- const dataUri = await getImageRasterCodec().encodeDataUri(pixels, mimeType);
317
- this.dataUriCache.set(mimeType, dataUri);
318
- return dataUri;
319
- }
320
- async getBlob(mimeType = "image/png") {
321
- if (this.source.kind === "blob" && (!mimeType || this.source.blob.type === mimeType)) {
322
- return this.source.blob;
323
- }
324
- const cached = this.blobCache.get(mimeType);
325
- if (cached)
326
- return cached;
327
- if (this.source.kind === "dataUri" && this.source.mimeType === mimeType) {
328
- const blob2 = dataUriToBlob(this.source.dataUri);
329
- this.blobCache.set(mimeType, blob2);
330
- return blob2;
331
- }
332
- const dataUri = await this.getDataUri(mimeType);
333
- const blob = dataUriToBlob(dataUri);
334
- this.blobCache.set(mimeType, blob);
335
- return blob;
336
- }
337
- async toFirstSupported(supports) {
338
- const canonical = this.canonicalSupport();
339
- if (canonical && supports.includes(canonical)) {
340
- return this.currentSourceValue();
341
- }
342
- for (const want of supports) {
343
- switch (want) {
344
- case "ImageBinary":
345
- return this.getPixels();
346
- case "Blob":
347
- return this.getBlob();
348
- case "DataUri":
349
- return this.getDataUri();
350
- case "RawImage": {
351
- const p = await this.getPixels();
352
- return new MediaRawImage(p.data, p.width, p.height, p.channels);
353
- }
354
- case "ImageBitmap":
355
- case "VideoFrame":
356
- case "OffscreenCanvas": {
357
- const asBrowser = this;
358
- if (asBrowser.toFirstSupportedBrowser) {
359
- const produced = await asBrowser.toFirstSupportedBrowser(want);
360
- if (produced !== undefined)
361
- return produced;
362
- }
363
- continue;
638
+ const texture = device.createTexture({
639
+ size: [width, height, 1],
640
+ format,
641
+ usage: TEXTURE_USAGE
642
+ });
643
+ const entry = { texture, width, height, format, inPool: false };
644
+ owners.set(texture, entry);
645
+ return texture;
646
+ },
647
+ release(texture) {
648
+ const entry = owners.get(texture);
649
+ if (!entry)
650
+ return;
651
+ if (entry.inPool)
652
+ return;
653
+ const k = sizeClassKey(entry.width, entry.height, entry.format);
654
+ let bucket = buckets.get(k);
655
+ if (!bucket) {
656
+ bucket = [];
657
+ buckets.set(k, bucket);
658
+ }
659
+ if (bucket.length >= capacity) {
660
+ owners.delete(texture);
661
+ texture.destroy();
662
+ return;
663
+ }
664
+ entry.inPool = true;
665
+ bucket.push(entry);
666
+ },
667
+ drain() {
668
+ for (const bucket of buckets.values()) {
669
+ for (const entry of bucket) {
670
+ owners.delete(entry.texture);
671
+ entry.texture.destroy();
364
672
  }
365
- case "Sharp":
366
- continue;
367
673
  }
674
+ buckets.clear();
368
675
  }
369
- throw new Error(`Image.toFirstSupported: none of [${supports.join(", ")}] can be produced on this platform`);
676
+ };
677
+ }
678
+ var singleton2 = null;
679
+ function getTexturePool(device) {
680
+ if (!singleton2 || singleton2.device !== device) {
681
+ singleton2?.pool.drain();
682
+ singleton2 = { device, pool: createTexturePool(device) };
370
683
  }
371
- toJSON() {
372
- if (this.source.kind === "dataUri") {
373
- return this.source.dataUri;
374
- }
375
- const pixels = this.source.kind === "pixels" ? this.source.pixels : this.pixelsCache;
376
- if (pixels) {
377
- return {
378
- data: Array.from(pixels.data),
379
- width: pixels.width,
380
- height: pixels.height,
381
- channels: pixels.channels
382
- };
383
- }
384
- return { unsynced: true, kind: this.source.kind };
385
- }
386
- canonicalSupport() {
387
- switch (this.source.kind) {
388
- case "dataUri":
389
- return "DataUri";
390
- case "pixels":
391
- return "ImageBinary";
392
- case "blob":
393
- return "Blob";
394
- case "bitmap":
395
- return "ImageBitmap";
396
- case "videoFrame":
397
- return "VideoFrame";
398
- case "offscreenCanvas":
399
- return "OffscreenCanvas";
684
+ return singleton2.pool;
685
+ }
686
+ function resetTexturePoolForTests() {
687
+ singleton2?.pool.drain();
688
+ singleton2 = null;
689
+ }
690
+ // src/media/sharpImage.node.ts
691
+ var cachedSharp = null;
692
+ async function loadSharp() {
693
+ if (cachedSharp)
694
+ return cachedSharp;
695
+ const mod = await import("sharp");
696
+ cachedSharp = mod.default ?? mod;
697
+ return cachedSharp;
698
+ }
699
+
700
+ class SharpImage {
701
+ pipeline;
702
+ width;
703
+ height;
704
+ channels;
705
+ backend = "sharp";
706
+ _previewScale;
707
+ constructor(pipeline, width, height, channels, previewScale = 1) {
708
+ this.pipeline = pipeline;
709
+ this.width = width;
710
+ this.height = height;
711
+ this.channels = channels;
712
+ this._previewScale = previewScale;
713
+ }
714
+ get previewScale() {
715
+ return this._previewScale;
716
+ }
717
+ _setPreviewScale(scale) {
718
+ this._previewScale = scale;
719
+ return this;
720
+ }
721
+ static async fromImageBinary(bin, previewScale = 1) {
722
+ const sharp = await loadSharp();
723
+ const buf = Buffer.from(bin.data.buffer, bin.data.byteOffset, bin.data.byteLength);
724
+ const pipeline = sharp(buf, {
725
+ raw: { width: bin.width, height: bin.height, channels: bin.channels }
726
+ });
727
+ return new SharpImage(pipeline, bin.width, bin.height, bin.channels, previewScale);
728
+ }
729
+ static async fromBuffer(buf) {
730
+ const sharp = await loadSharp();
731
+ const pipeline = sharp(buf);
732
+ const meta = await pipeline.clone().metadata();
733
+ if (typeof meta.width !== "number" || typeof meta.height !== "number") {
734
+ throw new Error("SharpImage.fromBuffer: input has no width/height metadata");
400
735
  }
736
+ return new SharpImage(pipeline, meta.width, meta.height, meta.channels ?? 4);
737
+ }
738
+ apply(op, outSize) {
739
+ const next = op(this.pipeline.clone());
740
+ return new SharpImage(next, outSize?.width ?? this.width, outSize?.height ?? this.height, outSize?.channels ?? this.channels, this._previewScale);
401
741
  }
402
- getSource() {
403
- return this.source;
404
- }
405
- setPixelsCache(pixels) {
406
- this.pixelsCache = pixels;
407
- }
408
- currentSourceValue() {
409
- switch (this.source.kind) {
410
- case "dataUri":
411
- return this.source.dataUri;
412
- case "pixels":
413
- return this.source.pixels;
414
- case "blob":
415
- return this.source.blob;
416
- case "bitmap":
417
- return this.source.bitmap;
418
- case "videoFrame":
419
- return this.source.frame;
420
- case "offscreenCanvas":
421
- return this.source.canvas;
742
+ async materialize() {
743
+ const result = await this.pipeline.clone().raw().toBuffer({ resolveWithObject: true });
744
+ if (!isObjectResult(result)) {
745
+ throw new Error("SharpImage.materialize: expected resolveWithObject result");
422
746
  }
747
+ const { data, info } = result;
748
+ const out = new Uint8ClampedArray(data.length);
749
+ out.set(data);
750
+ return {
751
+ data: out,
752
+ width: info.width,
753
+ height: info.height,
754
+ channels: info.channels
755
+ };
423
756
  }
757
+ async toCanvas(_canvas) {
758
+ throw new Error("SharpImage.toCanvas is not supported in node/bun environments");
759
+ }
760
+ async encode(format, quality) {
761
+ const p = this.pipeline.clone();
762
+ let result;
763
+ if (format === "png")
764
+ result = await p.png({ quality }).toBuffer();
765
+ else if (format === "jpeg")
766
+ result = await p.jpeg({ quality }).toBuffer();
767
+ else
768
+ result = await p.webp({ quality }).toBuffer();
769
+ const buf = result;
770
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
771
+ }
772
+ retain(_n = 1) {
773
+ return this;
774
+ }
775
+ release() {}
424
776
  }
425
- async function blobToDataUri(blob) {
426
- const buffer = await blob.arrayBuffer();
427
- const bytes = new Uint8Array(buffer);
428
- let binary = "";
429
- const CHUNK = 8192;
430
- for (let i = 0;i < bytes.byteLength; i += CHUNK) {
431
- binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
432
- }
433
- const mime = blob.type || "image/png";
434
- return `data:${mime};base64,${btoa(binary)}`;
777
+ function isObjectResult(r) {
778
+ return !!r && typeof r === "object" && "data" in r && "info" in r;
779
+ }
780
+ // src/media-node.ts
781
+ async function getGpuDevice() {
782
+ return null;
435
783
  }
784
+ function resetGpuDeviceForTests() {}
785
+ registerGpuImageFactory("fromImageBinaryAsync", (bin) => SharpImage.fromImageBinary(bin));
786
+ registerGpuImageFactory("fromDataUri", async (dataUri) => {
787
+ const bin = await getImageRasterCodec().decodeDataUri(dataUri);
788
+ return SharpImage.fromImageBinary(bin);
789
+ });
790
+ registerGpuImageFactory("fromBlob", async (blob) => {
791
+ const buf = Buffer.from(await blob.arrayBuffer());
792
+ return SharpImage.fromBuffer(buf);
793
+ });
436
794
  export {
437
795
  toHexColor,
796
+ setPreviewBudget,
438
797
  resolveColor,
798
+ resetTexturePoolForTests,
799
+ resetGpuDeviceForTests,
800
+ registerPreviewResizeFn,
439
801
  registerImageRasterCodec,
802
+ registerGpuImageFactory,
803
+ registerFilterOp,
804
+ previewSource,
440
805
  parseHexColor,
441
806
  parseDataUri,
442
807
  isMediaRawImageShape,
443
808
  isHexColor,
444
809
  isColorObject,
810
+ imageBinaryToDataUri,
811
+ imageBinaryToBlob,
812
+ imageBinaryToBase64Png,
813
+ hasFilterOp,
814
+ getTexturePool,
815
+ getShaderCache,
816
+ getPreviewBudget,
445
817
  getImageRasterCodec,
446
- dataUriToBlob,
818
+ getGpuImageFactory,
819
+ getGpuDevice,
820
+ encodeImageBinaryToPng,
821
+ createTexturePool,
822
+ createShaderCache,
823
+ applyFilter,
824
+ _resetFilterRegistryForTests,
825
+ VERTEX_PRELUDE,
826
+ SharpImage,
827
+ PASSTHROUGH_SHADER_SRC,
447
828
  MediaRawImage,
448
- Image
829
+ GpuImageSchema,
830
+ GpuImage as GpuImageFactory,
831
+ CpuImage
449
832
  };
450
833
 
451
- //# debugId=DC7CC1D941A4FA1F64756E2164756E21
834
+ //# debugId=2970EA408E9A184364756E2164756E21