supervision 0.1.7 → 0.2.0-next.0

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 (215) hide show
  1. package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
  2. package/dist/index.d.ts +11 -4
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8856 -4559
  5. package/dist/index.js.map +1 -1
  6. package/dist/mask-preparation.worker.js +310 -164
  7. package/dist/mask-preparation.worker.js.map +1 -1
  8. package/dist/media/display-pixel-ratio.d.ts +15 -0
  9. package/dist/media/display-pixel-ratio.d.ts.map +1 -0
  10. package/dist/media/engine-import-failure.d.ts +16 -0
  11. package/dist/media/engine-import-failure.d.ts.map +1 -0
  12. package/dist/media/media-condition-probe.d.ts +51 -0
  13. package/dist/media/media-condition-probe.d.ts.map +1 -0
  14. package/dist/media/media-conditions.d.ts +28 -0
  15. package/dist/media/media-conditions.d.ts.map +1 -0
  16. package/dist/media/media-errors.d.ts +6 -0
  17. package/dist/media/media-errors.d.ts.map +1 -1
  18. package/dist/media/media-normalization.d.ts.map +1 -1
  19. package/dist/media/media-probe.d.ts.map +1 -1
  20. package/dist/media/media-source.d.ts +17 -0
  21. package/dist/media/media-source.d.ts.map +1 -1
  22. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  23. package/dist/media/video-engine-media-source.d.ts +44 -0
  24. package/dist/media/video-engine-media-source.d.ts.map +1 -0
  25. package/dist/render-preparation/mask-frame-artifact.d.ts +46 -12
  26. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  27. package/dist/render-preparation/mask-frame-compositor.d.ts +5 -5
  28. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  29. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  30. package/dist/render-preparation/mask-preparation-worker-count.d.ts +4 -4
  31. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +12 -3
  32. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  33. package/dist/render-preparation/prepared-render-window.d.ts +23 -0
  34. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  35. package/dist/render-preparation/prepared-window-timeline.d.ts +2 -6
  36. package/dist/render-preparation/prepared-window-timeline.d.ts.map +1 -1
  37. package/dist/renderers/mask-palette.d.ts +14 -0
  38. package/dist/renderers/mask-palette.d.ts.map +1 -0
  39. package/dist/renderers/mask-vertex.d.ts +14 -0
  40. package/dist/renderers/mask-vertex.d.ts.map +1 -0
  41. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  42. package/dist/renderers/media-renderer-scene.d.ts +37 -0
  43. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  44. package/dist/renderers/media-renderer-state.d.ts +11 -1
  45. package/dist/renderers/media-renderer-state.d.ts.map +1 -1
  46. package/dist/renderers/media-renderer-transport.d.ts +59 -0
  47. package/dist/renderers/media-renderer-transport.d.ts.map +1 -0
  48. package/dist/renderers/pixi-box-layer.d.ts +5 -0
  49. package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
  50. package/dist/renderers/pixi-focus-layer.d.ts +18 -2
  51. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  52. package/dist/renderers/pixi-frame-present.d.ts +77 -0
  53. package/dist/renderers/pixi-frame-present.d.ts.map +1 -0
  54. package/dist/renderers/pixi-id-mask-shader.d.ts +12 -2
  55. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  56. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  57. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +13 -2
  58. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  59. package/dist/renderers/pixi-mask-halo.d.ts +24 -0
  60. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  61. package/dist/renderers/pixi-mask-layer.d.ts +71 -8
  62. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  63. package/dist/renderers/pixi-media-scene.d.ts +67 -0
  64. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  65. package/dist/renderers/pixi-polygon-layer.d.ts +7 -2
  66. package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
  67. package/dist/renderers/pixi-region-coverage-mask.d.ts +91 -0
  68. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -0
  69. package/dist/renderers/pixi-region-effect.d.ts +42 -0
  70. package/dist/renderers/pixi-region-effect.d.ts.map +1 -0
  71. package/dist/renderers/pixi-region-layer.d.ts +68 -2
  72. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  73. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  74. package/dist/renderers/prepared-annotation-window.d.ts +47 -0
  75. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -0
  76. package/dist/renderers/presented-frame-channel.d.ts +107 -0
  77. package/dist/renderers/presented-frame-channel.d.ts.map +1 -0
  78. package/dist/renderers/scene-render-scheduler.d.ts +20 -0
  79. package/dist/renderers/scene-render-scheduler.d.ts.map +1 -0
  80. package/dist/sessions/media-session-defaults.d.ts +10 -6
  81. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  82. package/dist/sessions/media-session-media.d.ts.map +1 -1
  83. package/dist/sessions/media-session-state.d.ts.map +1 -1
  84. package/dist/sessions/media-session.d.ts.map +1 -1
  85. package/dist/tracking.worker.js +58 -3
  86. package/dist/tracking.worker.js.map +1 -1
  87. package/dist/types/media-conditions.d.ts +149 -0
  88. package/dist/types/media-conditions.d.ts.map +1 -0
  89. package/dist/types/media-normalization.d.ts +34 -0
  90. package/dist/types/media-normalization.d.ts.map +1 -1
  91. package/dist/types/media-renderer.d.ts +31 -1
  92. package/dist/types/media-renderer.d.ts.map +1 -1
  93. package/dist/types/media-session.d.ts +89 -8
  94. package/dist/types/media-session.d.ts.map +1 -1
  95. package/dist/types/render-preparation.d.ts +101 -4
  96. package/dist/types/render-preparation.d.ts.map +1 -1
  97. package/dist/web-video-engine/analysis-session.d.ts +76 -0
  98. package/dist/web-video-engine/analysis-session.d.ts.map +1 -0
  99. package/dist/web-video-engine/analysis.d.ts +7 -0
  100. package/dist/web-video-engine/analysis.d.ts.map +1 -0
  101. package/dist/web-video-engine/analysis.js +1678 -0
  102. package/dist/web-video-engine/analysis.js.map +1 -0
  103. package/dist/web-video-engine/cache-budget.d.ts +19 -0
  104. package/dist/web-video-engine/cache-budget.d.ts.map +1 -0
  105. package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts +90 -0
  106. package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts.map +1 -0
  107. package/dist/web-video-engine/clock.d.ts +83 -0
  108. package/dist/web-video-engine/clock.d.ts.map +1 -0
  109. package/dist/web-video-engine/constants.d.ts +253 -0
  110. package/dist/web-video-engine/constants.d.ts.map +1 -0
  111. package/dist/web-video-engine/create-scrub-cursor.d.ts +62 -0
  112. package/dist/web-video-engine/create-scrub-cursor.d.ts.map +1 -0
  113. package/dist/web-video-engine/decode-resolution.d.ts +115 -0
  114. package/dist/web-video-engine/decode-resolution.d.ts.map +1 -0
  115. package/dist/web-video-engine/decode-scheduler.d.ts +356 -0
  116. package/dist/web-video-engine/decode-scheduler.d.ts.map +1 -0
  117. package/dist/web-video-engine/decode-session.d.ts +276 -0
  118. package/dist/web-video-engine/decode-session.d.ts.map +1 -0
  119. package/dist/web-video-engine/decode-source.d.ts +250 -0
  120. package/dist/web-video-engine/decode-source.d.ts.map +1 -0
  121. package/dist/web-video-engine/diagnostics-store.d.ts +18 -0
  122. package/dist/web-video-engine/diagnostics-store.d.ts.map +1 -0
  123. package/dist/web-video-engine/diagnostics.d.ts +316 -0
  124. package/dist/web-video-engine/diagnostics.d.ts.map +1 -0
  125. package/dist/web-video-engine/embedded-engine-worker.d.ts +2 -0
  126. package/dist/web-video-engine/embedded-engine-worker.d.ts.map +1 -0
  127. package/dist/web-video-engine/engine-core.d.ts +258 -0
  128. package/dist/web-video-engine/engine-core.d.ts.map +1 -0
  129. package/dist/web-video-engine/engine.d.ts +26 -0
  130. package/dist/web-video-engine/engine.d.ts.map +1 -0
  131. package/dist/web-video-engine/engine.js +873 -0
  132. package/dist/web-video-engine/engine.js.map +1 -0
  133. package/dist/web-video-engine/engine.worker.d.ts +2 -0
  134. package/dist/web-video-engine/engine.worker.d.ts.map +1 -0
  135. package/dist/web-video-engine/engine.worker.js +31320 -0
  136. package/dist/web-video-engine/engine.worker.js.map +1 -0
  137. package/dist/web-video-engine/frame-cache.d.ts +129 -0
  138. package/dist/web-video-engine/frame-cache.d.ts.map +1 -0
  139. package/dist/web-video-engine/frame-extractor.d.ts +28 -0
  140. package/dist/web-video-engine/frame-extractor.d.ts.map +1 -0
  141. package/dist/web-video-engine/frame-timeline-GINI8Gum.js +560 -0
  142. package/dist/web-video-engine/frame-timeline-GINI8Gum.js.map +1 -0
  143. package/dist/web-video-engine/frame-timeline.d.ts +71 -0
  144. package/dist/web-video-engine/frame-timeline.d.ts.map +1 -0
  145. package/dist/web-video-engine/frame-walker.d.ts +74 -0
  146. package/dist/web-video-engine/frame-walker.d.ts.map +1 -0
  147. package/dist/web-video-engine/index.d.ts +3 -0
  148. package/dist/web-video-engine/index.d.ts.map +1 -0
  149. package/dist/web-video-engine/index.js +3 -0
  150. package/dist/web-video-engine/index.js.map +1 -0
  151. package/dist/web-video-engine/key-packet.d.ts +65 -0
  152. package/dist/web-video-engine/key-packet.d.ts.map +1 -0
  153. package/dist/web-video-engine/keyframe-index.d.ts +91 -0
  154. package/dist/web-video-engine/keyframe-index.d.ts.map +1 -0
  155. package/dist/web-video-engine/mirror-store.d.ts +56 -0
  156. package/dist/web-video-engine/mirror-store.d.ts.map +1 -0
  157. package/dist/web-video-engine/renderer.d.ts +94 -0
  158. package/dist/web-video-engine/renderer.d.ts.map +1 -0
  159. package/dist/web-video-engine/rotation.d.ts +35 -0
  160. package/dist/web-video-engine/rotation.d.ts.map +1 -0
  161. package/dist/web-video-engine/scrub-controller.d.ts +376 -0
  162. package/dist/web-video-engine/scrub-controller.d.ts.map +1 -0
  163. package/dist/web-video-engine/scrub-cursor.d.ts +342 -0
  164. package/dist/web-video-engine/scrub-cursor.d.ts.map +1 -0
  165. package/dist/web-video-engine/scrub-trajectory.d.ts +30 -0
  166. package/dist/web-video-engine/scrub-trajectory.d.ts.map +1 -0
  167. package/dist/web-video-engine/source-residency.d.ts +57 -0
  168. package/dist/web-video-engine/source-residency.d.ts.map +1 -0
  169. package/dist/web-video-engine/trace-recorder.d.ts +164 -0
  170. package/dist/web-video-engine/trace-recorder.d.ts.map +1 -0
  171. package/dist/web-video-engine/types.d.ts +204 -0
  172. package/dist/web-video-engine/types.d.ts.map +1 -0
  173. package/dist/web-video-engine/video-engine.d.ts +426 -0
  174. package/dist/web-video-engine/video-engine.d.ts.map +1 -0
  175. package/dist/web-video-engine/webgpu-renderer.d.ts +62 -0
  176. package/dist/web-video-engine/webgpu-renderer.d.ts.map +1 -0
  177. package/dist/web-video-engine/worker-bridge.d.ts +12 -0
  178. package/dist/web-video-engine/worker-bridge.d.ts.map +1 -0
  179. package/dist/web-video-engine/worker-dispatch.d.ts +17 -0
  180. package/dist/web-video-engine/worker-dispatch.d.ts.map +1 -0
  181. package/dist/web-video-engine/worker-protocol.d.ts +267 -0
  182. package/dist/web-video-engine/worker-protocol.d.ts.map +1 -0
  183. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  184. package/node_modules/supervision-js-core/dist/detections/composite-detection-frame-source.d.ts.map +1 -1
  185. package/node_modules/supervision-js-core/dist/index.d.ts +4 -3
  186. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  187. package/node_modules/supervision-js-core/dist/index.js +1075 -266
  188. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  189. package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
  190. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts +4 -36
  191. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
  192. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts +5 -0
  193. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
  194. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +92 -7
  195. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  196. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts +179 -17
  197. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts.map +1 -1
  198. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +78 -2
  199. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  200. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts +2 -0
  201. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts.map +1 -1
  202. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts +16 -0
  203. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts.map +1 -1
  204. package/node_modules/supervision-js-core/dist/utils/detection-conversions.d.ts.map +1 -1
  205. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +5 -0
  206. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  207. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts +9 -0
  208. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts.map +1 -1
  209. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts +18 -0
  210. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts.map +1 -0
  211. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +15 -2
  212. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  213. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts +7 -0
  214. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts.map +1 -0
  215. package/package.json +22 -1
@@ -17,6 +17,12 @@
17
17
  (function (DetectionBufferStatus) {
18
18
  DetectionBufferStatus["Idle"] = "idle";
19
19
  DetectionBufferStatus["Loading"] = "loading";
20
+ /**
21
+ * The playback gate is holding playback until the source produces detections
22
+ * for the requested range. {@link DetectionBufferStatus.Loading} fetches
23
+ * detections the source already has; this one waits on a producer.
24
+ */
25
+ DetectionBufferStatus["AwaitingCoverage"] = "awaitingCoverage";
20
26
  DetectionBufferStatus["Ready"] = "ready";
21
27
  DetectionBufferStatus["Error"] = "error";
22
28
  DetectionBufferStatus["Destroyed"] = "destroyed";
@@ -29,12 +35,30 @@
29
35
  /**
30
36
  * Select the frame whose `[mediaTime, endTime)` interval contains the media
31
37
  * time. This is the default for interval annotations and timestamped sources.
38
+ *
39
+ * Frame times must come from the media itself. Times reconstructed from a
40
+ * nominal frame rate drift against a clip whose real rate differs, and once
41
+ * that drift passes the half-millisecond selection tolerance a playhead
42
+ * landing on a frame boundary selects the previous frame's detections.
43
+ *
44
+ * A frame that carries no `endTime` stays active until the next frame starts,
45
+ * which bridges an index the source never produced.
46
+ * {@link DetectionFrameSelectionMode.NearestFrameIndex} bounds each frame to
47
+ * one grid step instead, so a missing index reads as no detections.
32
48
  */
33
49
  DetectionFrameSelectionMode["Interval"] = "interval";
34
50
  /**
35
- * Select from a known inference frame grid using `frameIndex`, `frameRate`,
36
- * and optional `frameIndexOriginTime`. This is useful when inference was run
37
- * on normalized frames and playback should snap detections to that grid.
51
+ * Select from a known inference frame grid using `frameIndex` and
52
+ * `frameRate`. This is useful when inference was run on normalized frames and
53
+ * playback should snap detections to that grid.
54
+ *
55
+ * A frame speaks for one grid step starting at its own media time, and an
56
+ * index the source never produced selects nothing rather than a neighbour.
57
+ *
58
+ * A frame carrying no `frameIndex` speaks for its step on the same terms,
59
+ * reached by the time it starts at rather than by an index that names it. Its
60
+ * `endTime` does not widen it past that step, so a source that labels only
61
+ * part of what it writes cannot bridge the indexes it never produced.
38
62
  */
39
63
  DetectionFrameSelectionMode["NearestFrameIndex"] = "nearestFrameIndex";
40
64
  })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));
@@ -177,13 +201,16 @@
177
201
  width,
178
202
  };
179
203
  }
180
- function assertMaskDimensions(data, width, height) {
204
+ function assertMaskSize(width, height) {
181
205
  if (!Number.isInteger(width) || width <= 0) {
182
206
  throw new Error("Mask width must be a positive integer.");
183
207
  }
184
208
  if (!Number.isInteger(height) || height <= 0) {
185
209
  throw new Error("Mask height must be a positive integer.");
186
210
  }
211
+ }
212
+ function assertMaskDimensions(data, width, height) {
213
+ assertMaskSize(width, height);
187
214
  if (data.length !== width * height) {
188
215
  throw new Error("Mask data length must equal width * height.");
189
216
  }
@@ -362,9 +389,9 @@
362
389
  }
363
390
  intersections.sort((left, right) => left - right);
364
391
  for (let index = 0; index < intersections.length - 1; index += 2) {
365
- const left = Math.max(0, Math.ceil(intersections[index]));
366
- const right = Math.min(dimensions.width - 1, Math.floor(intersections[index + 1]));
367
- for (let x = left; x <= right; x += 1) {
392
+ const left = Math.max(0, Math.round(intersections[index]));
393
+ const right = Math.min(dimensions.width, Math.round(intersections[index + 1]));
394
+ for (let x = left; x < right; x += 1) {
368
395
  data[y * dimensions.width + x] = 1;
369
396
  }
370
397
  }
@@ -381,14 +408,28 @@
381
408
  return new Uint8Array(dimensions.width * dimensions.height);
382
409
  }
383
410
 
384
- const MAX_ID_MASK_PALETTE_ENTRIES = 64;
411
+ /**
412
+ * Palette slots an id raster can name, one of them the background. A GLSL
413
+ * fragment stage is guaranteed only 224 uniform vectors, and a palette entry
414
+ * costs 2.25 of them, so this stays a multiple of four and well inside that
415
+ * budget; past it a frame draws from the RGBA composite instead.
416
+ */
417
+ const MAX_ID_MASK_PALETTE_ENTRIES = 80;
385
418
  const MAX_ID_MASK_STROKE_WIDTH = 16;
386
- function createIdMaskFrame(instructions) {
419
+ function createIdMaskFrame(instructions, options = {}) {
387
420
  if (instructions.length === 0) {
388
421
  return undefined;
389
422
  }
390
- const width = Math.max(...instructions.map(({ mask }) => mask.width));
391
- const height = Math.max(...instructions.map(({ mask }) => mask.height));
423
+ const maskWidth = Math.max(...instructions.map(({ mask }) => mask.width));
424
+ const maskHeight = Math.max(...instructions.map(({ mask }) => mask.height));
425
+ const width = resolveRasterWidth(maskWidth, options.maxWidth);
426
+ const height = width === maskWidth
427
+ ? maskHeight
428
+ : Math.max(1, Math.round((width * maskHeight) / maskWidth));
429
+ const strokeScale = width / maskWidth;
430
+ const scaledAxes = width === maskWidth
431
+ ? undefined
432
+ : createScaledMaskAxes({ height, maskHeight, maskWidth, width });
392
433
  const data = new Uint8Array(new ArrayBuffer(width * height));
393
434
  const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
394
435
  const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
@@ -403,20 +444,17 @@
403
444
  }
404
445
  writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);
405
446
  if (instruction.stroke && instruction.stroke.width > 0) {
406
- const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);
447
+ const strokeWidth = Math.min(resolveStrokeTexels(instruction.stroke.width, strokeScale), MAX_ID_MASK_STROKE_WIDTH);
407
448
  hasStroke = true;
408
449
  strokeWidths[detectionMaskId] = strokeWidth;
409
450
  maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);
410
451
  writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);
411
452
  }
412
- const decodedMask = decodeCompressedRleMask(instruction.mask);
413
- for (let y = 0; y < decodedMask.height; y += 1) {
414
- for (let x = 0; x < decodedMask.width; x += 1) {
415
- const maskOffset = y * decodedMask.width + x;
416
- if (decodedMask.data[maskOffset]) {
417
- data[y * width + x] = detectionMaskId;
418
- }
419
- }
453
+ if (scaledAxes) {
454
+ writeScaledMaskRuns(data, scaledAxes, instruction.mask, detectionMaskId);
455
+ }
456
+ else {
457
+ writeMaskRuns(data, width, instruction.mask, detectionMaskId);
420
458
  }
421
459
  }
422
460
  return {
@@ -430,6 +468,103 @@
430
468
  width,
431
469
  };
432
470
  }
471
+ /**
472
+ * Compressed RLE counts runs down each column in turn, so a foreground run is
473
+ * a contiguous walk down one column that wraps into the next.
474
+ */
475
+ function writeMaskRuns(data, frameWidth, mask, detectionMaskId) {
476
+ const counts = decodeCompressedRleCounts(mask.counts);
477
+ const maskWidth = mask.width;
478
+ const maskHeight = mask.height;
479
+ let maskOffset = 0;
480
+ for (let index = 0; index < counts.length; index += 1) {
481
+ const runLength = counts[index] ?? 0;
482
+ if (index % 2 === 0 || runLength <= 0) {
483
+ maskOffset += runLength;
484
+ continue;
485
+ }
486
+ let x = Math.floor(maskOffset / maskHeight);
487
+ let y = maskOffset - x * maskHeight;
488
+ let frameOffset = y * frameWidth + x;
489
+ for (let step = 0; step < runLength; step += 1) {
490
+ if (x >= maskWidth) {
491
+ break;
492
+ }
493
+ data[frameOffset] = detectionMaskId;
494
+ y += 1;
495
+ frameOffset += frameWidth;
496
+ if (y === maskHeight) {
497
+ y = 0;
498
+ x += 1;
499
+ frameOffset = x;
500
+ }
501
+ }
502
+ maskOffset += runLength;
503
+ }
504
+ }
505
+ /**
506
+ * The scaled twin of writeMaskRuns. Every source pixel marks the texel it lands
507
+ * in, so a mask keeps every part of itself the smaller raster can hold and
508
+ * gains up to a texel at its edges.
509
+ */
510
+ function writeScaledMaskRuns(data, axes, mask, detectionMaskId) {
511
+ const counts = decodeCompressedRleCounts(mask.counts);
512
+ const maskWidth = mask.width;
513
+ const maskHeight = mask.height;
514
+ let maskOffset = 0;
515
+ for (let index = 0; index < counts.length; index += 1) {
516
+ const runLength = counts[index] ?? 0;
517
+ if (index % 2 === 0 || runLength <= 0) {
518
+ maskOffset += runLength;
519
+ continue;
520
+ }
521
+ let x = Math.floor(maskOffset / maskHeight);
522
+ let y = maskOffset - x * maskHeight;
523
+ let column = axes.columns[x];
524
+ for (let step = 0; step < runLength; step += 1) {
525
+ if (x >= maskWidth) {
526
+ break;
527
+ }
528
+ data[axes.rows[y] + column] = detectionMaskId;
529
+ y += 1;
530
+ if (y === maskHeight) {
531
+ y = 0;
532
+ x += 1;
533
+ column = axes.columns[x];
534
+ }
535
+ }
536
+ maskOffset += runLength;
537
+ }
538
+ }
539
+ function resolveRasterWidth(maskWidth, maxWidth) {
540
+ return maxWidth !== undefined && maxWidth > 0 && maxWidth < maskWidth
541
+ ? Math.max(1, Math.floor(maxWidth))
542
+ : maskWidth;
543
+ }
544
+ /**
545
+ * A stroke is measured in texels of the raster it is drawn on, so a coarser
546
+ * raster measures it in coarser texels. A stroke of a texel or more keeps at
547
+ * least one, the thinnest line the shader can draw; a narrower one keeps its
548
+ * own width, which the shader draws as an inner boundary at any scale.
549
+ */
550
+ function resolveStrokeTexels(strokeWidth, scale) {
551
+ return Math.max(strokeWidth * scale, Math.min(strokeWidth, 1));
552
+ }
553
+ function createScaledMaskAxes(frame) {
554
+ return {
555
+ columns: createMaskAxisMap(frame.maskWidth, frame.width, 1),
556
+ rows: createMaskAxisMap(frame.maskHeight, frame.height, frame.width),
557
+ };
558
+ }
559
+ /** Rows hold their destination offset, so writing a run is two lookups. */
560
+ function createMaskAxisMap(sourceLength, targetLength, stride) {
561
+ const map = new Int32Array(sourceLength);
562
+ const scale = targetLength / sourceLength;
563
+ for (let index = 0; index < sourceLength; index += 1) {
564
+ map[index] = Math.min(targetLength - 1, Math.floor(index * scale)) * stride;
565
+ }
566
+ return map;
567
+ }
433
568
  function writePaletteEntry(palette, id, color, alpha) {
434
569
  const offset = id * 4;
435
570
  palette[offset] = ((color >> 16) & 0xff) / 255;
@@ -496,6 +631,30 @@
496
631
  MediaSourceStatus["Error"] = "error";
497
632
  MediaSourceStatus["Destroyed"] = "destroyed";
498
633
  })(MediaSourceStatus || (MediaSourceStatus = {}));
634
+ /**
635
+ * Current renderer state.
636
+ */
637
+ /**
638
+ * What a playback gate holds back.
639
+ *
640
+ * A source the renderer pulls samples from can be held between any two frames,
641
+ * because the renderer decides when each one is drawn. A source that presents
642
+ * its own frames owns the playhead, so holding it means stopping the producer
643
+ * and starting it again, which the detection gate does and the
644
+ * render-preparation gate does not.
645
+ */
646
+ var PlaybackGateReach;
647
+ (function (PlaybackGateReach) {
648
+ /** No gate: the picture moves and unprepared layers are absent from it. */
649
+ PlaybackGateReach["Off"] = "off";
650
+ /**
651
+ * Playback waits to begin, and stops again at any frame whose artifacts are
652
+ * missing, until they arrive or the gate's own wait bound gives up on them.
653
+ */
654
+ PlaybackGateReach["EveryFrame"] = "everyFrame";
655
+ /** Playback waits to begin; frames after that are not held. */
656
+ PlaybackGateReach["StartOfPlayback"] = "startOfPlayback";
657
+ })(PlaybackGateReach || (PlaybackGateReach = {}));
499
658
 
500
659
  /**
501
660
  * Media-session operating mode.
@@ -536,6 +695,13 @@
536
695
  */
537
696
  var MediaSessionActivityKind;
538
697
  (function (MediaSessionActivityKind) {
698
+ /**
699
+ * Playback is held waiting for a producer to emit detections for the frame
700
+ * about to be shown, which is a wait on inference rather than on transfer or
701
+ * decode. `DetectionsBuffering` is the transfer of detections that already
702
+ * exist, and `PlaybackBuffering` is the wait for media bytes.
703
+ */
704
+ MediaSessionActivityKind["DetectionsAwaitingCoverage"] = "detectionsAwaitingCoverage";
539
705
  MediaSessionActivityKind["DetectionsBuffering"] = "detectionsBuffering";
540
706
  MediaSessionActivityKind["DetectionsLoading"] = "detectionsLoading";
541
707
  MediaSessionActivityKind["Error"] = "error";
@@ -554,9 +720,52 @@
554
720
  MediaSessionActivityStatus["Waiting"] = "waiting";
555
721
  })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
556
722
 
557
- const PNG_SIGNATURE = new Uint8Array([
558
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
559
- ]);
723
+ /** Builds compact, independent alpha crops so overlapping masks stay exact. */
724
+ function createRegionMaskCoverageFrame(instructions) {
725
+ const entries = [];
726
+ for (const instruction of instructions) {
727
+ if (!instruction.regionCoverageMask) {
728
+ continue;
729
+ }
730
+ const entry = cropCoverageMask(instruction.detectionIndex, decodeCompressedRleMask(instruction.regionCoverageMask));
731
+ if (entry) {
732
+ entries.push(entry);
733
+ }
734
+ }
735
+ return entries.length > 0 ? { entries } : undefined;
736
+ }
737
+ function cropCoverageMask(detectionIndex, decodedMask) {
738
+ let minX = decodedMask.width;
739
+ let minY = decodedMask.height;
740
+ let maxX = -1;
741
+ let maxY = -1;
742
+ for (let y = 0; y < decodedMask.height; y += 1) {
743
+ for (let x = 0; x < decodedMask.width; x += 1) {
744
+ if (!decodedMask.data[y * decodedMask.width + x]) {
745
+ continue;
746
+ }
747
+ minX = Math.min(minX, x);
748
+ minY = Math.min(minY, y);
749
+ maxX = Math.max(maxX, x);
750
+ maxY = Math.max(maxY, y);
751
+ }
752
+ }
753
+ if (maxX < minX || maxY < minY) {
754
+ return undefined;
755
+ }
756
+ const width = maxX - minX + 1;
757
+ const height = maxY - minY + 1;
758
+ const data = new Uint8Array(new ArrayBuffer(width * height));
759
+ for (let y = minY; y <= maxY; y += 1) {
760
+ for (let x = minX; x <= maxX; x += 1) {
761
+ if (!decodedMask.data[y * decodedMask.width + x]) {
762
+ continue;
763
+ }
764
+ data[(y - minY) * width + (x - minX)] = 255;
765
+ }
766
+ }
767
+ return { data, detectionIndex, height, width, x: minX, y: minY };
768
+ }
560
769
  function compositeMaskFrame(instructions) {
561
770
  const maskInstructions = materializeMaskInstructions(instructions);
562
771
  if (maskInstructions.length === 0) {
@@ -570,22 +779,23 @@
570
779
  }
571
780
  return { data, height, width };
572
781
  }
573
- function createMaskIdFrame(instructions) {
574
- return createIdMaskFrame(materializeMaskInstructions(instructions));
575
- }
576
- async function createPngIdMaskFrame(instructions) {
577
- const frame = createMaskIdFrame(instructions);
578
- if (!frame) {
782
+ function createIdMaskRasterFrame(instructions, maxRasterWidth) {
783
+ try {
784
+ return createIdMaskFrame(materializeMaskInstructions(instructions), {
785
+ maxWidth: maxRasterWidth,
786
+ });
787
+ }
788
+ catch {
789
+ // The id raster is the fast path, not the only one: answering with nothing
790
+ // puts the caller on the RGBA composite, which draws the same picture.
579
791
  return undefined;
580
792
  }
581
- return {
582
- ...frame,
583
- png: await encodeGrayscalePng({
584
- height: frame.height,
585
- pixels: frame.data,
586
- width: frame.width,
587
- }),
588
- };
793
+ }
794
+ function createIdMaskPlane(instructions, maxRasterWidth) {
795
+ const frame = createIdMaskRasterFrame(instructions, maxRasterWidth);
796
+ return frame
797
+ ? { data: frame.data, height: frame.height, width: frame.width }
798
+ : undefined;
589
799
  }
590
800
  function compositeInstruction(rgba, canvasWidth, instruction) {
591
801
  const decodedMask = decodeCompressedRleMask(instruction.mask);
@@ -596,7 +806,9 @@
596
806
  }
597
807
  }
598
808
  function materializeMaskInstructions(instructions) {
599
- return instructions.map((instruction) => {
809
+ return instructions
810
+ .filter((instruction) => instruction.visible !== false)
811
+ .map((instruction) => {
600
812
  if (instruction.mask) {
601
813
  return instruction;
602
814
  }
@@ -684,86 +896,23 @@
684
896
  rgba[rgbaOffset + 2] = color.blue;
685
897
  rgba[rgbaOffset + 3] = color.alpha;
686
898
  }
687
- async function encodeGrayscalePng(options) {
688
- if (typeof CompressionStream === "undefined") {
689
- throw new Error("CompressionStream is required to encode PNG ID masks.");
690
- }
691
- const rawScanlines = createFilterlessPngScanlines(options);
692
- const ihdr = new Uint8Array(13);
693
- const ihdrView = new DataView(ihdr.buffer);
694
- ihdrView.setUint32(0, options.width);
695
- ihdrView.setUint32(4, options.height);
696
- ihdr[8] = 8;
697
- ihdr[9] = 0;
698
- ihdr[10] = 0;
699
- ihdr[11] = 0;
700
- ihdr[12] = 0;
701
- const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])
702
- .stream()
703
- .pipeThrough(new CompressionStream("deflate"))).arrayBuffer());
704
- return concatUint8Arrays([
705
- PNG_SIGNATURE,
706
- createPngChunk("IHDR", ihdr),
707
- createPngChunk("IDAT", compressed),
708
- createPngChunk("IEND", new Uint8Array(0)),
709
- ]);
710
- }
711
- function createFilterlessPngScanlines(options) {
712
- const rowStride = options.width + 1;
713
- const scanlines = new Uint8Array(rowStride * options.height);
714
- for (let y = 0; y < options.height; y += 1) {
715
- const sourceOffset = y * options.width;
716
- const targetOffset = y * rowStride;
717
- scanlines[targetOffset] = 0;
718
- scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);
719
- }
720
- return scanlines;
721
- }
722
- function createPngChunk(type, data) {
723
- const typeBytes = new TextEncoder().encode(type);
724
- const chunk = new Uint8Array(12 + data.length);
725
- const view = new DataView(chunk.buffer);
726
- view.setUint32(0, data.length);
727
- chunk.set(typeBytes, 4);
728
- chunk.set(data, 8);
729
- view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));
730
- return chunk;
731
- }
732
- const crc32Table = createCrc32Table();
733
- function createCrc32Table() {
734
- const table = new Uint32Array(256);
735
- for (let index = 0; index < table.length; index += 1) {
736
- let value = index;
737
- for (let bit = 0; bit < 8; bit += 1) {
738
- value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
739
- }
740
- table[index] = value >>> 0;
741
- }
742
- return table;
743
- }
744
- function crc32(bytes) {
745
- let crc = 0xffffffff;
746
- for (const byte of bytes) {
747
- crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
748
- }
749
- return (crc ^ 0xffffffff) >>> 0;
750
- }
751
- function concatUint8Arrays(chunks) {
752
- const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);
753
- const result = new Uint8Array(totalLength);
754
- let offset = 0;
755
- for (const chunk of chunks) {
756
- result.set(chunk, offset);
757
- offset += chunk.length;
758
- }
759
- return result;
760
- }
761
899
 
762
900
  var PreparedMaskFrameKind;
763
901
  (function (PreparedMaskFrameKind) {
764
- PreparedMaskFrameKind["PngIdMask"] = "pngIdMask";
902
+ PreparedMaskFrameKind["IdMask"] = "idMask";
765
903
  PreparedMaskFrameKind["RgbaImage"] = "rgbaImage";
766
904
  })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));
905
+ /**
906
+ * How an id raster is laid out for the texture it is uploaded into. WebGPU
907
+ * takes any bytesPerRow, so ids go up one byte per pixel. WebGL aligns every
908
+ * uploaded row to four bytes and rejects a single-channel upload whose width
909
+ * is not a multiple of four, which is what the four-channel layout is for.
910
+ */
911
+ var IdMaskTextureFormat;
912
+ (function (IdMaskTextureFormat) {
913
+ IdMaskTextureFormat["R8"] = "r8unorm";
914
+ IdMaskTextureFormat["Rgba8"] = "rgba8unorm";
915
+ })(IdMaskTextureFormat || (IdMaskTextureFormat = {}));
767
916
 
768
917
  var MaskPreparationWorkerMessageType;
769
918
  (function (MaskPreparationWorkerMessageType) {
@@ -779,23 +928,39 @@
779
928
  if (message.type !== MaskPreparationWorkerMessageType.Prepare) {
780
929
  return;
781
930
  }
782
- void prepareMaskFrame(message);
931
+ prepareMaskFrame(message);
783
932
  });
784
- async function prepareMaskFrame(message) {
933
+ function prepareMaskFrame(message) {
785
934
  try {
786
- const pngIdMaskFrame = await createPngIdMaskWorkerResponse(message);
787
- if (pngIdMaskFrame) {
788
- workerScope.postMessage(pngIdMaskFrame, [
789
- pngIdMaskFrame.imageBitmap,
790
- pngIdMaskFrame.png.buffer,
791
- pngIdMaskFrame.fillPalette.buffer,
792
- pngIdMaskFrame.strokePalette.buffer,
793
- pngIdMaskFrame.strokeWidths.buffer,
935
+ const regionMaskCoverage = createRegionMaskCoverageFrame(message.job.instructions);
936
+ const coverageTransfers = getRegionMaskCoverageTransfers(regionMaskCoverage);
937
+ const idMaskFrame = createIdMaskRasterFrame(message.job.instructions, message.job.maxRasterWidth);
938
+ if (idMaskFrame) {
939
+ workerScope.postMessage({
940
+ artifactKind: PreparedMaskFrameKind.IdMask,
941
+ fillPalette: idMaskFrame.fillPalette,
942
+ hasStroke: idMaskFrame.hasStroke,
943
+ height: idMaskFrame.height,
944
+ key: message.job.key,
945
+ maxStrokeWidth: idMaskFrame.maxStrokeWidth,
946
+ raster: idMaskFrame.data,
947
+ regionMaskCoverage,
948
+ requestId: message.requestId,
949
+ strokePalette: idMaskFrame.strokePalette,
950
+ strokeWidths: idMaskFrame.strokeWidths,
951
+ type: MaskPreparationWorkerMessageType.Complete,
952
+ width: idMaskFrame.width,
953
+ }, [
954
+ idMaskFrame.data.buffer,
955
+ idMaskFrame.fillPalette.buffer,
956
+ idMaskFrame.strokePalette.buffer,
957
+ idMaskFrame.strokeWidths.buffer,
958
+ ...coverageTransfers,
794
959
  ]);
795
960
  return;
796
961
  }
797
962
  const compositedFrame = compositeMaskFrame(message.job.instructions);
798
- if (!compositedFrame) {
963
+ if (!compositedFrame && !regionMaskCoverage) {
799
964
  workerScope.postMessage({
800
965
  key: message.job.key,
801
966
  requestId: message.requestId,
@@ -803,26 +968,30 @@
803
968
  });
804
969
  return;
805
970
  }
806
- const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);
971
+ const preparedPixels = compositedFrame ?? createTransparentCoverageCarrier();
972
+ const imageData = new ImageData(preparedPixels.data, preparedPixels.width, preparedPixels.height);
807
973
  const imageBitmap = createImageBitmapFromImageData(imageData);
808
- const idMaskData = createMaskIdFrame(message.job.instructions)?.data;
974
+ const idMaskPlane = createIdMaskPlane(message.job.instructions, message.job.maxRasterWidth);
975
+ const idMaskTransfers = idMaskPlane ? [idMaskPlane.data.buffer] : [];
809
976
  if (imageBitmap) {
810
977
  workerScope.postMessage({
978
+ idMaskPlane,
811
979
  imageBitmap,
812
- idMaskData,
813
980
  key: message.job.key,
981
+ regionMaskCoverage,
814
982
  requestId: message.requestId,
815
983
  type: MaskPreparationWorkerMessageType.Complete,
816
- }, [...(idMaskData ? [idMaskData.buffer] : []), imageBitmap]);
984
+ }, [imageBitmap, ...idMaskTransfers, ...coverageTransfers]);
817
985
  return;
818
986
  }
819
987
  workerScope.postMessage({
820
- idMaskData,
988
+ idMaskPlane,
821
989
  imageData,
822
990
  key: message.job.key,
991
+ regionMaskCoverage,
823
992
  requestId: message.requestId,
824
993
  type: MaskPreparationWorkerMessageType.Complete,
825
- }, [...(idMaskData ? [idMaskData.buffer] : []), imageData.data.buffer]);
994
+ }, [imageData.data.buffer, ...idMaskTransfers, ...coverageTransfers]);
826
995
  }
827
996
  catch (error) {
828
997
  workerScope.postMessage({
@@ -835,43 +1004,20 @@
835
1004
  });
836
1005
  }
837
1006
  }
838
- async function createPngIdMaskWorkerResponse(message) {
839
- if (typeof Blob === "undefined" ||
840
- typeof createImageBitmap === "undefined" ||
841
- typeof CompressionStream === "undefined") {
842
- return undefined;
843
- }
844
- let frame;
845
- try {
846
- frame = await createPngIdMaskFrame(message.job.instructions);
847
- }
848
- catch {
849
- return undefined;
850
- }
851
- if (!frame) {
852
- return undefined;
853
- }
854
- let imageBitmap;
855
- try {
856
- imageBitmap = await createImageBitmap(new Blob([frame.png], { type: "image/png" }));
857
- }
858
- catch {
859
- return undefined;
860
- }
1007
+ /**
1008
+ * A frame that carries only region coverage has nothing to composite, and the
1009
+ * RGBA branch still has to produce an artifact for the coverage to ride on.
1010
+ */
1011
+ function createTransparentCoverageCarrier() {
861
1012
  return {
862
- artifactKind: PreparedMaskFrameKind.PngIdMask,
863
- fillPalette: frame.fillPalette,
864
- hasStroke: frame.hasStroke,
865
- imageBitmap,
866
- key: message.job.key,
867
- maxStrokeWidth: frame.maxStrokeWidth,
868
- png: frame.png,
869
- requestId: message.requestId,
870
- strokePalette: frame.strokePalette,
871
- strokeWidths: frame.strokeWidths,
872
- type: MaskPreparationWorkerMessageType.Complete,
1013
+ data: new Uint8ClampedArray(new ArrayBuffer(4)),
1014
+ height: 1,
1015
+ width: 1,
873
1016
  };
874
1017
  }
1018
+ function getRegionMaskCoverageTransfers(coverage) {
1019
+ return coverage?.entries.map(({ data }) => data.buffer) ?? [];
1020
+ }
875
1021
  function createImageBitmapFromImageData(imageData) {
876
1022
  if (typeof OffscreenCanvas === "undefined") {
877
1023
  return null;