supervision 0.1.7 → 0.2.0-next.1

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 (182) hide show
  1. package/dist/constants/media-renderer.d.ts +3 -0
  2. package/dist/constants/media-renderer.d.ts.map +1 -1
  3. package/dist/detections/chunked-detection-frame-source.d.ts.map +1 -1
  4. package/dist/index.d.ts +8 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +9281 -5038
  7. package/dist/index.js.map +1 -1
  8. package/dist/mask-preparation.worker.js +409 -185
  9. package/dist/mask-preparation.worker.js.map +1 -1
  10. package/dist/media/display-pixel-ratio.d.ts +15 -0
  11. package/dist/media/display-pixel-ratio.d.ts.map +1 -0
  12. package/dist/media/engine-import-failure.d.ts +16 -0
  13. package/dist/media/engine-import-failure.d.ts.map +1 -0
  14. package/dist/media/media-errors.d.ts +6 -0
  15. package/dist/media/media-errors.d.ts.map +1 -1
  16. package/dist/media/media-normalization.d.ts.map +1 -1
  17. package/dist/media/media-probe.d.ts.map +1 -1
  18. package/dist/media/media-source-state.d.ts.map +1 -1
  19. package/dist/media/media-source.d.ts +17 -0
  20. package/dist/media/media-source.d.ts.map +1 -1
  21. package/dist/media/mediabunny-media-source.d.ts.map +1 -1
  22. package/dist/media/video-engine-media-source.d.ts +54 -0
  23. package/dist/media/video-engine-media-source.d.ts.map +1 -0
  24. package/dist/media/video-engine-media-source.js +3 -0
  25. package/dist/media/video-engine-media-source.js.map +1 -0
  26. package/dist/playback/media-playback-controller.d.ts +7 -1
  27. package/dist/playback/media-playback-controller.d.ts.map +1 -1
  28. package/dist/render-preparation/mask-frame-artifact.d.ts +48 -12
  29. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  30. package/dist/render-preparation/mask-frame-compositor.d.ts +12 -5
  31. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  32. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  33. package/dist/render-preparation/mask-preparation-worker-count.d.ts +4 -4
  34. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +13 -3
  35. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  36. package/dist/render-preparation/prepared-render-window.d.ts +44 -2
  37. package/dist/render-preparation/prepared-render-window.d.ts.map +1 -1
  38. package/dist/render-preparation/prepared-window-timeline.d.ts +2 -6
  39. package/dist/render-preparation/prepared-window-timeline.d.ts.map +1 -1
  40. package/dist/renderers/injected-pixi.d.ts +38 -0
  41. package/dist/renderers/injected-pixi.d.ts.map +1 -0
  42. package/dist/renderers/mask-palette.d.ts +14 -0
  43. package/dist/renderers/mask-palette.d.ts.map +1 -0
  44. package/dist/renderers/mask-vertex.d.ts +14 -0
  45. package/dist/renderers/mask-vertex.d.ts.map +1 -0
  46. package/dist/renderers/media-renderer-core.d.ts +9 -0
  47. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  48. package/dist/renderers/media-renderer-scene.d.ts +50 -2
  49. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  50. package/dist/renderers/media-renderer-state.d.ts +20 -1
  51. package/dist/renderers/media-renderer-state.d.ts.map +1 -1
  52. package/dist/renderers/media-renderer-transport.d.ts +72 -0
  53. package/dist/renderers/media-renderer-transport.d.ts.map +1 -0
  54. package/dist/renderers/pixi-box-layer.d.ts +5 -0
  55. package/dist/renderers/pixi-box-layer.d.ts.map +1 -1
  56. package/dist/renderers/pixi-focus-layer.d.ts +19 -2
  57. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  58. package/dist/renderers/pixi-frame-present.d.ts +77 -0
  59. package/dist/renderers/pixi-frame-present.d.ts.map +1 -0
  60. package/dist/renderers/pixi-id-mask-shader.d.ts +6 -25
  61. package/dist/renderers/pixi-id-mask-shader.d.ts.map +1 -1
  62. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  63. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +12 -2
  64. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  65. package/dist/renderers/pixi-mask-halo.d.ts +18 -23
  66. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -1
  67. package/dist/renderers/pixi-mask-layer.d.ts +94 -32
  68. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  69. package/dist/renderers/pixi-media-scene.d.ts +67 -0
  70. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  71. package/dist/renderers/pixi-polygon-layer.d.ts +9 -2
  72. package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
  73. package/dist/renderers/pixi-region-coverage-mask.d.ts +62 -0
  74. package/dist/renderers/pixi-region-coverage-mask.d.ts.map +1 -0
  75. package/dist/renderers/pixi-region-effect.d.ts +42 -0
  76. package/dist/renderers/pixi-region-effect.d.ts.map +1 -0
  77. package/dist/renderers/pixi-region-layer.d.ts +43 -2
  78. package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
  79. package/dist/renderers/pixi-shader-lifecycle.d.ts +14 -0
  80. package/dist/renderers/pixi-shader-lifecycle.d.ts.map +1 -0
  81. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  82. package/dist/renderers/prepared-annotation-window.d.ts +47 -0
  83. package/dist/renderers/prepared-annotation-window.d.ts.map +1 -0
  84. package/dist/renderers/presented-frame-channel.d.ts +138 -0
  85. package/dist/renderers/presented-frame-channel.d.ts.map +1 -0
  86. package/dist/renderers/scene-render-scheduler.d.ts +20 -0
  87. package/dist/renderers/scene-render-scheduler.d.ts.map +1 -0
  88. package/dist/sessions/media-session-defaults.d.ts +10 -6
  89. package/dist/sessions/media-session-defaults.d.ts.map +1 -1
  90. package/dist/sessions/media-session-media.d.ts.map +1 -1
  91. package/dist/sessions/media-session-state.d.ts.map +1 -1
  92. package/dist/sessions/media-session.d.ts.map +1 -1
  93. package/dist/tracking.worker.js +73 -4
  94. package/dist/tracking.worker.js.map +1 -1
  95. package/dist/types/media-normalization.d.ts +34 -0
  96. package/dist/types/media-normalization.d.ts.map +1 -1
  97. package/dist/types/media-renderer.d.ts +31 -1
  98. package/dist/types/media-renderer.d.ts.map +1 -1
  99. package/dist/types/media-session.d.ts +89 -15
  100. package/dist/types/media-session.d.ts.map +1 -1
  101. package/dist/types/render-preparation.d.ts +149 -5
  102. package/dist/types/render-preparation.d.ts.map +1 -1
  103. package/dist/video-engine-media-source-CUXnOJOV.js +450 -0
  104. package/dist/video-engine-media-source-CUXnOJOV.js.map +1 -0
  105. package/dist/web-video-engine/analysis-session.d.ts +75 -0
  106. package/dist/web-video-engine/analysis.d.ts +6 -0
  107. package/dist/web-video-engine/analysis.js +1850 -0
  108. package/dist/web-video-engine/cache-budget.d.ts +18 -0
  109. package/dist/web-video-engine/canvas-sink-scrub-cursor.d.ts +89 -0
  110. package/dist/web-video-engine/clock.d.ts +82 -0
  111. package/dist/web-video-engine/constants.d.ts +261 -0
  112. package/dist/web-video-engine/create-scrub-cursor.d.ts +65 -0
  113. package/dist/web-video-engine/decode-resolution.d.ts +114 -0
  114. package/dist/web-video-engine/decode-scheduler.d.ts +371 -0
  115. package/dist/web-video-engine/decode-session.d.ts +283 -0
  116. package/dist/web-video-engine/decode-source.d.ts +258 -0
  117. package/dist/web-video-engine/diagnostics-store.d.ts +17 -0
  118. package/dist/web-video-engine/diagnostics.d.ts +316 -0
  119. package/dist/web-video-engine/embedded-engine-worker.d.ts +1 -0
  120. package/dist/web-video-engine/engine-core.d.ts +269 -0
  121. package/dist/web-video-engine/engine.d.ts +25 -0
  122. package/dist/web-video-engine/engine.js +912 -0
  123. package/dist/web-video-engine/engine.worker.d.ts +1 -0
  124. package/dist/web-video-engine/engine.worker.js +33624 -0
  125. package/dist/web-video-engine/frame-cache.d.ts +236 -0
  126. package/dist/web-video-engine/frame-extractor.d.ts +27 -0
  127. package/dist/web-video-engine/frame-timeline-DreEwsRY.js +616 -0
  128. package/dist/web-video-engine/frame-timeline.d.ts +80 -0
  129. package/dist/web-video-engine/frame-walker.d.ts +73 -0
  130. package/dist/web-video-engine/index.d.ts +13 -0
  131. package/dist/web-video-engine/index.d.ts.map +1 -0
  132. package/dist/web-video-engine/index.js +3 -0
  133. package/dist/web-video-engine/index.js.map +1 -0
  134. package/dist/web-video-engine/key-packet.d.ts +64 -0
  135. package/dist/web-video-engine/keyframe-index.d.ts +90 -0
  136. package/dist/web-video-engine/mirror-store.d.ts +55 -0
  137. package/dist/web-video-engine/renderer.d.ts +93 -0
  138. package/dist/web-video-engine/rotation.d.ts +34 -0
  139. package/dist/web-video-engine/scrub-controller.d.ts +375 -0
  140. package/dist/web-video-engine/scrub-cursor.d.ts +347 -0
  141. package/dist/web-video-engine/scrub-trajectory.d.ts +29 -0
  142. package/dist/web-video-engine/source-residency.d.ts +91 -0
  143. package/dist/web-video-engine/trace-recorder.d.ts +163 -0
  144. package/dist/web-video-engine/types.d.ts +200 -0
  145. package/dist/web-video-engine/video-engine.d.ts +437 -0
  146. package/dist/web-video-engine/webgpu-renderer.d.ts +61 -0
  147. package/dist/web-video-engine/worker-bridge.d.ts +11 -0
  148. package/dist/web-video-engine/worker-dispatch.d.ts +16 -0
  149. package/dist/web-video-engine/worker-protocol.d.ts +282 -0
  150. package/node_modules/supervision-js-core/dist/detections/buffered-detection-timeline.d.ts.map +1 -1
  151. package/node_modules/supervision-js-core/dist/detections/composite-detection-frame-source.d.ts.map +1 -1
  152. package/node_modules/supervision-js-core/dist/index.d.ts +5 -4
  153. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  154. package/node_modules/supervision-js-core/dist/index.js +1167 -280
  155. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  156. package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
  157. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts +4 -36
  158. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
  159. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts +5 -0
  160. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
  161. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +92 -7
  162. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  163. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts +191 -17
  164. package/node_modules/supervision-js-core/dist/types/detection-timeline.d.ts.map +1 -1
  165. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +83 -5
  166. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  167. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts +2 -0
  168. package/node_modules/supervision-js-core/dist/types/polyline-style.d.ts.map +1 -1
  169. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts +32 -0
  170. package/node_modules/supervision-js-core/dist/types/session-lifecycle.d.ts.map +1 -1
  171. package/node_modules/supervision-js-core/dist/utils/detection-conversions.d.ts.map +1 -1
  172. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts +5 -0
  173. package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
  174. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts +9 -0
  175. package/node_modules/supervision-js-core/dist/utils/detection-masks.d.ts.map +1 -1
  176. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts +18 -0
  177. package/node_modules/supervision-js-core/dist/utils/detection-ranges.d.ts.map +1 -0
  178. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts +34 -2
  179. package/node_modules/supervision-js-core/dist/utils/id-mask-frame.d.ts.map +1 -1
  180. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts +7 -0
  181. package/node_modules/supervision-js-core/dist/utils/wait-bound.d.ts.map +1 -0
  182. package/package.json +24 -3
@@ -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,27 @@
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 scaledAxes = width === maskWidth
430
+ ? undefined
431
+ : createScaledMaskAxes({ height, maskHeight, maskWidth, width });
392
432
  const data = new Uint8Array(new ArrayBuffer(width * height));
393
433
  const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
394
434
  const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));
@@ -396,27 +436,23 @@
396
436
  let hasStroke = false;
397
437
  let maxStrokeWidth = 0;
398
438
  for (const instruction of instructions) {
399
- const detectionMaskId = instruction.detectionIndex + 1;
400
- if (detectionMaskId <= 0 ||
401
- detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {
439
+ const detectionMaskId = resolveIdMaskPaletteId(instruction.detectionIndex);
440
+ if (detectionMaskId === undefined) {
402
441
  return undefined;
403
442
  }
404
- writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);
443
+ writeIdMaskPaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);
405
444
  if (instruction.stroke && instruction.stroke.width > 0) {
406
- const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);
445
+ const strokeWidth = resolveIdMaskStrokeTexels(instruction.stroke.width, maskWidth, width);
407
446
  hasStroke = true;
408
447
  strokeWidths[detectionMaskId] = strokeWidth;
409
448
  maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);
410
- writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);
449
+ writeIdMaskPaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);
411
450
  }
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
- }
451
+ if (scaledAxes) {
452
+ writeScaledMaskRuns(data, scaledAxes, instruction.mask, detectionMaskId);
453
+ }
454
+ else {
455
+ writeMaskRuns(data, width, instruction.mask, detectionMaskId);
420
456
  }
421
457
  }
422
458
  return {
@@ -425,13 +461,126 @@
425
461
  hasStroke,
426
462
  height,
427
463
  maxStrokeWidth,
464
+ sourceWidth: maskWidth,
428
465
  strokePalette,
429
466
  strokeWidths,
430
467
  width,
431
468
  };
432
469
  }
433
- function writePaletteEntry(palette, id, color, alpha) {
434
- const offset = id * 4;
470
+ /**
471
+ * Compressed RLE counts runs down each column in turn, so a foreground run is
472
+ * a contiguous walk down one column that wraps into the next.
473
+ */
474
+ function writeMaskRuns(data, frameWidth, mask, detectionMaskId) {
475
+ const counts = decodeCompressedRleCounts(mask.counts);
476
+ const maskWidth = mask.width;
477
+ const maskHeight = mask.height;
478
+ let maskOffset = 0;
479
+ for (let index = 0; index < counts.length; index += 1) {
480
+ const runLength = counts[index] ?? 0;
481
+ if (index % 2 === 0 || runLength <= 0) {
482
+ maskOffset += runLength;
483
+ continue;
484
+ }
485
+ let x = Math.floor(maskOffset / maskHeight);
486
+ let y = maskOffset - x * maskHeight;
487
+ let frameOffset = y * frameWidth + x;
488
+ for (let step = 0; step < runLength; step += 1) {
489
+ if (x >= maskWidth) {
490
+ break;
491
+ }
492
+ data[frameOffset] = detectionMaskId;
493
+ y += 1;
494
+ frameOffset += frameWidth;
495
+ if (y === maskHeight) {
496
+ y = 0;
497
+ x += 1;
498
+ frameOffset = x;
499
+ }
500
+ }
501
+ maskOffset += runLength;
502
+ }
503
+ }
504
+ /**
505
+ * The scaled twin of writeMaskRuns. Every source pixel marks the texel it lands
506
+ * in, so a mask keeps every part of itself the smaller raster can hold and
507
+ * gains up to a texel at its edges.
508
+ */
509
+ function writeScaledMaskRuns(data, axes, mask, detectionMaskId) {
510
+ const counts = decodeCompressedRleCounts(mask.counts);
511
+ const maskWidth = mask.width;
512
+ const maskHeight = mask.height;
513
+ let maskOffset = 0;
514
+ for (let index = 0; index < counts.length; index += 1) {
515
+ const runLength = counts[index] ?? 0;
516
+ if (index % 2 === 0 || runLength <= 0) {
517
+ maskOffset += runLength;
518
+ continue;
519
+ }
520
+ let x = Math.floor(maskOffset / maskHeight);
521
+ let y = maskOffset - x * maskHeight;
522
+ let column = axes.columns[x];
523
+ for (let step = 0; step < runLength; step += 1) {
524
+ if (x >= maskWidth) {
525
+ break;
526
+ }
527
+ data[axes.rows[y] + column] = detectionMaskId;
528
+ y += 1;
529
+ if (y === maskHeight) {
530
+ y = 0;
531
+ x += 1;
532
+ column = axes.columns[x];
533
+ }
534
+ }
535
+ maskOffset += runLength;
536
+ }
537
+ }
538
+ function resolveRasterWidth(maskWidth, maxWidth) {
539
+ return maxWidth !== undefined && maxWidth > 0 && maxWidth < maskWidth
540
+ ? Math.max(1, Math.floor(maxWidth))
541
+ : maskWidth;
542
+ }
543
+ /**
544
+ * A stroke is measured in texels of the raster it is drawn on, so a coarser
545
+ * raster measures it in coarser texels. A stroke of a texel or more keeps at
546
+ * least one, the thinnest line the shader can draw; a narrower one keeps its
547
+ * own width, which the shader draws as an inner boundary at any scale. The
548
+ * ceiling is the widest neighbourhood the shaders scan, so a wider stroke would
549
+ * be drawn at the ceiling anyway and every layer drawing the same annotation
550
+ * has to arrive at the same width.
551
+ */
552
+ function resolveIdMaskStrokeTexels(strokeWidth, maskWidth, rasterWidth) {
553
+ const scale = maskWidth > 0 ? rasterWidth / maskWidth : 1;
554
+ return Math.min(Math.max(strokeWidth * scale, Math.min(strokeWidth, 1)), MAX_ID_MASK_STROKE_WIDTH);
555
+ }
556
+ function createScaledMaskAxes(frame) {
557
+ return {
558
+ columns: createMaskAxisMap(frame.maskWidth, frame.width, 1),
559
+ rows: createMaskAxisMap(frame.maskHeight, frame.height, frame.width),
560
+ };
561
+ }
562
+ /** Rows hold their destination offset, so writing a run is two lookups. */
563
+ function createMaskAxisMap(sourceLength, targetLength, stride) {
564
+ const map = new Int32Array(sourceLength);
565
+ const scale = targetLength / sourceLength;
566
+ for (let index = 0; index < sourceLength; index += 1) {
567
+ map[index] = Math.min(targetLength - 1, Math.floor(index * scale)) * stride;
568
+ }
569
+ return map;
570
+ }
571
+ /**
572
+ * The palette slot a detection index names, or undefined when the palette has
573
+ * no slot for it. Slot zero is the background, so the ids start at one and the
574
+ * last detection the palette can name sits one index below the last slot.
575
+ */
576
+ function resolveIdMaskPaletteId(detectionIndex) {
577
+ const detectionMaskId = detectionIndex + 1;
578
+ return detectionMaskId > 0 && detectionMaskId < MAX_ID_MASK_PALETTE_ENTRIES
579
+ ? detectionMaskId
580
+ : undefined;
581
+ }
582
+ function writeIdMaskPaletteEntry(palette, detectionMaskId, color, alpha) {
583
+ const offset = detectionMaskId * 4;
435
584
  palette[offset] = ((color >> 16) & 0xff) / 255;
436
585
  palette[offset + 1] = ((color >> 8) & 0xff) / 255;
437
586
  palette[offset + 2] = (color & 0xff) / 255;
@@ -471,7 +620,7 @@
471
620
  */
472
621
  var MediaErrorKind;
473
622
  (function (MediaErrorKind) {
474
- /** The media could not be opened or read at all. */
623
+ /** The media could not be opened or read, or was refused before any read. */
475
624
  MediaErrorKind["Unreadable"] = "unreadable";
476
625
  /** The container or codec is not supported by this platform. */
477
626
  MediaErrorKind["UnsupportedFormat"] = "unsupportedFormat";
@@ -496,6 +645,28 @@
496
645
  MediaSourceStatus["Error"] = "error";
497
646
  MediaSourceStatus["Destroyed"] = "destroyed";
498
647
  })(MediaSourceStatus || (MediaSourceStatus = {}));
648
+ /**
649
+ * Current renderer state.
650
+ */
651
+ /**
652
+ * What a playback gate holds back.
653
+ *
654
+ * A source the renderer pulls samples from can be held between any two frames,
655
+ * because the renderer decides when each one is drawn. A source that presents
656
+ * its own frames owns the playhead, so holding it means stopping the producer
657
+ * and starting it again, and a gate that stops one has to bound its own wait
658
+ * or a producer nothing answers for never runs again.
659
+ */
660
+ var PlaybackGateReach;
661
+ (function (PlaybackGateReach) {
662
+ /** No gate: the picture moves and unprepared layers are absent from it. */
663
+ PlaybackGateReach["Off"] = "off";
664
+ /**
665
+ * Playback waits to begin, and stops again at any frame whose artifacts are
666
+ * missing, until they arrive or the gate's own wait bound gives up on them.
667
+ */
668
+ PlaybackGateReach["EveryFrame"] = "everyFrame";
669
+ })(PlaybackGateReach || (PlaybackGateReach = {}));
499
670
 
500
671
  /**
501
672
  * Media-session operating mode.
@@ -536,12 +707,35 @@
536
707
  */
537
708
  var MediaSessionActivityKind;
538
709
  (function (MediaSessionActivityKind) {
710
+ /**
711
+ * Playback is held waiting for a producer to emit detections for the frame
712
+ * about to be shown, which is a wait on inference rather than on transfer or
713
+ * decode. `DetectionsBuffering` is the transfer of detections that already
714
+ * exist, and `PlaybackBuffering` is the wait for media bytes.
715
+ */
716
+ MediaSessionActivityKind["DetectionsAwaitingCoverage"] = "detectionsAwaitingCoverage";
539
717
  MediaSessionActivityKind["DetectionsBuffering"] = "detectionsBuffering";
540
718
  MediaSessionActivityKind["DetectionsLoading"] = "detectionsLoading";
541
719
  MediaSessionActivityKind["Error"] = "error";
542
720
  MediaSessionActivityKind["MediaNormalizing"] = "mediaNormalizing";
543
721
  MediaSessionActivityKind["MediaOpening"] = "mediaOpening";
722
+ /**
723
+ * The picture is waiting on media the source has not handed over yet, which
724
+ * is the bytes and their decode rather than anything downstream of them.
725
+ * `PlaybackBuffering` is what a transport reports once it has already
726
+ * stopped; this is reported from the read itself, including the reads a seek
727
+ * makes while the transport still reads as paused.
728
+ */
729
+ MediaSessionActivityKind["MediaSourceReading"] = "mediaSourceReading";
544
730
  MediaSessionActivityKind["PlaybackBuffering"] = "playbackBuffering";
731
+ /**
732
+ * The render-preparation gate held the picture for artifacts that never
733
+ * arrived and let it go. Nothing is blocked: the frames reaching the screen
734
+ * are the ones whose artifacts were given up on. What is waited on is
735
+ * preparation finishing another frame, which is what lets the gate hold the
736
+ * picture again; `RenderPreparing` covers the holds that are running.
737
+ */
738
+ MediaSessionActivityKind["RenderPreparationAbandoned"] = "renderPreparationAbandoned";
545
739
  MediaSessionActivityKind["RenderPreparing"] = "renderPreparing";
546
740
  })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));
547
741
  /**
@@ -554,9 +748,52 @@
554
748
  MediaSessionActivityStatus["Waiting"] = "waiting";
555
749
  })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
556
750
 
557
- const PNG_SIGNATURE = new Uint8Array([
558
- 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
559
- ]);
751
+ /** Builds compact, independent alpha crops so overlapping masks stay exact. */
752
+ function createRegionMaskCoverageFrame(instructions) {
753
+ const entries = [];
754
+ for (const instruction of instructions) {
755
+ if (!instruction.regionCoverageMask) {
756
+ continue;
757
+ }
758
+ const entry = cropCoverageMask(instruction.detectionIndex, decodeCompressedRleMask(instruction.regionCoverageMask));
759
+ if (entry) {
760
+ entries.push(entry);
761
+ }
762
+ }
763
+ return entries.length > 0 ? { entries } : undefined;
764
+ }
765
+ function cropCoverageMask(detectionIndex, decodedMask) {
766
+ let minX = decodedMask.width;
767
+ let minY = decodedMask.height;
768
+ let maxX = -1;
769
+ let maxY = -1;
770
+ for (let y = 0; y < decodedMask.height; y += 1) {
771
+ for (let x = 0; x < decodedMask.width; x += 1) {
772
+ if (!decodedMask.data[y * decodedMask.width + x]) {
773
+ continue;
774
+ }
775
+ minX = Math.min(minX, x);
776
+ minY = Math.min(minY, y);
777
+ maxX = Math.max(maxX, x);
778
+ maxY = Math.max(maxY, y);
779
+ }
780
+ }
781
+ if (maxX < minX || maxY < minY) {
782
+ return undefined;
783
+ }
784
+ const width = maxX - minX + 1;
785
+ const height = maxY - minY + 1;
786
+ const data = new Uint8Array(new ArrayBuffer(width * height));
787
+ for (let y = minY; y <= maxY; y += 1) {
788
+ for (let x = minX; x <= maxX; x += 1) {
789
+ if (!decodedMask.data[y * decodedMask.width + x]) {
790
+ continue;
791
+ }
792
+ data[(y - minY) * width + (x - minX)] = 255;
793
+ }
794
+ }
795
+ return { data, detectionIndex, height, width, x: minX, y: minY };
796
+ }
560
797
  function compositeMaskFrame(instructions) {
561
798
  const maskInstructions = materializeMaskInstructions(instructions);
562
799
  if (maskInstructions.length === 0) {
@@ -570,33 +807,51 @@
570
807
  }
571
808
  return { data, height, width };
572
809
  }
573
- function createMaskIdFrame(instructions) {
574
- return createIdMaskFrame(materializeMaskInstructions(instructions));
810
+ function createIdMaskRasterFrame(instructions, maxRasterWidth) {
811
+ try {
812
+ return createIdMaskFrame(materializeMaskInstructions(instructions), {
813
+ maxWidth: maxRasterWidth,
814
+ });
815
+ }
816
+ catch {
817
+ // The id raster is the fast path, not the only one: answering with nothing
818
+ // puts the caller on the RGBA composite, which draws the same picture.
819
+ return undefined;
820
+ }
575
821
  }
576
- async function createPngIdMaskFrame(instructions) {
577
- const frame = createMaskIdFrame(instructions);
578
- if (!frame) {
822
+ /**
823
+ * Whether the palette has a slot for every detection this frame masks. An id
824
+ * names a detection by its index in the frame, so one masked detection past the
825
+ * last slot leaves the whole frame without an id raster however few masks it
826
+ * carries.
827
+ */
828
+ function canIdMaskPaletteNameFrame(instructions) {
829
+ return instructions.every((instruction) => instruction.visible === false ||
830
+ resolveIdMaskPaletteId(instruction.detectionIndex) !== undefined);
831
+ }
832
+ function createIdMaskPlane(instructions, maxRasterWidth) {
833
+ // The plane carries the ids a failed cook could not. A frame the palette
834
+ // cannot name has none to carry, and cooking it again reaches the same
835
+ // refusal after rasterizing every mask a second time.
836
+ if (!canIdMaskPaletteNameFrame(instructions)) {
579
837
  return undefined;
580
838
  }
581
- return {
582
- ...frame,
583
- png: await encodeGrayscalePng({
584
- height: frame.height,
585
- pixels: frame.data,
586
- width: frame.width,
587
- }),
588
- };
839
+ const frame = createIdMaskRasterFrame(instructions, maxRasterWidth);
840
+ return frame
841
+ ? { data: frame.data, height: frame.height, width: frame.width }
842
+ : undefined;
589
843
  }
590
844
  function compositeInstruction(rgba, canvasWidth, instruction) {
591
- const decodedMask = decodeCompressedRleMask(instruction.mask);
592
845
  const fill = resolveRgbaColor(instruction.color, instruction.alpha);
593
- compositeMaskFill(rgba, canvasWidth, decodedMask, fill);
594
- if (instruction.stroke) {
595
- compositeMaskStroke(rgba, canvasWidth, decodedMask, instruction.stroke);
846
+ const bounds = compositeMaskFill(rgba, canvasWidth, instruction.mask, fill);
847
+ if (instruction.stroke && bounds) {
848
+ compositeMaskStroke(rgba, canvasWidth, decodeCompressedRleMask(instruction.mask), bounds, instruction.stroke);
596
849
  }
597
850
  }
598
851
  function materializeMaskInstructions(instructions) {
599
- return instructions.map((instruction) => {
852
+ return instructions
853
+ .filter((instruction) => instruction.visible !== false)
854
+ .map((instruction) => {
600
855
  if (instruction.mask) {
601
856
  return instruction;
602
857
  }
@@ -610,25 +865,59 @@
610
865
  };
611
866
  });
612
867
  }
613
- function compositeMaskFill(rgba, canvasWidth, decodedMask, fill) {
614
- for (let y = 0; y < decodedMask.height; y += 1) {
615
- for (let x = 0; x < decodedMask.width; x += 1) {
616
- const maskOffset = y * decodedMask.width + x;
617
- if (!decodedMask.data[maskOffset]) {
618
- continue;
868
+ /**
869
+ * Compressed RLE counts runs down each column in turn, so a foreground run is
870
+ * a contiguous walk down one column that wraps into the next.
871
+ */
872
+ function compositeMaskFill(rgba, canvasWidth, mask, fill) {
873
+ const counts = decodeCompressedRleCounts(mask.counts);
874
+ const maskWidth = mask.width;
875
+ const maskHeight = mask.height;
876
+ const maskArea = maskWidth * maskHeight;
877
+ let maskOffset = 0;
878
+ let minX = maskWidth;
879
+ let minY = maskHeight;
880
+ let maxX = -1;
881
+ let maxY = -1;
882
+ for (let index = 0; index < counts.length; index += 1) {
883
+ const runLength = counts[index] ?? 0;
884
+ if (index % 2 === 0 || runLength <= 0) {
885
+ maskOffset += runLength;
886
+ continue;
887
+ }
888
+ let columnX = Math.floor(maskOffset / maskHeight);
889
+ let columnY = maskOffset - columnX * maskHeight;
890
+ for (let step = 0; step < runLength; step += 1) {
891
+ // A run can outlast the columns the mask has; the pixel it then names
892
+ // is wherever that column-major offset lands when read back row-major.
893
+ const rowMajorOffset = columnY * maskWidth + columnX;
894
+ if (rowMajorOffset < maskArea) {
895
+ const x = rowMajorOffset % maskWidth;
896
+ const y = (rowMajorOffset - x) / maskWidth;
897
+ writePixel(rgba, canvasWidth, x, y, fill);
898
+ minX = Math.min(minX, x);
899
+ minY = Math.min(minY, y);
900
+ maxX = Math.max(maxX, x);
901
+ maxY = Math.max(maxY, y);
902
+ }
903
+ columnY += 1;
904
+ if (columnY === maskHeight) {
905
+ columnY = 0;
906
+ columnX += 1;
619
907
  }
620
- writePixel(rgba, canvasWidth, x, y, fill);
621
908
  }
909
+ maskOffset += runLength;
622
910
  }
911
+ return maxX < minX || maxY < minY ? undefined : { maxX, maxY, minX, minY };
623
912
  }
624
- function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {
913
+ function compositeMaskStroke(rgba, canvasWidth, decodedMask, bounds, stroke) {
625
914
  const width = Math.round(stroke.width);
626
915
  if (width <= 0) {
627
916
  return;
628
917
  }
629
918
  const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);
630
- for (let y = 0; y < decodedMask.height; y += 1) {
631
- for (let x = 0; x < decodedMask.width; x += 1) {
919
+ for (let y = bounds.minY; y <= bounds.maxY; y += 1) {
920
+ for (let x = bounds.minX; x <= bounds.maxX; x += 1) {
632
921
  if (!isMaskPixel(decodedMask, x, y) ||
633
922
  !isBoundaryPixel(decodedMask, x, y)) {
634
923
  continue;
@@ -684,86 +973,23 @@
684
973
  rgba[rgbaOffset + 2] = color.blue;
685
974
  rgba[rgbaOffset + 3] = color.alpha;
686
975
  }
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
976
 
762
977
  var PreparedMaskFrameKind;
763
978
  (function (PreparedMaskFrameKind) {
764
- PreparedMaskFrameKind["PngIdMask"] = "pngIdMask";
979
+ PreparedMaskFrameKind["IdMask"] = "idMask";
765
980
  PreparedMaskFrameKind["RgbaImage"] = "rgbaImage";
766
981
  })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));
982
+ /**
983
+ * How an id raster is laid out for the texture it is uploaded into. WebGPU
984
+ * takes any bytesPerRow, so ids go up one byte per pixel. WebGL aligns every
985
+ * uploaded row to four bytes and rejects a single-channel upload whose width
986
+ * is not a multiple of four, which is what the four-channel layout is for.
987
+ */
988
+ var IdMaskTextureFormat;
989
+ (function (IdMaskTextureFormat) {
990
+ IdMaskTextureFormat["R8"] = "r8unorm";
991
+ IdMaskTextureFormat["Rgba8"] = "rgba8unorm";
992
+ })(IdMaskTextureFormat || (IdMaskTextureFormat = {}));
767
993
 
768
994
  var MaskPreparationWorkerMessageType;
769
995
  (function (MaskPreparationWorkerMessageType) {
@@ -779,23 +1005,40 @@
779
1005
  if (message.type !== MaskPreparationWorkerMessageType.Prepare) {
780
1006
  return;
781
1007
  }
782
- void prepareMaskFrame(message);
1008
+ prepareMaskFrame(message);
783
1009
  });
784
- async function prepareMaskFrame(message) {
1010
+ function prepareMaskFrame(message) {
785
1011
  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,
1012
+ const regionMaskCoverage = createRegionMaskCoverageFrame(message.job.instructions);
1013
+ const coverageTransfers = getRegionMaskCoverageTransfers(regionMaskCoverage);
1014
+ const idMaskFrame = createIdMaskRasterFrame(message.job.instructions, message.job.maxRasterWidth);
1015
+ if (idMaskFrame) {
1016
+ workerScope.postMessage({
1017
+ artifactKind: PreparedMaskFrameKind.IdMask,
1018
+ fillPalette: idMaskFrame.fillPalette,
1019
+ hasStroke: idMaskFrame.hasStroke,
1020
+ height: idMaskFrame.height,
1021
+ key: message.job.key,
1022
+ maxStrokeWidth: idMaskFrame.maxStrokeWidth,
1023
+ raster: idMaskFrame.data,
1024
+ regionMaskCoverage,
1025
+ requestId: message.requestId,
1026
+ sourceWidth: idMaskFrame.sourceWidth,
1027
+ strokePalette: idMaskFrame.strokePalette,
1028
+ strokeWidths: idMaskFrame.strokeWidths,
1029
+ type: MaskPreparationWorkerMessageType.Complete,
1030
+ width: idMaskFrame.width,
1031
+ }, [
1032
+ idMaskFrame.data.buffer,
1033
+ idMaskFrame.fillPalette.buffer,
1034
+ idMaskFrame.strokePalette.buffer,
1035
+ idMaskFrame.strokeWidths.buffer,
1036
+ ...coverageTransfers,
794
1037
  ]);
795
1038
  return;
796
1039
  }
797
1040
  const compositedFrame = compositeMaskFrame(message.job.instructions);
798
- if (!compositedFrame) {
1041
+ if (!compositedFrame && !regionMaskCoverage) {
799
1042
  workerScope.postMessage({
800
1043
  key: message.job.key,
801
1044
  requestId: message.requestId,
@@ -803,26 +1046,30 @@
803
1046
  });
804
1047
  return;
805
1048
  }
806
- const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);
1049
+ const preparedPixels = compositedFrame ?? createTransparentCoverageCarrier();
1050
+ const imageData = new ImageData(preparedPixels.data, preparedPixels.width, preparedPixels.height);
807
1051
  const imageBitmap = createImageBitmapFromImageData(imageData);
808
- const idMaskData = createMaskIdFrame(message.job.instructions)?.data;
1052
+ const idMaskPlane = createIdMaskPlane(message.job.instructions, message.job.maxRasterWidth);
1053
+ const idMaskTransfers = idMaskPlane ? [idMaskPlane.data.buffer] : [];
809
1054
  if (imageBitmap) {
810
1055
  workerScope.postMessage({
1056
+ idMaskPlane,
811
1057
  imageBitmap,
812
- idMaskData,
813
1058
  key: message.job.key,
1059
+ regionMaskCoverage,
814
1060
  requestId: message.requestId,
815
1061
  type: MaskPreparationWorkerMessageType.Complete,
816
- }, [...(idMaskData ? [idMaskData.buffer] : []), imageBitmap]);
1062
+ }, [imageBitmap, ...idMaskTransfers, ...coverageTransfers]);
817
1063
  return;
818
1064
  }
819
1065
  workerScope.postMessage({
820
- idMaskData,
1066
+ idMaskPlane,
821
1067
  imageData,
822
1068
  key: message.job.key,
1069
+ regionMaskCoverage,
823
1070
  requestId: message.requestId,
824
1071
  type: MaskPreparationWorkerMessageType.Complete,
825
- }, [...(idMaskData ? [idMaskData.buffer] : []), imageData.data.buffer]);
1072
+ }, [imageData.data.buffer, ...idMaskTransfers, ...coverageTransfers]);
826
1073
  }
827
1074
  catch (error) {
828
1075
  workerScope.postMessage({
@@ -835,43 +1082,20 @@
835
1082
  });
836
1083
  }
837
1084
  }
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
- }
1085
+ /**
1086
+ * A frame that carries only region coverage has nothing to composite, and the
1087
+ * RGBA branch still has to produce an artifact for the coverage to ride on.
1088
+ */
1089
+ function createTransparentCoverageCarrier() {
861
1090
  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,
1091
+ data: new Uint8ClampedArray(new ArrayBuffer(4)),
1092
+ height: 1,
1093
+ width: 1,
873
1094
  };
874
1095
  }
1096
+ function getRegionMaskCoverageTransfers(coverage) {
1097
+ return coverage?.entries.map(({ data }) => data.buffer) ?? [];
1098
+ }
875
1099
  function createImageBitmapFromImageData(imageData) {
876
1100
  if (typeof OffscreenCanvas === "undefined") {
877
1101
  return null;