supervision 0.2.5 → 0.2.7
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/depth-preview-probe-DG7McQEN.js +314 -0
- package/dist/depth-preview-probe-DG7McQEN.js.map +1 -0
- package/dist/depth-preview-track-BvgwNEE9.js +612 -0
- package/dist/depth-preview-track-BvgwNEE9.js.map +1 -0
- package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
- package/dist/editing.d.ts +1 -1
- package/dist/editing.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9845 -4759
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +788 -25
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/media/decoder-deadline.d.ts +18 -0
- package/dist/media/decoder-deadline.d.ts.map +1 -0
- package/dist/media/depth-preview-probe.d.ts +65 -0
- package/dist/media/depth-preview-probe.d.ts.map +1 -0
- package/dist/media/depth-preview-track.d.ts +96 -0
- package/dist/media/depth-preview-track.d.ts.map +1 -0
- package/dist/media/media-normalization.d.ts.map +1 -1
- package/dist/media/media-source.d.ts +7 -0
- package/dist/media/media-source.d.ts.map +1 -1
- package/dist/media/mediabunny-frame-clock.d.ts +7 -0
- package/dist/media/mediabunny-frame-clock.d.ts.map +1 -0
- package/dist/media/mediabunny-media-source.d.ts +3 -0
- package/dist/media/mediabunny-media-source.d.ts.map +1 -1
- package/dist/media/track-frame-index.d.ts +14 -0
- package/dist/media/track-frame-index.d.ts.map +1 -0
- package/dist/media/video-engine-media-source.d.ts.map +1 -1
- package/dist/media/video-engine-media-source.js +1 -1
- package/dist/render-preparation/depth/clip-preview.d.ts +41 -0
- package/dist/render-preparation/depth/clip-preview.d.ts.map +1 -0
- package/dist/render-preparation/depth/clip-timing.d.ts +18 -0
- package/dist/render-preparation/depth/clip-timing.d.ts.map +1 -0
- package/dist/render-preparation/depth/clip.d.ts +14 -0
- package/dist/render-preparation/depth/clip.d.ts.map +1 -0
- package/dist/render-preparation/depth/exact-at-rest.d.ts +44 -0
- package/dist/render-preparation/depth/exact-at-rest.d.ts.map +1 -0
- package/dist/render-preparation/depth/exact-playback.d.ts +40 -0
- package/dist/render-preparation/depth/exact-playback.d.ts.map +1 -0
- package/dist/render-preparation/depth/files.d.ts +55 -0
- package/dist/render-preparation/depth/files.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-decode.d.ts +20 -0
- package/dist/render-preparation/depth/frame-decode.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-preparer.d.ts +51 -0
- package/dist/render-preparation/depth/frame-preparer.d.ts.map +1 -0
- package/dist/render-preparation/depth/frame-window.d.ts +146 -0
- package/dist/render-preparation/depth/frame-window.d.ts.map +1 -0
- package/dist/render-preparation/depth/options.d.ts +37 -0
- package/dist/render-preparation/depth/options.d.ts.map +1 -0
- package/dist/render-preparation/depth/png16.d.ts +40 -0
- package/dist/render-preparation/depth/png16.d.ts.map +1 -0
- package/dist/render-preparation/depth/preview-luma-copier.d.ts +12 -0
- package/dist/render-preparation/depth/preview-luma-copier.d.ts.map +1 -0
- package/dist/render-preparation/depth/preview-luma.d.ts +48 -0
- package/dist/render-preparation/depth/preview-luma.d.ts.map +1 -0
- package/dist/render-preparation/depth/source.d.ts +123 -0
- package/dist/render-preparation/depth/source.d.ts.map +1 -0
- package/dist/render-preparation/depth/worker-protocol.d.ts +76 -0
- package/dist/render-preparation/depth/worker-protocol.d.ts.map +1 -0
- package/dist/render-preparation/mask-frame-artifact.d.ts +10 -0
- package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-compositor.d.ts +2 -1
- 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-outline-paths.d.ts +3 -0
- package/dist/render-preparation/mask-outline-paths.d.ts.map +1 -0
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +2 -1
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
- package/dist/render-preparation/playhead-motion.d.ts +67 -0
- package/dist/render-preparation/playhead-motion.d.ts.map +1 -0
- package/dist/render-preparation/prepared-render-window.d.ts +12 -0
- package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
- package/dist/renderers/depth-textures.d.ts +79 -0
- package/dist/renderers/depth-textures.d.ts.map +1 -0
- package/dist/renderers/detection-render-order.d.ts +4 -0
- package/dist/renderers/detection-render-order.d.ts.map +1 -0
- package/dist/renderers/id-mask-stroke-coverage.d.ts +3 -0
- package/dist/renderers/id-mask-stroke-coverage.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +16 -2
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-batch-texture-bindings.d.ts +9 -0
- package/dist/renderers/pixi-batch-texture-bindings.d.ts.map +1 -0
- package/dist/renderers/pixi-box-layer.d.ts +1 -0
- package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-depth-layer.d.ts +76 -0
- package/dist/renderers/pixi-depth-layer.d.ts.map +1 -0
- package/dist/renderers/pixi-depth-shader.d.ts +76 -0
- package/dist/renderers/pixi-depth-shader.d.ts.map +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-frame-present.d.ts +1 -0
- package/dist/renderers/pixi-frame-present.d.ts.map +1 -1
- package/dist/renderers/pixi-id-mask-shader.d.ts +1 -0
- package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts +1 -0
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-brush-preview.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-halo.d.ts +1 -0
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-layer.d.ts +8 -2
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-screen-stroke.d.ts +17 -0
- package/dist/renderers/pixi-mask-screen-stroke.d.ts.map +1 -0
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-region-coverage-mask.d.ts +1 -0
- package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -1
- package/dist/renderers/pixi-region-layer.d.ts +1 -0
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
- package/dist/renderers/pixi-vector-layer.d.ts +1 -0
- package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
- package/dist/renderers/prepared-annotation-window.d.ts +1 -0
- package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
- package/dist/renderers/render-preparation-report.d.ts +23 -0
- package/dist/renderers/render-preparation-report.d.ts.map +1 -0
- package/dist/sessions/media-session-defaults.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 +64 -0
- package/dist/tracking.worker.js.map +1 -1
- package/dist/types/media-depth.d.ts +55 -0
- package/dist/types/media-depth.d.ts.map +1 -0
- package/dist/types/media-renderer.d.ts +33 -0
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/types/media-session.d.ts +21 -0
- package/dist/types/media-session.d.ts.map +1 -1
- package/dist/types/render-preparation.d.ts +105 -3
- package/dist/types/render-preparation.d.ts.map +1 -1
- package/dist/{video-engine-media-source-DR-x1UfK.js → video-engine-media-source-DOFsuGS4.js} +35 -29
- package/dist/video-engine-media-source-DOFsuGS4.js.map +1 -0
- package/dist/web-video-engine/analysis.js +6 -43
- package/dist/web-video-engine/cache-budget.d.ts +4 -7
- package/dist/web-video-engine/constants.d.ts +3 -4
- package/dist/web-video-engine/create-scrub-cursor.d.ts +1 -0
- package/dist/web-video-engine/decode-resolution-fynpRIAG.js +109 -0
- package/dist/web-video-engine/decode-scheduler.d.ts +1 -0
- package/dist/web-video-engine/engine-core.d.ts +4 -2
- package/dist/web-video-engine/engine.js +8 -6
- package/dist/web-video-engine/engine.worker.js +164 -66
- package/dist/web-video-engine/frame-cache.d.ts +9 -0
- package/dist/web-video-engine/frame-index.d.ts +1 -0
- package/dist/web-video-engine/frame-index.js +97 -0
- package/dist/web-video-engine/{frame-timeline-DreEwsRY.js → frame-timeline-CYgzzmpA.js} +1 -109
- package/dist/web-video-engine/frame-timeline-reader.d.ts +21 -0
- package/dist/web-video-engine/video-engine.d.ts +9 -4
- package/node_modules/supervision-js-core/THIRD_PARTY_NOTICES.md +132 -0
- package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +8 -3
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +1172 -70
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts +2 -2
- package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +2 -2
- package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/viewport-controller.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +50 -3
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/depth-map.d.ts +203 -0
- package/node_modules/supervision-js-core/dist/types/depth-map.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/detections.d.ts +2 -1
- package/node_modules/supervision-js-core/dist/types/detections.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/editing.d.ts +9 -0
- package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts +4 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/mask-style.d.ts +3 -0
- package/node_modules/supervision-js-core/dist/types/mask-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +3 -0
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/viewport.d.ts +3 -0
- package/node_modules/supervision-js-core/dist/types/viewport.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts +91 -0
- package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts +30 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts +16 -0
- package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts +23 -0
- package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +21 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +3 -7
- package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
- package/node_modules/supervision-js-core/package.json +2 -1
- package/package.json +6 -1
- package/dist/video-engine-media-source-DR-x1UfK.js.map +0 -1
|
@@ -1,6 +1,74 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
/** Trace closed pixel boundaries, retaining holes and disconnected components. */
|
|
5
|
+
function traceMaskOutlinePaths(raster, width, height, ids) {
|
|
6
|
+
const edgesById = new Map();
|
|
7
|
+
const stride = width + 1;
|
|
8
|
+
const steps = [1, stride, -1, -stride];
|
|
9
|
+
const add = (id, vertex, direction) => {
|
|
10
|
+
let edges = edgesById.get(id);
|
|
11
|
+
if (!edges)
|
|
12
|
+
edgesById.set(id, (edges = new Map()));
|
|
13
|
+
const outgoing = edges.get(vertex);
|
|
14
|
+
if (outgoing)
|
|
15
|
+
outgoing.push(direction);
|
|
16
|
+
else
|
|
17
|
+
edges.set(vertex, [direction]);
|
|
18
|
+
};
|
|
19
|
+
for (let y = 0; y < height; y += 1) {
|
|
20
|
+
for (let x = 0; x < width; x += 1) {
|
|
21
|
+
const offset = y * width + x;
|
|
22
|
+
const id = raster[offset];
|
|
23
|
+
if (!ids.has(id))
|
|
24
|
+
continue;
|
|
25
|
+
const vertex = y * stride + x;
|
|
26
|
+
if (y === 0 || raster[offset - width] !== id)
|
|
27
|
+
add(id, vertex, 0);
|
|
28
|
+
if (x === width - 1 || raster[offset + 1] !== id)
|
|
29
|
+
add(id, vertex + 1, 1);
|
|
30
|
+
if (y === height - 1 || raster[offset + width] !== id)
|
|
31
|
+
add(id, vertex + stride + 1, 2);
|
|
32
|
+
if (x === 0 || raster[offset - 1] !== id)
|
|
33
|
+
add(id, vertex + stride, 3);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const result = new Map();
|
|
37
|
+
for (const [id, edges] of edgesById) {
|
|
38
|
+
const paths = [];
|
|
39
|
+
while (edges.size > 0) {
|
|
40
|
+
const start = edges.keys().next().value;
|
|
41
|
+
let vertex = start;
|
|
42
|
+
let previousDirection = -1;
|
|
43
|
+
let firstDirection = -1;
|
|
44
|
+
const points = [];
|
|
45
|
+
do {
|
|
46
|
+
const outgoing = edges.get(vertex);
|
|
47
|
+
// Turn right at diagonal contacts so separate islands stay separate.
|
|
48
|
+
const direction = previousDirection < 0
|
|
49
|
+
? outgoing[0]
|
|
50
|
+
: [1, 0, 3, 2]
|
|
51
|
+
.map((turn) => (previousDirection + turn) % 4)
|
|
52
|
+
.find((candidate) => outgoing.includes(candidate));
|
|
53
|
+
if (firstDirection < 0)
|
|
54
|
+
firstDirection = direction;
|
|
55
|
+
if (direction !== previousDirection)
|
|
56
|
+
points.push(vertex % stride, Math.floor(vertex / stride));
|
|
57
|
+
outgoing.splice(outgoing.indexOf(direction), 1);
|
|
58
|
+
if (outgoing.length === 0)
|
|
59
|
+
edges.delete(vertex);
|
|
60
|
+
vertex += steps[direction];
|
|
61
|
+
previousDirection = direction;
|
|
62
|
+
} while (vertex !== start);
|
|
63
|
+
if (previousDirection === firstDirection)
|
|
64
|
+
points.splice(0, 2);
|
|
65
|
+
paths.push(new Float32Array(points));
|
|
66
|
+
}
|
|
67
|
+
result.set(id, paths);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
4
72
|
/** COCO-compatible keypoint visibility values. */
|
|
5
73
|
var KeypointVisibility;
|
|
6
74
|
(function (KeypointVisibility) {
|
|
@@ -376,6 +444,68 @@
|
|
|
376
444
|
KeypointMarkerShape["Circle"] = "circle";
|
|
377
445
|
KeypointMarkerShape["Cross"] = "cross";
|
|
378
446
|
})(KeypointMarkerShape || (KeypointMarkerShape = {}));
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* What the stored values of a depth map measure.
|
|
450
|
+
*
|
|
451
|
+
* Every kind stores 0 where there is no depth.
|
|
452
|
+
*/
|
|
453
|
+
const DepthMapKind = {
|
|
454
|
+
/** Stereo disparity in pixels of the map. Larger is nearer. */
|
|
455
|
+
DisparityPx: "disparity_px",
|
|
456
|
+
/** Metric depth along the optical axis in metres. Smaller is nearer. */
|
|
457
|
+
DepthM: "depth_m",
|
|
458
|
+
/**
|
|
459
|
+
* Unitless inverse depth up to an unknown scale and shift, as monocular
|
|
460
|
+
* models produce it. Larger is nearer; it has no metric depth.
|
|
461
|
+
*/
|
|
462
|
+
RelativeInverse: "relative_inverse",
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* The quantity a depth renderer colours.
|
|
466
|
+
*
|
|
467
|
+
* Disparity is inverse depth, so it spends colour on near detail the way
|
|
468
|
+
* stereo measures it. Either way the nearest end of the range is the warm or
|
|
469
|
+
* bright end of the colour table.
|
|
470
|
+
*/
|
|
471
|
+
const DepthQuantity = {
|
|
472
|
+
Disparity: "disparity",
|
|
473
|
+
Depth: "depth",
|
|
474
|
+
};
|
|
475
|
+
const DepthColormap = {
|
|
476
|
+
Turbo: "turbo",
|
|
477
|
+
Viridis: "viridis",
|
|
478
|
+
Cividis: "cividis",
|
|
479
|
+
Inferno: "inferno",
|
|
480
|
+
Magma: "magma",
|
|
481
|
+
Grayscale: "grayscale",
|
|
482
|
+
};
|
|
483
|
+
/** How a depth map is sampled where one map pixel does not cover one media pixel. */
|
|
484
|
+
const DepthSampling = {
|
|
485
|
+
/** Nearest when the map is at least media size, edge-aware otherwise. */
|
|
486
|
+
Auto: "auto",
|
|
487
|
+
Nearest: "nearest",
|
|
488
|
+
/**
|
|
489
|
+
* Bilinear over valid neighbours only, falling back to the nearest one
|
|
490
|
+
* across a depth edge, so objects never bleed into the background.
|
|
491
|
+
*/
|
|
492
|
+
EdgeAware: "edge-aware",
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* The luma range a depth preview video's codes are written in.
|
|
496
|
+
*
|
|
497
|
+
* TV range is what browsers' hardware decoders return as written; a decoder
|
|
498
|
+
* may squeeze full range into TV range or move codes converting it to RGB.
|
|
499
|
+
*/
|
|
500
|
+
const DepthPreviewLevels = {
|
|
501
|
+
/** Every code from 0 to 255; the top code is 255. */
|
|
502
|
+
Full: "full",
|
|
503
|
+
/** Limited range: black is 16 and the top code is 235. */
|
|
504
|
+
Tv: "tv",
|
|
505
|
+
};
|
|
506
|
+
new Set(Object.values(DepthColormap));
|
|
507
|
+
new Set(Object.values(DepthQuantity));
|
|
508
|
+
new Set(Object.values(DepthSampling));
|
|
379
509
|
function rasterizePolygonToMask(points, dimensions) {
|
|
380
510
|
const data = createEmptyMask(dimensions);
|
|
381
511
|
if (points.length < 3) {
|
|
@@ -429,8 +559,10 @@
|
|
|
429
559
|
if (instructions.length === 0) {
|
|
430
560
|
return undefined;
|
|
431
561
|
}
|
|
432
|
-
|
|
433
|
-
|
|
562
|
+
/* Pixel-grid dimensions use integer parts throughout allocation and RLE
|
|
563
|
+
traversal. */
|
|
564
|
+
const maskWidth = Math.floor(Math.max(...instructions.map(({ mask }) => mask.width)));
|
|
565
|
+
const maskHeight = Math.floor(Math.max(...instructions.map(({ mask }) => mask.height)));
|
|
434
566
|
const width = resolveRasterWidth(maskWidth, options.maxWidth);
|
|
435
567
|
const height = width === maskWidth
|
|
436
568
|
? maskHeight
|
|
@@ -482,8 +614,8 @@
|
|
|
482
614
|
*/
|
|
483
615
|
function writeMaskRuns(data, frameWidth, mask, detectionMaskId) {
|
|
484
616
|
const counts = decodeCompressedRleCounts(mask.counts);
|
|
485
|
-
const maskWidth = mask.width;
|
|
486
|
-
const maskHeight = mask.height;
|
|
617
|
+
const maskWidth = Math.floor(mask.width);
|
|
618
|
+
const maskHeight = Math.floor(mask.height);
|
|
487
619
|
let maskOffset = 0;
|
|
488
620
|
for (let index = 0; index < counts.length; index += 1) {
|
|
489
621
|
const runLength = counts[index] ?? 0;
|
|
@@ -517,9 +649,10 @@
|
|
|
517
649
|
*/
|
|
518
650
|
function writeScaledMaskRuns(data, axes, mask, detectionMaskId) {
|
|
519
651
|
const counts = decodeCompressedRleCounts(mask.counts);
|
|
520
|
-
const maskWidth = mask.width;
|
|
521
|
-
const maskHeight = mask.height;
|
|
652
|
+
const maskWidth = Math.floor(mask.width);
|
|
653
|
+
const maskHeight = Math.floor(mask.height);
|
|
522
654
|
let maskOffset = 0;
|
|
655
|
+
const stride = axes.stride;
|
|
523
656
|
for (let index = 0; index < counts.length; index += 1) {
|
|
524
657
|
const runLength = counts[index] ?? 0;
|
|
525
658
|
if (index % 2 === 0 || runLength <= 0) {
|
|
@@ -528,17 +661,30 @@
|
|
|
528
661
|
}
|
|
529
662
|
let x = Math.floor(maskOffset / maskHeight);
|
|
530
663
|
let y = maskOffset - x * maskHeight;
|
|
531
|
-
let
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
664
|
+
let remaining = runLength;
|
|
665
|
+
/* A run walks down one column and wraps into the next, so it splits into
|
|
666
|
+
column-sized segments. Within a segment the destination offset only ever
|
|
667
|
+
steps by the row pitch or stays put: createMaskAxisMap builds
|
|
668
|
+
`rows[i] = min(height - 1, floor(i * scale)) * width` with scale <= 1, so
|
|
669
|
+
consecutive entries differ by exactly 0 or exactly `width`. Walking the
|
|
670
|
+
destination directly therefore touches the same bytes the per-pixel loop
|
|
671
|
+
touched, and drops the bounds test, the wrap test and one typed-array
|
|
672
|
+
load from every source pixel. Where the scale collapses several source
|
|
673
|
+
rows onto one destination row, this writes that byte once instead of
|
|
674
|
+
once per source row. */
|
|
675
|
+
while (remaining > 0 && x < maskWidth) {
|
|
676
|
+
const span = Math.min(remaining, maskHeight - y);
|
|
677
|
+
const column = axes.columns[x];
|
|
678
|
+
const first = axes.rows[y];
|
|
679
|
+
const last = axes.rows[y + span - 1];
|
|
680
|
+
for (let offset = first; offset <= last; offset += stride) {
|
|
681
|
+
data[offset + column] = detectionMaskId;
|
|
535
682
|
}
|
|
536
|
-
|
|
537
|
-
y +=
|
|
683
|
+
remaining -= span;
|
|
684
|
+
y += span;
|
|
538
685
|
if (y === maskHeight) {
|
|
539
686
|
y = 0;
|
|
540
687
|
x += 1;
|
|
541
|
-
column = axes.columns[x];
|
|
542
688
|
}
|
|
543
689
|
}
|
|
544
690
|
maskOffset += runLength;
|
|
@@ -550,22 +696,19 @@
|
|
|
550
696
|
: maskWidth;
|
|
551
697
|
}
|
|
552
698
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
* own width, which the shader draws as an inner boundary at any scale. The
|
|
557
|
-
* ceiling is the widest neighbourhood the shaders scan, so a wider stroke would
|
|
558
|
-
* be drawn at the ceiling anyway and every layer drawing the same annotation
|
|
559
|
-
* has to arrive at the same width.
|
|
699
|
+
* Converts a source-grid stroke width into raster texels. Fractional widths
|
|
700
|
+
* let coverage shaders draw a border within a texel. The ceiling bounds the
|
|
701
|
+
* neighbourhood the shaders scan.
|
|
560
702
|
*/
|
|
561
703
|
function resolveIdMaskStrokeTexels(strokeWidth, maskWidth, rasterWidth) {
|
|
562
704
|
const scale = maskWidth > 0 ? rasterWidth / maskWidth : 1;
|
|
563
|
-
return Math.min(
|
|
705
|
+
return Math.min(strokeWidth * scale, MAX_ID_MASK_STROKE_WIDTH);
|
|
564
706
|
}
|
|
565
707
|
function createScaledMaskAxes(frame) {
|
|
566
708
|
return {
|
|
567
709
|
columns: createMaskAxisMap(frame.maskWidth, frame.width, 1),
|
|
568
710
|
rows: createMaskAxisMap(frame.maskHeight, frame.height, frame.width),
|
|
711
|
+
stride: frame.width,
|
|
569
712
|
};
|
|
570
713
|
}
|
|
571
714
|
/** Rows hold their destination offset, so writing a run is two lookups. */
|
|
@@ -595,6 +738,8 @@
|
|
|
595
738
|
palette[offset + 2] = (color & 0xff) / 255;
|
|
596
739
|
palette[offset + 3] = Math.max(0, Math.min(alpha, 1));
|
|
597
740
|
}
|
|
741
|
+
new Set(Object.values(DepthPreviewLevels));
|
|
742
|
+
new Set(Object.values(DepthMapKind));
|
|
598
743
|
|
|
599
744
|
var MediaRendererFit;
|
|
600
745
|
(function (MediaRendererFit) {
|
|
@@ -818,7 +963,9 @@
|
|
|
818
963
|
}
|
|
819
964
|
function createIdMaskRasterFrame(instructions, maxRasterWidth) {
|
|
820
965
|
try {
|
|
821
|
-
return createIdMaskFrame(materializeMaskInstructions(instructions)
|
|
966
|
+
return createIdMaskFrame(materializeMaskInstructions(instructions).map((instruction) => instruction.stroke?.widthUnit === "screen"
|
|
967
|
+
? { ...instruction, stroke: undefined }
|
|
968
|
+
: instruction), {
|
|
822
969
|
maxWidth: maxRasterWidth,
|
|
823
970
|
});
|
|
824
971
|
}
|
|
@@ -853,10 +1000,47 @@
|
|
|
853
1000
|
function compositeInstruction(rgba, canvasWidth, instruction) {
|
|
854
1001
|
const fill = resolveRgbaColor(instruction.color, instruction.alpha);
|
|
855
1002
|
const bounds = compositeMaskFill(rgba, canvasWidth, instruction.mask, fill);
|
|
856
|
-
if (instruction.stroke &&
|
|
1003
|
+
if (instruction.stroke &&
|
|
1004
|
+
instruction.stroke.widthUnit !== "screen" &&
|
|
1005
|
+
bounds) {
|
|
857
1006
|
compositeMaskStroke(rgba, canvasWidth, decodeCompressedRleMask(instruction.mask), bounds, instruction.stroke);
|
|
858
1007
|
}
|
|
859
1008
|
}
|
|
1009
|
+
function createMaskScreenStrokes(instructions, width, height, preparedRaster) {
|
|
1010
|
+
const outlined = instructions.filter(({ visible, stroke }) => visible !== false &&
|
|
1011
|
+
stroke?.widthUnit === "screen" &&
|
|
1012
|
+
stroke.width > 0 &&
|
|
1013
|
+
stroke.alpha > 0);
|
|
1014
|
+
if (outlined.length === 0)
|
|
1015
|
+
return undefined;
|
|
1016
|
+
const masks = materializeMaskInstructions(instructions);
|
|
1017
|
+
const raster = preparedRaster ?? new Uint32Array(width * height);
|
|
1018
|
+
if (!preparedRaster) {
|
|
1019
|
+
for (const { detectionIndex, mask } of masks) {
|
|
1020
|
+
const counts = decodeCompressedRleCounts(mask.counts);
|
|
1021
|
+
let offset = 0;
|
|
1022
|
+
for (let index = 0; index < counts.length; index += 1) {
|
|
1023
|
+
const length = counts[index];
|
|
1024
|
+
if (index % 2 === 1) {
|
|
1025
|
+
for (let pixel = offset; pixel < offset + length; pixel += 1) {
|
|
1026
|
+
const x = Math.floor(pixel / mask.height);
|
|
1027
|
+
const y = pixel % mask.height;
|
|
1028
|
+
raster[y * width + x] = detectionIndex + 1;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
offset += length;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const paths = traceMaskOutlinePaths(raster, width, height, new Set(outlined.map(({ detectionIndex }) => detectionIndex + 1)));
|
|
1036
|
+
return outlined.map(({ detectionIndex, stroke }) => ({
|
|
1037
|
+
detectionIndex,
|
|
1038
|
+
alpha: stroke.alpha,
|
|
1039
|
+
color: stroke.color,
|
|
1040
|
+
width: stroke.width,
|
|
1041
|
+
paths: paths.get(detectionIndex + 1) ?? [],
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
860
1044
|
function materializeMaskInstructions(instructions) {
|
|
861
1045
|
return instructions
|
|
862
1046
|
.filter((instruction) => instruction.visible !== false)
|
|
@@ -1106,6 +1290,520 @@
|
|
|
1106
1290
|
MaskPreparationWorkerMessageType["Prepare"] = "prepare";
|
|
1107
1291
|
})(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));
|
|
1108
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Messages between the depth source and the render-preparation worker, which
|
|
1295
|
+
* decodes depth and confidence PNGs, and copies decoded preview frames' luma,
|
|
1296
|
+
* off the main thread. Internal: the worker script is a deployment asset, and
|
|
1297
|
+
* its protocol is not public.
|
|
1298
|
+
*/
|
|
1299
|
+
var DepthPreparationWorkerMessageType;
|
|
1300
|
+
(function (DepthPreparationWorkerMessageType) {
|
|
1301
|
+
DepthPreparationWorkerMessageType["Decode"] = "depth-decode";
|
|
1302
|
+
DepthPreparationWorkerMessageType["Complete"] = "depth-decode-complete";
|
|
1303
|
+
DepthPreparationWorkerMessageType["Error"] = "depth-decode-error";
|
|
1304
|
+
DepthPreparationWorkerMessageType["PreviewLuma"] = "depth-preview-luma";
|
|
1305
|
+
DepthPreparationWorkerMessageType["PreviewLumaComplete"] = "depth-preview-luma-complete";
|
|
1306
|
+
})(DepthPreparationWorkerMessageType || (DepthPreparationWorkerMessageType = {}));
|
|
1307
|
+
|
|
1308
|
+
/*
|
|
1309
|
+
* Grayscale PNG decoding for depth maps and their confidence planes.
|
|
1310
|
+
*
|
|
1311
|
+
* Every image path a browser offers (<img>, createImageBitmap, ImageDecoder,
|
|
1312
|
+
* canvas) hands back 8 bits per channel, so a 16-bit depth PNG is decoded
|
|
1313
|
+
* here: the browser's own zlib inflates the IDAT stream, and the row filters
|
|
1314
|
+
* are undone in JavaScript.
|
|
1315
|
+
*
|
|
1316
|
+
* This module imports nothing, so the render-preparation worker, the main
|
|
1317
|
+
* thread and the depth benchmark all run the same code.
|
|
1318
|
+
*/
|
|
1319
|
+
const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1320
|
+
const COLOR_TYPE_GRAY = 0;
|
|
1321
|
+
/** 16384 x 16384, beyond any GPU texture; a header claiming more is refused. */
|
|
1322
|
+
const MAX_PNG_PIXELS = 16_384 * 16_384;
|
|
1323
|
+
async function decodePng16(bytes, options = {}) {
|
|
1324
|
+
const header = readPngHeader(bytes, 16);
|
|
1325
|
+
const rowBytes = header.width * 2 + 1;
|
|
1326
|
+
const raw = await (options.inflate ?? inflateWithDecompressionStream)(header.idat, rowBytes * header.height);
|
|
1327
|
+
const values = new Uint16Array(header.width * header.height);
|
|
1328
|
+
checkInflatedLength(raw, rowBytes * header.height);
|
|
1329
|
+
unfilterGray16(raw, header.width, header.height, values);
|
|
1330
|
+
return {
|
|
1331
|
+
height: header.height,
|
|
1332
|
+
paddedUpload: options.padRowsForWebGl && header.width % 2 === 1
|
|
1333
|
+
? padRows(values, header.width, header.height)
|
|
1334
|
+
: undefined,
|
|
1335
|
+
values,
|
|
1336
|
+
width: header.width,
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
async function decodePng8Gray(bytes, options = {}) {
|
|
1340
|
+
const header = readPngHeader(bytes, 8);
|
|
1341
|
+
const rowBytes = header.width + 1;
|
|
1342
|
+
const raw = await (options.inflate ?? inflateWithDecompressionStream)(header.idat, rowBytes * header.height);
|
|
1343
|
+
const values = new Uint8Array(header.width * header.height);
|
|
1344
|
+
checkInflatedLength(raw, rowBytes * header.height);
|
|
1345
|
+
unfilterGray8(raw, header.width, header.height, values);
|
|
1346
|
+
return { height: header.height, values, width: header.width };
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Inflates with the platform's `DecompressionStream("deflate")` (zlib
|
|
1350
|
+
* framing, which is what PNG uses) straight into one buffer of the expected
|
|
1351
|
+
* size, so no second copy joins the output chunks.
|
|
1352
|
+
*/
|
|
1353
|
+
async function inflateWithDecompressionStream(chunks, byteLength) {
|
|
1354
|
+
const stream = new DecompressionStream("deflate");
|
|
1355
|
+
const writer = stream.writable.getWriter();
|
|
1356
|
+
const reader = stream.readable.getReader();
|
|
1357
|
+
// Writers split IDAT into many small chunks (libpng's default is 8 KiB).
|
|
1358
|
+
// One write of the joined stream costs a copy of the compressed bytes and
|
|
1359
|
+
// saves a stream round trip per chunk.
|
|
1360
|
+
const zlib = chunks.length === 1 ? chunks[0] : joinChunks(chunks);
|
|
1361
|
+
const writing = (async () => {
|
|
1362
|
+
await writer.write(zlib);
|
|
1363
|
+
await writer.close();
|
|
1364
|
+
})();
|
|
1365
|
+
const output = new Uint8Array(byteLength);
|
|
1366
|
+
let offset = 0;
|
|
1367
|
+
// A corrupt stream rejects both sides; the reader's error is the one thrown.
|
|
1368
|
+
writing.catch(() => undefined);
|
|
1369
|
+
try {
|
|
1370
|
+
for (;;) {
|
|
1371
|
+
const { done, value } = await reader.read();
|
|
1372
|
+
if (done)
|
|
1373
|
+
break;
|
|
1374
|
+
if (offset + value.byteLength > byteLength) {
|
|
1375
|
+
throw new RangeError("PNG image data is longer than its header says.");
|
|
1376
|
+
}
|
|
1377
|
+
output.set(value, offset);
|
|
1378
|
+
offset += value.byteLength;
|
|
1379
|
+
}
|
|
1380
|
+
await writing;
|
|
1381
|
+
}
|
|
1382
|
+
catch (error) {
|
|
1383
|
+
await reader.cancel().catch(() => undefined);
|
|
1384
|
+
throw error instanceof RangeError
|
|
1385
|
+
? error
|
|
1386
|
+
: new RangeError(`PNG image data does not inflate: ${error instanceof Error ? error.message : String(error)}`);
|
|
1387
|
+
}
|
|
1388
|
+
return offset === byteLength ? output : output.subarray(0, offset);
|
|
1389
|
+
}
|
|
1390
|
+
function joinChunks(chunks) {
|
|
1391
|
+
const joined = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
1392
|
+
let offset = 0;
|
|
1393
|
+
for (const chunk of chunks) {
|
|
1394
|
+
joined.set(chunk, offset);
|
|
1395
|
+
offset += chunk.byteLength;
|
|
1396
|
+
}
|
|
1397
|
+
return joined;
|
|
1398
|
+
}
|
|
1399
|
+
function readPngHeader(input, bitDepth) {
|
|
1400
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
1401
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1402
|
+
if (bytes.byteLength < 8 ||
|
|
1403
|
+
PNG_SIGNATURE.some((value, index) => bytes[index] !== value)) {
|
|
1404
|
+
throw new RangeError("Not a PNG file.");
|
|
1405
|
+
}
|
|
1406
|
+
let offset = 8;
|
|
1407
|
+
let width = 0;
|
|
1408
|
+
let height = 0;
|
|
1409
|
+
let sawHeader = false;
|
|
1410
|
+
let sawEnd = false;
|
|
1411
|
+
const idat = [];
|
|
1412
|
+
while (offset + 12 <= bytes.byteLength) {
|
|
1413
|
+
const length = view.getUint32(offset);
|
|
1414
|
+
const type = view.getUint32(offset + 4);
|
|
1415
|
+
const dataStart = offset + 8;
|
|
1416
|
+
const dataEnd = dataStart + length;
|
|
1417
|
+
if (dataEnd + 4 > bytes.byteLength) {
|
|
1418
|
+
throw new RangeError("PNG file is truncated.");
|
|
1419
|
+
}
|
|
1420
|
+
if (type === 0x49484452) {
|
|
1421
|
+
// IHDR
|
|
1422
|
+
if (length !== 13)
|
|
1423
|
+
throw new RangeError("PNG header is malformed.");
|
|
1424
|
+
width = view.getUint32(dataStart);
|
|
1425
|
+
height = view.getUint32(dataStart + 4);
|
|
1426
|
+
const depth = bytes[dataStart + 8];
|
|
1427
|
+
const colorType = bytes[dataStart + 9];
|
|
1428
|
+
if (bytes[dataStart + 10] !== 0 || bytes[dataStart + 11] !== 0) {
|
|
1429
|
+
throw new RangeError("PNG uses an unknown compression or filter.");
|
|
1430
|
+
}
|
|
1431
|
+
if (bytes[dataStart + 12] !== 0) {
|
|
1432
|
+
throw new RangeError("Interlaced PNGs are not supported.");
|
|
1433
|
+
}
|
|
1434
|
+
if (colorType !== COLOR_TYPE_GRAY || depth !== bitDepth) {
|
|
1435
|
+
throw new RangeError(`Expected a ${bitDepth}-bit grayscale PNG, got bit depth ${depth} with colour type ${colorType}.`);
|
|
1436
|
+
}
|
|
1437
|
+
sawHeader = true;
|
|
1438
|
+
}
|
|
1439
|
+
else if (type === 0x49444154) {
|
|
1440
|
+
// IDAT
|
|
1441
|
+
idat.push(bytes.subarray(dataStart, dataEnd));
|
|
1442
|
+
}
|
|
1443
|
+
else if (type === 0x49454e44) {
|
|
1444
|
+
// IEND
|
|
1445
|
+
sawEnd = true;
|
|
1446
|
+
break;
|
|
1447
|
+
}
|
|
1448
|
+
offset = dataEnd + 4;
|
|
1449
|
+
}
|
|
1450
|
+
if (!sawHeader)
|
|
1451
|
+
throw new RangeError("PNG has no header.");
|
|
1452
|
+
if (!sawEnd)
|
|
1453
|
+
throw new RangeError("PNG file is truncated.");
|
|
1454
|
+
if (width === 0 || height === 0 || width * height > MAX_PNG_PIXELS) {
|
|
1455
|
+
throw new RangeError(`PNG size ${width}x${height} is not supported.`);
|
|
1456
|
+
}
|
|
1457
|
+
if (idat.length === 0)
|
|
1458
|
+
throw new RangeError("PNG has no image data.");
|
|
1459
|
+
return { height, idat, width };
|
|
1460
|
+
}
|
|
1461
|
+
function checkInflatedLength(raw, expected) {
|
|
1462
|
+
if (raw.byteLength !== expected) {
|
|
1463
|
+
throw new RangeError("PNG image data is shorter than its header says.");
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
/*
|
|
1467
|
+
* Undoes the row filters of a 16-bit grayscale image and writes host-order
|
|
1468
|
+
* samples, in one pass. Each filter runs on bytes, and the two bytes of a
|
|
1469
|
+
* sample never carry into each other, so the loops keep a sample's high and
|
|
1470
|
+
* low bytes apart and join them only for the store. The previous row is read
|
|
1471
|
+
* back from the output, which already holds it unfiltered; the first row reads
|
|
1472
|
+
* a row of zeros, which is what PNG defines above the image.
|
|
1473
|
+
*
|
|
1474
|
+
* Each filter is its own small function with one loop, called once per row,
|
|
1475
|
+
* so engines compile each loop on its own: SpiderMonkey slowed a Paeth loop
|
|
1476
|
+
* that shared a function with the other filters.
|
|
1477
|
+
*/
|
|
1478
|
+
function unfilterGray16(raw, width, height, out) {
|
|
1479
|
+
const rowBytes = width * 2 + 1;
|
|
1480
|
+
const zeroRow = new Uint16Array(width);
|
|
1481
|
+
for (let y = 0; y < height; y += 1) {
|
|
1482
|
+
const filter = raw[y * rowBytes];
|
|
1483
|
+
const unfilterRow = ROW_FILTERS_16[filter];
|
|
1484
|
+
if (!unfilterRow) {
|
|
1485
|
+
throw new RangeError(`PNG row ${y} has unknown filter ${filter}.`);
|
|
1486
|
+
}
|
|
1487
|
+
const o = y * width;
|
|
1488
|
+
unfilterRow(raw, y * rowBytes + 1, out, o, o + width, y === 0 ? zeroRow : out, y === 0 ? 0 : o - width);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function noneRow16(raw, s, out, o, end) {
|
|
1492
|
+
for (let i = o; i < end; i += 1, s += 2) {
|
|
1493
|
+
out[i] = (raw[s] << 8) | raw[s + 1];
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
function subRow16(raw, s, out, o, end) {
|
|
1497
|
+
let hi = 0;
|
|
1498
|
+
let lo = 0;
|
|
1499
|
+
for (let i = o; i < end; i += 1, s += 2) {
|
|
1500
|
+
hi = (raw[s] + hi) & 0xff;
|
|
1501
|
+
lo = (raw[s + 1] + lo) & 0xff;
|
|
1502
|
+
out[i] = (hi << 8) | lo;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
function upRow16(raw, s, out, o, end, prev, p) {
|
|
1506
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1507
|
+
const up = prev[p];
|
|
1508
|
+
out[i] = (((raw[s] + (up >> 8)) & 0xff) << 8) | ((raw[s + 1] + up) & 0xff);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
function averageRow16(raw, s, out, o, end, prev, p) {
|
|
1512
|
+
let hi = 0;
|
|
1513
|
+
let lo = 0;
|
|
1514
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1515
|
+
const up = prev[p];
|
|
1516
|
+
hi = (raw[s] + ((hi + (up >> 8)) >> 1)) & 0xff;
|
|
1517
|
+
lo = (raw[s + 1] + ((lo + (up & 0xff)) >> 1)) & 0xff;
|
|
1518
|
+
out[i] = (hi << 8) | lo;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
/** Paeth: per byte, whichever of left, up and up-left is closest to left + up - up-left. */
|
|
1522
|
+
function paethRow16(raw, s, out, o, end, prev, p) {
|
|
1523
|
+
let hi = 0;
|
|
1524
|
+
let lo = 0;
|
|
1525
|
+
let upLeftHi = 0;
|
|
1526
|
+
let upLeftLo = 0;
|
|
1527
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1528
|
+
const up = prev[p];
|
|
1529
|
+
const upHi = up >> 8;
|
|
1530
|
+
const upLo = up & 0xff;
|
|
1531
|
+
let pa = Math.abs(upHi - upLeftHi);
|
|
1532
|
+
let pb = Math.abs(hi - upLeftHi);
|
|
1533
|
+
let pc = Math.abs(hi + upHi - upLeftHi - upLeftHi);
|
|
1534
|
+
hi =
|
|
1535
|
+
(raw[s] + (pa <= pb && pa <= pc ? hi : pb <= pc ? upHi : upLeftHi)) &
|
|
1536
|
+
0xff;
|
|
1537
|
+
pa = Math.abs(upLo - upLeftLo);
|
|
1538
|
+
pb = Math.abs(lo - upLeftLo);
|
|
1539
|
+
pc = Math.abs(lo + upLo - upLeftLo - upLeftLo);
|
|
1540
|
+
lo =
|
|
1541
|
+
(raw[s + 1] + (pa <= pb && pa <= pc ? lo : pb <= pc ? upLo : upLeftLo)) &
|
|
1542
|
+
0xff;
|
|
1543
|
+
out[i] = (hi << 8) | lo;
|
|
1544
|
+
upLeftHi = upHi;
|
|
1545
|
+
upLeftLo = upLo;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
/** Indexed by the PNG filter type byte: None, Sub, Up, Average, Paeth. */
|
|
1549
|
+
const ROW_FILTERS_16 = [
|
|
1550
|
+
noneRow16,
|
|
1551
|
+
subRow16,
|
|
1552
|
+
upRow16,
|
|
1553
|
+
averageRow16,
|
|
1554
|
+
paethRow16,
|
|
1555
|
+
];
|
|
1556
|
+
function unfilterGray8(raw, width, height, out) {
|
|
1557
|
+
const rowBytes = width + 1;
|
|
1558
|
+
const zeroRow = new Uint8Array(width);
|
|
1559
|
+
for (let y = 0; y < height; y += 1) {
|
|
1560
|
+
const filter = raw[y * rowBytes];
|
|
1561
|
+
const unfilterRow = ROW_FILTERS_8[filter];
|
|
1562
|
+
if (!unfilterRow) {
|
|
1563
|
+
throw new RangeError(`PNG row ${y} has unknown filter ${filter}.`);
|
|
1564
|
+
}
|
|
1565
|
+
const o = y * width;
|
|
1566
|
+
unfilterRow(raw, y * rowBytes + 1, out, o, o + width, y === 0 ? zeroRow : out, y === 0 ? 0 : o - width);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
function noneRow8(raw, s, out, o, end) {
|
|
1570
|
+
out.set(raw.subarray(s, s + end - o), o);
|
|
1571
|
+
}
|
|
1572
|
+
function subRow8(raw, s, out, o, end) {
|
|
1573
|
+
let left = 0;
|
|
1574
|
+
for (let i = o; i < end; i += 1, s += 1) {
|
|
1575
|
+
left = (raw[s] + left) & 0xff;
|
|
1576
|
+
out[i] = left;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function upRow8(raw, s, out, o, end, prev, p) {
|
|
1580
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1581
|
+
out[i] = (raw[s] + prev[p]) & 0xff;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function averageRow8(raw, s, out, o, end, prev, p) {
|
|
1585
|
+
let left = 0;
|
|
1586
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1587
|
+
left = (raw[s] + ((left + prev[p]) >> 1)) & 0xff;
|
|
1588
|
+
out[i] = left;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
function paethRow8(raw, s, out, o, end, prev, p) {
|
|
1592
|
+
let left = 0;
|
|
1593
|
+
let upLeft = 0;
|
|
1594
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1595
|
+
const up = prev[p];
|
|
1596
|
+
const pa = Math.abs(up - upLeft);
|
|
1597
|
+
const pb = Math.abs(left - upLeft);
|
|
1598
|
+
const pc = Math.abs(left + up - upLeft - upLeft);
|
|
1599
|
+
left =
|
|
1600
|
+
(raw[s] + (pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft)) & 0xff;
|
|
1601
|
+
out[i] = left;
|
|
1602
|
+
upLeft = up;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const ROW_FILTERS_8 = [
|
|
1606
|
+
noneRow8,
|
|
1607
|
+
subRow8,
|
|
1608
|
+
upRow8,
|
|
1609
|
+
averageRow8,
|
|
1610
|
+
paethRow8,
|
|
1611
|
+
];
|
|
1612
|
+
/**
|
|
1613
|
+
* WebGL unpacks rows on four-byte boundaries, so an odd width of two-byte
|
|
1614
|
+
* texels needs one padding texel per row.
|
|
1615
|
+
*/
|
|
1616
|
+
function padRows(values, width, height) {
|
|
1617
|
+
const textureWidth = width + 1;
|
|
1618
|
+
const padded = new Uint16Array(textureWidth * height);
|
|
1619
|
+
for (let y = 0; y < height; y += 1) {
|
|
1620
|
+
padded.set(values.subarray(y * width, (y + 1) * width), y * textureWidth);
|
|
1621
|
+
}
|
|
1622
|
+
return { bytes: new Uint8Array(padded.buffer), textureWidth };
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/**
|
|
1626
|
+
* Answers one decode request, in the worker or on the main thread: the reply
|
|
1627
|
+
* and the buffers it can transfer. A file that does not decode is an error
|
|
1628
|
+
* reply, never a thrown exception, so the caller can tell a bad file from a
|
|
1629
|
+
* broken worker.
|
|
1630
|
+
*/
|
|
1631
|
+
async function decodeDepthPreparationRequest(request) {
|
|
1632
|
+
const requestId = request.requestId ?? 0;
|
|
1633
|
+
try {
|
|
1634
|
+
if (request.bitDepth === 8) {
|
|
1635
|
+
const decoded = await decodePng8Gray(request.bytes);
|
|
1636
|
+
return {
|
|
1637
|
+
response: {
|
|
1638
|
+
bitDepth: 8,
|
|
1639
|
+
height: decoded.height,
|
|
1640
|
+
requestId,
|
|
1641
|
+
type: DepthPreparationWorkerMessageType.Complete,
|
|
1642
|
+
values: decoded.values.buffer,
|
|
1643
|
+
width: decoded.width,
|
|
1644
|
+
},
|
|
1645
|
+
transfer: [decoded.values.buffer],
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
const decoded = await decodePng16(request.bytes, {
|
|
1649
|
+
padRowsForWebGl: request.padRowsForWebGl,
|
|
1650
|
+
});
|
|
1651
|
+
const padded = decoded.paddedUpload;
|
|
1652
|
+
const decimated = request.decimateBy !== undefined && request.decimateBy >= 2
|
|
1653
|
+
? decimateDepthUpload(decoded.values, decoded.width, decoded.height, Math.floor(request.decimateBy), request.padRowsForWebGl === true)
|
|
1654
|
+
: null;
|
|
1655
|
+
const transfer = [decoded.values.buffer];
|
|
1656
|
+
if (padded)
|
|
1657
|
+
transfer.push(padded.bytes.buffer);
|
|
1658
|
+
if (decimated)
|
|
1659
|
+
transfer.push(decimated.bytes.buffer);
|
|
1660
|
+
return {
|
|
1661
|
+
response: {
|
|
1662
|
+
bitDepth: 16,
|
|
1663
|
+
decimatedUpload: decimated
|
|
1664
|
+
? { ...decimated, bytes: decimated.bytes.buffer }
|
|
1665
|
+
: undefined,
|
|
1666
|
+
height: decoded.height,
|
|
1667
|
+
paddedUpload: padded
|
|
1668
|
+
? {
|
|
1669
|
+
bytes: padded.bytes.buffer,
|
|
1670
|
+
textureWidth: padded.textureWidth,
|
|
1671
|
+
}
|
|
1672
|
+
: undefined,
|
|
1673
|
+
requestId,
|
|
1674
|
+
type: DepthPreparationWorkerMessageType.Complete,
|
|
1675
|
+
values: decoded.values.buffer,
|
|
1676
|
+
width: decoded.width,
|
|
1677
|
+
},
|
|
1678
|
+
transfer,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
catch (error) {
|
|
1682
|
+
return {
|
|
1683
|
+
response: {
|
|
1684
|
+
error: error instanceof Error ? error.message : "Unable to decode depth.",
|
|
1685
|
+
requestId,
|
|
1686
|
+
type: DepthPreparationWorkerMessageType.Error,
|
|
1687
|
+
},
|
|
1688
|
+
transfer: [],
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Depth for a texture `factor` times smaller on each side, each texel the
|
|
1694
|
+
* sample at its block's centre, as the renderer decimates a map larger than
|
|
1695
|
+
* the GPU allows.
|
|
1696
|
+
*/
|
|
1697
|
+
function decimateDepthUpload(values, width, height, factor, padRowsForWebGl) {
|
|
1698
|
+
if (factor < 2)
|
|
1699
|
+
return null;
|
|
1700
|
+
const targetWidth = Math.ceil(width / factor);
|
|
1701
|
+
const targetHeight = Math.ceil(height / factor);
|
|
1702
|
+
// WebGL reads two-byte texels in rows of four bytes.
|
|
1703
|
+
const textureWidth = padRowsForWebGl
|
|
1704
|
+
? targetWidth + (targetWidth % 2)
|
|
1705
|
+
: targetWidth;
|
|
1706
|
+
const texels = new Uint16Array(textureWidth * targetHeight);
|
|
1707
|
+
const columns = new Uint32Array(targetWidth);
|
|
1708
|
+
for (let x = 0; x < targetWidth; x += 1) {
|
|
1709
|
+
columns[x] = Math.min(width - 1, Math.floor(((x + 0.5) * width) / targetWidth));
|
|
1710
|
+
}
|
|
1711
|
+
for (let y = 0; y < targetHeight; y += 1) {
|
|
1712
|
+
const row = Math.min(height - 1, Math.floor(((y + 0.5) * height) / targetHeight)) *
|
|
1713
|
+
width;
|
|
1714
|
+
const target = y * textureWidth;
|
|
1715
|
+
for (let x = 0; x < targetWidth; x += 1) {
|
|
1716
|
+
texels[target + x] = values[row + columns[x]];
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
return {
|
|
1720
|
+
bytes: new Uint8Array(texels.buffer),
|
|
1721
|
+
height: targetHeight,
|
|
1722
|
+
textureWidth,
|
|
1723
|
+
width: targetWidth,
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/** Formats whose first plane is 8-bit luma, which is the code as written. */
|
|
1728
|
+
const PLANAR_8_BIT_FORMATS = new Set([
|
|
1729
|
+
"I420",
|
|
1730
|
+
"I420A",
|
|
1731
|
+
"I422",
|
|
1732
|
+
"I422A",
|
|
1733
|
+
"I444",
|
|
1734
|
+
"I444A",
|
|
1735
|
+
"NV12",
|
|
1736
|
+
]);
|
|
1737
|
+
const RGB_FORMATS = new Set(["RGBA", "RGBX", "BGRA", "BGRX"]);
|
|
1738
|
+
/**
|
|
1739
|
+
* Copies a decoded preview frame's luma codes out of it, mapped through
|
|
1740
|
+
* `correction` when one is given. The frame stays open; its owner closes
|
|
1741
|
+
* it.
|
|
1742
|
+
*/
|
|
1743
|
+
async function readVideoFrameLuma(frame, scratch, correction = null) {
|
|
1744
|
+
const rect = frame.visibleRect ?? {
|
|
1745
|
+
height: frame.codedHeight,
|
|
1746
|
+
width: frame.codedWidth,
|
|
1747
|
+
x: 0,
|
|
1748
|
+
y: 0,
|
|
1749
|
+
};
|
|
1750
|
+
const { width, height } = rect;
|
|
1751
|
+
const format = frame.format;
|
|
1752
|
+
const planar = format !== null && PLANAR_8_BIT_FORMATS.has(format);
|
|
1753
|
+
let bytes;
|
|
1754
|
+
let plane;
|
|
1755
|
+
if (planar || (format !== null && RGB_FORMATS.has(format))) {
|
|
1756
|
+
const size = frame.allocationSize({ rect });
|
|
1757
|
+
if (scratch.buffer.byteLength < size)
|
|
1758
|
+
scratch.buffer = new ArrayBuffer(size);
|
|
1759
|
+
[plane] = await frame.copyTo(scratch.buffer, { rect });
|
|
1760
|
+
bytes = new Uint8Array(scratch.buffer);
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
// A frame in a layout no copy can read is drawn to a canvas, which
|
|
1764
|
+
// converts it to RGB as the browser sees fit.
|
|
1765
|
+
bytes = drawFrameRgba(frame, width, height);
|
|
1766
|
+
}
|
|
1767
|
+
const luma = new Uint8Array(width * height);
|
|
1768
|
+
const stride = plane?.stride ?? (planar ? width : width * 4);
|
|
1769
|
+
const offset = plane?.offset ?? 0;
|
|
1770
|
+
if (!planar) {
|
|
1771
|
+
// Every RGB layout keeps green second, and luma with neutral chroma
|
|
1772
|
+
// converts to three equal channels.
|
|
1773
|
+
for (let y = 0; y < height; y += 1) {
|
|
1774
|
+
const row = offset + y * stride + 1;
|
|
1775
|
+
const target = y * width;
|
|
1776
|
+
for (let x = 0; x < width; x += 1) {
|
|
1777
|
+
luma[target + x] = bytes[row + x * 4];
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
else if (stride === width) {
|
|
1782
|
+
luma.set(bytes.subarray(offset, offset + width * height));
|
|
1783
|
+
}
|
|
1784
|
+
else {
|
|
1785
|
+
for (let y = 0; y < height; y += 1) {
|
|
1786
|
+
luma.set(bytes.subarray(offset + y * stride, offset + y * stride + width), y * width);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if (correction) {
|
|
1790
|
+
for (let index = 0; index < luma.length; index += 1) {
|
|
1791
|
+
luma[index] = correction[luma[index]];
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
return { height, luma, path: planar ? "plane" : "rgb", width };
|
|
1795
|
+
}
|
|
1796
|
+
function drawFrameRgba(frame, width, height) {
|
|
1797
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
1798
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
1799
|
+
if (!context) {
|
|
1800
|
+
throw new Error("The depth preview needs a 2D canvas to read this frame.");
|
|
1801
|
+
}
|
|
1802
|
+
context.drawImage(frame, 0, 0, width, height);
|
|
1803
|
+
const { data } = context.getImageData(0, 0, width, height);
|
|
1804
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1109
1807
|
const workerScope = globalThis;
|
|
1110
1808
|
workerScope.addEventListener("message", (event) => {
|
|
1111
1809
|
const message = event.data;
|
|
@@ -1113,6 +1811,14 @@
|
|
|
1113
1811
|
prepareHeatmap(message);
|
|
1114
1812
|
return;
|
|
1115
1813
|
}
|
|
1814
|
+
if (message.type === DepthPreparationWorkerMessageType.Decode) {
|
|
1815
|
+
void decodeDepthFrame(message);
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
if (message.type === DepthPreparationWorkerMessageType.PreviewLuma) {
|
|
1819
|
+
void copyPreviewLuma(message);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1116
1822
|
if (message.type !== MaskPreparationWorkerMessageType.Prepare) {
|
|
1117
1823
|
return;
|
|
1118
1824
|
}
|
|
@@ -1145,12 +1851,52 @@
|
|
|
1145
1851
|
});
|
|
1146
1852
|
}
|
|
1147
1853
|
}
|
|
1854
|
+
/** Decoding waits on the platform's inflate, so replies arrive out of order. */
|
|
1855
|
+
async function decodeDepthFrame(message) {
|
|
1856
|
+
const { response, transfer } = await decodeDepthPreparationRequest(message);
|
|
1857
|
+
workerScope.postMessage(response, transfer);
|
|
1858
|
+
}
|
|
1859
|
+
const previewLumaScratch = {
|
|
1860
|
+
buffer: new ArrayBuffer(0),
|
|
1861
|
+
};
|
|
1862
|
+
/** Frames arrive in order and are copied in order, through one scratch. */
|
|
1863
|
+
let previewLumaQueue = Promise.resolve();
|
|
1864
|
+
function copyPreviewLuma(message) {
|
|
1865
|
+
previewLumaQueue = previewLumaQueue.then(async () => {
|
|
1866
|
+
try {
|
|
1867
|
+
const copied = await readVideoFrameLuma(message.frame, previewLumaScratch, message.correction);
|
|
1868
|
+
workerScope.postMessage({
|
|
1869
|
+
height: copied.height,
|
|
1870
|
+
luma: copied.luma.buffer,
|
|
1871
|
+
path: copied.path,
|
|
1872
|
+
requestId: message.requestId,
|
|
1873
|
+
type: DepthPreparationWorkerMessageType.PreviewLumaComplete,
|
|
1874
|
+
width: copied.width,
|
|
1875
|
+
}, [copied.luma.buffer]);
|
|
1876
|
+
}
|
|
1877
|
+
catch (error) {
|
|
1878
|
+
workerScope.postMessage({
|
|
1879
|
+
error: error instanceof Error
|
|
1880
|
+
? error.message
|
|
1881
|
+
: "Unable to copy the depth preview frame.",
|
|
1882
|
+
requestId: message.requestId,
|
|
1883
|
+
type: DepthPreparationWorkerMessageType.Error,
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
finally {
|
|
1887
|
+
message.frame.close();
|
|
1888
|
+
}
|
|
1889
|
+
});
|
|
1890
|
+
return previewLumaQueue;
|
|
1891
|
+
}
|
|
1148
1892
|
function prepareMaskFrame(message) {
|
|
1149
1893
|
try {
|
|
1150
1894
|
const regionMaskCoverage = createRegionMaskCoverageFrame(message.job.instructions);
|
|
1151
1895
|
const coverageTransfers = getRegionMaskCoverageTransfers(regionMaskCoverage);
|
|
1152
1896
|
const idMaskFrame = createIdMaskRasterFrame(message.job.instructions, message.job.maxRasterWidth);
|
|
1153
1897
|
if (idMaskFrame) {
|
|
1898
|
+
const screenStrokes = createMaskScreenStrokes(message.job.instructions, idMaskFrame.width, idMaskFrame.height, idMaskFrame.data);
|
|
1899
|
+
const strokeTransfers = screenStrokes?.flatMap(({ paths }) => paths.map((path) => path.buffer)) ?? [];
|
|
1154
1900
|
workerScope.postMessage({
|
|
1155
1901
|
artifactKind: PreparedMaskFrameKind.IdMask,
|
|
1156
1902
|
fillPalette: idMaskFrame.fillPalette,
|
|
@@ -1159,6 +1905,7 @@
|
|
|
1159
1905
|
key: message.job.key,
|
|
1160
1906
|
maxStrokeWidth: idMaskFrame.maxStrokeWidth,
|
|
1161
1907
|
raster: idMaskFrame.data,
|
|
1908
|
+
screenStrokes,
|
|
1162
1909
|
regionMaskCoverage,
|
|
1163
1910
|
requestId: message.requestId,
|
|
1164
1911
|
sourceWidth: idMaskFrame.sourceWidth,
|
|
@@ -1171,6 +1918,7 @@
|
|
|
1171
1918
|
idMaskFrame.fillPalette.buffer,
|
|
1172
1919
|
idMaskFrame.strokePalette.buffer,
|
|
1173
1920
|
idMaskFrame.strokeWidths.buffer,
|
|
1921
|
+
...strokeTransfers,
|
|
1174
1922
|
...coverageTransfers,
|
|
1175
1923
|
]);
|
|
1176
1924
|
return;
|
|
@@ -1189,25 +1937,40 @@
|
|
|
1189
1937
|
const imageBitmap = createImageBitmapFromImageData(imageData);
|
|
1190
1938
|
const idMaskPlane = createIdMaskPlane(message.job.instructions, message.job.maxRasterWidth);
|
|
1191
1939
|
const idMaskTransfers = idMaskPlane ? [idMaskPlane.data.buffer] : [];
|
|
1940
|
+
const screenStrokes = createMaskScreenStrokes(message.job.instructions, preparedPixels.width, preparedPixels.height);
|
|
1941
|
+
const strokeTransfers = screenStrokes?.flatMap(({ paths }) => paths.map((path) => path.buffer)) ??
|
|
1942
|
+
[];
|
|
1192
1943
|
if (imageBitmap) {
|
|
1193
1944
|
workerScope.postMessage({
|
|
1194
1945
|
idMaskPlane,
|
|
1195
1946
|
imageBitmap,
|
|
1947
|
+
screenStrokes,
|
|
1196
1948
|
key: message.job.key,
|
|
1197
1949
|
regionMaskCoverage,
|
|
1198
1950
|
requestId: message.requestId,
|
|
1199
1951
|
type: MaskPreparationWorkerMessageType.Complete,
|
|
1200
|
-
}, [
|
|
1952
|
+
}, [
|
|
1953
|
+
imageBitmap,
|
|
1954
|
+
...idMaskTransfers,
|
|
1955
|
+
...strokeTransfers,
|
|
1956
|
+
...coverageTransfers,
|
|
1957
|
+
]);
|
|
1201
1958
|
return;
|
|
1202
1959
|
}
|
|
1203
1960
|
workerScope.postMessage({
|
|
1204
1961
|
idMaskPlane,
|
|
1205
1962
|
imageData,
|
|
1963
|
+
screenStrokes,
|
|
1206
1964
|
key: message.job.key,
|
|
1207
1965
|
regionMaskCoverage,
|
|
1208
1966
|
requestId: message.requestId,
|
|
1209
1967
|
type: MaskPreparationWorkerMessageType.Complete,
|
|
1210
|
-
}, [
|
|
1968
|
+
}, [
|
|
1969
|
+
imageData.data.buffer,
|
|
1970
|
+
...idMaskTransfers,
|
|
1971
|
+
...strokeTransfers,
|
|
1972
|
+
...coverageTransfers,
|
|
1973
|
+
]);
|
|
1211
1974
|
}
|
|
1212
1975
|
catch (error) {
|
|
1213
1976
|
workerScope.postMessage({
|