supervision 0.2.5 → 0.2.6
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/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9504 -4719
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +658 -21
- 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/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/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 +14 -1
- 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-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-halo.d.ts +1 -0
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-layer.d.ts +4 -0
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- 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/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 +19 -0
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/types/media-session.d.ts +19 -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 +7 -2
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +1128 -59
- 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.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/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/editing.d.ts +2 -0
- package/node_modules/supervision-js-core/dist/types/editing.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/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
|
@@ -376,6 +376,68 @@
|
|
|
376
376
|
KeypointMarkerShape["Circle"] = "circle";
|
|
377
377
|
KeypointMarkerShape["Cross"] = "cross";
|
|
378
378
|
})(KeypointMarkerShape || (KeypointMarkerShape = {}));
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* What the stored values of a depth map measure.
|
|
382
|
+
*
|
|
383
|
+
* Every kind stores 0 where there is no depth.
|
|
384
|
+
*/
|
|
385
|
+
const DepthMapKind = {
|
|
386
|
+
/** Stereo disparity in pixels of the map. Larger is nearer. */
|
|
387
|
+
DisparityPx: "disparity_px",
|
|
388
|
+
/** Metric depth along the optical axis in metres. Smaller is nearer. */
|
|
389
|
+
DepthM: "depth_m",
|
|
390
|
+
/**
|
|
391
|
+
* Unitless inverse depth up to an unknown scale and shift, as monocular
|
|
392
|
+
* models produce it. Larger is nearer; it has no metric depth.
|
|
393
|
+
*/
|
|
394
|
+
RelativeInverse: "relative_inverse",
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* The quantity a depth renderer colours.
|
|
398
|
+
*
|
|
399
|
+
* Disparity is inverse depth, so it spends colour on near detail the way
|
|
400
|
+
* stereo measures it. Either way the nearest end of the range is the warm or
|
|
401
|
+
* bright end of the colour table.
|
|
402
|
+
*/
|
|
403
|
+
const DepthQuantity = {
|
|
404
|
+
Disparity: "disparity",
|
|
405
|
+
Depth: "depth",
|
|
406
|
+
};
|
|
407
|
+
const DepthColormap = {
|
|
408
|
+
Turbo: "turbo",
|
|
409
|
+
Viridis: "viridis",
|
|
410
|
+
Cividis: "cividis",
|
|
411
|
+
Inferno: "inferno",
|
|
412
|
+
Magma: "magma",
|
|
413
|
+
Grayscale: "grayscale",
|
|
414
|
+
};
|
|
415
|
+
/** How a depth map is sampled where one map pixel does not cover one media pixel. */
|
|
416
|
+
const DepthSampling = {
|
|
417
|
+
/** Nearest when the map is at least media size, edge-aware otherwise. */
|
|
418
|
+
Auto: "auto",
|
|
419
|
+
Nearest: "nearest",
|
|
420
|
+
/**
|
|
421
|
+
* Bilinear over valid neighbours only, falling back to the nearest one
|
|
422
|
+
* across a depth edge, so objects never bleed into the background.
|
|
423
|
+
*/
|
|
424
|
+
EdgeAware: "edge-aware",
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
* The luma range a depth preview video's codes are written in.
|
|
428
|
+
*
|
|
429
|
+
* TV range is what browsers' hardware decoders return as written; a decoder
|
|
430
|
+
* may squeeze full range into TV range or move codes converting it to RGB.
|
|
431
|
+
*/
|
|
432
|
+
const DepthPreviewLevels = {
|
|
433
|
+
/** Every code from 0 to 255; the top code is 255. */
|
|
434
|
+
Full: "full",
|
|
435
|
+
/** Limited range: black is 16 and the top code is 235. */
|
|
436
|
+
Tv: "tv",
|
|
437
|
+
};
|
|
438
|
+
new Set(Object.values(DepthColormap));
|
|
439
|
+
new Set(Object.values(DepthQuantity));
|
|
440
|
+
new Set(Object.values(DepthSampling));
|
|
379
441
|
function rasterizePolygonToMask(points, dimensions) {
|
|
380
442
|
const data = createEmptyMask(dimensions);
|
|
381
443
|
if (points.length < 3) {
|
|
@@ -429,8 +491,10 @@
|
|
|
429
491
|
if (instructions.length === 0) {
|
|
430
492
|
return undefined;
|
|
431
493
|
}
|
|
432
|
-
|
|
433
|
-
|
|
494
|
+
/* Pixel-grid dimensions use integer parts throughout allocation and RLE
|
|
495
|
+
traversal. */
|
|
496
|
+
const maskWidth = Math.floor(Math.max(...instructions.map(({ mask }) => mask.width)));
|
|
497
|
+
const maskHeight = Math.floor(Math.max(...instructions.map(({ mask }) => mask.height)));
|
|
434
498
|
const width = resolveRasterWidth(maskWidth, options.maxWidth);
|
|
435
499
|
const height = width === maskWidth
|
|
436
500
|
? maskHeight
|
|
@@ -482,8 +546,8 @@
|
|
|
482
546
|
*/
|
|
483
547
|
function writeMaskRuns(data, frameWidth, mask, detectionMaskId) {
|
|
484
548
|
const counts = decodeCompressedRleCounts(mask.counts);
|
|
485
|
-
const maskWidth = mask.width;
|
|
486
|
-
const maskHeight = mask.height;
|
|
549
|
+
const maskWidth = Math.floor(mask.width);
|
|
550
|
+
const maskHeight = Math.floor(mask.height);
|
|
487
551
|
let maskOffset = 0;
|
|
488
552
|
for (let index = 0; index < counts.length; index += 1) {
|
|
489
553
|
const runLength = counts[index] ?? 0;
|
|
@@ -517,9 +581,10 @@
|
|
|
517
581
|
*/
|
|
518
582
|
function writeScaledMaskRuns(data, axes, mask, detectionMaskId) {
|
|
519
583
|
const counts = decodeCompressedRleCounts(mask.counts);
|
|
520
|
-
const maskWidth = mask.width;
|
|
521
|
-
const maskHeight = mask.height;
|
|
584
|
+
const maskWidth = Math.floor(mask.width);
|
|
585
|
+
const maskHeight = Math.floor(mask.height);
|
|
522
586
|
let maskOffset = 0;
|
|
587
|
+
const stride = axes.stride;
|
|
523
588
|
for (let index = 0; index < counts.length; index += 1) {
|
|
524
589
|
const runLength = counts[index] ?? 0;
|
|
525
590
|
if (index % 2 === 0 || runLength <= 0) {
|
|
@@ -528,17 +593,30 @@
|
|
|
528
593
|
}
|
|
529
594
|
let x = Math.floor(maskOffset / maskHeight);
|
|
530
595
|
let y = maskOffset - x * maskHeight;
|
|
531
|
-
let
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
596
|
+
let remaining = runLength;
|
|
597
|
+
/* A run walks down one column and wraps into the next, so it splits into
|
|
598
|
+
column-sized segments. Within a segment the destination offset only ever
|
|
599
|
+
steps by the row pitch or stays put: createMaskAxisMap builds
|
|
600
|
+
`rows[i] = min(height - 1, floor(i * scale)) * width` with scale <= 1, so
|
|
601
|
+
consecutive entries differ by exactly 0 or exactly `width`. Walking the
|
|
602
|
+
destination directly therefore touches the same bytes the per-pixel loop
|
|
603
|
+
touched, and drops the bounds test, the wrap test and one typed-array
|
|
604
|
+
load from every source pixel. Where the scale collapses several source
|
|
605
|
+
rows onto one destination row, this writes that byte once instead of
|
|
606
|
+
once per source row. */
|
|
607
|
+
while (remaining > 0 && x < maskWidth) {
|
|
608
|
+
const span = Math.min(remaining, maskHeight - y);
|
|
609
|
+
const column = axes.columns[x];
|
|
610
|
+
const first = axes.rows[y];
|
|
611
|
+
const last = axes.rows[y + span - 1];
|
|
612
|
+
for (let offset = first; offset <= last; offset += stride) {
|
|
613
|
+
data[offset + column] = detectionMaskId;
|
|
535
614
|
}
|
|
536
|
-
|
|
537
|
-
y +=
|
|
615
|
+
remaining -= span;
|
|
616
|
+
y += span;
|
|
538
617
|
if (y === maskHeight) {
|
|
539
618
|
y = 0;
|
|
540
619
|
x += 1;
|
|
541
|
-
column = axes.columns[x];
|
|
542
620
|
}
|
|
543
621
|
}
|
|
544
622
|
maskOffset += runLength;
|
|
@@ -550,22 +628,19 @@
|
|
|
550
628
|
: maskWidth;
|
|
551
629
|
}
|
|
552
630
|
/**
|
|
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.
|
|
631
|
+
* Converts a source-grid stroke width into raster texels. Fractional widths
|
|
632
|
+
* let coverage shaders draw a border within a texel. The ceiling bounds the
|
|
633
|
+
* neighbourhood the shaders scan.
|
|
560
634
|
*/
|
|
561
635
|
function resolveIdMaskStrokeTexels(strokeWidth, maskWidth, rasterWidth) {
|
|
562
636
|
const scale = maskWidth > 0 ? rasterWidth / maskWidth : 1;
|
|
563
|
-
return Math.min(
|
|
637
|
+
return Math.min(strokeWidth * scale, MAX_ID_MASK_STROKE_WIDTH);
|
|
564
638
|
}
|
|
565
639
|
function createScaledMaskAxes(frame) {
|
|
566
640
|
return {
|
|
567
641
|
columns: createMaskAxisMap(frame.maskWidth, frame.width, 1),
|
|
568
642
|
rows: createMaskAxisMap(frame.maskHeight, frame.height, frame.width),
|
|
643
|
+
stride: frame.width,
|
|
569
644
|
};
|
|
570
645
|
}
|
|
571
646
|
/** Rows hold their destination offset, so writing a run is two lookups. */
|
|
@@ -595,6 +670,8 @@
|
|
|
595
670
|
palette[offset + 2] = (color & 0xff) / 255;
|
|
596
671
|
palette[offset + 3] = Math.max(0, Math.min(alpha, 1));
|
|
597
672
|
}
|
|
673
|
+
new Set(Object.values(DepthPreviewLevels));
|
|
674
|
+
new Set(Object.values(DepthMapKind));
|
|
598
675
|
|
|
599
676
|
var MediaRendererFit;
|
|
600
677
|
(function (MediaRendererFit) {
|
|
@@ -1106,6 +1183,520 @@
|
|
|
1106
1183
|
MaskPreparationWorkerMessageType["Prepare"] = "prepare";
|
|
1107
1184
|
})(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));
|
|
1108
1185
|
|
|
1186
|
+
/**
|
|
1187
|
+
* Messages between the depth source and the render-preparation worker, which
|
|
1188
|
+
* decodes depth and confidence PNGs, and copies decoded preview frames' luma,
|
|
1189
|
+
* off the main thread. Internal: the worker script is a deployment asset, and
|
|
1190
|
+
* its protocol is not public.
|
|
1191
|
+
*/
|
|
1192
|
+
var DepthPreparationWorkerMessageType;
|
|
1193
|
+
(function (DepthPreparationWorkerMessageType) {
|
|
1194
|
+
DepthPreparationWorkerMessageType["Decode"] = "depth-decode";
|
|
1195
|
+
DepthPreparationWorkerMessageType["Complete"] = "depth-decode-complete";
|
|
1196
|
+
DepthPreparationWorkerMessageType["Error"] = "depth-decode-error";
|
|
1197
|
+
DepthPreparationWorkerMessageType["PreviewLuma"] = "depth-preview-luma";
|
|
1198
|
+
DepthPreparationWorkerMessageType["PreviewLumaComplete"] = "depth-preview-luma-complete";
|
|
1199
|
+
})(DepthPreparationWorkerMessageType || (DepthPreparationWorkerMessageType = {}));
|
|
1200
|
+
|
|
1201
|
+
/*
|
|
1202
|
+
* Grayscale PNG decoding for depth maps and their confidence planes.
|
|
1203
|
+
*
|
|
1204
|
+
* Every image path a browser offers (<img>, createImageBitmap, ImageDecoder,
|
|
1205
|
+
* canvas) hands back 8 bits per channel, so a 16-bit depth PNG is decoded
|
|
1206
|
+
* here: the browser's own zlib inflates the IDAT stream, and the row filters
|
|
1207
|
+
* are undone in JavaScript.
|
|
1208
|
+
*
|
|
1209
|
+
* This module imports nothing, so the render-preparation worker, the main
|
|
1210
|
+
* thread and the depth benchmark all run the same code.
|
|
1211
|
+
*/
|
|
1212
|
+
const PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1213
|
+
const COLOR_TYPE_GRAY = 0;
|
|
1214
|
+
/** 16384 x 16384, beyond any GPU texture; a header claiming more is refused. */
|
|
1215
|
+
const MAX_PNG_PIXELS = 16_384 * 16_384;
|
|
1216
|
+
async function decodePng16(bytes, options = {}) {
|
|
1217
|
+
const header = readPngHeader(bytes, 16);
|
|
1218
|
+
const rowBytes = header.width * 2 + 1;
|
|
1219
|
+
const raw = await (options.inflate ?? inflateWithDecompressionStream)(header.idat, rowBytes * header.height);
|
|
1220
|
+
const values = new Uint16Array(header.width * header.height);
|
|
1221
|
+
checkInflatedLength(raw, rowBytes * header.height);
|
|
1222
|
+
unfilterGray16(raw, header.width, header.height, values);
|
|
1223
|
+
return {
|
|
1224
|
+
height: header.height,
|
|
1225
|
+
paddedUpload: options.padRowsForWebGl && header.width % 2 === 1
|
|
1226
|
+
? padRows(values, header.width, header.height)
|
|
1227
|
+
: undefined,
|
|
1228
|
+
values,
|
|
1229
|
+
width: header.width,
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
async function decodePng8Gray(bytes, options = {}) {
|
|
1233
|
+
const header = readPngHeader(bytes, 8);
|
|
1234
|
+
const rowBytes = header.width + 1;
|
|
1235
|
+
const raw = await (options.inflate ?? inflateWithDecompressionStream)(header.idat, rowBytes * header.height);
|
|
1236
|
+
const values = new Uint8Array(header.width * header.height);
|
|
1237
|
+
checkInflatedLength(raw, rowBytes * header.height);
|
|
1238
|
+
unfilterGray8(raw, header.width, header.height, values);
|
|
1239
|
+
return { height: header.height, values, width: header.width };
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Inflates with the platform's `DecompressionStream("deflate")` (zlib
|
|
1243
|
+
* framing, which is what PNG uses) straight into one buffer of the expected
|
|
1244
|
+
* size, so no second copy joins the output chunks.
|
|
1245
|
+
*/
|
|
1246
|
+
async function inflateWithDecompressionStream(chunks, byteLength) {
|
|
1247
|
+
const stream = new DecompressionStream("deflate");
|
|
1248
|
+
const writer = stream.writable.getWriter();
|
|
1249
|
+
const reader = stream.readable.getReader();
|
|
1250
|
+
// Writers split IDAT into many small chunks (libpng's default is 8 KiB).
|
|
1251
|
+
// One write of the joined stream costs a copy of the compressed bytes and
|
|
1252
|
+
// saves a stream round trip per chunk.
|
|
1253
|
+
const zlib = chunks.length === 1 ? chunks[0] : joinChunks(chunks);
|
|
1254
|
+
const writing = (async () => {
|
|
1255
|
+
await writer.write(zlib);
|
|
1256
|
+
await writer.close();
|
|
1257
|
+
})();
|
|
1258
|
+
const output = new Uint8Array(byteLength);
|
|
1259
|
+
let offset = 0;
|
|
1260
|
+
// A corrupt stream rejects both sides; the reader's error is the one thrown.
|
|
1261
|
+
writing.catch(() => undefined);
|
|
1262
|
+
try {
|
|
1263
|
+
for (;;) {
|
|
1264
|
+
const { done, value } = await reader.read();
|
|
1265
|
+
if (done)
|
|
1266
|
+
break;
|
|
1267
|
+
if (offset + value.byteLength > byteLength) {
|
|
1268
|
+
throw new RangeError("PNG image data is longer than its header says.");
|
|
1269
|
+
}
|
|
1270
|
+
output.set(value, offset);
|
|
1271
|
+
offset += value.byteLength;
|
|
1272
|
+
}
|
|
1273
|
+
await writing;
|
|
1274
|
+
}
|
|
1275
|
+
catch (error) {
|
|
1276
|
+
await reader.cancel().catch(() => undefined);
|
|
1277
|
+
throw error instanceof RangeError
|
|
1278
|
+
? error
|
|
1279
|
+
: new RangeError(`PNG image data does not inflate: ${error instanceof Error ? error.message : String(error)}`);
|
|
1280
|
+
}
|
|
1281
|
+
return offset === byteLength ? output : output.subarray(0, offset);
|
|
1282
|
+
}
|
|
1283
|
+
function joinChunks(chunks) {
|
|
1284
|
+
const joined = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
|
|
1285
|
+
let offset = 0;
|
|
1286
|
+
for (const chunk of chunks) {
|
|
1287
|
+
joined.set(chunk, offset);
|
|
1288
|
+
offset += chunk.byteLength;
|
|
1289
|
+
}
|
|
1290
|
+
return joined;
|
|
1291
|
+
}
|
|
1292
|
+
function readPngHeader(input, bitDepth) {
|
|
1293
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
1294
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1295
|
+
if (bytes.byteLength < 8 ||
|
|
1296
|
+
PNG_SIGNATURE.some((value, index) => bytes[index] !== value)) {
|
|
1297
|
+
throw new RangeError("Not a PNG file.");
|
|
1298
|
+
}
|
|
1299
|
+
let offset = 8;
|
|
1300
|
+
let width = 0;
|
|
1301
|
+
let height = 0;
|
|
1302
|
+
let sawHeader = false;
|
|
1303
|
+
let sawEnd = false;
|
|
1304
|
+
const idat = [];
|
|
1305
|
+
while (offset + 12 <= bytes.byteLength) {
|
|
1306
|
+
const length = view.getUint32(offset);
|
|
1307
|
+
const type = view.getUint32(offset + 4);
|
|
1308
|
+
const dataStart = offset + 8;
|
|
1309
|
+
const dataEnd = dataStart + length;
|
|
1310
|
+
if (dataEnd + 4 > bytes.byteLength) {
|
|
1311
|
+
throw new RangeError("PNG file is truncated.");
|
|
1312
|
+
}
|
|
1313
|
+
if (type === 0x49484452) {
|
|
1314
|
+
// IHDR
|
|
1315
|
+
if (length !== 13)
|
|
1316
|
+
throw new RangeError("PNG header is malformed.");
|
|
1317
|
+
width = view.getUint32(dataStart);
|
|
1318
|
+
height = view.getUint32(dataStart + 4);
|
|
1319
|
+
const depth = bytes[dataStart + 8];
|
|
1320
|
+
const colorType = bytes[dataStart + 9];
|
|
1321
|
+
if (bytes[dataStart + 10] !== 0 || bytes[dataStart + 11] !== 0) {
|
|
1322
|
+
throw new RangeError("PNG uses an unknown compression or filter.");
|
|
1323
|
+
}
|
|
1324
|
+
if (bytes[dataStart + 12] !== 0) {
|
|
1325
|
+
throw new RangeError("Interlaced PNGs are not supported.");
|
|
1326
|
+
}
|
|
1327
|
+
if (colorType !== COLOR_TYPE_GRAY || depth !== bitDepth) {
|
|
1328
|
+
throw new RangeError(`Expected a ${bitDepth}-bit grayscale PNG, got bit depth ${depth} with colour type ${colorType}.`);
|
|
1329
|
+
}
|
|
1330
|
+
sawHeader = true;
|
|
1331
|
+
}
|
|
1332
|
+
else if (type === 0x49444154) {
|
|
1333
|
+
// IDAT
|
|
1334
|
+
idat.push(bytes.subarray(dataStart, dataEnd));
|
|
1335
|
+
}
|
|
1336
|
+
else if (type === 0x49454e44) {
|
|
1337
|
+
// IEND
|
|
1338
|
+
sawEnd = true;
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
offset = dataEnd + 4;
|
|
1342
|
+
}
|
|
1343
|
+
if (!sawHeader)
|
|
1344
|
+
throw new RangeError("PNG has no header.");
|
|
1345
|
+
if (!sawEnd)
|
|
1346
|
+
throw new RangeError("PNG file is truncated.");
|
|
1347
|
+
if (width === 0 || height === 0 || width * height > MAX_PNG_PIXELS) {
|
|
1348
|
+
throw new RangeError(`PNG size ${width}x${height} is not supported.`);
|
|
1349
|
+
}
|
|
1350
|
+
if (idat.length === 0)
|
|
1351
|
+
throw new RangeError("PNG has no image data.");
|
|
1352
|
+
return { height, idat, width };
|
|
1353
|
+
}
|
|
1354
|
+
function checkInflatedLength(raw, expected) {
|
|
1355
|
+
if (raw.byteLength !== expected) {
|
|
1356
|
+
throw new RangeError("PNG image data is shorter than its header says.");
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
/*
|
|
1360
|
+
* Undoes the row filters of a 16-bit grayscale image and writes host-order
|
|
1361
|
+
* samples, in one pass. Each filter runs on bytes, and the two bytes of a
|
|
1362
|
+
* sample never carry into each other, so the loops keep a sample's high and
|
|
1363
|
+
* low bytes apart and join them only for the store. The previous row is read
|
|
1364
|
+
* back from the output, which already holds it unfiltered; the first row reads
|
|
1365
|
+
* a row of zeros, which is what PNG defines above the image.
|
|
1366
|
+
*
|
|
1367
|
+
* Each filter is its own small function with one loop, called once per row,
|
|
1368
|
+
* so engines compile each loop on its own: SpiderMonkey slowed a Paeth loop
|
|
1369
|
+
* that shared a function with the other filters.
|
|
1370
|
+
*/
|
|
1371
|
+
function unfilterGray16(raw, width, height, out) {
|
|
1372
|
+
const rowBytes = width * 2 + 1;
|
|
1373
|
+
const zeroRow = new Uint16Array(width);
|
|
1374
|
+
for (let y = 0; y < height; y += 1) {
|
|
1375
|
+
const filter = raw[y * rowBytes];
|
|
1376
|
+
const unfilterRow = ROW_FILTERS_16[filter];
|
|
1377
|
+
if (!unfilterRow) {
|
|
1378
|
+
throw new RangeError(`PNG row ${y} has unknown filter ${filter}.`);
|
|
1379
|
+
}
|
|
1380
|
+
const o = y * width;
|
|
1381
|
+
unfilterRow(raw, y * rowBytes + 1, out, o, o + width, y === 0 ? zeroRow : out, y === 0 ? 0 : o - width);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
function noneRow16(raw, s, out, o, end) {
|
|
1385
|
+
for (let i = o; i < end; i += 1, s += 2) {
|
|
1386
|
+
out[i] = (raw[s] << 8) | raw[s + 1];
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
function subRow16(raw, s, out, o, end) {
|
|
1390
|
+
let hi = 0;
|
|
1391
|
+
let lo = 0;
|
|
1392
|
+
for (let i = o; i < end; i += 1, s += 2) {
|
|
1393
|
+
hi = (raw[s] + hi) & 0xff;
|
|
1394
|
+
lo = (raw[s + 1] + lo) & 0xff;
|
|
1395
|
+
out[i] = (hi << 8) | lo;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
function upRow16(raw, s, out, o, end, prev, p) {
|
|
1399
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1400
|
+
const up = prev[p];
|
|
1401
|
+
out[i] = (((raw[s] + (up >> 8)) & 0xff) << 8) | ((raw[s + 1] + up) & 0xff);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function averageRow16(raw, s, out, o, end, prev, p) {
|
|
1405
|
+
let hi = 0;
|
|
1406
|
+
let lo = 0;
|
|
1407
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1408
|
+
const up = prev[p];
|
|
1409
|
+
hi = (raw[s] + ((hi + (up >> 8)) >> 1)) & 0xff;
|
|
1410
|
+
lo = (raw[s + 1] + ((lo + (up & 0xff)) >> 1)) & 0xff;
|
|
1411
|
+
out[i] = (hi << 8) | lo;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
/** Paeth: per byte, whichever of left, up and up-left is closest to left + up - up-left. */
|
|
1415
|
+
function paethRow16(raw, s, out, o, end, prev, p) {
|
|
1416
|
+
let hi = 0;
|
|
1417
|
+
let lo = 0;
|
|
1418
|
+
let upLeftHi = 0;
|
|
1419
|
+
let upLeftLo = 0;
|
|
1420
|
+
for (let i = o; i < end; i += 1, s += 2, p += 1) {
|
|
1421
|
+
const up = prev[p];
|
|
1422
|
+
const upHi = up >> 8;
|
|
1423
|
+
const upLo = up & 0xff;
|
|
1424
|
+
let pa = Math.abs(upHi - upLeftHi);
|
|
1425
|
+
let pb = Math.abs(hi - upLeftHi);
|
|
1426
|
+
let pc = Math.abs(hi + upHi - upLeftHi - upLeftHi);
|
|
1427
|
+
hi =
|
|
1428
|
+
(raw[s] + (pa <= pb && pa <= pc ? hi : pb <= pc ? upHi : upLeftHi)) &
|
|
1429
|
+
0xff;
|
|
1430
|
+
pa = Math.abs(upLo - upLeftLo);
|
|
1431
|
+
pb = Math.abs(lo - upLeftLo);
|
|
1432
|
+
pc = Math.abs(lo + upLo - upLeftLo - upLeftLo);
|
|
1433
|
+
lo =
|
|
1434
|
+
(raw[s + 1] + (pa <= pb && pa <= pc ? lo : pb <= pc ? upLo : upLeftLo)) &
|
|
1435
|
+
0xff;
|
|
1436
|
+
out[i] = (hi << 8) | lo;
|
|
1437
|
+
upLeftHi = upHi;
|
|
1438
|
+
upLeftLo = upLo;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
/** Indexed by the PNG filter type byte: None, Sub, Up, Average, Paeth. */
|
|
1442
|
+
const ROW_FILTERS_16 = [
|
|
1443
|
+
noneRow16,
|
|
1444
|
+
subRow16,
|
|
1445
|
+
upRow16,
|
|
1446
|
+
averageRow16,
|
|
1447
|
+
paethRow16,
|
|
1448
|
+
];
|
|
1449
|
+
function unfilterGray8(raw, width, height, out) {
|
|
1450
|
+
const rowBytes = width + 1;
|
|
1451
|
+
const zeroRow = new Uint8Array(width);
|
|
1452
|
+
for (let y = 0; y < height; y += 1) {
|
|
1453
|
+
const filter = raw[y * rowBytes];
|
|
1454
|
+
const unfilterRow = ROW_FILTERS_8[filter];
|
|
1455
|
+
if (!unfilterRow) {
|
|
1456
|
+
throw new RangeError(`PNG row ${y} has unknown filter ${filter}.`);
|
|
1457
|
+
}
|
|
1458
|
+
const o = y * width;
|
|
1459
|
+
unfilterRow(raw, y * rowBytes + 1, out, o, o + width, y === 0 ? zeroRow : out, y === 0 ? 0 : o - width);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
function noneRow8(raw, s, out, o, end) {
|
|
1463
|
+
out.set(raw.subarray(s, s + end - o), o);
|
|
1464
|
+
}
|
|
1465
|
+
function subRow8(raw, s, out, o, end) {
|
|
1466
|
+
let left = 0;
|
|
1467
|
+
for (let i = o; i < end; i += 1, s += 1) {
|
|
1468
|
+
left = (raw[s] + left) & 0xff;
|
|
1469
|
+
out[i] = left;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
function upRow8(raw, s, out, o, end, prev, p) {
|
|
1473
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1474
|
+
out[i] = (raw[s] + prev[p]) & 0xff;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function averageRow8(raw, s, out, o, end, prev, p) {
|
|
1478
|
+
let left = 0;
|
|
1479
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1480
|
+
left = (raw[s] + ((left + prev[p]) >> 1)) & 0xff;
|
|
1481
|
+
out[i] = left;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
function paethRow8(raw, s, out, o, end, prev, p) {
|
|
1485
|
+
let left = 0;
|
|
1486
|
+
let upLeft = 0;
|
|
1487
|
+
for (let i = o; i < end; i += 1, s += 1, p += 1) {
|
|
1488
|
+
const up = prev[p];
|
|
1489
|
+
const pa = Math.abs(up - upLeft);
|
|
1490
|
+
const pb = Math.abs(left - upLeft);
|
|
1491
|
+
const pc = Math.abs(left + up - upLeft - upLeft);
|
|
1492
|
+
left =
|
|
1493
|
+
(raw[s] + (pa <= pb && pa <= pc ? left : pb <= pc ? up : upLeft)) & 0xff;
|
|
1494
|
+
out[i] = left;
|
|
1495
|
+
upLeft = up;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
const ROW_FILTERS_8 = [
|
|
1499
|
+
noneRow8,
|
|
1500
|
+
subRow8,
|
|
1501
|
+
upRow8,
|
|
1502
|
+
averageRow8,
|
|
1503
|
+
paethRow8,
|
|
1504
|
+
];
|
|
1505
|
+
/**
|
|
1506
|
+
* WebGL unpacks rows on four-byte boundaries, so an odd width of two-byte
|
|
1507
|
+
* texels needs one padding texel per row.
|
|
1508
|
+
*/
|
|
1509
|
+
function padRows(values, width, height) {
|
|
1510
|
+
const textureWidth = width + 1;
|
|
1511
|
+
const padded = new Uint16Array(textureWidth * height);
|
|
1512
|
+
for (let y = 0; y < height; y += 1) {
|
|
1513
|
+
padded.set(values.subarray(y * width, (y + 1) * width), y * textureWidth);
|
|
1514
|
+
}
|
|
1515
|
+
return { bytes: new Uint8Array(padded.buffer), textureWidth };
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Answers one decode request, in the worker or on the main thread: the reply
|
|
1520
|
+
* and the buffers it can transfer. A file that does not decode is an error
|
|
1521
|
+
* reply, never a thrown exception, so the caller can tell a bad file from a
|
|
1522
|
+
* broken worker.
|
|
1523
|
+
*/
|
|
1524
|
+
async function decodeDepthPreparationRequest(request) {
|
|
1525
|
+
const requestId = request.requestId ?? 0;
|
|
1526
|
+
try {
|
|
1527
|
+
if (request.bitDepth === 8) {
|
|
1528
|
+
const decoded = await decodePng8Gray(request.bytes);
|
|
1529
|
+
return {
|
|
1530
|
+
response: {
|
|
1531
|
+
bitDepth: 8,
|
|
1532
|
+
height: decoded.height,
|
|
1533
|
+
requestId,
|
|
1534
|
+
type: DepthPreparationWorkerMessageType.Complete,
|
|
1535
|
+
values: decoded.values.buffer,
|
|
1536
|
+
width: decoded.width,
|
|
1537
|
+
},
|
|
1538
|
+
transfer: [decoded.values.buffer],
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
const decoded = await decodePng16(request.bytes, {
|
|
1542
|
+
padRowsForWebGl: request.padRowsForWebGl,
|
|
1543
|
+
});
|
|
1544
|
+
const padded = decoded.paddedUpload;
|
|
1545
|
+
const decimated = request.decimateBy !== undefined && request.decimateBy >= 2
|
|
1546
|
+
? decimateDepthUpload(decoded.values, decoded.width, decoded.height, Math.floor(request.decimateBy), request.padRowsForWebGl === true)
|
|
1547
|
+
: null;
|
|
1548
|
+
const transfer = [decoded.values.buffer];
|
|
1549
|
+
if (padded)
|
|
1550
|
+
transfer.push(padded.bytes.buffer);
|
|
1551
|
+
if (decimated)
|
|
1552
|
+
transfer.push(decimated.bytes.buffer);
|
|
1553
|
+
return {
|
|
1554
|
+
response: {
|
|
1555
|
+
bitDepth: 16,
|
|
1556
|
+
decimatedUpload: decimated
|
|
1557
|
+
? { ...decimated, bytes: decimated.bytes.buffer }
|
|
1558
|
+
: undefined,
|
|
1559
|
+
height: decoded.height,
|
|
1560
|
+
paddedUpload: padded
|
|
1561
|
+
? {
|
|
1562
|
+
bytes: padded.bytes.buffer,
|
|
1563
|
+
textureWidth: padded.textureWidth,
|
|
1564
|
+
}
|
|
1565
|
+
: undefined,
|
|
1566
|
+
requestId,
|
|
1567
|
+
type: DepthPreparationWorkerMessageType.Complete,
|
|
1568
|
+
values: decoded.values.buffer,
|
|
1569
|
+
width: decoded.width,
|
|
1570
|
+
},
|
|
1571
|
+
transfer,
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
catch (error) {
|
|
1575
|
+
return {
|
|
1576
|
+
response: {
|
|
1577
|
+
error: error instanceof Error ? error.message : "Unable to decode depth.",
|
|
1578
|
+
requestId,
|
|
1579
|
+
type: DepthPreparationWorkerMessageType.Error,
|
|
1580
|
+
},
|
|
1581
|
+
transfer: [],
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Depth for a texture `factor` times smaller on each side, each texel the
|
|
1587
|
+
* sample at its block's centre, as the renderer decimates a map larger than
|
|
1588
|
+
* the GPU allows.
|
|
1589
|
+
*/
|
|
1590
|
+
function decimateDepthUpload(values, width, height, factor, padRowsForWebGl) {
|
|
1591
|
+
if (factor < 2)
|
|
1592
|
+
return null;
|
|
1593
|
+
const targetWidth = Math.ceil(width / factor);
|
|
1594
|
+
const targetHeight = Math.ceil(height / factor);
|
|
1595
|
+
// WebGL reads two-byte texels in rows of four bytes.
|
|
1596
|
+
const textureWidth = padRowsForWebGl
|
|
1597
|
+
? targetWidth + (targetWidth % 2)
|
|
1598
|
+
: targetWidth;
|
|
1599
|
+
const texels = new Uint16Array(textureWidth * targetHeight);
|
|
1600
|
+
const columns = new Uint32Array(targetWidth);
|
|
1601
|
+
for (let x = 0; x < targetWidth; x += 1) {
|
|
1602
|
+
columns[x] = Math.min(width - 1, Math.floor(((x + 0.5) * width) / targetWidth));
|
|
1603
|
+
}
|
|
1604
|
+
for (let y = 0; y < targetHeight; y += 1) {
|
|
1605
|
+
const row = Math.min(height - 1, Math.floor(((y + 0.5) * height) / targetHeight)) *
|
|
1606
|
+
width;
|
|
1607
|
+
const target = y * textureWidth;
|
|
1608
|
+
for (let x = 0; x < targetWidth; x += 1) {
|
|
1609
|
+
texels[target + x] = values[row + columns[x]];
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
return {
|
|
1613
|
+
bytes: new Uint8Array(texels.buffer),
|
|
1614
|
+
height: targetHeight,
|
|
1615
|
+
textureWidth,
|
|
1616
|
+
width: targetWidth,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/** Formats whose first plane is 8-bit luma, which is the code as written. */
|
|
1621
|
+
const PLANAR_8_BIT_FORMATS = new Set([
|
|
1622
|
+
"I420",
|
|
1623
|
+
"I420A",
|
|
1624
|
+
"I422",
|
|
1625
|
+
"I422A",
|
|
1626
|
+
"I444",
|
|
1627
|
+
"I444A",
|
|
1628
|
+
"NV12",
|
|
1629
|
+
]);
|
|
1630
|
+
const RGB_FORMATS = new Set(["RGBA", "RGBX", "BGRA", "BGRX"]);
|
|
1631
|
+
/**
|
|
1632
|
+
* Copies a decoded preview frame's luma codes out of it, mapped through
|
|
1633
|
+
* `correction` when one is given. The frame stays open; its owner closes
|
|
1634
|
+
* it.
|
|
1635
|
+
*/
|
|
1636
|
+
async function readVideoFrameLuma(frame, scratch, correction = null) {
|
|
1637
|
+
const rect = frame.visibleRect ?? {
|
|
1638
|
+
height: frame.codedHeight,
|
|
1639
|
+
width: frame.codedWidth,
|
|
1640
|
+
x: 0,
|
|
1641
|
+
y: 0,
|
|
1642
|
+
};
|
|
1643
|
+
const { width, height } = rect;
|
|
1644
|
+
const format = frame.format;
|
|
1645
|
+
const planar = format !== null && PLANAR_8_BIT_FORMATS.has(format);
|
|
1646
|
+
let bytes;
|
|
1647
|
+
let plane;
|
|
1648
|
+
if (planar || (format !== null && RGB_FORMATS.has(format))) {
|
|
1649
|
+
const size = frame.allocationSize({ rect });
|
|
1650
|
+
if (scratch.buffer.byteLength < size)
|
|
1651
|
+
scratch.buffer = new ArrayBuffer(size);
|
|
1652
|
+
[plane] = await frame.copyTo(scratch.buffer, { rect });
|
|
1653
|
+
bytes = new Uint8Array(scratch.buffer);
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
// A frame in a layout no copy can read is drawn to a canvas, which
|
|
1657
|
+
// converts it to RGB as the browser sees fit.
|
|
1658
|
+
bytes = drawFrameRgba(frame, width, height);
|
|
1659
|
+
}
|
|
1660
|
+
const luma = new Uint8Array(width * height);
|
|
1661
|
+
const stride = plane?.stride ?? (planar ? width : width * 4);
|
|
1662
|
+
const offset = plane?.offset ?? 0;
|
|
1663
|
+
if (!planar) {
|
|
1664
|
+
// Every RGB layout keeps green second, and luma with neutral chroma
|
|
1665
|
+
// converts to three equal channels.
|
|
1666
|
+
for (let y = 0; y < height; y += 1) {
|
|
1667
|
+
const row = offset + y * stride + 1;
|
|
1668
|
+
const target = y * width;
|
|
1669
|
+
for (let x = 0; x < width; x += 1) {
|
|
1670
|
+
luma[target + x] = bytes[row + x * 4];
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
else if (stride === width) {
|
|
1675
|
+
luma.set(bytes.subarray(offset, offset + width * height));
|
|
1676
|
+
}
|
|
1677
|
+
else {
|
|
1678
|
+
for (let y = 0; y < height; y += 1) {
|
|
1679
|
+
luma.set(bytes.subarray(offset + y * stride, offset + y * stride + width), y * width);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (correction) {
|
|
1683
|
+
for (let index = 0; index < luma.length; index += 1) {
|
|
1684
|
+
luma[index] = correction[luma[index]];
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return { height, luma, path: planar ? "plane" : "rgb", width };
|
|
1688
|
+
}
|
|
1689
|
+
function drawFrameRgba(frame, width, height) {
|
|
1690
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
1691
|
+
const context = canvas.getContext("2d", { willReadFrequently: true });
|
|
1692
|
+
if (!context) {
|
|
1693
|
+
throw new Error("The depth preview needs a 2D canvas to read this frame.");
|
|
1694
|
+
}
|
|
1695
|
+
context.drawImage(frame, 0, 0, width, height);
|
|
1696
|
+
const { data } = context.getImageData(0, 0, width, height);
|
|
1697
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1109
1700
|
const workerScope = globalThis;
|
|
1110
1701
|
workerScope.addEventListener("message", (event) => {
|
|
1111
1702
|
const message = event.data;
|
|
@@ -1113,6 +1704,14 @@
|
|
|
1113
1704
|
prepareHeatmap(message);
|
|
1114
1705
|
return;
|
|
1115
1706
|
}
|
|
1707
|
+
if (message.type === DepthPreparationWorkerMessageType.Decode) {
|
|
1708
|
+
void decodeDepthFrame(message);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (message.type === DepthPreparationWorkerMessageType.PreviewLuma) {
|
|
1712
|
+
void copyPreviewLuma(message);
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1116
1715
|
if (message.type !== MaskPreparationWorkerMessageType.Prepare) {
|
|
1117
1716
|
return;
|
|
1118
1717
|
}
|
|
@@ -1145,6 +1744,44 @@
|
|
|
1145
1744
|
});
|
|
1146
1745
|
}
|
|
1147
1746
|
}
|
|
1747
|
+
/** Decoding waits on the platform's inflate, so replies arrive out of order. */
|
|
1748
|
+
async function decodeDepthFrame(message) {
|
|
1749
|
+
const { response, transfer } = await decodeDepthPreparationRequest(message);
|
|
1750
|
+
workerScope.postMessage(response, transfer);
|
|
1751
|
+
}
|
|
1752
|
+
const previewLumaScratch = {
|
|
1753
|
+
buffer: new ArrayBuffer(0),
|
|
1754
|
+
};
|
|
1755
|
+
/** Frames arrive in order and are copied in order, through one scratch. */
|
|
1756
|
+
let previewLumaQueue = Promise.resolve();
|
|
1757
|
+
function copyPreviewLuma(message) {
|
|
1758
|
+
previewLumaQueue = previewLumaQueue.then(async () => {
|
|
1759
|
+
try {
|
|
1760
|
+
const copied = await readVideoFrameLuma(message.frame, previewLumaScratch, message.correction);
|
|
1761
|
+
workerScope.postMessage({
|
|
1762
|
+
height: copied.height,
|
|
1763
|
+
luma: copied.luma.buffer,
|
|
1764
|
+
path: copied.path,
|
|
1765
|
+
requestId: message.requestId,
|
|
1766
|
+
type: DepthPreparationWorkerMessageType.PreviewLumaComplete,
|
|
1767
|
+
width: copied.width,
|
|
1768
|
+
}, [copied.luma.buffer]);
|
|
1769
|
+
}
|
|
1770
|
+
catch (error) {
|
|
1771
|
+
workerScope.postMessage({
|
|
1772
|
+
error: error instanceof Error
|
|
1773
|
+
? error.message
|
|
1774
|
+
: "Unable to copy the depth preview frame.",
|
|
1775
|
+
requestId: message.requestId,
|
|
1776
|
+
type: DepthPreparationWorkerMessageType.Error,
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
finally {
|
|
1780
|
+
message.frame.close();
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
return previewLumaQueue;
|
|
1784
|
+
}
|
|
1148
1785
|
function prepareMaskFrame(message) {
|
|
1149
1786
|
try {
|
|
1150
1787
|
const regionMaskCoverage = createRegionMaskCoverageFrame(message.job.instructions);
|