@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-browser.js
CHANGED
|
@@ -1,16 +1,342 @@
|
|
|
1
|
-
// src/media/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/media/imageCacheCodec.ts
|
|
2
|
+
import { registerPortCodec } from "@workglow/util";
|
|
3
|
+
|
|
4
|
+
// src/media/gpuImage.ts
|
|
5
|
+
var GLOBAL_FACTORY_KEY = Symbol.for("@workglow/util/media/gpuImageFactory");
|
|
6
|
+
var _g = globalThis;
|
|
7
|
+
if (!_g[GLOBAL_FACTORY_KEY]) {
|
|
8
|
+
_g[GLOBAL_FACTORY_KEY] = {};
|
|
9
|
+
}
|
|
10
|
+
var factory = _g[GLOBAL_FACTORY_KEY];
|
|
11
|
+
function registerGpuImageFactory(key, fn) {
|
|
12
|
+
factory[key] = fn;
|
|
13
|
+
}
|
|
14
|
+
function getGpuImageFactory(key) {
|
|
15
|
+
const fn = factory[key];
|
|
16
|
+
return typeof fn === "function" ? fn : undefined;
|
|
17
|
+
}
|
|
18
|
+
var GpuImage = new Proxy({}, {
|
|
19
|
+
get(_t, prop) {
|
|
20
|
+
if (typeof prop !== "string" || prop === "then")
|
|
21
|
+
return;
|
|
22
|
+
const fn = factory[prop];
|
|
23
|
+
if (typeof fn !== "function") {
|
|
24
|
+
throw new Error(`GpuImage.${prop} is not registered. Import the platform entry point.`);
|
|
25
|
+
}
|
|
26
|
+
return fn;
|
|
6
27
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// src/media/imageRasterCodecRegistry.ts
|
|
31
|
+
var GLOBAL_CODEC_KEY = Symbol.for("@workglow/util/media/imageRasterCodec");
|
|
32
|
+
var _g2 = globalThis;
|
|
33
|
+
if (!_g2[GLOBAL_CODEC_KEY]) {
|
|
34
|
+
_g2[GLOBAL_CODEC_KEY] = { value: null };
|
|
35
|
+
}
|
|
36
|
+
var slot = _g2[GLOBAL_CODEC_KEY];
|
|
37
|
+
function registerImageRasterCodec(next) {
|
|
38
|
+
slot.value = next;
|
|
39
|
+
}
|
|
40
|
+
function getImageRasterCodec() {
|
|
41
|
+
if (!slot.value) {
|
|
42
|
+
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.");
|
|
43
|
+
}
|
|
44
|
+
return slot.value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/media/cpuImage.ts
|
|
48
|
+
var FORMAT_TO_MIME = {
|
|
49
|
+
png: "image/png",
|
|
50
|
+
jpeg: "image/jpeg",
|
|
51
|
+
webp: "image/webp"
|
|
52
|
+
};
|
|
53
|
+
function dataUriToBytes(dataUri) {
|
|
54
|
+
const comma = dataUri.indexOf(",");
|
|
55
|
+
const b64 = dataUri.slice(comma + 1);
|
|
56
|
+
const bin = atob(b64);
|
|
57
|
+
const bytes = new Uint8Array(bin.length);
|
|
58
|
+
for (let i = 0;i < bin.length; i++)
|
|
59
|
+
bytes[i] = bin.charCodeAt(i);
|
|
60
|
+
return bytes;
|
|
61
|
+
}
|
|
62
|
+
function expandToRgba(bin) {
|
|
63
|
+
if (bin.channels === 4)
|
|
64
|
+
return bin.data;
|
|
65
|
+
const px = bin.width * bin.height;
|
|
66
|
+
const out = new Uint8ClampedArray(px * 4);
|
|
67
|
+
if (bin.channels === 3) {
|
|
68
|
+
for (let i = 0;i < px; i++) {
|
|
69
|
+
out[i * 4 + 0] = bin.data[i * 3 + 0] ?? 0;
|
|
70
|
+
out[i * 4 + 1] = bin.data[i * 3 + 1] ?? 0;
|
|
71
|
+
out[i * 4 + 2] = bin.data[i * 3 + 2] ?? 0;
|
|
72
|
+
out[i * 4 + 3] = 255;
|
|
73
|
+
}
|
|
74
|
+
} else if (bin.channels === 1) {
|
|
75
|
+
for (let i = 0;i < px; i++) {
|
|
76
|
+
const g = bin.data[i] ?? 0;
|
|
77
|
+
out[i * 4 + 0] = g;
|
|
78
|
+
out[i * 4 + 1] = g;
|
|
79
|
+
out[i * 4 + 2] = g;
|
|
80
|
+
out[i * 4 + 3] = 255;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class CpuImage {
|
|
87
|
+
bin;
|
|
88
|
+
backend = "cpu";
|
|
89
|
+
_previewScale;
|
|
90
|
+
constructor(bin, previewScale = 1) {
|
|
91
|
+
this.bin = bin;
|
|
92
|
+
this._previewScale = previewScale;
|
|
93
|
+
}
|
|
94
|
+
get width() {
|
|
95
|
+
return this.bin.width;
|
|
96
|
+
}
|
|
97
|
+
get height() {
|
|
98
|
+
return this.bin.height;
|
|
99
|
+
}
|
|
100
|
+
get channels() {
|
|
101
|
+
return this.bin.channels;
|
|
102
|
+
}
|
|
103
|
+
get previewScale() {
|
|
104
|
+
return this._previewScale;
|
|
105
|
+
}
|
|
106
|
+
_setPreviewScale(scale) {
|
|
107
|
+
this._previewScale = scale;
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
async materialize() {
|
|
111
|
+
return this.bin;
|
|
112
|
+
}
|
|
113
|
+
getBinary() {
|
|
114
|
+
return this.bin;
|
|
115
|
+
}
|
|
116
|
+
async toCanvas(canvas) {
|
|
117
|
+
if (typeof ImageData === "undefined") {
|
|
118
|
+
throw new Error("CpuImage.toCanvas requires a browser environment with ImageData");
|
|
119
|
+
}
|
|
120
|
+
const rgba = expandToRgba(this.bin);
|
|
121
|
+
const id = new ImageData(new Uint8ClampedArray(rgba.buffer, rgba.byteOffset, rgba.byteLength), this.bin.width, this.bin.height);
|
|
122
|
+
if (canvas.width !== this.bin.width)
|
|
123
|
+
canvas.width = this.bin.width;
|
|
124
|
+
if (canvas.height !== this.bin.height)
|
|
125
|
+
canvas.height = this.bin.height;
|
|
126
|
+
const ctx = canvas.getContext("2d");
|
|
127
|
+
if (!ctx)
|
|
128
|
+
throw new Error("CpuImage.toCanvas could not acquire a 2D context");
|
|
129
|
+
ctx.putImageData(id, 0, 0);
|
|
130
|
+
}
|
|
131
|
+
async encode(format, _quality) {
|
|
132
|
+
const codec = getImageRasterCodec();
|
|
133
|
+
const dataUri = await codec.encodeDataUri(this.bin, FORMAT_TO_MIME[format]);
|
|
134
|
+
return dataUriToBytes(dataUri);
|
|
135
|
+
}
|
|
136
|
+
retain(_n = 1) {
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
release() {}
|
|
140
|
+
static fromImageBinary(bin, previewScale = 1) {
|
|
141
|
+
return new CpuImage(bin, previewScale);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
registerGpuImageFactory("fromImageBinary", CpuImage.fromImageBinary);
|
|
145
|
+
|
|
146
|
+
// src/media/imageCacheCodec.ts
|
|
147
|
+
registerPortCodec("image", {
|
|
148
|
+
async serialize(value) {
|
|
149
|
+
if (typeof value.materialize !== "function") {
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
const bin = await value.materialize();
|
|
153
|
+
return {
|
|
154
|
+
kind: "image-binary",
|
|
155
|
+
width: bin.width,
|
|
156
|
+
height: bin.height,
|
|
157
|
+
channels: bin.channels,
|
|
158
|
+
data: bin.data
|
|
159
|
+
};
|
|
160
|
+
},
|
|
161
|
+
async deserialize(cached) {
|
|
162
|
+
if (cached.kind !== "image-binary") {
|
|
163
|
+
return cached;
|
|
164
|
+
}
|
|
165
|
+
return CpuImage.fromImageBinary({
|
|
166
|
+
data: cached.data,
|
|
167
|
+
width: cached.width,
|
|
168
|
+
height: cached.height,
|
|
169
|
+
channels: cached.channels
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// src/di/Container.ts
|
|
175
|
+
class Container {
|
|
176
|
+
services = new Map;
|
|
177
|
+
factories = new Map;
|
|
178
|
+
singletons = new Set;
|
|
179
|
+
resolving = [];
|
|
180
|
+
register(token, factory2, singleton = true) {
|
|
181
|
+
this.factories.set(token, factory2);
|
|
182
|
+
if (singleton) {
|
|
183
|
+
this.singletons.add(token);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
registerIfAbsent(token, factory2, singleton = true) {
|
|
187
|
+
if (this.factories.has(token) || this.services.has(token)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.register(token, factory2, singleton);
|
|
191
|
+
}
|
|
192
|
+
registerInstance(token, instance) {
|
|
193
|
+
this.services.set(token, instance);
|
|
194
|
+
this.singletons.add(token);
|
|
195
|
+
}
|
|
196
|
+
get(token) {
|
|
197
|
+
if (this.services.has(token)) {
|
|
198
|
+
return this.services.get(token);
|
|
199
|
+
}
|
|
200
|
+
const factory2 = this.factories.get(token);
|
|
201
|
+
if (!factory2) {
|
|
202
|
+
throw new Error(`Service not registered: ${String(token)}`);
|
|
203
|
+
}
|
|
204
|
+
if (this.resolving.includes(token)) {
|
|
205
|
+
const cycle = [...this.resolving.slice(this.resolving.indexOf(token)), token];
|
|
206
|
+
throw new Error(`Circular dependency detected: ${cycle.join(" -> ")}`);
|
|
207
|
+
}
|
|
208
|
+
this.resolving.push(token);
|
|
209
|
+
try {
|
|
210
|
+
const instance = factory2();
|
|
211
|
+
if (this.singletons.has(token)) {
|
|
212
|
+
this.services.set(token, instance);
|
|
213
|
+
}
|
|
214
|
+
return instance;
|
|
215
|
+
} finally {
|
|
216
|
+
this.resolving.pop();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
has(token) {
|
|
220
|
+
return this.services.has(token) || this.factories.has(token);
|
|
221
|
+
}
|
|
222
|
+
remove(token) {
|
|
223
|
+
this.services.delete(token);
|
|
224
|
+
this.factories.delete(token);
|
|
225
|
+
this.singletons.delete(token);
|
|
226
|
+
}
|
|
227
|
+
async dispose() {
|
|
228
|
+
const errors = [];
|
|
229
|
+
try {
|
|
230
|
+
for (const service of this.services.values()) {
|
|
231
|
+
if (service == null)
|
|
232
|
+
continue;
|
|
233
|
+
try {
|
|
234
|
+
if (typeof service[Symbol.asyncDispose] === "function") {
|
|
235
|
+
await service[Symbol.asyncDispose]();
|
|
236
|
+
} else if (typeof service[Symbol.dispose] === "function") {
|
|
237
|
+
service[Symbol.dispose]();
|
|
238
|
+
} else if (typeof service.dispose === "function") {
|
|
239
|
+
await service.dispose();
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
errors.push(err);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
} finally {
|
|
246
|
+
this.services.clear();
|
|
247
|
+
this.factories.clear();
|
|
248
|
+
this.singletons.clear();
|
|
249
|
+
}
|
|
250
|
+
if (errors.length > 0) {
|
|
251
|
+
throw new AggregateError(errors, "One or more services failed to dispose");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async[Symbol.asyncDispose]() {
|
|
255
|
+
await this.dispose();
|
|
256
|
+
}
|
|
257
|
+
createChildContainer() {
|
|
258
|
+
const child = new Container;
|
|
259
|
+
this.factories.forEach((factory2, token) => {
|
|
260
|
+
child.factories.set(token, factory2);
|
|
261
|
+
if (this.singletons.has(token)) {
|
|
262
|
+
child.singletons.add(token);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.services.forEach((service, token) => {
|
|
266
|
+
if (this.singletons.has(token)) {
|
|
267
|
+
child.services.set(token, service);
|
|
268
|
+
child.singletons.add(token);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return child;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
var GLOBAL_CONTAINER_KEY = Symbol.for("@workglow/util/di/globalContainer");
|
|
275
|
+
var _g3 = globalThis;
|
|
276
|
+
if (!_g3[GLOBAL_CONTAINER_KEY]) {
|
|
277
|
+
_g3[GLOBAL_CONTAINER_KEY] = new Container;
|
|
11
278
|
}
|
|
279
|
+
var globalContainer = _g3[GLOBAL_CONTAINER_KEY];
|
|
280
|
+
|
|
281
|
+
// src/di/ServiceRegistry.ts
|
|
282
|
+
function createServiceToken(id) {
|
|
283
|
+
return { id, _type: null };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
class ServiceRegistry {
|
|
287
|
+
container;
|
|
288
|
+
constructor(container = globalContainer) {
|
|
289
|
+
this.container = container;
|
|
290
|
+
}
|
|
291
|
+
register(token, factory2, singleton = true) {
|
|
292
|
+
this.container.register(token.id, factory2, singleton);
|
|
293
|
+
}
|
|
294
|
+
registerIfAbsent(token, factory2, singleton = true) {
|
|
295
|
+
this.container.registerIfAbsent(token.id, factory2, singleton);
|
|
296
|
+
}
|
|
297
|
+
registerInstance(token, instance) {
|
|
298
|
+
this.container.registerInstance(token.id, instance);
|
|
299
|
+
}
|
|
300
|
+
get(token) {
|
|
301
|
+
return this.container.get(token.id);
|
|
302
|
+
}
|
|
303
|
+
has(token) {
|
|
304
|
+
return this.container.has(token.id);
|
|
305
|
+
}
|
|
306
|
+
async dispose() {
|
|
307
|
+
await this.container.dispose();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
var globalServiceRegistry = new ServiceRegistry(globalContainer);
|
|
311
|
+
|
|
312
|
+
// src/di/InputResolverRegistry.ts
|
|
313
|
+
var INPUT_RESOLVERS = createServiceToken("task.input.resolvers");
|
|
314
|
+
globalServiceRegistry.registerIfAbsent(INPUT_RESOLVERS, () => new Map, true);
|
|
315
|
+
function getInputResolvers() {
|
|
316
|
+
return globalServiceRegistry.get(INPUT_RESOLVERS);
|
|
317
|
+
}
|
|
318
|
+
function registerInputResolver(formatPrefix, resolver) {
|
|
319
|
+
const resolvers = getInputResolvers();
|
|
320
|
+
resolvers.set(formatPrefix, resolver);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/media/imageHydrationResolver.ts
|
|
324
|
+
async function resolveImageString(id, _format, _registry) {
|
|
325
|
+
if (typeof id !== "string")
|
|
326
|
+
return id;
|
|
327
|
+
if (id.startsWith("data:")) {
|
|
328
|
+
return GpuImage.fromDataUri(id);
|
|
329
|
+
}
|
|
330
|
+
const preview = id.length > 32 ? `${id.slice(0, 32)}...` : id;
|
|
331
|
+
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).`);
|
|
332
|
+
}
|
|
333
|
+
registerInputResolver("image", resolveImageString);
|
|
334
|
+
|
|
12
335
|
// src/media/color.ts
|
|
13
336
|
var HEX_PATTERN = /^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
337
|
+
var CSS_RGB_CHANNEL = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)";
|
|
338
|
+
var CSS_RGB_ALPHA = "(?:0(?:\\.\\d+)?|1(?:\\.0+)?)";
|
|
339
|
+
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
340
|
function parseHexColor(hex) {
|
|
15
341
|
if (typeof hex !== "string" || !HEX_PATTERN.test(hex)) {
|
|
16
342
|
throw new Error(`Invalid hex color: ${String(hex)}`);
|
|
@@ -81,9 +407,29 @@ function isColorObject(value) {
|
|
|
81
407
|
function isHexColor(value) {
|
|
82
408
|
return typeof value === "string" && HEX_PATTERN.test(value);
|
|
83
409
|
}
|
|
410
|
+
function parseCssRgbColor(value) {
|
|
411
|
+
const match = CSS_RGB_PATTERN.exec(value);
|
|
412
|
+
if (!match) {
|
|
413
|
+
throw new Error(`Invalid CSS rgb color: ${String(value)}`);
|
|
414
|
+
}
|
|
415
|
+
const r = Number.parseInt(match[1] ?? "", 10);
|
|
416
|
+
const g = Number.parseInt(match[2] ?? "", 10);
|
|
417
|
+
const b = Number.parseInt(match[3] ?? "", 10);
|
|
418
|
+
const alpha = match[4] === undefined ? 1 : Number.parseFloat(match[4]);
|
|
419
|
+
assertChannel("r", r);
|
|
420
|
+
assertChannel("g", g);
|
|
421
|
+
assertChannel("b", b);
|
|
422
|
+
if (!Number.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
|
423
|
+
throw new Error(`Color alpha out of range (0-1 number): ${match[4]}`);
|
|
424
|
+
}
|
|
425
|
+
return { r, g, b, a: Math.round(alpha * 255) };
|
|
426
|
+
}
|
|
84
427
|
function resolveColor(value) {
|
|
85
|
-
if (typeof value === "string")
|
|
86
|
-
|
|
428
|
+
if (typeof value === "string") {
|
|
429
|
+
if (isHexColor(value))
|
|
430
|
+
return parseHexColor(value);
|
|
431
|
+
return parseCssRgbColor(value);
|
|
432
|
+
}
|
|
87
433
|
if (!isColorObject(value)) {
|
|
88
434
|
throw new Error(`Invalid color value: ${JSON.stringify(value)}`);
|
|
89
435
|
}
|
|
@@ -94,16 +440,101 @@ function resolveColor(value) {
|
|
|
94
440
|
a: value.a ?? 255
|
|
95
441
|
};
|
|
96
442
|
}
|
|
97
|
-
// src/media/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
443
|
+
// src/media/encode.ts
|
|
444
|
+
async function encodeImageBinaryBytes(bin, mimeType) {
|
|
445
|
+
const dataUri = await getImageRasterCodec().encodeDataUri(bin, mimeType);
|
|
446
|
+
const b64 = dataUri.slice(dataUri.indexOf(",") + 1);
|
|
447
|
+
const decoded = atob(b64);
|
|
448
|
+
const bytes = new Uint8Array(decoded.length);
|
|
449
|
+
for (let i = 0;i < decoded.length; i++)
|
|
450
|
+
bytes[i] = decoded.charCodeAt(i);
|
|
451
|
+
return bytes;
|
|
101
452
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
453
|
+
async function encodeImageBinaryToPng(bin) {
|
|
454
|
+
return encodeImageBinaryBytes(bin, "image/png");
|
|
455
|
+
}
|
|
456
|
+
async function imageBinaryToBase64Png(bin) {
|
|
457
|
+
const dataUri = await getImageRasterCodec().encodeDataUri(bin, "image/png");
|
|
458
|
+
return dataUri.slice(dataUri.indexOf(",") + 1);
|
|
459
|
+
}
|
|
460
|
+
async function imageBinaryToDataUri(bin, mimeType = "image/png") {
|
|
461
|
+
return getImageRasterCodec().encodeDataUri(bin, mimeType);
|
|
462
|
+
}
|
|
463
|
+
async function imageBinaryToBlob(bin, mimeType = "image/png") {
|
|
464
|
+
const bytes = await encodeImageBinaryBytes(bin, mimeType);
|
|
465
|
+
return new Blob([bytes.buffer], { type: mimeType });
|
|
466
|
+
}
|
|
467
|
+
// src/media/filterRegistry.ts
|
|
468
|
+
var GLOBAL_REGISTRY_KEY = Symbol.for("@workglow/util/media/filterRegistry");
|
|
469
|
+
var _g4 = globalThis;
|
|
470
|
+
function getRegistry() {
|
|
471
|
+
let reg = _g4[GLOBAL_REGISTRY_KEY];
|
|
472
|
+
if (!reg) {
|
|
473
|
+
reg = new Map;
|
|
474
|
+
_g4[GLOBAL_REGISTRY_KEY] = reg;
|
|
475
|
+
}
|
|
476
|
+
return reg;
|
|
477
|
+
}
|
|
478
|
+
var key = (backend, filter) => `${backend}:${filter}`;
|
|
479
|
+
function registerFilterOp(backend, filter, fn) {
|
|
480
|
+
getRegistry().set(key(backend, filter), fn);
|
|
481
|
+
}
|
|
482
|
+
function applyFilter(image, filter, params) {
|
|
483
|
+
const fn = getRegistry().get(key(image.backend, filter));
|
|
484
|
+
if (!fn) {
|
|
485
|
+
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.`);
|
|
105
486
|
}
|
|
106
|
-
return
|
|
487
|
+
return fn(image, params);
|
|
488
|
+
}
|
|
489
|
+
function hasFilterOp(backend, filter) {
|
|
490
|
+
return getRegistry().has(key(backend, filter));
|
|
491
|
+
}
|
|
492
|
+
function _resetFilterRegistryForTests() {
|
|
493
|
+
getRegistry().clear();
|
|
494
|
+
}
|
|
495
|
+
// src/media/gpuDevice.browser.ts
|
|
496
|
+
var cached = null;
|
|
497
|
+
async function getGpuDevice() {
|
|
498
|
+
if (cached)
|
|
499
|
+
return cached;
|
|
500
|
+
cached = (async () => {
|
|
501
|
+
if (typeof navigator === "undefined" || !("gpu" in navigator))
|
|
502
|
+
return null;
|
|
503
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
504
|
+
if (!adapter)
|
|
505
|
+
return null;
|
|
506
|
+
const device = await adapter.requestDevice();
|
|
507
|
+
device.lost.then(() => {
|
|
508
|
+
cached = null;
|
|
509
|
+
});
|
|
510
|
+
return device;
|
|
511
|
+
})();
|
|
512
|
+
return cached;
|
|
513
|
+
}
|
|
514
|
+
function resetGpuDeviceForTests() {
|
|
515
|
+
cached = null;
|
|
516
|
+
}
|
|
517
|
+
// src/media/gpuImageSchema.ts
|
|
518
|
+
function GpuImageSchema(annotations = {}) {
|
|
519
|
+
return {
|
|
520
|
+
type: ["string", "object"],
|
|
521
|
+
properties: {},
|
|
522
|
+
title: "Image",
|
|
523
|
+
description: "Image (hydrated to GpuImage by the runner)",
|
|
524
|
+
...annotations,
|
|
525
|
+
format: "image"
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
// src/media/imageTypes.ts
|
|
529
|
+
function parseDataUri(dataUri) {
|
|
530
|
+
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
531
|
+
if (!match) {
|
|
532
|
+
throw new Error("Invalid base64 data URI");
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
mimeType: match[1],
|
|
536
|
+
base64: match[2]
|
|
537
|
+
};
|
|
107
538
|
}
|
|
108
539
|
// src/media/MediaRawImage.ts
|
|
109
540
|
class MediaRawImage {
|
|
@@ -124,430 +555,445 @@ function isMediaRawImageShape(value) {
|
|
|
124
555
|
const v = value;
|
|
125
556
|
return v.data instanceof Uint8ClampedArray && typeof v.width === "number" && typeof v.height === "number" && typeof v.channels === "number";
|
|
126
557
|
}
|
|
127
|
-
// src/media/
|
|
128
|
-
var
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
558
|
+
// src/media/previewBudget.ts
|
|
559
|
+
var GLOBAL_RESIZE_KEY = Symbol.for("@workglow/util/media/previewResizeFn");
|
|
560
|
+
var GLOBAL_BUDGET_KEY = Symbol.for("@workglow/util/media/previewBudget");
|
|
561
|
+
var _g5 = globalThis;
|
|
562
|
+
var DEFAULT_BUDGET = 512;
|
|
563
|
+
if (typeof _g5[GLOBAL_BUDGET_KEY] !== "number") {
|
|
564
|
+
_g5[GLOBAL_BUDGET_KEY] = DEFAULT_BUDGET;
|
|
133
565
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return
|
|
566
|
+
function registerPreviewResizeFn(fn) {
|
|
567
|
+
_g5[GLOBAL_RESIZE_KEY] = fn;
|
|
568
|
+
}
|
|
569
|
+
function getPreviewResizeFn() {
|
|
570
|
+
return _g5[GLOBAL_RESIZE_KEY];
|
|
571
|
+
}
|
|
572
|
+
function getPreviewBudget() {
|
|
573
|
+
return _g5[GLOBAL_BUDGET_KEY];
|
|
142
574
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
throw new Error(`Image: unsupported channel count ${ch}`);
|
|
575
|
+
function setPreviewBudget(px) {
|
|
576
|
+
if (!Number.isFinite(px) || px <= 0) {
|
|
577
|
+
throw new Error(`setPreviewBudget: invalid value ${px}; expected a positive finite number`);
|
|
147
578
|
}
|
|
148
|
-
|
|
579
|
+
_g5[GLOBAL_BUDGET_KEY] = Math.floor(px);
|
|
580
|
+
}
|
|
581
|
+
function previewSource(image) {
|
|
582
|
+
if (image.backend !== "webgpu")
|
|
583
|
+
return image;
|
|
584
|
+
const budget = getPreviewBudget();
|
|
585
|
+
const long = Math.max(image.width, image.height);
|
|
586
|
+
if (long <= budget)
|
|
587
|
+
return image;
|
|
588
|
+
const ratio = budget / long;
|
|
589
|
+
const resize = getPreviewResizeFn();
|
|
590
|
+
if (!resize)
|
|
591
|
+
return image;
|
|
592
|
+
const result = resize(image, Math.round(image.width * ratio), Math.round(image.height * ratio));
|
|
593
|
+
const composed = image.previewScale * ratio;
|
|
594
|
+
return result._setPreviewScale(composed);
|
|
595
|
+
}
|
|
596
|
+
// src/media/shaderRegistry.browser.ts
|
|
597
|
+
var VERTEX_PRELUDE = `
|
|
598
|
+
@group(0) @binding(0) var src: texture_2d<f32>;
|
|
599
|
+
@group(0) @binding(1) var src_sampler: sampler;
|
|
600
|
+
|
|
601
|
+
struct VsOut {
|
|
602
|
+
@builtin(position) pos: vec4f,
|
|
603
|
+
@location(0) uv: vec2f,
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
@vertex
|
|
607
|
+
fn vs(@builtin(vertex_index) vid: u32) -> VsOut {
|
|
608
|
+
let xy = vec2f(f32((vid << 1u) & 2u), f32(vid & 2u));
|
|
609
|
+
var out: VsOut;
|
|
610
|
+
out.pos = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
|
|
611
|
+
out.uv = vec2f(xy.x, 1.0 - xy.y);
|
|
612
|
+
return out;
|
|
613
|
+
}`;
|
|
614
|
+
var PASSTHROUGH_SHADER_SRC = `${VERTEX_PRELUDE}
|
|
615
|
+
@fragment
|
|
616
|
+
fn fs(in: VsOut) -> @location(0) vec4f {
|
|
617
|
+
return textureSample(src, src_sampler, in.uv);
|
|
618
|
+
}
|
|
619
|
+
`;
|
|
620
|
+
function createShaderCache(device) {
|
|
621
|
+
const map = new Map;
|
|
149
622
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
623
|
+
get(source) {
|
|
624
|
+
let mod = map.get(source);
|
|
625
|
+
if (!mod) {
|
|
626
|
+
mod = device.createShaderModule({ code: source });
|
|
627
|
+
map.set(source, mod);
|
|
628
|
+
}
|
|
629
|
+
return mod;
|
|
630
|
+
}
|
|
155
631
|
};
|
|
156
632
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (ArrayBuffer.isView(data)) {
|
|
162
|
-
return new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
|
|
633
|
+
var singleton = null;
|
|
634
|
+
function getShaderCache(device) {
|
|
635
|
+
if (!singleton || singleton.device !== device) {
|
|
636
|
+
singleton = { device, cache: createShaderCache(device) };
|
|
163
637
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
638
|
+
return singleton.cache;
|
|
639
|
+
}
|
|
640
|
+
// src/media/texturePool.browser.ts
|
|
641
|
+
var DEFAULT_CAPACITY_PER_SIZE = 8;
|
|
642
|
+
var TEXTURE_USAGE = 4 | 16 | 1 | 2;
|
|
643
|
+
function createTexturePool(device, opts = {}) {
|
|
644
|
+
const capacity = opts.capacityPerSize ?? DEFAULT_CAPACITY_PER_SIZE;
|
|
645
|
+
const buckets = new Map;
|
|
646
|
+
const owners = new WeakMap;
|
|
647
|
+
const sizeClassKey = (w, h, f) => `${w}x${h}:${f}`;
|
|
648
|
+
return {
|
|
649
|
+
acquire(width, height, format) {
|
|
650
|
+
const k = sizeClassKey(width, height, format);
|
|
651
|
+
const bucket = buckets.get(k);
|
|
652
|
+
if (bucket && bucket.length > 0) {
|
|
653
|
+
const reused = bucket.pop();
|
|
654
|
+
reused.inPool = false;
|
|
655
|
+
return reused.texture;
|
|
656
|
+
}
|
|
657
|
+
const texture = device.createTexture({
|
|
658
|
+
size: [width, height, 1],
|
|
659
|
+
format,
|
|
660
|
+
usage: TEXTURE_USAGE
|
|
661
|
+
});
|
|
662
|
+
const entry = { texture, width, height, format, inPool: false };
|
|
663
|
+
owners.set(texture, entry);
|
|
664
|
+
return texture;
|
|
665
|
+
},
|
|
666
|
+
release(texture) {
|
|
667
|
+
const entry = owners.get(texture);
|
|
668
|
+
if (!entry)
|
|
669
|
+
return;
|
|
670
|
+
if (entry.inPool)
|
|
671
|
+
return;
|
|
672
|
+
const k = sizeClassKey(entry.width, entry.height, entry.format);
|
|
673
|
+
let bucket = buckets.get(k);
|
|
674
|
+
if (!bucket) {
|
|
675
|
+
bucket = [];
|
|
676
|
+
buckets.set(k, bucket);
|
|
677
|
+
}
|
|
678
|
+
if (bucket.length >= capacity) {
|
|
679
|
+
owners.delete(texture);
|
|
680
|
+
texture.destroy();
|
|
681
|
+
return;
|
|
174
682
|
}
|
|
175
|
-
|
|
683
|
+
entry.inPool = true;
|
|
684
|
+
bucket.push(entry);
|
|
685
|
+
},
|
|
686
|
+
drain() {
|
|
687
|
+
for (const bucket of buckets.values()) {
|
|
688
|
+
for (const entry of bucket) {
|
|
689
|
+
owners.delete(entry.texture);
|
|
690
|
+
entry.texture.destroy();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
buckets.clear();
|
|
176
694
|
}
|
|
177
|
-
}
|
|
178
|
-
throw new Error("Image: pixel data is not array-like");
|
|
695
|
+
};
|
|
179
696
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 });
|
|
697
|
+
var singleton2 = null;
|
|
698
|
+
function getTexturePool(device) {
|
|
699
|
+
if (!singleton2 || singleton2.device !== device) {
|
|
700
|
+
singleton2?.pool.drain();
|
|
701
|
+
singleton2 = { device, pool: createTexturePool(device) };
|
|
198
702
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
703
|
+
return singleton2.pool;
|
|
704
|
+
}
|
|
705
|
+
function resetTexturePoolForTests() {
|
|
706
|
+
singleton2?.pool.drain();
|
|
707
|
+
singleton2 = null;
|
|
708
|
+
}
|
|
709
|
+
// src/media/webGpuImage.browser.ts
|
|
710
|
+
var TEX_FORMAT = "rgba8unorm";
|
|
711
|
+
var finalizers = typeof FinalizationRegistry !== "undefined" ? new FinalizationRegistry((fn) => {
|
|
712
|
+
try {
|
|
713
|
+
fn();
|
|
714
|
+
} catch {}
|
|
715
|
+
}) : undefined;
|
|
716
|
+
function expandToRgba2(bin) {
|
|
717
|
+
if (bin.channels === 4)
|
|
718
|
+
return bin.data;
|
|
719
|
+
const px = bin.width * bin.height;
|
|
720
|
+
const out = new Uint8ClampedArray(px * 4);
|
|
721
|
+
if (bin.channels === 3) {
|
|
722
|
+
for (let i = 0;i < px; i++) {
|
|
723
|
+
out[i * 4 + 0] = bin.data[i * 3 + 0] ?? 0;
|
|
724
|
+
out[i * 4 + 1] = bin.data[i * 3 + 1] ?? 0;
|
|
725
|
+
out[i * 4 + 2] = bin.data[i * 3 + 2] ?? 0;
|
|
726
|
+
out[i * 4 + 3] = 255;
|
|
220
727
|
}
|
|
221
|
-
|
|
222
|
-
|
|
728
|
+
} else if (bin.channels === 1) {
|
|
729
|
+
for (let i = 0;i < px; i++) {
|
|
730
|
+
const g = bin.data[i] ?? 0;
|
|
731
|
+
out[i * 4 + 0] = g;
|
|
732
|
+
out[i * 4 + 1] = g;
|
|
733
|
+
out[i * 4 + 2] = g;
|
|
734
|
+
out[i * 4 + 3] = 255;
|
|
223
735
|
}
|
|
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
736
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
737
|
+
return out;
|
|
738
|
+
}
|
|
739
|
+
function align(n, m) {
|
|
740
|
+
return Math.ceil(n / m) * m;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
class WebGpuImage {
|
|
744
|
+
device;
|
|
745
|
+
texture;
|
|
746
|
+
width;
|
|
747
|
+
height;
|
|
748
|
+
backend = "webgpu";
|
|
749
|
+
channels = 4;
|
|
750
|
+
refcount = 1;
|
|
751
|
+
_previewScale;
|
|
752
|
+
constructor(device, texture, width, height, previewScale = 1) {
|
|
753
|
+
this.device = device;
|
|
754
|
+
this.texture = texture;
|
|
755
|
+
this.width = width;
|
|
756
|
+
this.height = height;
|
|
757
|
+
this._previewScale = previewScale;
|
|
758
|
+
if (finalizers && texture) {
|
|
759
|
+
const dev = device;
|
|
760
|
+
const tex = texture;
|
|
761
|
+
finalizers.register(this, () => {
|
|
762
|
+
try {
|
|
763
|
+
getTexturePool(dev).release(tex);
|
|
764
|
+
} catch {}
|
|
765
|
+
}, this);
|
|
248
766
|
}
|
|
249
|
-
throw new Error("Image.fromJSON: value does not match any known Image shape");
|
|
250
767
|
}
|
|
251
|
-
get
|
|
252
|
-
return this.
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (this.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
this.pixelsCache = this.source.pixels;
|
|
293
|
-
return this.pixelsCache;
|
|
768
|
+
get previewScale() {
|
|
769
|
+
return this._previewScale;
|
|
770
|
+
}
|
|
771
|
+
_setPreviewScale(scale) {
|
|
772
|
+
this._previewScale = scale;
|
|
773
|
+
return this;
|
|
774
|
+
}
|
|
775
|
+
static async fromImageBinary(bin) {
|
|
776
|
+
const dev = await getGpuDevice();
|
|
777
|
+
if (!dev)
|
|
778
|
+
throw new Error("WebGPU device unavailable; use CpuImage.fromImageBinary instead");
|
|
779
|
+
const tex = getTexturePool(dev).acquire(bin.width, bin.height, TEX_FORMAT);
|
|
780
|
+
const rgba = bin.channels === 4 ? bin.data : expandToRgba2(bin);
|
|
781
|
+
dev.queue.writeTexture({ texture: tex }, rgba, { bytesPerRow: bin.width * 4, rowsPerImage: bin.height }, [bin.width, bin.height, 1]);
|
|
782
|
+
return new WebGpuImage(dev, tex, bin.width, bin.height);
|
|
783
|
+
}
|
|
784
|
+
apply(params) {
|
|
785
|
+
if (!this.texture)
|
|
786
|
+
throw new Error("WebGpuImage.apply called on a released image");
|
|
787
|
+
const outW = params.outSize?.width ?? this.width;
|
|
788
|
+
const outH = params.outSize?.height ?? this.height;
|
|
789
|
+
const out = getTexturePool(this.device).acquire(outW, outH, TEX_FORMAT);
|
|
790
|
+
const shaderModule = getShaderCache(this.device).get(params.shader);
|
|
791
|
+
const pipeline = this.device.createRenderPipeline({
|
|
792
|
+
layout: "auto",
|
|
793
|
+
vertex: { module: shaderModule, entryPoint: "vs" },
|
|
794
|
+
fragment: { module: shaderModule, entryPoint: "fs", targets: [{ format: TEX_FORMAT }] },
|
|
795
|
+
primitive: { topology: "triangle-list" }
|
|
796
|
+
});
|
|
797
|
+
const sampler = this.device.createSampler({ magFilter: "linear", minFilter: "linear" });
|
|
798
|
+
const bindEntries = [
|
|
799
|
+
{ binding: 0, resource: this.texture.createView() },
|
|
800
|
+
{ binding: 1, resource: sampler }
|
|
801
|
+
];
|
|
802
|
+
if (params.uniforms) {
|
|
803
|
+
const ubo = this.device.createBuffer({
|
|
804
|
+
size: params.uniforms.byteLength,
|
|
805
|
+
usage: 64 | 8
|
|
806
|
+
});
|
|
807
|
+
this.device.queue.writeBuffer(ubo, 0, params.uniforms);
|
|
808
|
+
bindEntries.push({ binding: 2, resource: { buffer: ubo } });
|
|
294
809
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
810
|
+
const bindGroup = this.device.createBindGroup({
|
|
811
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
812
|
+
entries: bindEntries
|
|
813
|
+
});
|
|
814
|
+
const enc = this.device.createCommandEncoder();
|
|
815
|
+
const pass = enc.beginRenderPass({
|
|
816
|
+
colorAttachments: [{
|
|
817
|
+
view: out.createView(),
|
|
818
|
+
loadOp: "clear",
|
|
819
|
+
storeOp: "store",
|
|
820
|
+
clearValue: [0, 0, 0, 0]
|
|
821
|
+
}]
|
|
822
|
+
});
|
|
823
|
+
pass.setPipeline(pipeline);
|
|
824
|
+
pass.setBindGroup(0, bindGroup);
|
|
825
|
+
pass.draw(3);
|
|
826
|
+
pass.end();
|
|
827
|
+
this.device.queue.submit([enc.finish()]);
|
|
828
|
+
return new WebGpuImage(this.device, out, outW, outH, this._previewScale);
|
|
829
|
+
}
|
|
830
|
+
async materialize() {
|
|
831
|
+
if (!this.texture)
|
|
832
|
+
throw new Error("WebGpuImage.materialize called on a released image");
|
|
833
|
+
const bytesPerRow = align(this.width * 4, 256);
|
|
834
|
+
const buffer = this.device.createBuffer({
|
|
835
|
+
size: bytesPerRow * this.height,
|
|
836
|
+
usage: 1 | 8
|
|
837
|
+
});
|
|
838
|
+
const enc = this.device.createCommandEncoder();
|
|
839
|
+
enc.copyTextureToBuffer({ texture: this.texture }, { buffer, bytesPerRow, rowsPerImage: this.height }, [this.width, this.height, 1]);
|
|
840
|
+
this.device.queue.submit([enc.finish()]);
|
|
841
|
+
await buffer.mapAsync(1);
|
|
842
|
+
const mapped = new Uint8Array(buffer.getMappedRange());
|
|
843
|
+
const tightStride = this.width * 4;
|
|
844
|
+
const tight = new Uint8ClampedArray(this.width * this.height * 4);
|
|
845
|
+
if (bytesPerRow === tightStride) {
|
|
846
|
+
tight.set(mapped);
|
|
847
|
+
} else {
|
|
848
|
+
for (let y = 0;y < this.height; y++) {
|
|
849
|
+
tight.set(mapped.subarray(y * bytesPerRow, y * bytesPerRow + tightStride), y * tightStride);
|
|
310
850
|
}
|
|
311
851
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
852
|
+
buffer.unmap();
|
|
853
|
+
buffer.destroy();
|
|
854
|
+
return { data: tight, width: this.width, height: this.height, channels: 4 };
|
|
855
|
+
}
|
|
856
|
+
async toCanvas(canvas) {
|
|
857
|
+
if (!this.texture)
|
|
858
|
+
throw new Error("WebGpuImage.toCanvas called on a released image");
|
|
859
|
+
if (canvas.width !== this.width)
|
|
860
|
+
canvas.width = this.width;
|
|
861
|
+
if (canvas.height !== this.height)
|
|
862
|
+
canvas.height = this.height;
|
|
863
|
+
const ctx = canvas.getContext("webgpu");
|
|
864
|
+
if (!ctx)
|
|
865
|
+
throw new Error("WebGpuImage.toCanvas requires a webgpu context");
|
|
866
|
+
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
867
|
+
ctx.configure({ device: this.device, format: presentationFormat, alphaMode: "premultiplied" });
|
|
868
|
+
const view = ctx.getCurrentTexture().createView();
|
|
869
|
+
const shaderModule = getShaderCache(this.device).get(PASSTHROUGH_SHADER_SRC);
|
|
870
|
+
const pipeline = this.device.createRenderPipeline({
|
|
871
|
+
layout: "auto",
|
|
872
|
+
vertex: { module: shaderModule, entryPoint: "vs" },
|
|
873
|
+
fragment: { module: shaderModule, entryPoint: "fs", targets: [{ format: presentationFormat }] },
|
|
874
|
+
primitive: { topology: "triangle-list" }
|
|
875
|
+
});
|
|
876
|
+
const sampler = this.device.createSampler({ magFilter: "linear", minFilter: "linear" });
|
|
877
|
+
const bindGroup = this.device.createBindGroup({
|
|
878
|
+
layout: pipeline.getBindGroupLayout(0),
|
|
879
|
+
entries: [
|
|
880
|
+
{ binding: 0, resource: this.texture.createView() },
|
|
881
|
+
{ binding: 1, resource: sampler }
|
|
882
|
+
]
|
|
883
|
+
});
|
|
884
|
+
const enc = this.device.createCommandEncoder();
|
|
885
|
+
const pass = enc.beginRenderPass({
|
|
886
|
+
colorAttachments: [{ view, loadOp: "clear", storeOp: "store", clearValue: [0, 0, 0, 0] }]
|
|
887
|
+
});
|
|
888
|
+
pass.setPipeline(pipeline);
|
|
889
|
+
pass.setBindGroup(0, bindGroup);
|
|
890
|
+
pass.draw(3);
|
|
891
|
+
pass.end();
|
|
892
|
+
this.device.queue.submit([enc.finish()]);
|
|
893
|
+
}
|
|
894
|
+
async encode(format, quality) {
|
|
895
|
+
const bin = await this.materialize();
|
|
896
|
+
if (typeof OffscreenCanvas === "undefined") {
|
|
897
|
+
throw new Error("WebGpuImage.encode requires an OffscreenCanvas environment");
|
|
341
898
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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;
|
|
364
|
-
}
|
|
365
|
-
case "Sharp":
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
899
|
+
const off = new OffscreenCanvas(this.width, this.height);
|
|
900
|
+
const ctx = off.getContext("2d");
|
|
901
|
+
if (!ctx)
|
|
902
|
+
throw new Error("WebGpuImage.encode could not acquire a 2D context");
|
|
903
|
+
ctx.putImageData(new ImageData(bin.data, this.width, this.height), 0, 0);
|
|
904
|
+
const blob = await off.convertToBlob({ type: `image/${format}`, quality });
|
|
905
|
+
return new Uint8Array(await blob.arrayBuffer());
|
|
906
|
+
}
|
|
907
|
+
retain(n = 1) {
|
|
908
|
+
if (this.refcount <= 0) {
|
|
909
|
+
throw new Error("WebGpuImage.retain called on a released image");
|
|
368
910
|
}
|
|
369
|
-
|
|
911
|
+
this.refcount += n;
|
|
912
|
+
return this;
|
|
370
913
|
}
|
|
371
|
-
|
|
372
|
-
if (this.
|
|
373
|
-
|
|
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
|
-
};
|
|
914
|
+
release() {
|
|
915
|
+
if (this.refcount <= 0) {
|
|
916
|
+
throw new Error("WebGpuImage.release called on a released image");
|
|
383
917
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
return "Blob";
|
|
394
|
-
case "bitmap":
|
|
395
|
-
return "ImageBitmap";
|
|
396
|
-
case "videoFrame":
|
|
397
|
-
return "VideoFrame";
|
|
398
|
-
case "offscreenCanvas":
|
|
399
|
-
return "OffscreenCanvas";
|
|
400
|
-
}
|
|
401
|
-
}
|
|
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;
|
|
918
|
+
this.refcount -= 1;
|
|
919
|
+
if (this.refcount > 0)
|
|
920
|
+
return;
|
|
921
|
+
if (this.texture) {
|
|
922
|
+
const tex = this.texture;
|
|
923
|
+
this.texture = null;
|
|
924
|
+
if (finalizers)
|
|
925
|
+
finalizers.unregister(this);
|
|
926
|
+
getTexturePool(this.device).release(tex);
|
|
422
927
|
}
|
|
423
928
|
}
|
|
424
929
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
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)}`;
|
|
435
|
-
}
|
|
436
|
-
// src/media/Image.browser.ts
|
|
437
|
-
Image.fromBitmap = function fromBitmap(bitmap) {
|
|
438
|
-
return Image.from(bitmap);
|
|
439
|
-
};
|
|
440
|
-
Image.fromVideoFrame = function fromVideoFrame(frame) {
|
|
441
|
-
return Image.from(frame);
|
|
442
|
-
};
|
|
443
|
-
Image.fromOffscreenCanvas = function fromOffscreenCanvas(canvas) {
|
|
444
|
-
return Image.from(canvas);
|
|
445
|
-
};
|
|
446
|
-
function rasterToImageData(image) {
|
|
447
|
-
const { width, height, channels, data } = image;
|
|
448
|
-
const id = new ImageData(width, height);
|
|
449
|
-
if (channels === 4) {
|
|
450
|
-
id.data.set(data);
|
|
451
|
-
return id;
|
|
452
|
-
}
|
|
453
|
-
if (channels === 3) {
|
|
454
|
-
for (let i = 0;i < width * height; i++) {
|
|
455
|
-
id.data[i * 4] = data[i * 3];
|
|
456
|
-
id.data[i * 4 + 1] = data[i * 3 + 1];
|
|
457
|
-
id.data[i * 4 + 2] = data[i * 3 + 2];
|
|
458
|
-
id.data[i * 4 + 3] = 255;
|
|
459
|
-
}
|
|
460
|
-
return id;
|
|
461
|
-
}
|
|
462
|
-
if (channels === 1) {
|
|
463
|
-
for (let i = 0;i < width * height; i++) {
|
|
464
|
-
const v = data[i];
|
|
465
|
-
id.data[i * 4] = v;
|
|
466
|
-
id.data[i * 4 + 1] = v;
|
|
467
|
-
id.data[i * 4 + 2] = v;
|
|
468
|
-
id.data[i * 4 + 3] = 255;
|
|
469
|
-
}
|
|
470
|
-
return id;
|
|
471
|
-
}
|
|
472
|
-
throw new Error(`Image.getImageData: unsupported channel count ${channels}`);
|
|
930
|
+
// src/media-browser.ts
|
|
931
|
+
async function _preferGpu(bin) {
|
|
932
|
+
const dev = await getGpuDevice();
|
|
933
|
+
return dev ? WebGpuImage.fromImageBinary(bin) : CpuImage.fromImageBinary(bin);
|
|
473
934
|
}
|
|
474
|
-
|
|
935
|
+
registerGpuImageFactory("fromImageBinaryAsync", _preferGpu);
|
|
936
|
+
registerGpuImageFactory("fromDataUri", async (dataUri) => {
|
|
937
|
+
const bin = await getImageRasterCodec().decodeDataUri(dataUri);
|
|
938
|
+
return _preferGpu(bin);
|
|
939
|
+
});
|
|
940
|
+
registerGpuImageFactory("fromBlob", async (blob) => {
|
|
475
941
|
const bitmap = await createImageBitmap(blob);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const ctx = canvas.getContext("2d");
|
|
479
|
-
if (!ctx)
|
|
480
|
-
throw new Error("Image: failed to get 2D context on OffscreenCanvas");
|
|
481
|
-
ctx.drawImage(bitmap, 0, 0);
|
|
482
|
-
return canvas;
|
|
483
|
-
} finally {
|
|
484
|
-
bitmap.close();
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
Image.prototype.getImageData = async function getImageData() {
|
|
488
|
-
const pixels = await this.getPixels();
|
|
489
|
-
return rasterToImageData(pixels);
|
|
490
|
-
};
|
|
491
|
-
Image.prototype.getImageBitmap = async function getImageBitmap() {
|
|
492
|
-
const source = this.getSource();
|
|
493
|
-
if (source.kind === "bitmap")
|
|
494
|
-
return source.bitmap;
|
|
495
|
-
if (source.kind === "blob")
|
|
496
|
-
return createImageBitmap(source.blob);
|
|
497
|
-
if (source.kind === "offscreenCanvas") {
|
|
498
|
-
return source.canvas.transferToImageBitmap();
|
|
499
|
-
}
|
|
500
|
-
if (source.kind === "dataUri") {
|
|
501
|
-
return createImageBitmap(dataUriToBlob(source.dataUri));
|
|
502
|
-
}
|
|
503
|
-
const id = await this.getImageData();
|
|
504
|
-
return createImageBitmap(id);
|
|
505
|
-
};
|
|
506
|
-
Image.prototype.getVideoFrame = async function getVideoFrame() {
|
|
507
|
-
const source = this.getSource();
|
|
508
|
-
if (source.kind === "videoFrame")
|
|
509
|
-
return source.frame;
|
|
510
|
-
const bitmap = await this.getImageBitmap();
|
|
511
|
-
return new VideoFrame(bitmap, { timestamp: 0 });
|
|
512
|
-
};
|
|
513
|
-
Image.prototype.getOffscreenCanvas = async function getOffscreenCanvas() {
|
|
514
|
-
const source = this.getSource();
|
|
515
|
-
if (source.kind === "offscreenCanvas")
|
|
516
|
-
return source.canvas;
|
|
517
|
-
if (source.kind === "blob")
|
|
518
|
-
return blobToOffscreenCanvas(source.blob);
|
|
519
|
-
if (source.kind === "dataUri")
|
|
520
|
-
return blobToOffscreenCanvas(dataUriToBlob(source.dataUri));
|
|
521
|
-
const id = await this.getImageData();
|
|
522
|
-
const canvas = new OffscreenCanvas(id.width, id.height);
|
|
523
|
-
const ctx = canvas.getContext("2d");
|
|
942
|
+
const off = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
943
|
+
const ctx = off.getContext("2d");
|
|
524
944
|
if (!ctx)
|
|
525
|
-
throw new Error("
|
|
526
|
-
ctx.
|
|
527
|
-
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
};
|
|
945
|
+
throw new Error("fromBlob: could not acquire 2D context");
|
|
946
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
947
|
+
const id = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
|
|
948
|
+
return _preferGpu({ data: id.data, width: bitmap.width, height: bitmap.height, channels: 4 });
|
|
949
|
+
});
|
|
950
|
+
registerGpuImageFactory("fromImageBitmap", async (bitmap) => {
|
|
951
|
+
const off = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
952
|
+
const ctx = off.getContext("2d");
|
|
953
|
+
if (!ctx)
|
|
954
|
+
throw new Error("fromImageBitmap: could not acquire 2D context");
|
|
955
|
+
ctx.drawImage(bitmap, 0, 0);
|
|
956
|
+
const id = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
|
|
957
|
+
return _preferGpu({ data: id.data, width: bitmap.width, height: bitmap.height, channels: 4 });
|
|
958
|
+
});
|
|
538
959
|
export {
|
|
539
960
|
toHexColor,
|
|
961
|
+
setPreviewBudget,
|
|
540
962
|
resolveColor,
|
|
963
|
+
resetTexturePoolForTests,
|
|
964
|
+
resetGpuDeviceForTests,
|
|
965
|
+
registerPreviewResizeFn,
|
|
541
966
|
registerImageRasterCodec,
|
|
967
|
+
registerGpuImageFactory,
|
|
968
|
+
registerFilterOp,
|
|
969
|
+
previewSource,
|
|
542
970
|
parseHexColor,
|
|
543
971
|
parseDataUri,
|
|
544
972
|
isMediaRawImageShape,
|
|
545
973
|
isHexColor,
|
|
546
974
|
isColorObject,
|
|
975
|
+
imageBinaryToDataUri,
|
|
976
|
+
imageBinaryToBlob,
|
|
977
|
+
imageBinaryToBase64Png,
|
|
978
|
+
hasFilterOp,
|
|
979
|
+
getTexturePool,
|
|
980
|
+
getShaderCache,
|
|
981
|
+
getPreviewBudget,
|
|
547
982
|
getImageRasterCodec,
|
|
548
|
-
|
|
983
|
+
getGpuImageFactory,
|
|
984
|
+
getGpuDevice,
|
|
985
|
+
encodeImageBinaryToPng,
|
|
986
|
+
createTexturePool,
|
|
987
|
+
createShaderCache,
|
|
988
|
+
applyFilter,
|
|
989
|
+
_resetFilterRegistryForTests,
|
|
990
|
+
WebGpuImage,
|
|
991
|
+
VERTEX_PRELUDE,
|
|
992
|
+
PASSTHROUGH_SHADER_SRC,
|
|
549
993
|
MediaRawImage,
|
|
550
|
-
|
|
994
|
+
GpuImageSchema,
|
|
995
|
+
GpuImage as GpuImageFactory,
|
|
996
|
+
CpuImage
|
|
551
997
|
};
|
|
552
998
|
|
|
553
|
-
//# debugId=
|
|
999
|
+
//# debugId=006D8DBD8329F1E964756E2164756E21
|