@workglow/util 0.2.16 → 0.2.18
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/browser.js +54 -23
- package/dist/browser.js.map +7 -6
- package/dist/bun.js +54 -23
- package/dist/bun.js.map +7 -6
- package/dist/di/Container.d.ts +0 -3
- package/dist/di/Container.d.ts.map +1 -1
- package/dist/di/PortCodecRegistry.d.ts +14 -0
- package/dist/di/PortCodecRegistry.d.ts.map +1 -0
- package/dist/di/index.d.ts +1 -0
- package/dist/di/index.d.ts.map +1 -1
- package/dist/media/color.d.ts +3 -3
- package/dist/media/color.d.ts.map +1 -1
- package/dist/media/cpuImage.d.ts +29 -0
- package/dist/media/cpuImage.d.ts.map +1 -0
- package/dist/media/encode.d.ts +11 -0
- package/dist/media/encode.d.ts.map +1 -0
- package/dist/media/filterRegistry.d.ts +13 -0
- package/dist/media/filterRegistry.d.ts.map +1 -0
- package/dist/media/gpuDevice.browser.d.ts +8 -0
- package/dist/media/gpuDevice.browser.d.ts.map +1 -0
- package/dist/media/gpuImage.d.ts +50 -0
- package/dist/media/gpuImage.d.ts.map +1 -0
- package/dist/media/gpuImageSchema.d.ts +8 -0
- package/dist/media/gpuImageSchema.d.ts.map +1 -0
- package/dist/media/imageCacheCodec.d.ts +9 -0
- package/dist/media/imageCacheCodec.d.ts.map +1 -0
- package/dist/media/imageHydrationResolver.d.ts +2 -0
- package/dist/media/imageHydrationResolver.d.ts.map +1 -0
- package/dist/media/imageRasterCodecRegistry.d.ts.map +1 -1
- package/dist/media/previewBudget.d.ts +23 -0
- package/dist/media/previewBudget.d.ts.map +1 -0
- package/dist/media/shaderRegistry.browser.d.ts +13 -0
- package/dist/media/shaderRegistry.browser.d.ts.map +1 -0
- package/dist/media/sharpImage.bun.d.ts +7 -0
- package/dist/media/sharpImage.bun.d.ts.map +1 -0
- package/dist/media/sharpImage.node.d.ts +93 -0
- package/dist/media/sharpImage.node.d.ts.map +1 -0
- package/dist/media/texturePool.browser.d.ts +17 -0
- package/dist/media/texturePool.browser.d.ts.map +1 -0
- package/dist/media/webGpuImage.browser.d.ts +40 -0
- package/dist/media/webGpuImage.browser.d.ts.map +1 -0
- package/dist/media-browser.d.ts +18 -3
- package/dist/media-browser.d.ts.map +1 -1
- package/dist/media-browser.js +850 -404
- package/dist/media-browser.js.map +21 -7
- package/dist/media-node.d.ts +21 -2
- package/dist/media-node.d.ts.map +1 -1
- package/dist/media-node.js +695 -312
- package/dist/media-node.js.map +20 -6
- package/dist/node.js +54 -23
- package/dist/node.js.map +7 -6
- package/dist/worker/WorkerManager.d.ts +6 -6
- package/dist/worker/WorkerManager.d.ts.map +1 -1
- package/dist/worker/WorkerServerBase.d.ts +8 -8
- package/dist/worker/WorkerServerBase.d.ts.map +1 -1
- package/dist/worker-browser.js +54 -23
- package/dist/worker-browser.js.map +7 -6
- package/dist/worker-bun.js +54 -23
- package/dist/worker-bun.js.map +7 -6
- package/dist/worker-node.js +54 -23
- package/dist/worker-node.js.map +7 -6
- package/package.json +4 -1
- package/dist/media/Image.browser.d.ts +0 -24
- package/dist/media/Image.browser.d.ts.map +0 -1
- package/dist/media/Image.d.ts +0 -100
- package/dist/media/Image.d.ts.map +0 -1
package/dist/media-node.js
CHANGED
|
@@ -1,16 +1,345 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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/
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
135
|
-
|
|
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
|
|
144
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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
|
-
|
|
829
|
+
GpuImageSchema,
|
|
830
|
+
GpuImage as GpuImageFactory,
|
|
831
|
+
CpuImage
|
|
449
832
|
};
|
|
450
833
|
|
|
451
|
-
//# debugId=
|
|
834
|
+
//# debugId=2970EA408E9A184364756E2164756E21
|