supervision 0.1.7 → 0.2.0-next.1
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/constants/media-renderer.d.ts +3 -0
- package/dist/constants/media-renderer.d.ts.map +1 -1
- package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9281 -5038
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +409 -185
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/media/display-pixel-ratio.d.ts +15 -0
- package/dist/media/display-pixel-ratio.d.ts.map +1 -0
- package/dist/media/engine-import-failure.d.ts +16 -0
- package/dist/media/engine-import-failure.d.ts.map +1 -0
- package/dist/media/media-errors.d.ts +6 -0
- package/dist/media/media-errors.d.ts.map +1 -1
- package/dist/media/media-normalization.d.ts.map +1 -1
- package/dist/media/media-probe.d.ts.map +1 -1
- package/dist/media/media-source-state.d.ts.map +1 -1
- package/dist/media/media-source.d.ts +17 -0
- package/dist/media/media-source.d.ts.map +1 -1
- package/dist/media/mediabunny-media-source.d.ts.map +1 -1
- package/dist/media/video-engine-media-source.d.ts +54 -0
- package/dist/media/video-engine-media-source.d.ts.map +1 -0
- package/dist/media/video-engine-media-source.js +3 -0
- package/dist/media/video-engine-media-source.js.map +1 -0
- package/dist/playback/media-playback-controller.d.ts +7 -1
- package/dist/playback/media-playback-controller.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-artifact.d.ts +48 -12
- package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-compositor.d.ts +12 -5
- package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
- package/dist/render-preparation/mask-preparation-worker-count.d.ts +4 -4
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +13 -3
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
- package/dist/render-preparation/prepared-render-window.d.ts +44 -2
- package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
- package/dist/render-preparation/prepared-window-timeline.d.ts +2 -6
- package/dist/render-preparation/prepared-window-timeline.d.ts.map +1 -1
- package/dist/renderers/injected-pixi.d.ts +38 -0
- package/dist/renderers/injected-pixi.d.ts.map +1 -0
- package/dist/renderers/mask-palette.d.ts +14 -0
- package/dist/renderers/mask-palette.d.ts.map +1 -0
- package/dist/renderers/mask-vertex.d.ts +14 -0
- package/dist/renderers/mask-vertex.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts +9 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +50 -2
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/media-renderer-state.d.ts +20 -1
- package/dist/renderers/media-renderer-state.d.ts.map +1 -1
- package/dist/renderers/media-renderer-transport.d.ts +72 -0
- package/dist/renderers/media-renderer-transport.d.ts.map +1 -0
- package/dist/renderers/pixi-box-layer.d.ts +5 -0
- package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-focus-layer.d.ts +19 -2
- package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-frame-present.d.ts +77 -0
- package/dist/renderers/pixi-frame-present.d.ts.map +1 -0
- package/dist/renderers/pixi-id-mask-shader.d.ts +6 -25
- package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts +12 -2
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-halo.d.ts +18 -23
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-layer.d.ts +94 -32
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts +67 -0
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-polygon-layer.d.ts +9 -2
- package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-region-coverage-mask.d.ts +62 -0
- package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -0
- package/dist/renderers/pixi-region-effect.d.ts +42 -0
- package/dist/renderers/pixi-region-effect.d.ts.map +1 -0
- package/dist/renderers/pixi-region-layer.d.ts +43 -2
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-shader-lifecycle.d.ts +14 -0
- package/dist/renderers/pixi-shader-lifecycle.d.ts.map +1 -0
- package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
- package/dist/renderers/prepared-annotation-window.d.ts +47 -0
- package/dist/renderers/prepared-annotation-window.d.ts.map +1 -0
- package/dist/renderers/presented-frame-channel.d.ts +138 -0
- package/dist/renderers/presented-frame-channel.d.ts.map +1 -0
- package/dist/renderers/scene-render-scheduler.d.ts +20 -0
- package/dist/renderers/scene-render-scheduler.d.ts.map +1 -0
- package/dist/sessions/media-session-defaults.d.ts +10 -6
- package/dist/sessions/media-session-defaults.d.ts.map +1 -1
- package/dist/sessions/media-session-media.d.ts.map +1 -1
- package/dist/sessions/media-session-state.d.ts.map +1 -1
- package/dist/sessions/media-session.d.ts.map +1 -1
- package/dist/tracking.worker.js +73 -4
- package/dist/tracking.worker.js.map +1 -1
- package/dist/types/media-normalization.d.ts +34 -0
- package/dist/types/media-normalization.d.ts.map +1 -1
- package/dist/types/media-renderer.d.ts +31 -1
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/types/media-session.d.ts +89 -15
- package/dist/types/media-session.d.ts.map +1 -1
- package/dist/types/render-preparation.d.ts +149 -5
- package/dist/types/render-preparation.d.ts.map +1 -1
- package/dist/video-engine-media-source-CUXnOJOV.js +450 -0
- package/dist/video-engine-media-source-CUXnOJOV.js.map +1 -0
- package/dist/web-video-engine/analysis-session.d.ts +75 -0
- package/dist/web-video-engine/analysis.d.ts +6 -0
- package/dist/web-video-engine/analysis.js +1850 -0
- package/dist/web-video-engine/cache-budget.d.ts +18 -0
- package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts +89 -0
- package/dist/web-video-engine/clock.d.ts +82 -0
- package/dist/web-video-engine/constants.d.ts +261 -0
- package/dist/web-video-engine/create-scrub-cursor.d.ts +65 -0
- package/dist/web-video-engine/decode-resolution.d.ts +114 -0
- package/dist/web-video-engine/decode-scheduler.d.ts +371 -0
- package/dist/web-video-engine/decode-session.d.ts +283 -0
- package/dist/web-video-engine/decode-source.d.ts +258 -0
- package/dist/web-video-engine/diagnostics-store.d.ts +17 -0
- package/dist/web-video-engine/diagnostics.d.ts +316 -0
- package/dist/web-video-engine/embedded-engine-worker.d.ts +1 -0
- package/dist/web-video-engine/engine-core.d.ts +269 -0
- package/dist/web-video-engine/engine.d.ts +25 -0
- package/dist/web-video-engine/engine.js +912 -0
- package/dist/web-video-engine/engine.worker.d.ts +1 -0
- package/dist/web-video-engine/engine.worker.js +33624 -0
- package/dist/web-video-engine/frame-cache.d.ts +236 -0
- package/dist/web-video-engine/frame-extractor.d.ts +27 -0
- package/dist/web-video-engine/frame-timeline-DreEwsRY.js +616 -0
- package/dist/web-video-engine/frame-timeline.d.ts +80 -0
- package/dist/web-video-engine/frame-walker.d.ts +73 -0
- package/dist/web-video-engine/index.d.ts +13 -0
- package/dist/web-video-engine/index.d.ts.map +1 -0
- package/dist/web-video-engine/index.js +3 -0
- package/dist/web-video-engine/index.js.map +1 -0
- package/dist/web-video-engine/key-packet.d.ts +64 -0
- package/dist/web-video-engine/keyframe-index.d.ts +90 -0
- package/dist/web-video-engine/mirror-store.d.ts +55 -0
- package/dist/web-video-engine/renderer.d.ts +93 -0
- package/dist/web-video-engine/rotation.d.ts +34 -0
- package/dist/web-video-engine/scrub-controller.d.ts +375 -0
- package/dist/web-video-engine/scrub-cursor.d.ts +347 -0
- package/dist/web-video-engine/scrub-trajectory.d.ts +29 -0
- package/dist/web-video-engine/source-residency.d.ts +91 -0
- package/dist/web-video-engine/trace-recorder.d.ts +163 -0
- package/dist/web-video-engine/types.d.ts +200 -0
- package/dist/web-video-engine/video-engine.d.ts +437 -0
- package/dist/web-video-engine/webgpu-renderer.d.ts +61 -0
- package/dist/web-video-engine/worker-bridge.d.ts +11 -0
- package/dist/web-video-engine/worker-dispatch.d.ts +16 -0
- package/dist/web-video-engine/worker-protocol.d.ts +282 -0
- package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/detections/composite-detection-frame-source.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +5 -4
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +1167 -280
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts +4 -36
- package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts +5 -0
- package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +92 -7
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts +191 -17
- package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +83 -5
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts +2 -0
- package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts +32 -0
- package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/detection-conversions.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +5 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts +9 -0
- package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts +18 -0
- package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +34 -2
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts +7 -0
- package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts.map +1 -0
- package/package.json +24 -3
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
(function (DetectionBufferStatus) {
|
|
18
18
|
DetectionBufferStatus["Idle"] = "idle";
|
|
19
19
|
DetectionBufferStatus["Loading"] = "loading";
|
|
20
|
+
/**
|
|
21
|
+
* The playback gate is holding playback until the source produces detections
|
|
22
|
+
* for the requested range. {@link DetectionBufferStatus.Loading} fetches
|
|
23
|
+
* detections the source already has; this one waits on a producer.
|
|
24
|
+
*/
|
|
25
|
+
DetectionBufferStatus["AwaitingCoverage"] = "awaitingCoverage";
|
|
20
26
|
DetectionBufferStatus["Ready"] = "ready";
|
|
21
27
|
DetectionBufferStatus["Error"] = "error";
|
|
22
28
|
DetectionBufferStatus["Destroyed"] = "destroyed";
|
|
@@ -29,12 +35,30 @@
|
|
|
29
35
|
/**
|
|
30
36
|
* Select the frame whose `[mediaTime, endTime)` interval contains the media
|
|
31
37
|
* time. This is the default for interval annotations and timestamped sources.
|
|
38
|
+
*
|
|
39
|
+
* Frame times must come from the media itself. Times reconstructed from a
|
|
40
|
+
* nominal frame rate drift against a clip whose real rate differs, and once
|
|
41
|
+
* that drift passes the half-millisecond selection tolerance a playhead
|
|
42
|
+
* landing on a frame boundary selects the previous frame's detections.
|
|
43
|
+
*
|
|
44
|
+
* A frame that carries no `endTime` stays active until the next frame starts,
|
|
45
|
+
* which bridges an index the source never produced.
|
|
46
|
+
* {@link DetectionFrameSelectionMode.NearestFrameIndex} bounds each frame to
|
|
47
|
+
* one grid step instead, so a missing index reads as no detections.
|
|
32
48
|
*/
|
|
33
49
|
DetectionFrameSelectionMode["Interval"] = "interval";
|
|
34
50
|
/**
|
|
35
|
-
* Select from a known inference frame grid using `frameIndex
|
|
36
|
-
*
|
|
37
|
-
*
|
|
51
|
+
* Select from a known inference frame grid using `frameIndex` and
|
|
52
|
+
* `frameRate`. This is useful when inference was run on normalized frames and
|
|
53
|
+
* playback should snap detections to that grid.
|
|
54
|
+
*
|
|
55
|
+
* A frame speaks for one grid step starting at its own media time, and an
|
|
56
|
+
* index the source never produced selects nothing rather than a neighbour.
|
|
57
|
+
*
|
|
58
|
+
* A frame carrying no `frameIndex` speaks for its step on the same terms,
|
|
59
|
+
* reached by the time it starts at rather than by an index that names it. Its
|
|
60
|
+
* `endTime` does not widen it past that step, so a source that labels only
|
|
61
|
+
* part of what it writes cannot bridge the indexes it never produced.
|
|
38
62
|
*/
|
|
39
63
|
DetectionFrameSelectionMode["NearestFrameIndex"] = "nearestFrameIndex";
|
|
40
64
|
})(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));
|
|
@@ -177,13 +201,16 @@
|
|
|
177
201
|
width,
|
|
178
202
|
};
|
|
179
203
|
}
|
|
180
|
-
function
|
|
204
|
+
function assertMaskSize(width, height) {
|
|
181
205
|
if (!Number.isInteger(width) || width <= 0) {
|
|
182
206
|
throw new Error("Mask width must be a positive integer.");
|
|
183
207
|
}
|
|
184
208
|
if (!Number.isInteger(height) || height <= 0) {
|
|
185
209
|
throw new Error("Mask height must be a positive integer.");
|
|
186
210
|
}
|
|
211
|
+
}
|
|
212
|
+
function assertMaskDimensions(data, width, height) {
|
|
213
|
+
assertMaskSize(width, height);
|
|
187
214
|
if (data.length !== width * height) {
|
|
188
215
|
throw new Error("Mask data length must equal width * height.");
|
|
189
216
|
}
|
|
@@ -362,9 +389,9 @@
|
|
|
362
389
|
}
|
|
363
390
|
intersections.sort((left, right) => left - right);
|
|
364
391
|
for (let index = 0; index < intersections.length - 1; index += 2) {
|
|
365
|
-
const left = Math.max(0, Math.
|
|
366
|
-
const right = Math.min(dimensions.width
|
|
367
|
-
for (let x = left; x
|
|
392
|
+
const left = Math.max(0, Math.round(intersections[index]));
|
|
393
|
+
const right = Math.min(dimensions.width, Math.round(intersections[index + 1]));
|
|
394
|
+
for (let x = left; x < right; x += 1) {
|
|
368
395
|
data[y * dimensions.width + x] = 1;
|
|
369
396
|
}
|
|
370
397
|
}
|
|
@@ -381,14 +408,27 @@
|
|
|
381
408
|
return new Uint8Array(dimensions.width * dimensions.height);
|
|
382
409
|
}
|
|
383
410
|
|
|
384
|
-
|
|
411
|
+
/**
|
|
412
|
+
* Palette slots an id raster can name, one of them the background. A GLSL
|
|
413
|
+
* fragment stage is guaranteed only 224 uniform vectors, and a palette entry
|
|
414
|
+
* costs 2.25 of them, so this stays a multiple of four and well inside that
|
|
415
|
+
* budget; past it a frame draws from the RGBA composite instead.
|
|
416
|
+
*/
|
|
417
|
+
const MAX_ID_MASK_PALETTE_ENTRIES = 80;
|
|
385
418
|
const MAX_ID_MASK_STROKE_WIDTH = 16;
|
|
386
|
-
function createIdMaskFrame(instructions) {
|
|
419
|
+
function createIdMaskFrame(instructions, options = {}) {
|
|
387
420
|
if (instructions.length === 0) {
|
|
388
421
|
return undefined;
|
|
389
422
|
}
|
|
390
|
-
const
|
|
391
|
-
const
|
|
423
|
+
const maskWidth = Math.max(...instructions.map(({ mask }) => mask.width));
|
|
424
|
+
const maskHeight = Math.max(...instructions.map(({ mask }) => mask.height));
|
|
425
|
+
const width = resolveRasterWidth(maskWidth, options.maxWidth);
|
|
426
|
+
const height = width === maskWidth
|
|
427
|
+
? maskHeight
|
|
428
|
+
: Math.max(1, Math.round((width * maskHeight) / maskWidth));
|
|
429
|
+
const scaledAxes = width === maskWidth
|
|
430
|
+
? undefined
|
|
431
|
+
: createScaledMaskAxes({ height, maskHeight, maskWidth, width });
|
|
392
432
|
const data = new Uint8Array(new ArrayBuffer(width * height));
|
|
393
433
|
const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
|
|
394
434
|
const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
|
|
@@ -396,27 +436,23 @@
|
|
|
396
436
|
let hasStroke = false;
|
|
397
437
|
let maxStrokeWidth = 0;
|
|
398
438
|
for (const instruction of instructions) {
|
|
399
|
-
const detectionMaskId = instruction.detectionIndex
|
|
400
|
-
if (detectionMaskId
|
|
401
|
-
detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {
|
|
439
|
+
const detectionMaskId = resolveIdMaskPaletteId(instruction.detectionIndex);
|
|
440
|
+
if (detectionMaskId === undefined) {
|
|
402
441
|
return undefined;
|
|
403
442
|
}
|
|
404
|
-
|
|
443
|
+
writeIdMaskPaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);
|
|
405
444
|
if (instruction.stroke && instruction.stroke.width > 0) {
|
|
406
|
-
const strokeWidth =
|
|
445
|
+
const strokeWidth = resolveIdMaskStrokeTexels(instruction.stroke.width, maskWidth, width);
|
|
407
446
|
hasStroke = true;
|
|
408
447
|
strokeWidths[detectionMaskId] = strokeWidth;
|
|
409
448
|
maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);
|
|
410
|
-
|
|
449
|
+
writeIdMaskPaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);
|
|
411
450
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
data[y * width + x] = detectionMaskId;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
451
|
+
if (scaledAxes) {
|
|
452
|
+
writeScaledMaskRuns(data, scaledAxes, instruction.mask, detectionMaskId);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
writeMaskRuns(data, width, instruction.mask, detectionMaskId);
|
|
420
456
|
}
|
|
421
457
|
}
|
|
422
458
|
return {
|
|
@@ -425,13 +461,126 @@
|
|
|
425
461
|
hasStroke,
|
|
426
462
|
height,
|
|
427
463
|
maxStrokeWidth,
|
|
464
|
+
sourceWidth: maskWidth,
|
|
428
465
|
strokePalette,
|
|
429
466
|
strokeWidths,
|
|
430
467
|
width,
|
|
431
468
|
};
|
|
432
469
|
}
|
|
433
|
-
|
|
434
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Compressed RLE counts runs down each column in turn, so a foreground run is
|
|
472
|
+
* a contiguous walk down one column that wraps into the next.
|
|
473
|
+
*/
|
|
474
|
+
function writeMaskRuns(data, frameWidth, mask, detectionMaskId) {
|
|
475
|
+
const counts = decodeCompressedRleCounts(mask.counts);
|
|
476
|
+
const maskWidth = mask.width;
|
|
477
|
+
const maskHeight = mask.height;
|
|
478
|
+
let maskOffset = 0;
|
|
479
|
+
for (let index = 0; index < counts.length; index += 1) {
|
|
480
|
+
const runLength = counts[index] ?? 0;
|
|
481
|
+
if (index % 2 === 0 || runLength <= 0) {
|
|
482
|
+
maskOffset += runLength;
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
let x = Math.floor(maskOffset / maskHeight);
|
|
486
|
+
let y = maskOffset - x * maskHeight;
|
|
487
|
+
let frameOffset = y * frameWidth + x;
|
|
488
|
+
for (let step = 0; step < runLength; step += 1) {
|
|
489
|
+
if (x >= maskWidth) {
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
data[frameOffset] = detectionMaskId;
|
|
493
|
+
y += 1;
|
|
494
|
+
frameOffset += frameWidth;
|
|
495
|
+
if (y === maskHeight) {
|
|
496
|
+
y = 0;
|
|
497
|
+
x += 1;
|
|
498
|
+
frameOffset = x;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
maskOffset += runLength;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* The scaled twin of writeMaskRuns. Every source pixel marks the texel it lands
|
|
506
|
+
* in, so a mask keeps every part of itself the smaller raster can hold and
|
|
507
|
+
* gains up to a texel at its edges.
|
|
508
|
+
*/
|
|
509
|
+
function writeScaledMaskRuns(data, axes, mask, detectionMaskId) {
|
|
510
|
+
const counts = decodeCompressedRleCounts(mask.counts);
|
|
511
|
+
const maskWidth = mask.width;
|
|
512
|
+
const maskHeight = mask.height;
|
|
513
|
+
let maskOffset = 0;
|
|
514
|
+
for (let index = 0; index < counts.length; index += 1) {
|
|
515
|
+
const runLength = counts[index] ?? 0;
|
|
516
|
+
if (index % 2 === 0 || runLength <= 0) {
|
|
517
|
+
maskOffset += runLength;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
let x = Math.floor(maskOffset / maskHeight);
|
|
521
|
+
let y = maskOffset - x * maskHeight;
|
|
522
|
+
let column = axes.columns[x];
|
|
523
|
+
for (let step = 0; step < runLength; step += 1) {
|
|
524
|
+
if (x >= maskWidth) {
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
data[axes.rows[y] + column] = detectionMaskId;
|
|
528
|
+
y += 1;
|
|
529
|
+
if (y === maskHeight) {
|
|
530
|
+
y = 0;
|
|
531
|
+
x += 1;
|
|
532
|
+
column = axes.columns[x];
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
maskOffset += runLength;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function resolveRasterWidth(maskWidth, maxWidth) {
|
|
539
|
+
return maxWidth !== undefined && maxWidth > 0 && maxWidth < maskWidth
|
|
540
|
+
? Math.max(1, Math.floor(maxWidth))
|
|
541
|
+
: maskWidth;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* A stroke is measured in texels of the raster it is drawn on, so a coarser
|
|
545
|
+
* raster measures it in coarser texels. A stroke of a texel or more keeps at
|
|
546
|
+
* least one, the thinnest line the shader can draw; a narrower one keeps its
|
|
547
|
+
* own width, which the shader draws as an inner boundary at any scale. The
|
|
548
|
+
* ceiling is the widest neighbourhood the shaders scan, so a wider stroke would
|
|
549
|
+
* be drawn at the ceiling anyway and every layer drawing the same annotation
|
|
550
|
+
* has to arrive at the same width.
|
|
551
|
+
*/
|
|
552
|
+
function resolveIdMaskStrokeTexels(strokeWidth, maskWidth, rasterWidth) {
|
|
553
|
+
const scale = maskWidth > 0 ? rasterWidth / maskWidth : 1;
|
|
554
|
+
return Math.min(Math.max(strokeWidth * scale, Math.min(strokeWidth, 1)), MAX_ID_MASK_STROKE_WIDTH);
|
|
555
|
+
}
|
|
556
|
+
function createScaledMaskAxes(frame) {
|
|
557
|
+
return {
|
|
558
|
+
columns: createMaskAxisMap(frame.maskWidth, frame.width, 1),
|
|
559
|
+
rows: createMaskAxisMap(frame.maskHeight, frame.height, frame.width),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/** Rows hold their destination offset, so writing a run is two lookups. */
|
|
563
|
+
function createMaskAxisMap(sourceLength, targetLength, stride) {
|
|
564
|
+
const map = new Int32Array(sourceLength);
|
|
565
|
+
const scale = targetLength / sourceLength;
|
|
566
|
+
for (let index = 0; index < sourceLength; index += 1) {
|
|
567
|
+
map[index] = Math.min(targetLength - 1, Math.floor(index * scale)) * stride;
|
|
568
|
+
}
|
|
569
|
+
return map;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* The palette slot a detection index names, or undefined when the palette has
|
|
573
|
+
* no slot for it. Slot zero is the background, so the ids start at one and the
|
|
574
|
+
* last detection the palette can name sits one index below the last slot.
|
|
575
|
+
*/
|
|
576
|
+
function resolveIdMaskPaletteId(detectionIndex) {
|
|
577
|
+
const detectionMaskId = detectionIndex + 1;
|
|
578
|
+
return detectionMaskId > 0 && detectionMaskId < MAX_ID_MASK_PALETTE_ENTRIES
|
|
579
|
+
? detectionMaskId
|
|
580
|
+
: undefined;
|
|
581
|
+
}
|
|
582
|
+
function writeIdMaskPaletteEntry(palette, detectionMaskId, color, alpha) {
|
|
583
|
+
const offset = detectionMaskId * 4;
|
|
435
584
|
palette[offset] = ((color >> 16) & 0xff) / 255;
|
|
436
585
|
palette[offset + 1] = ((color >> 8) & 0xff) / 255;
|
|
437
586
|
palette[offset + 2] = (color & 0xff) / 255;
|
|
@@ -471,7 +620,7 @@
|
|
|
471
620
|
*/
|
|
472
621
|
var MediaErrorKind;
|
|
473
622
|
(function (MediaErrorKind) {
|
|
474
|
-
/** The media could not be opened or read
|
|
623
|
+
/** The media could not be opened or read, or was refused before any read. */
|
|
475
624
|
MediaErrorKind["Unreadable"] = "unreadable";
|
|
476
625
|
/** The container or codec is not supported by this platform. */
|
|
477
626
|
MediaErrorKind["UnsupportedFormat"] = "unsupportedFormat";
|
|
@@ -496,6 +645,28 @@
|
|
|
496
645
|
MediaSourceStatus["Error"] = "error";
|
|
497
646
|
MediaSourceStatus["Destroyed"] = "destroyed";
|
|
498
647
|
})(MediaSourceStatus || (MediaSourceStatus = {}));
|
|
648
|
+
/**
|
|
649
|
+
* Current renderer state.
|
|
650
|
+
*/
|
|
651
|
+
/**
|
|
652
|
+
* What a playback gate holds back.
|
|
653
|
+
*
|
|
654
|
+
* A source the renderer pulls samples from can be held between any two frames,
|
|
655
|
+
* because the renderer decides when each one is drawn. A source that presents
|
|
656
|
+
* its own frames owns the playhead, so holding it means stopping the producer
|
|
657
|
+
* and starting it again, and a gate that stops one has to bound its own wait
|
|
658
|
+
* or a producer nothing answers for never runs again.
|
|
659
|
+
*/
|
|
660
|
+
var PlaybackGateReach;
|
|
661
|
+
(function (PlaybackGateReach) {
|
|
662
|
+
/** No gate: the picture moves and unprepared layers are absent from it. */
|
|
663
|
+
PlaybackGateReach["Off"] = "off";
|
|
664
|
+
/**
|
|
665
|
+
* Playback waits to begin, and stops again at any frame whose artifacts are
|
|
666
|
+
* missing, until they arrive or the gate's own wait bound gives up on them.
|
|
667
|
+
*/
|
|
668
|
+
PlaybackGateReach["EveryFrame"] = "everyFrame";
|
|
669
|
+
})(PlaybackGateReach || (PlaybackGateReach = {}));
|
|
499
670
|
|
|
500
671
|
/**
|
|
501
672
|
* Media-session operating mode.
|
|
@@ -536,12 +707,35 @@
|
|
|
536
707
|
*/
|
|
537
708
|
var MediaSessionActivityKind;
|
|
538
709
|
(function (MediaSessionActivityKind) {
|
|
710
|
+
/**
|
|
711
|
+
* Playback is held waiting for a producer to emit detections for the frame
|
|
712
|
+
* about to be shown, which is a wait on inference rather than on transfer or
|
|
713
|
+
* decode. `DetectionsBuffering` is the transfer of detections that already
|
|
714
|
+
* exist, and `PlaybackBuffering` is the wait for media bytes.
|
|
715
|
+
*/
|
|
716
|
+
MediaSessionActivityKind["DetectionsAwaitingCoverage"] = "detectionsAwaitingCoverage";
|
|
539
717
|
MediaSessionActivityKind["DetectionsBuffering"] = "detectionsBuffering";
|
|
540
718
|
MediaSessionActivityKind["DetectionsLoading"] = "detectionsLoading";
|
|
541
719
|
MediaSessionActivityKind["Error"] = "error";
|
|
542
720
|
MediaSessionActivityKind["MediaNormalizing"] = "mediaNormalizing";
|
|
543
721
|
MediaSessionActivityKind["MediaOpening"] = "mediaOpening";
|
|
722
|
+
/**
|
|
723
|
+
* The picture is waiting on media the source has not handed over yet, which
|
|
724
|
+
* is the bytes and their decode rather than anything downstream of them.
|
|
725
|
+
* `PlaybackBuffering` is what a transport reports once it has already
|
|
726
|
+
* stopped; this is reported from the read itself, including the reads a seek
|
|
727
|
+
* makes while the transport still reads as paused.
|
|
728
|
+
*/
|
|
729
|
+
MediaSessionActivityKind["MediaSourceReading"] = "mediaSourceReading";
|
|
544
730
|
MediaSessionActivityKind["PlaybackBuffering"] = "playbackBuffering";
|
|
731
|
+
/**
|
|
732
|
+
* The render-preparation gate held the picture for artifacts that never
|
|
733
|
+
* arrived and let it go. Nothing is blocked: the frames reaching the screen
|
|
734
|
+
* are the ones whose artifacts were given up on. What is waited on is
|
|
735
|
+
* preparation finishing another frame, which is what lets the gate hold the
|
|
736
|
+
* picture again; `RenderPreparing` covers the holds that are running.
|
|
737
|
+
*/
|
|
738
|
+
MediaSessionActivityKind["RenderPreparationAbandoned"] = "renderPreparationAbandoned";
|
|
545
739
|
MediaSessionActivityKind["RenderPreparing"] = "renderPreparing";
|
|
546
740
|
})(MediaSessionActivityKind || (MediaSessionActivityKind = {}));
|
|
547
741
|
/**
|
|
@@ -554,9 +748,52 @@
|
|
|
554
748
|
MediaSessionActivityStatus["Waiting"] = "waiting";
|
|
555
749
|
})(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
|
|
556
750
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
751
|
+
/** Builds compact, independent alpha crops so overlapping masks stay exact. */
|
|
752
|
+
function createRegionMaskCoverageFrame(instructions) {
|
|
753
|
+
const entries = [];
|
|
754
|
+
for (const instruction of instructions) {
|
|
755
|
+
if (!instruction.regionCoverageMask) {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const entry = cropCoverageMask(instruction.detectionIndex, decodeCompressedRleMask(instruction.regionCoverageMask));
|
|
759
|
+
if (entry) {
|
|
760
|
+
entries.push(entry);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return entries.length > 0 ? { entries } : undefined;
|
|
764
|
+
}
|
|
765
|
+
function cropCoverageMask(detectionIndex, decodedMask) {
|
|
766
|
+
let minX = decodedMask.width;
|
|
767
|
+
let minY = decodedMask.height;
|
|
768
|
+
let maxX = -1;
|
|
769
|
+
let maxY = -1;
|
|
770
|
+
for (let y = 0; y < decodedMask.height; y += 1) {
|
|
771
|
+
for (let x = 0; x < decodedMask.width; x += 1) {
|
|
772
|
+
if (!decodedMask.data[y * decodedMask.width + x]) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
minX = Math.min(minX, x);
|
|
776
|
+
minY = Math.min(minY, y);
|
|
777
|
+
maxX = Math.max(maxX, x);
|
|
778
|
+
maxY = Math.max(maxY, y);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (maxX < minX || maxY < minY) {
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
const width = maxX - minX + 1;
|
|
785
|
+
const height = maxY - minY + 1;
|
|
786
|
+
const data = new Uint8Array(new ArrayBuffer(width * height));
|
|
787
|
+
for (let y = minY; y <= maxY; y += 1) {
|
|
788
|
+
for (let x = minX; x <= maxX; x += 1) {
|
|
789
|
+
if (!decodedMask.data[y * decodedMask.width + x]) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
data[(y - minY) * width + (x - minX)] = 255;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return { data, detectionIndex, height, width, x: minX, y: minY };
|
|
796
|
+
}
|
|
560
797
|
function compositeMaskFrame(instructions) {
|
|
561
798
|
const maskInstructions = materializeMaskInstructions(instructions);
|
|
562
799
|
if (maskInstructions.length === 0) {
|
|
@@ -570,33 +807,51 @@
|
|
|
570
807
|
}
|
|
571
808
|
return { data, height, width };
|
|
572
809
|
}
|
|
573
|
-
function
|
|
574
|
-
|
|
810
|
+
function createIdMaskRasterFrame(instructions, maxRasterWidth) {
|
|
811
|
+
try {
|
|
812
|
+
return createIdMaskFrame(materializeMaskInstructions(instructions), {
|
|
813
|
+
maxWidth: maxRasterWidth,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
catch {
|
|
817
|
+
// The id raster is the fast path, not the only one: answering with nothing
|
|
818
|
+
// puts the caller on the RGBA composite, which draws the same picture.
|
|
819
|
+
return undefined;
|
|
820
|
+
}
|
|
575
821
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Whether the palette has a slot for every detection this frame masks. An id
|
|
824
|
+
* names a detection by its index in the frame, so one masked detection past the
|
|
825
|
+
* last slot leaves the whole frame without an id raster however few masks it
|
|
826
|
+
* carries.
|
|
827
|
+
*/
|
|
828
|
+
function canIdMaskPaletteNameFrame(instructions) {
|
|
829
|
+
return instructions.every((instruction) => instruction.visible === false ||
|
|
830
|
+
resolveIdMaskPaletteId(instruction.detectionIndex) !== undefined);
|
|
831
|
+
}
|
|
832
|
+
function createIdMaskPlane(instructions, maxRasterWidth) {
|
|
833
|
+
// The plane carries the ids a failed cook could not. A frame the palette
|
|
834
|
+
// cannot name has none to carry, and cooking it again reaches the same
|
|
835
|
+
// refusal after rasterizing every mask a second time.
|
|
836
|
+
if (!canIdMaskPaletteNameFrame(instructions)) {
|
|
579
837
|
return undefined;
|
|
580
838
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
pixels: frame.data,
|
|
586
|
-
width: frame.width,
|
|
587
|
-
}),
|
|
588
|
-
};
|
|
839
|
+
const frame = createIdMaskRasterFrame(instructions, maxRasterWidth);
|
|
840
|
+
return frame
|
|
841
|
+
? { data: frame.data, height: frame.height, width: frame.width }
|
|
842
|
+
: undefined;
|
|
589
843
|
}
|
|
590
844
|
function compositeInstruction(rgba, canvasWidth, instruction) {
|
|
591
|
-
const decodedMask = decodeCompressedRleMask(instruction.mask);
|
|
592
845
|
const fill = resolveRgbaColor(instruction.color, instruction.alpha);
|
|
593
|
-
compositeMaskFill(rgba, canvasWidth,
|
|
594
|
-
if (instruction.stroke) {
|
|
595
|
-
compositeMaskStroke(rgba, canvasWidth,
|
|
846
|
+
const bounds = compositeMaskFill(rgba, canvasWidth, instruction.mask, fill);
|
|
847
|
+
if (instruction.stroke && bounds) {
|
|
848
|
+
compositeMaskStroke(rgba, canvasWidth, decodeCompressedRleMask(instruction.mask), bounds, instruction.stroke);
|
|
596
849
|
}
|
|
597
850
|
}
|
|
598
851
|
function materializeMaskInstructions(instructions) {
|
|
599
|
-
return instructions
|
|
852
|
+
return instructions
|
|
853
|
+
.filter((instruction) => instruction.visible !== false)
|
|
854
|
+
.map((instruction) => {
|
|
600
855
|
if (instruction.mask) {
|
|
601
856
|
return instruction;
|
|
602
857
|
}
|
|
@@ -610,25 +865,59 @@
|
|
|
610
865
|
};
|
|
611
866
|
});
|
|
612
867
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
868
|
+
/**
|
|
869
|
+
* Compressed RLE counts runs down each column in turn, so a foreground run is
|
|
870
|
+
* a contiguous walk down one column that wraps into the next.
|
|
871
|
+
*/
|
|
872
|
+
function compositeMaskFill(rgba, canvasWidth, mask, fill) {
|
|
873
|
+
const counts = decodeCompressedRleCounts(mask.counts);
|
|
874
|
+
const maskWidth = mask.width;
|
|
875
|
+
const maskHeight = mask.height;
|
|
876
|
+
const maskArea = maskWidth * maskHeight;
|
|
877
|
+
let maskOffset = 0;
|
|
878
|
+
let minX = maskWidth;
|
|
879
|
+
let minY = maskHeight;
|
|
880
|
+
let maxX = -1;
|
|
881
|
+
let maxY = -1;
|
|
882
|
+
for (let index = 0; index < counts.length; index += 1) {
|
|
883
|
+
const runLength = counts[index] ?? 0;
|
|
884
|
+
if (index % 2 === 0 || runLength <= 0) {
|
|
885
|
+
maskOffset += runLength;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
let columnX = Math.floor(maskOffset / maskHeight);
|
|
889
|
+
let columnY = maskOffset - columnX * maskHeight;
|
|
890
|
+
for (let step = 0; step < runLength; step += 1) {
|
|
891
|
+
// A run can outlast the columns the mask has; the pixel it then names
|
|
892
|
+
// is wherever that column-major offset lands when read back row-major.
|
|
893
|
+
const rowMajorOffset = columnY * maskWidth + columnX;
|
|
894
|
+
if (rowMajorOffset < maskArea) {
|
|
895
|
+
const x = rowMajorOffset % maskWidth;
|
|
896
|
+
const y = (rowMajorOffset - x) / maskWidth;
|
|
897
|
+
writePixel(rgba, canvasWidth, x, y, fill);
|
|
898
|
+
minX = Math.min(minX, x);
|
|
899
|
+
minY = Math.min(minY, y);
|
|
900
|
+
maxX = Math.max(maxX, x);
|
|
901
|
+
maxY = Math.max(maxY, y);
|
|
902
|
+
}
|
|
903
|
+
columnY += 1;
|
|
904
|
+
if (columnY === maskHeight) {
|
|
905
|
+
columnY = 0;
|
|
906
|
+
columnX += 1;
|
|
619
907
|
}
|
|
620
|
-
writePixel(rgba, canvasWidth, x, y, fill);
|
|
621
908
|
}
|
|
909
|
+
maskOffset += runLength;
|
|
622
910
|
}
|
|
911
|
+
return maxX < minX || maxY < minY ? undefined : { maxX, maxY, minX, minY };
|
|
623
912
|
}
|
|
624
|
-
function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {
|
|
913
|
+
function compositeMaskStroke(rgba, canvasWidth, decodedMask, bounds, stroke) {
|
|
625
914
|
const width = Math.round(stroke.width);
|
|
626
915
|
if (width <= 0) {
|
|
627
916
|
return;
|
|
628
917
|
}
|
|
629
918
|
const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);
|
|
630
|
-
for (let y =
|
|
631
|
-
for (let x =
|
|
919
|
+
for (let y = bounds.minY; y <= bounds.maxY; y += 1) {
|
|
920
|
+
for (let x = bounds.minX; x <= bounds.maxX; x += 1) {
|
|
632
921
|
if (!isMaskPixel(decodedMask, x, y) ||
|
|
633
922
|
!isBoundaryPixel(decodedMask, x, y)) {
|
|
634
923
|
continue;
|
|
@@ -684,86 +973,23 @@
|
|
|
684
973
|
rgba[rgbaOffset + 2] = color.blue;
|
|
685
974
|
rgba[rgbaOffset + 3] = color.alpha;
|
|
686
975
|
}
|
|
687
|
-
async function encodeGrayscalePng(options) {
|
|
688
|
-
if (typeof CompressionStream === "undefined") {
|
|
689
|
-
throw new Error("CompressionStream is required to encode PNG ID masks.");
|
|
690
|
-
}
|
|
691
|
-
const rawScanlines = createFilterlessPngScanlines(options);
|
|
692
|
-
const ihdr = new Uint8Array(13);
|
|
693
|
-
const ihdrView = new DataView(ihdr.buffer);
|
|
694
|
-
ihdrView.setUint32(0, options.width);
|
|
695
|
-
ihdrView.setUint32(4, options.height);
|
|
696
|
-
ihdr[8] = 8;
|
|
697
|
-
ihdr[9] = 0;
|
|
698
|
-
ihdr[10] = 0;
|
|
699
|
-
ihdr[11] = 0;
|
|
700
|
-
ihdr[12] = 0;
|
|
701
|
-
const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])
|
|
702
|
-
.stream()
|
|
703
|
-
.pipeThrough(new CompressionStream("deflate"))).arrayBuffer());
|
|
704
|
-
return concatUint8Arrays([
|
|
705
|
-
PNG_SIGNATURE,
|
|
706
|
-
createPngChunk("IHDR", ihdr),
|
|
707
|
-
createPngChunk("IDAT", compressed),
|
|
708
|
-
createPngChunk("IEND", new Uint8Array(0)),
|
|
709
|
-
]);
|
|
710
|
-
}
|
|
711
|
-
function createFilterlessPngScanlines(options) {
|
|
712
|
-
const rowStride = options.width + 1;
|
|
713
|
-
const scanlines = new Uint8Array(rowStride * options.height);
|
|
714
|
-
for (let y = 0; y < options.height; y += 1) {
|
|
715
|
-
const sourceOffset = y * options.width;
|
|
716
|
-
const targetOffset = y * rowStride;
|
|
717
|
-
scanlines[targetOffset] = 0;
|
|
718
|
-
scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);
|
|
719
|
-
}
|
|
720
|
-
return scanlines;
|
|
721
|
-
}
|
|
722
|
-
function createPngChunk(type, data) {
|
|
723
|
-
const typeBytes = new TextEncoder().encode(type);
|
|
724
|
-
const chunk = new Uint8Array(12 + data.length);
|
|
725
|
-
const view = new DataView(chunk.buffer);
|
|
726
|
-
view.setUint32(0, data.length);
|
|
727
|
-
chunk.set(typeBytes, 4);
|
|
728
|
-
chunk.set(data, 8);
|
|
729
|
-
view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));
|
|
730
|
-
return chunk;
|
|
731
|
-
}
|
|
732
|
-
const crc32Table = createCrc32Table();
|
|
733
|
-
function createCrc32Table() {
|
|
734
|
-
const table = new Uint32Array(256);
|
|
735
|
-
for (let index = 0; index < table.length; index += 1) {
|
|
736
|
-
let value = index;
|
|
737
|
-
for (let bit = 0; bit < 8; bit += 1) {
|
|
738
|
-
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
739
|
-
}
|
|
740
|
-
table[index] = value >>> 0;
|
|
741
|
-
}
|
|
742
|
-
return table;
|
|
743
|
-
}
|
|
744
|
-
function crc32(bytes) {
|
|
745
|
-
let crc = 0xffffffff;
|
|
746
|
-
for (const byte of bytes) {
|
|
747
|
-
crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
748
|
-
}
|
|
749
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
750
|
-
}
|
|
751
|
-
function concatUint8Arrays(chunks) {
|
|
752
|
-
const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);
|
|
753
|
-
const result = new Uint8Array(totalLength);
|
|
754
|
-
let offset = 0;
|
|
755
|
-
for (const chunk of chunks) {
|
|
756
|
-
result.set(chunk, offset);
|
|
757
|
-
offset += chunk.length;
|
|
758
|
-
}
|
|
759
|
-
return result;
|
|
760
|
-
}
|
|
761
976
|
|
|
762
977
|
var PreparedMaskFrameKind;
|
|
763
978
|
(function (PreparedMaskFrameKind) {
|
|
764
|
-
PreparedMaskFrameKind["
|
|
979
|
+
PreparedMaskFrameKind["IdMask"] = "idMask";
|
|
765
980
|
PreparedMaskFrameKind["RgbaImage"] = "rgbaImage";
|
|
766
981
|
})(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));
|
|
982
|
+
/**
|
|
983
|
+
* How an id raster is laid out for the texture it is uploaded into. WebGPU
|
|
984
|
+
* takes any bytesPerRow, so ids go up one byte per pixel. WebGL aligns every
|
|
985
|
+
* uploaded row to four bytes and rejects a single-channel upload whose width
|
|
986
|
+
* is not a multiple of four, which is what the four-channel layout is for.
|
|
987
|
+
*/
|
|
988
|
+
var IdMaskTextureFormat;
|
|
989
|
+
(function (IdMaskTextureFormat) {
|
|
990
|
+
IdMaskTextureFormat["R8"] = "r8unorm";
|
|
991
|
+
IdMaskTextureFormat["Rgba8"] = "rgba8unorm";
|
|
992
|
+
})(IdMaskTextureFormat || (IdMaskTextureFormat = {}));
|
|
767
993
|
|
|
768
994
|
var MaskPreparationWorkerMessageType;
|
|
769
995
|
(function (MaskPreparationWorkerMessageType) {
|
|
@@ -779,23 +1005,40 @@
|
|
|
779
1005
|
if (message.type !== MaskPreparationWorkerMessageType.Prepare) {
|
|
780
1006
|
return;
|
|
781
1007
|
}
|
|
782
|
-
|
|
1008
|
+
prepareMaskFrame(message);
|
|
783
1009
|
});
|
|
784
|
-
|
|
1010
|
+
function prepareMaskFrame(message) {
|
|
785
1011
|
try {
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1012
|
+
const regionMaskCoverage = createRegionMaskCoverageFrame(message.job.instructions);
|
|
1013
|
+
const coverageTransfers = getRegionMaskCoverageTransfers(regionMaskCoverage);
|
|
1014
|
+
const idMaskFrame = createIdMaskRasterFrame(message.job.instructions, message.job.maxRasterWidth);
|
|
1015
|
+
if (idMaskFrame) {
|
|
1016
|
+
workerScope.postMessage({
|
|
1017
|
+
artifactKind: PreparedMaskFrameKind.IdMask,
|
|
1018
|
+
fillPalette: idMaskFrame.fillPalette,
|
|
1019
|
+
hasStroke: idMaskFrame.hasStroke,
|
|
1020
|
+
height: idMaskFrame.height,
|
|
1021
|
+
key: message.job.key,
|
|
1022
|
+
maxStrokeWidth: idMaskFrame.maxStrokeWidth,
|
|
1023
|
+
raster: idMaskFrame.data,
|
|
1024
|
+
regionMaskCoverage,
|
|
1025
|
+
requestId: message.requestId,
|
|
1026
|
+
sourceWidth: idMaskFrame.sourceWidth,
|
|
1027
|
+
strokePalette: idMaskFrame.strokePalette,
|
|
1028
|
+
strokeWidths: idMaskFrame.strokeWidths,
|
|
1029
|
+
type: MaskPreparationWorkerMessageType.Complete,
|
|
1030
|
+
width: idMaskFrame.width,
|
|
1031
|
+
}, [
|
|
1032
|
+
idMaskFrame.data.buffer,
|
|
1033
|
+
idMaskFrame.fillPalette.buffer,
|
|
1034
|
+
idMaskFrame.strokePalette.buffer,
|
|
1035
|
+
idMaskFrame.strokeWidths.buffer,
|
|
1036
|
+
...coverageTransfers,
|
|
794
1037
|
]);
|
|
795
1038
|
return;
|
|
796
1039
|
}
|
|
797
1040
|
const compositedFrame = compositeMaskFrame(message.job.instructions);
|
|
798
|
-
if (!compositedFrame) {
|
|
1041
|
+
if (!compositedFrame && !regionMaskCoverage) {
|
|
799
1042
|
workerScope.postMessage({
|
|
800
1043
|
key: message.job.key,
|
|
801
1044
|
requestId: message.requestId,
|
|
@@ -803,26 +1046,30 @@
|
|
|
803
1046
|
});
|
|
804
1047
|
return;
|
|
805
1048
|
}
|
|
806
|
-
const
|
|
1049
|
+
const preparedPixels = compositedFrame ?? createTransparentCoverageCarrier();
|
|
1050
|
+
const imageData = new ImageData(preparedPixels.data, preparedPixels.width, preparedPixels.height);
|
|
807
1051
|
const imageBitmap = createImageBitmapFromImageData(imageData);
|
|
808
|
-
const
|
|
1052
|
+
const idMaskPlane = createIdMaskPlane(message.job.instructions, message.job.maxRasterWidth);
|
|
1053
|
+
const idMaskTransfers = idMaskPlane ? [idMaskPlane.data.buffer] : [];
|
|
809
1054
|
if (imageBitmap) {
|
|
810
1055
|
workerScope.postMessage({
|
|
1056
|
+
idMaskPlane,
|
|
811
1057
|
imageBitmap,
|
|
812
|
-
idMaskData,
|
|
813
1058
|
key: message.job.key,
|
|
1059
|
+
regionMaskCoverage,
|
|
814
1060
|
requestId: message.requestId,
|
|
815
1061
|
type: MaskPreparationWorkerMessageType.Complete,
|
|
816
|
-
}, [...
|
|
1062
|
+
}, [imageBitmap, ...idMaskTransfers, ...coverageTransfers]);
|
|
817
1063
|
return;
|
|
818
1064
|
}
|
|
819
1065
|
workerScope.postMessage({
|
|
820
|
-
|
|
1066
|
+
idMaskPlane,
|
|
821
1067
|
imageData,
|
|
822
1068
|
key: message.job.key,
|
|
1069
|
+
regionMaskCoverage,
|
|
823
1070
|
requestId: message.requestId,
|
|
824
1071
|
type: MaskPreparationWorkerMessageType.Complete,
|
|
825
|
-
}, [
|
|
1072
|
+
}, [imageData.data.buffer, ...idMaskTransfers, ...coverageTransfers]);
|
|
826
1073
|
}
|
|
827
1074
|
catch (error) {
|
|
828
1075
|
workerScope.postMessage({
|
|
@@ -835,43 +1082,20 @@
|
|
|
835
1082
|
});
|
|
836
1083
|
}
|
|
837
1084
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
let frame;
|
|
845
|
-
try {
|
|
846
|
-
frame = await createPngIdMaskFrame(message.job.instructions);
|
|
847
|
-
}
|
|
848
|
-
catch {
|
|
849
|
-
return undefined;
|
|
850
|
-
}
|
|
851
|
-
if (!frame) {
|
|
852
|
-
return undefined;
|
|
853
|
-
}
|
|
854
|
-
let imageBitmap;
|
|
855
|
-
try {
|
|
856
|
-
imageBitmap = await createImageBitmap(new Blob([frame.png], { type: "image/png" }));
|
|
857
|
-
}
|
|
858
|
-
catch {
|
|
859
|
-
return undefined;
|
|
860
|
-
}
|
|
1085
|
+
/**
|
|
1086
|
+
* A frame that carries only region coverage has nothing to composite, and the
|
|
1087
|
+
* RGBA branch still has to produce an artifact for the coverage to ride on.
|
|
1088
|
+
*/
|
|
1089
|
+
function createTransparentCoverageCarrier() {
|
|
861
1090
|
return {
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
imageBitmap,
|
|
866
|
-
key: message.job.key,
|
|
867
|
-
maxStrokeWidth: frame.maxStrokeWidth,
|
|
868
|
-
png: frame.png,
|
|
869
|
-
requestId: message.requestId,
|
|
870
|
-
strokePalette: frame.strokePalette,
|
|
871
|
-
strokeWidths: frame.strokeWidths,
|
|
872
|
-
type: MaskPreparationWorkerMessageType.Complete,
|
|
1091
|
+
data: new Uint8ClampedArray(new ArrayBuffer(4)),
|
|
1092
|
+
height: 1,
|
|
1093
|
+
width: 1,
|
|
873
1094
|
};
|
|
874
1095
|
}
|
|
1096
|
+
function getRegionMaskCoverageTransfers(coverage) {
|
|
1097
|
+
return coverage?.entries.map(({ data }) => data.buffer) ?? [];
|
|
1098
|
+
}
|
|
875
1099
|
function createImageBitmapFromImageData(imageData) {
|
|
876
1100
|
if (typeof OffscreenCanvas === "undefined") {
|
|
877
1101
|
return null;
|