@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.
- package/dist/media-browser.d.ts +6 -5
- package/dist/media-browser.d.ts.map +1 -1
- package/dist/media-browser.js +506 -501
- package/dist/media-browser.js.map +11 -11
- package/package.json +1 -1
package/dist/media-browser.js
CHANGED
|
@@ -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,
|
|
195
|
-
this.factories.set(token,
|
|
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,
|
|
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,
|
|
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
|
|
215
|
-
if (!
|
|
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 =
|
|
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((
|
|
274
|
-
child.factories.set(token,
|
|
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
|
|
290
|
-
if (!
|
|
291
|
-
|
|
482
|
+
var _g3 = globalThis;
|
|
483
|
+
if (!_g3[GLOBAL_CONTAINER_KEY]) {
|
|
484
|
+
_g3[GLOBAL_CONTAINER_KEY] = new Container;
|
|
292
485
|
}
|
|
293
|
-
var globalContainer =
|
|
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,
|
|
306
|
-
this.container.register(token.id,
|
|
498
|
+
register(token, factory2, singleton = true) {
|
|
499
|
+
this.container.register(token.id, factory2, singleton);
|
|
307
500
|
}
|
|
308
|
-
registerIfAbsent(token,
|
|
309
|
-
this.container.registerIfAbsent(token.id,
|
|
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/
|
|
351
|
-
var
|
|
352
|
-
var
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
651
|
+
function getShaderCache(device) {
|
|
869
652
|
if (!singleton2 || singleton2.device !== device) {
|
|
870
|
-
singleton2
|
|
871
|
-
singleton2 = { device, pool: createTexturePool(device) };
|
|
653
|
+
singleton2 = { device, cache: createShaderCache(device) };
|
|
872
654
|
}
|
|
873
|
-
return singleton2.
|
|
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=
|
|
1101
|
+
//# debugId=DBD4B12E61C4A72264756E2164756E21
|