supervision 0.2.4 → 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.
Files changed (166) hide show
  1. package/dist/depth-preview-probe-DG7McQEN.js +314 -0
  2. package/dist/depth-preview-probe-DG7McQEN.js.map +1 -0
  3. package/dist/depth-preview-track-BvgwNEE9.js +612 -0
  4. package/dist/depth-preview-track-BvgwNEE9.js.map +1 -0
  5. package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
  6. package/dist/index.d.ts +4 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +9504 -4719
  9. package/dist/index.js.map +1 -1
  10. package/dist/mask-preparation.worker.js +658 -21
  11. package/dist/mask-preparation.worker.js.map +1 -1
  12. package/dist/media/decoder-deadline.d.ts +18 -0
  13. package/dist/media/decoder-deadline.d.ts.map +1 -0
  14. package/dist/media/depth-preview-probe.d.ts +65 -0
  15. package/dist/media/depth-preview-probe.d.ts.map +1 -0
  16. package/dist/media/depth-preview-track.d.ts +96 -0
  17. package/dist/media/depth-preview-track.d.ts.map +1 -0
  18. package/dist/media/media-normalization.d.ts.map +1 -1
  19. package/dist/media/media-source.d.ts +7 -0
  20. package/dist/media/media-source.d.ts.map +1 -1
  21. package/dist/media/mediabunny-frame-clock.d.ts +7 -0
  22. package/dist/media/mediabunny-frame-clock.d.ts.map +1 -0
  23. package/dist/media/mediabunny-media-source.d.ts +3 -0
  24. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  25. package/dist/media/track-frame-index.d.ts +14 -0
  26. package/dist/media/track-frame-index.d.ts.map +1 -0
  27. package/dist/media/video-engine-media-source.d.ts.map +1 -1
  28. package/dist/media/video-engine-media-source.js +1 -1
  29. package/dist/render-preparation/depth/clip-preview.d.ts +41 -0
  30. package/dist/render-preparation/depth/clip-preview.d.ts.map +1 -0
  31. package/dist/render-preparation/depth/clip-timing.d.ts +18 -0
  32. package/dist/render-preparation/depth/clip-timing.d.ts.map +1 -0
  33. package/dist/render-preparation/depth/clip.d.ts +14 -0
  34. package/dist/render-preparation/depth/clip.d.ts.map +1 -0
  35. package/dist/render-preparation/depth/exact-at-rest.d.ts +44 -0
  36. package/dist/render-preparation/depth/exact-at-rest.d.ts.map +1 -0
  37. package/dist/render-preparation/depth/exact-playback.d.ts +40 -0
  38. package/dist/render-preparation/depth/exact-playback.d.ts.map +1 -0
  39. package/dist/render-preparation/depth/files.d.ts +55 -0
  40. package/dist/render-preparation/depth/files.d.ts.map +1 -0
  41. package/dist/render-preparation/depth/frame-decode.d.ts +20 -0
  42. package/dist/render-preparation/depth/frame-decode.d.ts.map +1 -0
  43. package/dist/render-preparation/depth/frame-preparer.d.ts +51 -0
  44. package/dist/render-preparation/depth/frame-preparer.d.ts.map +1 -0
  45. package/dist/render-preparation/depth/frame-window.d.ts +146 -0
  46. package/dist/render-preparation/depth/frame-window.d.ts.map +1 -0
  47. package/dist/render-preparation/depth/options.d.ts +37 -0
  48. package/dist/render-preparation/depth/options.d.ts.map +1 -0
  49. package/dist/render-preparation/depth/png16.d.ts +40 -0
  50. package/dist/render-preparation/depth/png16.d.ts.map +1 -0
  51. package/dist/render-preparation/depth/preview-luma-copier.d.ts +12 -0
  52. package/dist/render-preparation/depth/preview-luma-copier.d.ts.map +1 -0
  53. package/dist/render-preparation/depth/preview-luma.d.ts +48 -0
  54. package/dist/render-preparation/depth/preview-luma.d.ts.map +1 -0
  55. package/dist/render-preparation/depth/source.d.ts +123 -0
  56. package/dist/render-preparation/depth/source.d.ts.map +1 -0
  57. package/dist/render-preparation/depth/worker-protocol.d.ts +76 -0
  58. package/dist/render-preparation/depth/worker-protocol.d.ts.map +1 -0
  59. package/dist/render-preparation/playhead-motion.d.ts +67 -0
  60. package/dist/render-preparation/playhead-motion.d.ts.map +1 -0
  61. package/dist/render-preparation/prepared-render-window.d.ts +12 -0
  62. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  63. package/dist/renderers/depth-textures.d.ts +79 -0
  64. package/dist/renderers/depth-textures.d.ts.map +1 -0
  65. package/dist/renderers/id-mask-stroke-coverage.d.ts +3 -0
  66. package/dist/renderers/id-mask-stroke-coverage.d.ts.map +1 -0
  67. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  68. package/dist/renderers/media-renderer-scene.d.ts +14 -1
  69. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  70. package/dist/renderers/pixi-batch-texture-bindings.d.ts +9 -0
  71. package/dist/renderers/pixi-batch-texture-bindings.d.ts.map +1 -0
  72. package/dist/renderers/pixi-depth-layer.d.ts +76 -0
  73. package/dist/renderers/pixi-depth-layer.d.ts.map +1 -0
  74. package/dist/renderers/pixi-depth-shader.d.ts +76 -0
  75. package/dist/renderers/pixi-depth-shader.d.ts.map +1 -0
  76. package/dist/renderers/pixi-focus-layer.d.ts +1 -0
  77. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  78. package/dist/renderers/pixi-frame-present.d.ts +1 -0
  79. package/dist/renderers/pixi-frame-present.d.ts.map +1 -1
  80. package/dist/renderers/pixi-id-mask-shader.d.ts +1 -0
  81. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  82. package/dist/renderers/pixi-interaction-layer.d.ts +1 -1
  83. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  84. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +1 -0
  85. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  86. package/dist/renderers/pixi-mask-halo.d.ts +1 -0
  87. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  88. package/dist/renderers/pixi-mask-layer.d.ts +4 -0
  89. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  90. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  91. package/dist/renderers/pixi-region-coverage-mask.d.ts +1 -0
  92. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -1
  93. package/dist/renderers/pixi-region-layer.d.ts +1 -0
  94. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  95. package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
  96. package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
  97. package/dist/renderers/prepared-annotation-window.d.ts +1 -0
  98. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
  99. package/dist/renderers/render-preparation-report.d.ts +23 -0
  100. package/dist/renderers/render-preparation-report.d.ts.map +1 -0
  101. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  102. package/dist/sessions/media-session-state.d.ts.map +1 -1
  103. package/dist/sessions/media-session.d.ts.map +1 -1
  104. package/dist/tracking.worker.js +64 -0
  105. package/dist/tracking.worker.js.map +1 -1
  106. package/dist/types/media-depth.d.ts +55 -0
  107. package/dist/types/media-depth.d.ts.map +1 -0
  108. package/dist/types/media-renderer.d.ts +19 -0
  109. package/dist/types/media-renderer.d.ts.map +1 -1
  110. package/dist/types/media-session.d.ts +19 -0
  111. package/dist/types/media-session.d.ts.map +1 -1
  112. package/dist/types/render-preparation.d.ts +105 -3
  113. package/dist/types/render-preparation.d.ts.map +1 -1
  114. package/dist/{video-engine-media-source-DR-x1UfK.js → video-engine-media-source-DOFsuGS4.js} +35 -29
  115. package/dist/video-engine-media-source-DOFsuGS4.js.map +1 -0
  116. package/dist/web-video-engine/analysis.js +6 -43
  117. package/dist/web-video-engine/cache-budget.d.ts +4 -7
  118. package/dist/web-video-engine/constants.d.ts +3 -4
  119. package/dist/web-video-engine/create-scrub-cursor.d.ts +1 -0
  120. package/dist/web-video-engine/decode-resolution-fynpRIAG.js +109 -0
  121. package/dist/web-video-engine/decode-scheduler.d.ts +1 -0
  122. package/dist/web-video-engine/engine-core.d.ts +4 -2
  123. package/dist/web-video-engine/engine.js +8 -6
  124. package/dist/web-video-engine/engine.worker.js +164 -66
  125. package/dist/web-video-engine/frame-cache.d.ts +9 -0
  126. package/dist/web-video-engine/frame-index.d.ts +1 -0
  127. package/dist/web-video-engine/frame-index.js +97 -0
  128. package/dist/web-video-engine/{frame-timeline-DreEwsRY.js → frame-timeline-CYgzzmpA.js} +1 -109
  129. package/dist/web-video-engine/frame-timeline-reader.d.ts +21 -0
  130. package/dist/web-video-engine/video-engine.d.ts +9 -4
  131. package/node_modules/supervision-js-core/THIRD_PARTY_NOTICES.md +132 -0
  132. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  133. package/node_modules/supervision-js-core/dist/index.d.ts +7 -2
  134. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  135. package/node_modules/supervision-js-core/dist/index.js +1138 -62
  136. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  137. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
  138. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +2 -2
  139. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
  140. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
  141. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
  142. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +50 -3
  143. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  144. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts +203 -0
  145. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts.map +1 -0
  146. package/node_modules/supervision-js-core/dist/types/editing.d.ts +4 -0
  147. package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
  148. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +3 -0
  149. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  150. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts +91 -0
  151. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts.map +1 -0
  152. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts +30 -0
  153. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts.map +1 -0
  154. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts +16 -0
  155. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts.map +1 -0
  156. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts +23 -0
  157. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts.map +1 -0
  158. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts +14 -0
  159. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts.map +1 -0
  160. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +21 -0
  161. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  162. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +3 -7
  163. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  164. package/node_modules/supervision-js-core/package.json +2 -1
  165. package/package.json +6 -1
  166. 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
- const maskWidth = Math.max(...instructions.map(({ mask }) => mask.width));
433
- const maskHeight = Math.max(...instructions.map(({ mask }) => mask.height));
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 column = axes.columns[x];
532
- for (let step = 0; step < runLength; step += 1) {
533
- if (x >= maskWidth) {
534
- break;
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
- data[axes.rows[y] + column] = detectionMaskId;
537
- y += 1;
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
- * A stroke is measured in texels of the raster it is drawn on, so a coarser
554
- * raster measures it in coarser texels. A stroke of a texel or more keeps at
555
- * least one, the thinnest line the shader can draw; a narrower one keeps its
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(Math.max(strokeWidth * scale, Math.min(strokeWidth, 1)), MAX_ID_MASK_STROKE_WIDTH);
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);