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.
Files changed (197) 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/editing.d.ts +1 -1
  7. package/dist/editing.d.ts.map +1 -1
  8. package/dist/index.d.ts +4 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +9845 -4759
  11. package/dist/index.js.map +1 -1
  12. package/dist/mask-preparation.worker.js +788 -25
  13. package/dist/mask-preparation.worker.js.map +1 -1
  14. package/dist/media/decoder-deadline.d.ts +18 -0
  15. package/dist/media/decoder-deadline.d.ts.map +1 -0
  16. package/dist/media/depth-preview-probe.d.ts +65 -0
  17. package/dist/media/depth-preview-probe.d.ts.map +1 -0
  18. package/dist/media/depth-preview-track.d.ts +96 -0
  19. package/dist/media/depth-preview-track.d.ts.map +1 -0
  20. package/dist/media/media-normalization.d.ts.map +1 -1
  21. package/dist/media/media-source.d.ts +7 -0
  22. package/dist/media/media-source.d.ts.map +1 -1
  23. package/dist/media/mediabunny-frame-clock.d.ts +7 -0
  24. package/dist/media/mediabunny-frame-clock.d.ts.map +1 -0
  25. package/dist/media/mediabunny-media-source.d.ts +3 -0
  26. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  27. package/dist/media/track-frame-index.d.ts +14 -0
  28. package/dist/media/track-frame-index.d.ts.map +1 -0
  29. package/dist/media/video-engine-media-source.d.ts.map +1 -1
  30. package/dist/media/video-engine-media-source.js +1 -1
  31. package/dist/render-preparation/depth/clip-preview.d.ts +41 -0
  32. package/dist/render-preparation/depth/clip-preview.d.ts.map +1 -0
  33. package/dist/render-preparation/depth/clip-timing.d.ts +18 -0
  34. package/dist/render-preparation/depth/clip-timing.d.ts.map +1 -0
  35. package/dist/render-preparation/depth/clip.d.ts +14 -0
  36. package/dist/render-preparation/depth/clip.d.ts.map +1 -0
  37. package/dist/render-preparation/depth/exact-at-rest.d.ts +44 -0
  38. package/dist/render-preparation/depth/exact-at-rest.d.ts.map +1 -0
  39. package/dist/render-preparation/depth/exact-playback.d.ts +40 -0
  40. package/dist/render-preparation/depth/exact-playback.d.ts.map +1 -0
  41. package/dist/render-preparation/depth/files.d.ts +55 -0
  42. package/dist/render-preparation/depth/files.d.ts.map +1 -0
  43. package/dist/render-preparation/depth/frame-decode.d.ts +20 -0
  44. package/dist/render-preparation/depth/frame-decode.d.ts.map +1 -0
  45. package/dist/render-preparation/depth/frame-preparer.d.ts +51 -0
  46. package/dist/render-preparation/depth/frame-preparer.d.ts.map +1 -0
  47. package/dist/render-preparation/depth/frame-window.d.ts +146 -0
  48. package/dist/render-preparation/depth/frame-window.d.ts.map +1 -0
  49. package/dist/render-preparation/depth/options.d.ts +37 -0
  50. package/dist/render-preparation/depth/options.d.ts.map +1 -0
  51. package/dist/render-preparation/depth/png16.d.ts +40 -0
  52. package/dist/render-preparation/depth/png16.d.ts.map +1 -0
  53. package/dist/render-preparation/depth/preview-luma-copier.d.ts +12 -0
  54. package/dist/render-preparation/depth/preview-luma-copier.d.ts.map +1 -0
  55. package/dist/render-preparation/depth/preview-luma.d.ts +48 -0
  56. package/dist/render-preparation/depth/preview-luma.d.ts.map +1 -0
  57. package/dist/render-preparation/depth/source.d.ts +123 -0
  58. package/dist/render-preparation/depth/source.d.ts.map +1 -0
  59. package/dist/render-preparation/depth/worker-protocol.d.ts +76 -0
  60. package/dist/render-preparation/depth/worker-protocol.d.ts.map +1 -0
  61. package/dist/render-preparation/mask-frame-artifact.d.ts +10 -0
  62. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  63. package/dist/render-preparation/mask-frame-compositor.d.ts +2 -1
  64. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  65. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  66. package/dist/render-preparation/mask-outline-paths.d.ts +3 -0
  67. package/dist/render-preparation/mask-outline-paths.d.ts.map +1 -0
  68. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +2 -1
  69. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  70. package/dist/render-preparation/playhead-motion.d.ts +67 -0
  71. package/dist/render-preparation/playhead-motion.d.ts.map +1 -0
  72. package/dist/render-preparation/prepared-render-window.d.ts +12 -0
  73. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  74. package/dist/renderers/depth-textures.d.ts +79 -0
  75. package/dist/renderers/depth-textures.d.ts.map +1 -0
  76. package/dist/renderers/detection-render-order.d.ts +4 -0
  77. package/dist/renderers/detection-render-order.d.ts.map +1 -0
  78. package/dist/renderers/id-mask-stroke-coverage.d.ts +3 -0
  79. package/dist/renderers/id-mask-stroke-coverage.d.ts.map +1 -0
  80. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  81. package/dist/renderers/media-renderer-scene.d.ts +16 -2
  82. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  83. package/dist/renderers/pixi-batch-texture-bindings.d.ts +9 -0
  84. package/dist/renderers/pixi-batch-texture-bindings.d.ts.map +1 -0
  85. package/dist/renderers/pixi-box-layer.d.ts +1 -0
  86. package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
  87. package/dist/renderers/pixi-depth-layer.d.ts +76 -0
  88. package/dist/renderers/pixi-depth-layer.d.ts.map +1 -0
  89. package/dist/renderers/pixi-depth-shader.d.ts +76 -0
  90. package/dist/renderers/pixi-depth-shader.d.ts.map +1 -0
  91. package/dist/renderers/pixi-focus-layer.d.ts +1 -0
  92. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  93. package/dist/renderers/pixi-frame-present.d.ts +1 -0
  94. package/dist/renderers/pixi-frame-present.d.ts.map +1 -1
  95. package/dist/renderers/pixi-id-mask-shader.d.ts +1 -0
  96. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  97. package/dist/renderers/pixi-interaction-layer.d.ts +1 -1
  98. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  99. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +1 -0
  100. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  101. package/dist/renderers/pixi-mask-brush-preview.d.ts.map +1 -1
  102. package/dist/renderers/pixi-mask-halo.d.ts +1 -0
  103. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  104. package/dist/renderers/pixi-mask-layer.d.ts +8 -2
  105. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  106. package/dist/renderers/pixi-mask-screen-stroke.d.ts +17 -0
  107. package/dist/renderers/pixi-mask-screen-stroke.d.ts.map +1 -0
  108. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  109. package/dist/renderers/pixi-region-coverage-mask.d.ts +1 -0
  110. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -1
  111. package/dist/renderers/pixi-region-layer.d.ts +1 -0
  112. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  113. package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
  114. package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
  115. package/dist/renderers/pixi-vector-layer.d.ts +1 -0
  116. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  117. package/dist/renderers/prepared-annotation-window.d.ts +1 -0
  118. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -1
  119. package/dist/renderers/render-preparation-report.d.ts +23 -0
  120. package/dist/renderers/render-preparation-report.d.ts.map +1 -0
  121. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  122. package/dist/sessions/media-session-state.d.ts.map +1 -1
  123. package/dist/sessions/media-session.d.ts.map +1 -1
  124. package/dist/tracking.worker.js +64 -0
  125. package/dist/tracking.worker.js.map +1 -1
  126. package/dist/types/media-depth.d.ts +55 -0
  127. package/dist/types/media-depth.d.ts.map +1 -0
  128. package/dist/types/media-renderer.d.ts +33 -0
  129. package/dist/types/media-renderer.d.ts.map +1 -1
  130. package/dist/types/media-session.d.ts +21 -0
  131. package/dist/types/media-session.d.ts.map +1 -1
  132. package/dist/types/render-preparation.d.ts +105 -3
  133. package/dist/types/render-preparation.d.ts.map +1 -1
  134. package/dist/{video-engine-media-source-DR-x1UfK.js → video-engine-media-source-DOFsuGS4.js} +35 -29
  135. package/dist/video-engine-media-source-DOFsuGS4.js.map +1 -0
  136. package/dist/web-video-engine/analysis.js +6 -43
  137. package/dist/web-video-engine/cache-budget.d.ts +4 -7
  138. package/dist/web-video-engine/constants.d.ts +3 -4
  139. package/dist/web-video-engine/create-scrub-cursor.d.ts +1 -0
  140. package/dist/web-video-engine/decode-resolution-fynpRIAG.js +109 -0
  141. package/dist/web-video-engine/decode-scheduler.d.ts +1 -0
  142. package/dist/web-video-engine/engine-core.d.ts +4 -2
  143. package/dist/web-video-engine/engine.js +8 -6
  144. package/dist/web-video-engine/engine.worker.js +164 -66
  145. package/dist/web-video-engine/frame-cache.d.ts +9 -0
  146. package/dist/web-video-engine/frame-index.d.ts +1 -0
  147. package/dist/web-video-engine/frame-index.js +97 -0
  148. package/dist/web-video-engine/{frame-timeline-DreEwsRY.js → frame-timeline-CYgzzmpA.js} +1 -109
  149. package/dist/web-video-engine/frame-timeline-reader.d.ts +21 -0
  150. package/dist/web-video-engine/video-engine.d.ts +9 -4
  151. package/node_modules/supervision-js-core/THIRD_PARTY_NOTICES.md +132 -0
  152. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  153. package/node_modules/supervision-js-core/dist/index.d.ts +8 -3
  154. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  155. package/node_modules/supervision-js-core/dist/index.js +1172 -70
  156. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  157. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts +2 -2
  158. package/node_modules/supervision-js-core/dist/interactions/annotation-editing-engine.d.ts.map +1 -1
  159. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts +2 -2
  160. package/node_modules/supervision-js-core/dist/interactions/annotation-handles.d.ts.map +1 -1
  161. package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
  162. package/node_modules/supervision-js-core/dist/interactions/viewport-controller.d.ts.map +1 -1
  163. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
  164. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
  165. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +50 -3
  166. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  167. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts +203 -0
  168. package/node_modules/supervision-js-core/dist/types/depth-map.d.ts.map +1 -0
  169. package/node_modules/supervision-js-core/dist/types/detections.d.ts +2 -1
  170. package/node_modules/supervision-js-core/dist/types/detections.d.ts.map +1 -1
  171. package/node_modules/supervision-js-core/dist/types/editing.d.ts +9 -0
  172. package/node_modules/supervision-js-core/dist/types/editing.d.ts.map +1 -1
  173. package/node_modules/supervision-js-core/dist/types/interaction.d.ts +4 -0
  174. package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
  175. package/node_modules/supervision-js-core/dist/types/mask-style.d.ts +3 -0
  176. package/node_modules/supervision-js-core/dist/types/mask-style.d.ts.map +1 -1
  177. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +3 -0
  178. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  179. package/node_modules/supervision-js-core/dist/types/viewport.d.ts +3 -0
  180. package/node_modules/supervision-js-core/dist/types/viewport.d.ts.map +1 -1
  181. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts +91 -0
  182. package/node_modules/supervision-js-core/dist/utils/depth-color-mapping.d.ts.map +1 -0
  183. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts +30 -0
  184. package/node_modules/supervision-js-core/dist/utils/depth-colormap-tables.d.ts.map +1 -0
  185. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts +16 -0
  186. package/node_modules/supervision-js-core/dist/utils/depth-colormaps.d.ts.map +1 -0
  187. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts +23 -0
  188. package/node_modules/supervision-js-core/dist/utils/depth-manifest.d.ts.map +1 -0
  189. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts +14 -0
  190. package/node_modules/supervision-js-core/dist/utils/depth-readout.d.ts.map +1 -0
  191. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +21 -0
  192. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  193. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +3 -7
  194. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  195. package/node_modules/supervision-js-core/package.json +2 -1
  196. package/package.json +6 -1
  197. 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
- const maskWidth = Math.max(...instructions.map(({ mask }) => mask.width));
433
- const maskHeight = Math.max(...instructions.map(({ mask }) => mask.height));
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 column = axes.columns[x];
532
- for (let step = 0; step < runLength; step += 1) {
533
- if (x >= maskWidth) {
534
- break;
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
- data[axes.rows[y] + column] = detectionMaskId;
537
- y += 1;
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
- * 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.
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(Math.max(strokeWidth * scale, Math.min(strokeWidth, 1)), MAX_ID_MASK_STROKE_WIDTH);
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 && bounds) {
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
- }, [imageBitmap, ...idMaskTransfers, ...coverageTransfers]);
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
- }, [imageData.data.buffer, ...idMaskTransfers, ...coverageTransfers]);
1968
+ }, [
1969
+ imageData.data.buffer,
1970
+ ...idMaskTransfers,
1971
+ ...strokeTransfers,
1972
+ ...coverageTransfers,
1973
+ ]);
1211
1974
  }
1212
1975
  catch (error) {
1213
1976
  workerScope.postMessage({