supervision 0.1.3 → 0.1.4

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 (89) hide show
  1. package/dist/index.d.ts +11 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1158 -97
  4. package/dist/index.js.map +1 -1
  5. package/dist/mask-preparation.worker.js +43 -9
  6. package/dist/mask-preparation.worker.js.map +1 -1
  7. package/dist/render-preparation/mask-frame-artifact.d.ts +6 -0
  8. package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
  9. package/dist/render-preparation/mask-frame-compositor.d.ts +1 -0
  10. package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
  11. package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
  12. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +1 -0
  13. package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
  14. package/dist/renderers/annotation-shape-styles.d.ts +17 -0
  15. package/dist/renderers/annotation-shape-styles.d.ts.map +1 -0
  16. package/dist/renderers/media-renderer-core.d.ts.map +1 -1
  17. package/dist/renderers/media-renderer-scene.d.ts +17 -2
  18. package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
  19. package/dist/renderers/pixi-annotation-overlay-layer.d.ts.map +1 -1
  20. package/dist/renderers/pixi-focus-layer.d.ts +1 -0
  21. package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
  22. package/dist/renderers/pixi-interaction-layer.d.ts +2 -0
  23. package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
  24. package/dist/renderers/pixi-interaction-presentation-layer.d.ts +2 -1
  25. package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
  26. package/dist/renderers/pixi-mask-halo.d.ts +94 -0
  27. package/dist/renderers/pixi-mask-halo.d.ts.map +1 -0
  28. package/dist/renderers/pixi-mask-layer.d.ts +10 -1
  29. package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
  30. package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
  31. package/dist/renderers/pixi-path.d.ts +13 -7
  32. package/dist/renderers/pixi-path.d.ts.map +1 -1
  33. package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
  34. package/dist/renderers/pixi-region-layer.d.ts +42 -0
  35. package/dist/renderers/pixi-region-layer.d.ts.map +1 -0
  36. package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
  37. package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
  38. package/dist/renderers/pixi-vector-layer.d.ts +3 -1
  39. package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
  40. package/dist/sessions/media-session.d.ts.map +1 -1
  41. package/dist/types/media-renderer.d.ts +1 -1
  42. package/dist/types/media-renderer.d.ts.map +1 -1
  43. package/node_modules/supervision-js-core/dist/index.d.ts +17 -3
  44. package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
  45. package/node_modules/supervision-js-core/dist/index.js +708 -74
  46. package/node_modules/supervision-js-core/dist/index.js.map +1 -1
  47. package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts +18 -0
  48. package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
  49. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
  50. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts +13 -6
  51. package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
  52. package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts +14 -0
  53. package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts.map +1 -0
  54. package/node_modules/supervision-js-core/dist/styles/box-style.d.ts.map +1 -1
  55. package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts +19 -0
  56. package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
  57. package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
  58. package/node_modules/supervision-js-core/dist/styles/keypoint-style.d.ts.map +1 -1
  59. package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts +14 -0
  60. package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts.map +1 -0
  61. package/node_modules/supervision-js-core/dist/styles/polygon-style.d.ts.map +1 -1
  62. package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
  63. package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts +8 -0
  64. package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts.map +1 -1
  65. package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts +12 -0
  66. package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts.map +1 -0
  67. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +124 -4
  68. package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
  69. package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts +31 -0
  70. package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts.map +1 -0
  71. package/node_modules/supervision-js-core/dist/types/box-style.d.ts +4 -17
  72. package/node_modules/supervision-js-core/dist/types/box-style.d.ts.map +1 -1
  73. package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts +30 -0
  74. package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts.map +1 -0
  75. package/node_modules/supervision-js-core/dist/types/interaction.d.ts +12 -0
  76. package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
  77. package/node_modules/supervision-js-core/dist/types/marker-style.d.ts +46 -0
  78. package/node_modules/supervision-js-core/dist/types/marker-style.d.ts.map +1 -0
  79. package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts +29 -0
  80. package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts.map +1 -0
  81. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +18 -2
  82. package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
  83. package/node_modules/supervision-js-core/dist/types/paint-style.d.ts +36 -0
  84. package/node_modules/supervision-js-core/dist/types/paint-style.d.ts.map +1 -0
  85. package/node_modules/supervision-js-core/dist/types/shape-style.d.ts +112 -0
  86. package/node_modules/supervision-js-core/dist/types/shape-style.d.ts.map +1 -0
  87. package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts +50 -0
  88. package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts.map +1 -0
  89. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { copySortedDetectionFrames, detectionFrameOverlapsRange, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode, BoxStrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, decodeCompressedRleMask, extractMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, pickAnnotationHandle, getAnnotationHandles, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, KeypointMarkerShape, BaseInteractionStyle, DetectionInteractionState, MAX_ID_MASK_PALETTE_ENTRIES, DetectionBufferStatus, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_STROKE_WIDTH, resolveMaskStyleOpacity, pickDetectionByMaskId, resolveStyleValue, lightenColor, createViewportController, resolveAnnotationStyleState, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation } from 'supervision-js-core';
2
- export { BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, SUPERVISION_ROBOFLOW_COLOR, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, normalizeDetectionClassName, pickDetectionAtPoint, resolveDetectionClassColorStyle } from 'supervision-js-core';
1
+ import { copySortedDetectionFrames, detectionFrameOverlapsRange, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, decodeCompressedRleMask, extractMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, MAX_ID_MASK_PALETTE_ENTRIES, DetectionBufferStatus, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_STROKE_WIDTH, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, resolveStyleValue, resolveAnnotationStyleState, lightenColor, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation } from 'supervision-js-core';
2
+ export { BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, normalizeDetectionClassName, pickDetectionAtPoint, resolveDetectionClassColorStyle } from 'supervision-js-core';
3
3
 
4
4
  const DEFAULT_DATABASE_NAME = "supervision-js-detection-frames";
5
5
  const DEFAULT_CHUNK_DURATION_SECONDS = 1;
@@ -2239,11 +2239,15 @@ async function createMediaRendererCore(options, providers) {
2239
2239
  annotationOverlayStyle: options.annotationOverlayStyle,
2240
2240
  backgroundColor: options.backgroundColor,
2241
2241
  boxStyle: options.boxStyle,
2242
+ boxCornerStyle: options.boxCornerStyle,
2243
+ ellipseStyle: options.ellipseStyle,
2242
2244
  focusStyle: options.focusStyle,
2243
2245
  interactionStyle: options.interactionStyle,
2244
2246
  keypointStyle: options.keypointStyle,
2245
2247
  labelStyle: options.labelStyle,
2248
+ maskHaloStyle: options.maskHaloStyle,
2246
2249
  maskStyle: options.maskStyle,
2250
+ markerStyle: options.markerStyle,
2247
2251
  polygonStyle: options.polygonStyle,
2248
2252
  polylineStyle: options.polylineStyle,
2249
2253
  renderers: options.renderers,
@@ -2538,6 +2542,8 @@ async function createMediaRendererCore(options, providers) {
2538
2542
  annotationOverlayStyle: currentPresentation.annotationOverlayStyle,
2539
2543
  backgroundColor: currentPresentation.backgroundColor,
2540
2544
  boxStyle: currentPresentation.boxStyle,
2545
+ boxCornerStyle: currentPresentation.boxCornerStyle,
2546
+ ellipseStyle: currentPresentation.ellipseStyle,
2541
2547
  canInteract: () => canInteract(options, runtimeState.isPlaybackActive()),
2542
2548
  container: options.container,
2543
2549
  detectionTimeline,
@@ -2550,10 +2556,18 @@ async function createMediaRendererCore(options, providers) {
2550
2556
  keypointStyle: currentPresentation.keypointStyle,
2551
2557
  labelStyle: currentPresentation.labelStyle,
2552
2558
  maskBrush: options.createMaskBrush?.(mediaDimensions) ?? options.maskBrush,
2559
+ maskHaloStyle: currentPresentation.maskHaloStyle,
2553
2560
  maskStyle: currentPresentation.maskStyle,
2561
+ markerStyle: currentPresentation.markerStyle,
2554
2562
  maxDevicePixelRatio: options.maxDevicePixelRatio,
2563
+ onPresentationUpdate(presentedSample) {
2564
+ if (!runtimeState.isDestroyed()) {
2565
+ runtimeState.recordPresentationUpdate(presentedSample);
2566
+ }
2567
+ },
2555
2568
  polygonStyle: currentPresentation.polygonStyle,
2556
2569
  polylineStyle: currentPresentation.polylineStyle,
2570
+ regionRenderers: resolveRegionRenderers(currentPresentation),
2557
2571
  previewOverlay: options.previewOverlay,
2558
2572
  renderPreparation: options.renderPreparation
2559
2573
  ? {
@@ -2699,6 +2713,10 @@ async function createMediaRendererCore(options, providers) {
2699
2713
  }
2700
2714
  }
2701
2715
  }
2716
+ function resolveRegionRenderers(presentation) {
2717
+ return (presentation.renderers?.filter((renderer) => renderer.kind === "region") ??
2718
+ []);
2719
+ }
2702
2720
  async function openRendererMediaSource(options, providers) {
2703
2721
  if (options.src !== undefined && options.source !== undefined) {
2704
2722
  throw new Error("Provide either src or source, not both.");
@@ -2792,10 +2810,10 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
2792
2810
  if (points.length < 2) {
2793
2811
  return;
2794
2812
  }
2795
- if (stroke.dash?.length) {
2796
- appendDashedPath(graphics, points, closed, stroke.dash, viewportScale);
2797
- }
2798
- else {
2813
+ const appendedDash = stroke.dash?.length
2814
+ ? appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
2815
+ : false;
2816
+ if (!appendedDash) {
2799
2817
  graphics.moveTo(points[0].x, points[0].y);
2800
2818
  for (let index = 1; index < points.length; index += 1) {
2801
2819
  graphics.lineTo(points[index].x, points[index].y);
@@ -2809,16 +2827,21 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
2809
2827
  function resolvePixiStroke(stroke, viewportScale) {
2810
2828
  const pixiStroke = {
2811
2829
  alpha: stroke.alpha,
2830
+ ...(stroke.cap === undefined ? {} : { cap: stroke.cap }),
2812
2831
  color: stroke.color,
2832
+ ...(stroke.join === undefined ? {} : { join: stroke.join }),
2833
+ ...(stroke.miterLimit === undefined
2834
+ ? {}
2835
+ : { miterLimit: stroke.miterLimit }),
2813
2836
  width: resolveScreenLength(stroke.width, viewportScale),
2814
2837
  };
2815
2838
  if (stroke.alignment === undefined)
2816
2839
  return pixiStroke;
2817
2840
  return {
2818
2841
  ...pixiStroke,
2819
- alignment: stroke.alignment === BoxStrokeAlignment.Inside
2842
+ alignment: stroke.alignment === StrokeAlignment.Inside
2820
2843
  ? 1
2821
- : stroke.alignment === BoxStrokeAlignment.Outside
2844
+ : stroke.alignment === StrokeAlignment.Outside
2822
2845
  ? 0
2823
2846
  : 0.5,
2824
2847
  };
@@ -2828,7 +2851,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
2828
2851
  .filter((value) => Number.isFinite(value) && value > 0)
2829
2852
  .map((value) => resolveScreenLength(value, viewportScale));
2830
2853
  if (pattern.length === 0) {
2831
- return;
2854
+ return false;
2832
2855
  }
2833
2856
  if (pattern.length % 2 === 1) {
2834
2857
  pattern.push(...pattern);
@@ -2836,6 +2859,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
2836
2859
  let patternIndex = 0;
2837
2860
  let patternRemaining = pattern[0];
2838
2861
  let drawing = true;
2862
+ let penDown = false;
2839
2863
  const segmentCount = closed ? points.length : points.length - 1;
2840
2864
  for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
2841
2865
  const start = points[segmentIndex];
@@ -2856,8 +2880,14 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
2856
2880
  const x2 = start.x + dx * endRatio;
2857
2881
  const y2 = start.y + dy * endRatio;
2858
2882
  if (drawing) {
2859
- graphics.moveTo(x1, y1);
2883
+ if (!penDown) {
2884
+ graphics.moveTo(x1, y1);
2885
+ }
2860
2886
  graphics.lineTo(x2, y2);
2887
+ penDown = true;
2888
+ }
2889
+ else {
2890
+ penDown = false;
2861
2891
  }
2862
2892
  offset += step;
2863
2893
  patternRemaining -= step;
@@ -2865,9 +2895,13 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
2865
2895
  patternIndex = (patternIndex + 1) % pattern.length;
2866
2896
  patternRemaining = pattern[patternIndex];
2867
2897
  drawing = !drawing;
2898
+ if (!drawing) {
2899
+ penDown = false;
2900
+ }
2868
2901
  }
2869
2902
  }
2870
2903
  }
2904
+ return true;
2871
2905
  }
2872
2906
 
2873
2907
  function createPixiBoxLayer(options) {
@@ -3104,13 +3138,19 @@ function createPixiFocusLayer(options) {
3104
3138
  hide();
3105
3139
  return;
3106
3140
  }
3107
- const instruction = focusStyle.resolve({
3141
+ const resolvedInstruction = focusStyle.resolve({
3108
3142
  frame: context.frame,
3109
3143
  hoveredPick: context.hoveredPick,
3110
3144
  mediaTime: context.mediaTime,
3111
3145
  selectedPick: context.selectedPick,
3112
3146
  viewportScale: context.viewportScale,
3113
3147
  });
3148
+ const instruction = resolvedInstruction
3149
+ ? {
3150
+ ...resolvedInstruction,
3151
+ targets: resolvedInstruction.targets.filter(({ detection }) => options.isDetectionVisible?.(detection) ?? true),
3152
+ }
3153
+ : undefined;
3114
3154
  if (!instruction || instruction.targets.length === 0) {
3115
3155
  transitionToHidden();
3116
3156
  return;
@@ -3322,7 +3362,7 @@ function createFocusIdMaskRenderer(options) {
3322
3362
  autoGenerateMipmaps: false,
3323
3363
  dynamic: false,
3324
3364
  height: 1,
3325
- resource: createPlaceholderCanvas$2(),
3365
+ resource: createPlaceholderCanvas$3(),
3326
3366
  scaleMode: "nearest",
3327
3367
  width: 1,
3328
3368
  });
@@ -3414,7 +3454,7 @@ function createPremultipliedColor(fill) {
3414
3454
  alpha,
3415
3455
  ]);
3416
3456
  }
3417
- function createPlaceholderCanvas$2() {
3457
+ function createPlaceholderCanvas$3() {
3418
3458
  const canvas = document.createElement("canvas");
3419
3459
  canvas.height = 1;
3420
3460
  canvas.width = 1;
@@ -3536,15 +3576,21 @@ function createPixiInteractionLayer(options) {
3536
3576
  drawFrame(mediaTime) {
3537
3577
  currentMediaTime = mediaTime;
3538
3578
  activeFrame = options.detectionTimeline.selectFrame(mediaTime);
3539
- if (!canHandleInteraction()) {
3540
- pointerPoint = null;
3541
- marqueeRect = null;
3542
- setHoveredPick(null);
3543
- setSelectedPick(null);
3544
- }
3545
- else {
3579
+ if (canHandleInteraction()) {
3546
3580
  clearStalePicks(activeFrame);
3581
+ return;
3582
+ }
3583
+ pointerPoint = null;
3584
+ marqueeRect = null;
3585
+ setHoveredPick(null);
3586
+ if (isDestroyed || mode === MediaInteractionMode.Disabled) {
3587
+ setSelectedPick(null);
3588
+ return;
3547
3589
  }
3590
+ // Paused-only interaction gates new picks during playback, but an
3591
+ // existing selection keeps following its detection so selection-driven
3592
+ // presentation such as focus survives frame advances.
3593
+ followSelectedPicks(activeFrame);
3548
3594
  },
3549
3595
  setSelectedDetection(selection) {
3550
3596
  if (selection === null) {
@@ -3563,14 +3609,15 @@ function createPixiInteractionLayer(options) {
3563
3609
  return createState();
3564
3610
  },
3565
3611
  cycleSelection(direction = 1) {
3566
- if (!activeFrame?.detections.length)
3612
+ const visibleDetections = activeFrame?.detections.flatMap((detection, detectionIndex) => canPickDetection(detection) ? [{ detection, detectionIndex }] : []);
3613
+ if (!activeFrame || !visibleDetections?.length)
3567
3614
  return null;
3568
- const currentIndex = selectedPick?.detectionIndex ?? (direction > 0 ? -1 : 0);
3569
- const nextIndex = (currentIndex + direction + activeFrame.detections.length) %
3570
- activeFrame.detections.length;
3571
- const detection = activeFrame.detections[nextIndex];
3615
+ const currentVisibleIndex = visibleDetections.findIndex(({ detectionIndex }) => detectionIndex === selectedPick?.detectionIndex);
3616
+ const nextVisibleIndex = (currentVisibleIndex + direction + visibleDetections.length) %
3617
+ visibleDetections.length;
3618
+ const { detection, detectionIndex } = visibleDetections[nextVisibleIndex];
3572
3619
  const next = createPickFromSelection({
3573
- detectionIndex: nextIndex,
3620
+ detectionIndex,
3574
3621
  point: getDetectionCenter(detection),
3575
3622
  });
3576
3623
  setSelectedPick(next);
@@ -3699,6 +3746,8 @@ function createPixiInteractionLayer(options) {
3699
3746
  return;
3700
3747
  if (didDragMarquee && marqueeRect && activeFrame) {
3701
3748
  selectedPicks = activeFrame.detections.flatMap((detection, detectionIndex) => {
3749
+ if (!canPickDetection(detection))
3750
+ return [];
3702
3751
  const rect = getDetectionRect(detection);
3703
3752
  if (!rect || !intersects(rect, marqueeRect))
3704
3753
  return [];
@@ -3735,10 +3784,13 @@ function createPixiInteractionLayer(options) {
3735
3784
  const pickPoint = { x: point.x, y: point.y };
3736
3785
  const labelPick = options.pickLabelDetectionAtPoint?.(pickPoint, currentMediaTime);
3737
3786
  const activeLabelPick = rebaseDetectionPickToFrame(labelPick ?? null, activeFrame);
3738
- if (activeLabelPick)
3787
+ if (activeLabelPick && canPickDetection(activeLabelPick.detection)) {
3739
3788
  return activeLabelPick;
3789
+ }
3740
3790
  const geometryPick = pickDetectionAtPoint(activeFrame, pickPoint, {
3741
3791
  ...options.interaction,
3792
+ filter: (detection, detectionIndex) => canPickDetection(detection) &&
3793
+ (options.interaction.filter?.(detection, detectionIndex) ?? true),
3742
3794
  includeMasks: options.pickMaskDetectionAtPoint === undefined,
3743
3795
  maskMediaDimensions: options.getMediaDimensions?.(),
3744
3796
  padding: pickPadding,
@@ -3748,7 +3800,9 @@ function createPixiInteractionLayer(options) {
3748
3800
  }
3749
3801
  const maskPick = options.pickMaskDetectionAtPoint?.(pickPoint, currentMediaTime);
3750
3802
  const activeMaskPick = rebaseDetectionPickToFrame(maskPick ?? null, activeFrame);
3751
- return activeMaskPick ?? geometryPick;
3803
+ return activeMaskPick && canPickDetection(activeMaskPick.detection)
3804
+ ? activeMaskPick
3805
+ : geometryPick;
3752
3806
  }
3753
3807
  function createPickFromSelection(selection) {
3754
3808
  const frame = selection.mediaTime === undefined
@@ -3760,7 +3814,10 @@ function createPixiInteractionLayer(options) {
3760
3814
  const detection = detectionIndex === undefined || detectionIndex < 0
3761
3815
  ? undefined
3762
3816
  : frame?.detections[detectionIndex];
3763
- if (!frame || detectionIndex === undefined || !detection) {
3817
+ if (!frame ||
3818
+ detectionIndex === undefined ||
3819
+ !detection ||
3820
+ !canPickDetection(detection)) {
3764
3821
  return null;
3765
3822
  }
3766
3823
  return {
@@ -3774,19 +3831,44 @@ function createPixiInteractionLayer(options) {
3774
3831
  };
3775
3832
  }
3776
3833
  function clearStalePicks(frame) {
3777
- const nextHoveredPick = rebaseDetectionPickToFrame(hoveredPick, frame);
3778
- const nextSelectedPick = rebaseDetectionPickToFrame(selectedPick, frame);
3834
+ // Hover stays bound to the pointer, so it keeps a per-frame lifetime.
3835
+ const nextHoveredPick = filterPick(rebaseDetectionPickToFrame(hoveredPick, frame));
3779
3836
  if (hoveredPick && !nextHoveredPick) {
3780
3837
  setHoveredPick(null);
3781
3838
  }
3782
3839
  else if (nextHoveredPick && nextHoveredPick !== hoveredPick) {
3783
- setHoveredPick(nextHoveredPick);
3840
+ // Hover remains pointer-owned. Refresh its frame snapshot without
3841
+ // emitting a second event-driven redraw from the normal frame path.
3842
+ hoveredPick = nextHoveredPick;
3843
+ hoveredPickKey = createDetectionPickKey(nextHoveredPick);
3784
3844
  }
3785
- if (selectedPick && !nextSelectedPick) {
3786
- setSelectedPick(null);
3845
+ followSelectedPicks(frame);
3846
+ }
3847
+ function followSelectedPicks(frame) {
3848
+ if (selectedPicks.length === 0) {
3849
+ return;
3787
3850
  }
3788
- else if (nextSelectedPick && nextSelectedPick !== selectedPick) {
3789
- setSelectedPick(nextSelectedPick);
3851
+ const nextSelectedPicks = selectedPicks.flatMap((pick) => {
3852
+ const nextPick = filterPick(followDetectionPickAcrossFrames(pick, frame));
3853
+ return nextPick ? [nextPick] : [];
3854
+ });
3855
+ const currentKeys = selectedPicks.map(createDetectionPickKey).join("|");
3856
+ const nextKeys = nextSelectedPicks.map(createDetectionPickKey).join("|");
3857
+ const identityChanged = !haveSameDetectionPickIdentities(selectedPicks, nextSelectedPicks);
3858
+ selectedPicks = nextSelectedPicks;
3859
+ selectedPick = nextSelectedPicks.at(-1) ?? null;
3860
+ if (nextKeys === currentKeys) {
3861
+ return;
3862
+ }
3863
+ selectedPickKey = createDetectionPickKey(selectedPick);
3864
+ // A followed pick re-bases onto every new frame, so its full pick key
3865
+ // changes at playback rate. The scene's normal frame path consumes this
3866
+ // refreshed state immediately; emitting onStateChange here would redraw
3867
+ // focus and interaction a second time. Public callbacks only report
3868
+ // identity or membership changes.
3869
+ if (identityChanged) {
3870
+ options.interaction.onSelect?.(selectedPick);
3871
+ options.interaction.onSelectionChange?.(selectedPicks);
3790
3872
  }
3791
3873
  }
3792
3874
  function setHoveredPick(nextPick) {
@@ -3895,6 +3977,12 @@ function createPixiInteractionLayer(options) {
3895
3977
  }
3896
3978
  return options.canInteract();
3897
3979
  }
3980
+ function canPickDetection(detection) {
3981
+ return options.canPickDetection?.(detection) ?? true;
3982
+ }
3983
+ function filterPick(pick) {
3984
+ return pick && canPickDetection(pick.detection) ? pick : null;
3985
+ }
3898
3986
  }
3899
3987
  function getDetectionCenter(detection) {
3900
3988
  const rect = getDetectionRect(detection);
@@ -3940,8 +4028,11 @@ function compositeMaskFrame(instructions) {
3940
4028
  }
3941
4029
  return { data, height, width };
3942
4030
  }
4031
+ function createMaskIdFrame(instructions) {
4032
+ return createIdMaskFrame(materializeMaskInstructions(instructions));
4033
+ }
3943
4034
  async function createPngIdMaskFrame(instructions) {
3944
- const frame = createIdMaskFrame(materializeMaskInstructions(instructions));
4035
+ const frame = createMaskIdFrame(instructions);
3945
4036
  if (!frame) {
3946
4037
  return undefined;
3947
4038
  }
@@ -4480,6 +4571,9 @@ function createPixiVectorLayer(options) {
4480
4571
  let keypointStyle = options.keypointStyle === undefined
4481
4572
  ? new BaseKeypointStyle()
4482
4573
  : options.keypointStyle;
4574
+ // Shape decorations are opt-in; no default style exists so configuring
4575
+ // nothing keeps the semantic-geometry skip untouched.
4576
+ let shapeStyle = options.shapeStyle ?? null;
4483
4577
  let styleVersion = 0;
4484
4578
  let drawnStyleVersion = -1;
4485
4579
  // A versioned source can replace a frame without changing its timeline key.
@@ -4516,7 +4610,8 @@ function createPixiVectorLayer(options) {
4516
4610
  const resolvedDetections = frame.detections.flatMap((detection, detectionIndex) => {
4517
4611
  if (!detection.polygon &&
4518
4612
  !detection.polyline &&
4519
- !detection.keypoints) {
4613
+ !detection.keypoints &&
4614
+ !shapeStyle) {
4520
4615
  return [];
4521
4616
  }
4522
4617
  const resolved = resolveDetection(detection, detectionIndex, frame, mediaTime, viewportScale);
@@ -4551,6 +4646,8 @@ function createPixiVectorLayer(options) {
4551
4646
  polylineStyle = styles.polylineStyle;
4552
4647
  if (styles.keypointStyle !== undefined)
4553
4648
  keypointStyle = styles.keypointStyle;
4649
+ if (styles.shapeStyle !== undefined)
4650
+ shapeStyle = styles.shapeStyle;
4554
4651
  styleVersion += 1;
4555
4652
  },
4556
4653
  destroy() {
@@ -4595,13 +4692,16 @@ function createPixiVectorLayer(options) {
4595
4692
  const polygon = polygonStyle?.resolve(detection, context);
4596
4693
  const polyline = polylineStyle?.resolve(detection, context);
4597
4694
  const keypoints = keypointStyle?.resolve(detection, context);
4598
- if (!polygon && !polyline && !keypoints)
4695
+ const shapes = shapeStyle?.resolve(detection, context);
4696
+ if (!polygon && !polyline && !keypoints && !shapes?.length) {
4599
4697
  return undefined;
4698
+ }
4600
4699
  return {
4601
4700
  key: detectionKey(detection, detectionIndex),
4602
4701
  keypoints,
4603
4702
  polygon,
4604
4703
  polyline,
4704
+ shapes,
4605
4705
  zIndex: detection.zIndex ?? detectionIndex,
4606
4706
  };
4607
4707
  }
@@ -4610,7 +4710,10 @@ function createPixiVectorLayer(options) {
4610
4710
  if (!entry.cleared)
4611
4711
  graphics.clear();
4612
4712
  entry.cleared = false;
4613
- const { keypoints, polygon, polyline } = detection;
4713
+ const { keypoints, polygon, polyline, shapes } = detection;
4714
+ for (const shape of shapes ?? []) {
4715
+ drawShapeInstruction(graphics, shape, viewportScale);
4716
+ }
4614
4717
  if (polygon) {
4615
4718
  graphics.poly(polygon.points.flatMap(({ x, y }) => [x, y]), true);
4616
4719
  if (polygon.fill)
@@ -4628,31 +4731,102 @@ function createPixiVectorLayer(options) {
4628
4731
  drawPixiPath(graphics, [edge.from, edge.to], false, edge.stroke, viewportScale);
4629
4732
  }
4630
4733
  for (const marker of keypoints.markers) {
4631
- const radius = resolveScreenLength(marker.radius, viewportScale);
4632
4734
  if (marker.shape === KeypointMarkerShape.Cross) {
4633
- drawPixiPath(graphics, [
4634
- { x: marker.point.x - radius, y: marker.point.y - radius },
4635
- { x: marker.point.x + radius, y: marker.point.y + radius },
4636
- ], false, marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }, viewportScale);
4637
- drawPixiPath(graphics, [
4638
- { x: marker.point.x + radius, y: marker.point.y - radius },
4639
- { x: marker.point.x - radius, y: marker.point.y + radius },
4640
- ], false, marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }, viewportScale);
4735
+ drawMarkerInstruction(graphics, {
4736
+ center: marker.point,
4737
+ kind: ShapeInstructionKind.Marker,
4738
+ shape: MarkerShape.Cross,
4739
+ size: marker.radius * 2,
4740
+ sizeSpace: MarkerSizeSpace.Screen,
4741
+ stroke: asOpenStroke(marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }),
4742
+ }, viewportScale);
4641
4743
  }
4642
4744
  else {
4643
- graphics.circle(marker.point.x, marker.point.y, radius);
4644
- if (marker.fill)
4645
- graphics.fill(marker.fill);
4646
- if (marker.stroke)
4647
- graphics.stroke({
4648
- alpha: marker.stroke.alpha,
4649
- color: marker.stroke.color,
4650
- width: resolveScreenLength(marker.stroke.width, viewportScale),
4651
- });
4745
+ drawMarkerInstruction(graphics, {
4746
+ center: marker.point,
4747
+ fill: marker.fill,
4748
+ kind: ShapeInstructionKind.Marker,
4749
+ shape: MarkerShape.Circle,
4750
+ size: marker.radius * 2,
4751
+ sizeSpace: MarkerSizeSpace.Screen,
4752
+ stroke: marker.stroke,
4753
+ }, viewportScale);
4754
+ }
4755
+ }
4756
+ }
4757
+ }
4758
+ function drawShapeInstruction(graphics, instruction, viewportScale) {
4759
+ if (instruction.kind === ShapeInstructionKind.Ellipse) {
4760
+ const { closed, points } = sampleEllipseArc(instruction, resolveEllipseSegmentCount(instruction, viewportScale));
4761
+ if (closed && instruction.fill) {
4762
+ graphics.poly(points.flatMap(({ x, y }) => [x, y]), true);
4763
+ graphics.fill(instruction.fill);
4764
+ }
4765
+ if (instruction.stroke) {
4766
+ drawPixiPath(graphics, points, closed, instruction.stroke, viewportScale);
4767
+ }
4768
+ return;
4769
+ }
4770
+ if (instruction.kind === ShapeInstructionKind.Marker) {
4771
+ drawMarkerInstruction(graphics, instruction, viewportScale);
4772
+ return;
4773
+ }
4774
+ for (const segment of instruction.segments) {
4775
+ if (instruction.closed && instruction.fill) {
4776
+ graphics.poly(segment.flatMap(({ x, y }) => [x, y]), true);
4777
+ graphics.fill(instruction.fill);
4778
+ }
4779
+ drawPixiPath(graphics, segment, instruction.closed, instruction.stroke, viewportScale);
4780
+ }
4781
+ }
4782
+ function drawMarkerInstruction(graphics, instruction, viewportScale) {
4783
+ const geometry = resolveMarkerGeometry(instruction, viewportScale);
4784
+ if (geometry.kind === "circle") {
4785
+ const dashed = Boolean(instruction.stroke?.dash?.length);
4786
+ if (instruction.fill || !dashed) {
4787
+ graphics.circle(geometry.center.x, geometry.center.y, geometry.radius);
4788
+ if (instruction.fill)
4789
+ graphics.fill(instruction.fill);
4790
+ }
4791
+ if (instruction.stroke) {
4792
+ if (dashed) {
4793
+ const ellipse = {
4794
+ center: geometry.center,
4795
+ radiusX: geometry.radius,
4796
+ radiusY: geometry.radius,
4797
+ };
4798
+ const { points } = sampleEllipseArc(ellipse, resolveEllipseSegmentCount(ellipse, viewportScale));
4799
+ drawPixiPath(graphics, points, true, instruction.stroke, viewportScale);
4800
+ }
4801
+ else {
4802
+ graphics.stroke(resolvePixiStroke(instruction.stroke, viewportScale));
4652
4803
  }
4653
4804
  }
4805
+ return;
4806
+ }
4807
+ for (const subpath of geometry.subpaths) {
4808
+ if (geometry.closed && instruction.fill) {
4809
+ graphics.poly(subpath.flatMap(({ x, y }) => [x, y]), true);
4810
+ graphics.fill(instruction.fill);
4811
+ }
4812
+ if (instruction.stroke) {
4813
+ drawPixiPath(graphics, subpath, geometry.closed, instruction.stroke, viewportScale);
4814
+ }
4654
4815
  }
4655
4816
  }
4817
+ function asOpenStroke(stroke) {
4818
+ if (!stroke)
4819
+ return undefined;
4820
+ return {
4821
+ alpha: stroke.alpha,
4822
+ cap: stroke.cap,
4823
+ color: stroke.color,
4824
+ dash: stroke.dash,
4825
+ join: stroke.join,
4826
+ miterLimit: stroke.miterLimit,
4827
+ width: stroke.width,
4828
+ };
4829
+ }
4656
4830
  function detectionKey(detection, detectionIndex) {
4657
4831
  return detection.id === undefined
4658
4832
  ? `index:${detectionIndex}`
@@ -4782,7 +4956,8 @@ function createPixiInteractionPresentationLayer(options) {
4782
4956
  }
4783
4957
  function createActivePick(pick, frame, mediaTime, state) {
4784
4958
  const activePick = rebaseDetectionPickToFrame(pick, frame);
4785
- if (!activePick) {
4959
+ if (!activePick ||
4960
+ options.isDetectionVisible?.(activePick.detection) === false) {
4786
4961
  return null;
4787
4962
  }
4788
4963
  const context = {
@@ -5039,7 +5214,7 @@ function createInteractionMaskRenderer(options) {
5039
5214
  autoGenerateMipmaps: false,
5040
5215
  dynamic: false,
5041
5216
  height: 1,
5042
- resource: createPlaceholderCanvas$1(),
5217
+ resource: createPlaceholderCanvas$2(),
5043
5218
  scaleMode: "nearest",
5044
5219
  width: 1,
5045
5220
  });
@@ -5150,7 +5325,7 @@ function writePaletteEntry(palette, maskId, color, alpha) {
5150
5325
  palette[offset + 2] = (color & 0xff) / 255;
5151
5326
  palette[offset + 3] = clampedAlpha;
5152
5327
  }
5153
- function createPlaceholderCanvas$1() {
5328
+ function createPlaceholderCanvas$2() {
5154
5329
  const canvas = document.createElement("canvas");
5155
5330
  canvas.height = 1;
5156
5331
  canvas.width = 1;
@@ -5298,7 +5473,7 @@ void main(void) {
5298
5473
  }
5299
5474
  `;
5300
5475
 
5301
- const EMBEDDED_MASK_PREPARATION_WORKER_SOURCE = "(function () {\n 'use strict';\n\n /** COCO-compatible keypoint visibility values. */\n var KeypointVisibility;\n (function (KeypointVisibility) {\n KeypointVisibility[KeypointVisibility[\"NotLabeled\"] = 0] = \"NotLabeled\";\n KeypointVisibility[KeypointVisibility[\"Occluded\"] = 1] = \"Occluded\";\n KeypointVisibility[KeypointVisibility[\"Visible\"] = 2] = \"Visible\";\n })(KeypointVisibility || (KeypointVisibility = {}));\n var DetectionMaskEncoding;\n (function (DetectionMaskEncoding) {\n DetectionMaskEncoding[\"CompressedRle\"] = \"compressedRle\";\n })(DetectionMaskEncoding || (DetectionMaskEncoding = {}));\n\n var DetectionBufferStatus;\n (function (DetectionBufferStatus) {\n DetectionBufferStatus[\"Idle\"] = \"idle\";\n DetectionBufferStatus[\"Loading\"] = \"loading\";\n DetectionBufferStatus[\"Ready\"] = \"ready\";\n DetectionBufferStatus[\"Error\"] = \"error\";\n DetectionBufferStatus[\"Destroyed\"] = \"destroyed\";\n })(DetectionBufferStatus || (DetectionBufferStatus = {}));\n /**\n * How the renderer selects the active detection frame for a media timestamp.\n */\n var DetectionFrameSelectionMode;\n (function (DetectionFrameSelectionMode) {\n /**\n * Select the frame whose `[mediaTime, endTime)` interval contains the media\n * time. This is the default for interval annotations and timestamped sources.\n */\n DetectionFrameSelectionMode[\"Interval\"] = \"interval\";\n /**\n * Select from a known inference frame grid using `frameIndex`, `frameRate`,\n * and optional `frameIndexOriginTime`. This is useful when inference was run\n * on normalized frames and playback should snap detections to that grid.\n */\n DetectionFrameSelectionMode[\"NearestFrameIndex\"] = \"nearestFrameIndex\";\n })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));\n var DetectionFrameRetentionMode;\n (function (DetectionFrameRetentionMode) {\n /**\n * Keep writable detections only in an in-memory store. Useful for ephemeral\n * live streams where old predictions should disappear when evicted.\n */\n DetectionFrameRetentionMode[\"MemoryOnly\"] = \"memoryOnly\";\n /**\n * Persist every written detection frame. Useful for finite media where seek\n * and replay should not require recomputing detections.\n */\n DetectionFrameRetentionMode[\"PersistAll\"] = \"persistAll\";\n /**\n * Persist only the most recent retention window. Useful for long-running\n * streams where replay is bounded to a recent time horizon.\n */\n DetectionFrameRetentionMode[\"PersistWindow\"] = \"persistWindow\";\n })(DetectionFrameRetentionMode || (DetectionFrameRetentionMode = {}));\n function decodeCompressedRleMask(mask) {\n if (mask.encoding !== DetectionMaskEncoding.CompressedRle) {\n throw new Error(`Unsupported detection mask encoding: ${mask.encoding}`);\n }\n const data = new Uint8Array(mask.width * mask.height);\n const counts = decodeCompressedRleCounts(mask.counts);\n let offset = 0;\n for (let index = 0; index < counts.length; index += 1) {\n const runLength = counts[index] ?? 0;\n const isForeground = index % 2 === 1;\n if (isForeground) {\n for (let runOffset = 0; runOffset < runLength; runOffset += 1) {\n const maskOffset = offset + runOffset;\n const x = Math.floor(maskOffset / mask.height);\n const y = maskOffset % mask.height;\n const rowMajorOffset = y * mask.width + x;\n if (rowMajorOffset < data.length) {\n data[rowMajorOffset] = 1;\n }\n }\n }\n offset += runLength;\n }\n return {\n data,\n height: mask.height,\n width: mask.width,\n };\n }\n function decodeCompressedRleCounts(counts) {\n const decoded = [];\n let index = 0;\n while (index < counts.length) {\n let value = 0;\n let shift = 0;\n let charCode;\n do {\n charCode = counts.charCodeAt(index) - 48;\n index += 1;\n value |= (charCode & 0x1f) << shift;\n shift += 5;\n } while (charCode & 0x20);\n if (charCode & 0x10) {\n value |= -1 << shift;\n }\n if (decoded.length > 2) {\n value += decoded[decoded.length - 2] ?? 0;\n }\n decoded.push(value);\n }\n return decoded;\n }\n function encodeCompressedRleCounts(counts) {\n return counts\n .map((count, index) => {\n let value = index > 2 ? count - counts[index - 2] : count;\n let encoded = \"\";\n let more = true;\n while (more) {\n let charCode = value & 0x1f;\n value >>= 5;\n more = !((value === 0 && (charCode & 0x10) === 0) ||\n (value === -1 && (charCode & 0x10) !== 0));\n if (more) {\n charCode |= 0x20;\n }\n encoded += String.fromCharCode(charCode + 48);\n }\n return encoded;\n })\n .join(\"\");\n }\n\n var AnnotationFrameMutationKind;\n (function (AnnotationFrameMutationKind) {\n AnnotationFrameMutationKind[\"Add\"] = \"add\";\n AnnotationFrameMutationKind[\"Remove\"] = \"remove\";\n AnnotationFrameMutationKind[\"Replace\"] = \"replace\";\n AnnotationFrameMutationKind[\"Transact\"] = \"transact\";\n AnnotationFrameMutationKind[\"Update\"] = \"update\";\n })(AnnotationFrameMutationKind || (AnnotationFrameMutationKind = {}));\n\n var DetectionPickTarget;\n (function (DetectionPickTarget) {\n DetectionPickTarget[\"Box\"] = \"box\";\n DetectionPickTarget[\"Edge\"] = \"edge\";\n DetectionPickTarget[\"Keypoint\"] = \"keypoint\";\n DetectionPickTarget[\"Label\"] = \"label\";\n DetectionPickTarget[\"Mask\"] = \"mask\";\n DetectionPickTarget[\"Polygon\"] = \"polygon\";\n DetectionPickTarget[\"Polyline\"] = \"polyline\";\n })(DetectionPickTarget || (DetectionPickTarget = {}));\n var MediaInteractionMode;\n (function (MediaInteractionMode) {\n MediaInteractionMode[\"Always\"] = \"always\";\n MediaInteractionMode[\"Disabled\"] = \"disabled\";\n MediaInteractionMode[\"PausedOnly\"] = \"pausedOnly\";\n })(MediaInteractionMode || (MediaInteractionMode = {}));\n\n function centerRectToTopLeftRect(rect) {\n return {\n height: rect.height,\n width: rect.width,\n x: rect.x - rect.width / 2,\n y: rect.y - rect.height / 2,\n };\n }\n function getPointsRect(points) {\n if (points.length === 0) {\n return undefined;\n }\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n for (const point of points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n return {\n height: maxY - minY,\n width: maxX - minX,\n x: (minX + maxX) / 2,\n y: (minY + maxY) / 2,\n };\n }\n\n var AnnotationGeometryKind;\n (function (AnnotationGeometryKind) {\n AnnotationGeometryKind[\"Box\"] = \"box\";\n AnnotationGeometryKind[\"Polygon\"] = \"polygon\";\n AnnotationGeometryKind[\"Polyline\"] = \"polyline\";\n AnnotationGeometryKind[\"Keypoints\"] = \"keypoints\";\n AnnotationGeometryKind[\"Mask\"] = \"mask\";\n })(AnnotationGeometryKind || (AnnotationGeometryKind = {}));\n var AnnotationGestureStateKind;\n (function (AnnotationGestureStateKind) {\n AnnotationGestureStateKind[\"Idle\"] = \"idle\";\n AnnotationGestureStateKind[\"Creating\"] = \"creating\";\n AnnotationGestureStateKind[\"Moving\"] = \"moving\";\n AnnotationGestureStateKind[\"Resizing\"] = \"resizing\";\n AnnotationGestureStateKind[\"DragSelecting\"] = \"dragSelecting\";\n })(AnnotationGestureStateKind || (AnnotationGestureStateKind = {}));\n var AnnotationHandleKind;\n (function (AnnotationHandleKind) {\n AnnotationHandleKind[\"Resize\"] = \"resize\";\n AnnotationHandleKind[\"Vertex\"] = \"vertex\";\n AnnotationHandleKind[\"AddVertex\"] = \"addVertex\";\n AnnotationHandleKind[\"Keypoint\"] = \"keypoint\";\n })(AnnotationHandleKind || (AnnotationHandleKind = {}));\n\n var BoxShape;\n (function (BoxShape) {\n BoxShape[\"Rect\"] = \"rect\";\n BoxShape[\"RoundedRect\"] = \"roundedRect\";\n })(BoxShape || (BoxShape = {}));\n var BoxStrokeAlignment;\n (function (BoxStrokeAlignment) {\n BoxStrokeAlignment[\"Inside\"] = \"inside\";\n BoxStrokeAlignment[\"Center\"] = \"center\";\n BoxStrokeAlignment[\"Outside\"] = \"outside\";\n })(BoxStrokeAlignment || (BoxStrokeAlignment = {}));\n\n var FocusTargetMode;\n (function (FocusTargetMode) {\n FocusTargetMode[\"Hovered\"] = \"hovered\";\n FocusTargetMode[\"Selected\"] = \"selected\";\n FocusTargetMode[\"HoveredAndSelected\"] = \"hoveredAndSelected\";\n FocusTargetMode[\"Ambient\"] = \"ambient\";\n })(FocusTargetMode || (FocusTargetMode = {}));\n\n var DetectionInteractionState;\n (function (DetectionInteractionState) {\n DetectionInteractionState[\"Hovered\"] = \"hovered\";\n DetectionInteractionState[\"Selected\"] = \"selected\";\n })(DetectionInteractionState || (DetectionInteractionState = {}));\n\n var LabelPlacement;\n (function (LabelPlacement) {\n LabelPlacement[\"Top\"] = \"top\";\n LabelPlacement[\"Bottom\"] = \"bottom\";\n LabelPlacement[\"InsideTop\"] = \"insideTop\";\n LabelPlacement[\"InsideBottom\"] = \"insideBottom\";\n LabelPlacement[\"Center\"] = \"center\";\n })(LabelPlacement || (LabelPlacement = {}));\n var LabelVisibilityMode;\n (function (LabelVisibilityMode) {\n LabelVisibilityMode[\"Always\"] = \"always\";\n LabelVisibilityMode[\"HoveredOnly\"] = \"hoveredOnly\";\n })(LabelVisibilityMode || (LabelVisibilityMode = {}));\n\n var MaskRenderMode;\n (function (MaskRenderMode) {\n MaskRenderMode[\"FillAndStroke\"] = \"fillAndStroke\";\n MaskRenderMode[\"FillOnly\"] = \"fillOnly\";\n MaskRenderMode[\"StrokeOnly\"] = \"strokeOnly\";\n })(MaskRenderMode || (MaskRenderMode = {}));\n\n var KeypointMarkerShape;\n (function (KeypointMarkerShape) {\n KeypointMarkerShape[\"Circle\"] = \"circle\";\n KeypointMarkerShape[\"Cross\"] = \"cross\";\n })(KeypointMarkerShape || (KeypointMarkerShape = {}));\n\n var DetectionMaskPayloadFormat;\n (function (DetectionMaskPayloadFormat) {\n DetectionMaskPayloadFormat[\"RawCocoRle\"] = \"rawCocoRle\";\n DetectionMaskPayloadFormat[\"DeflatedBase64\"] = \"deflatedBase64\";\n })(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));\n function encodeBinaryMask(data, width, height) {\n assertMaskDimensions(data, width, height);\n const runs = [];\n let currentValue = 0;\n let runLength = 0;\n for (let x = 0; x < width; x += 1) {\n for (let y = 0; y < height; y += 1) {\n const value = data[y * width + x] ? 1 : 0;\n if (value === currentValue) {\n runLength += 1;\n }\n else {\n runs.push(runLength);\n currentValue = value;\n runLength = 1;\n }\n }\n }\n runs.push(runLength);\n return {\n counts: encodeCompressedRleCounts(runs),\n encoding: DetectionMaskEncoding.CompressedRle,\n height,\n width,\n };\n }\n function assertMaskDimensions(data, width, height) {\n if (!Number.isInteger(width) || width <= 0) {\n throw new Error(\"Mask width must be a positive integer.\");\n }\n if (!Number.isInteger(height) || height <= 0) {\n throw new Error(\"Mask height must be a positive integer.\");\n }\n if (data.length !== width * height) {\n throw new Error(\"Mask data length must equal width * height.\");\n }\n }\n function rasterizePolygonToMask(points, dimensions) {\n const data = createEmptyMask(dimensions);\n if (points.length < 3) {\n return data;\n }\n const bounds = centerRectToTopLeftRect(getPointsRect(points));\n const startY = Math.max(0, Math.floor(bounds.y));\n const endY = Math.min(dimensions.height - 1, Math.ceil(bounds.y + bounds.height));\n for (let y = startY; y <= endY; y += 1) {\n const scanY = y + 0.5;\n const intersections = [];\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index];\n const next = points[(index + 1) % points.length];\n if ((current.y <= scanY && next.y > scanY) ||\n (next.y <= scanY && current.y > scanY)) {\n const ratio = (scanY - current.y) / (next.y - current.y);\n intersections.push(current.x + ratio * (next.x - current.x));\n }\n }\n intersections.sort((left, right) => left - right);\n for (let index = 0; index < intersections.length - 1; index += 2) {\n const left = Math.max(0, Math.ceil(intersections[index]));\n const right = Math.min(dimensions.width - 1, Math.floor(intersections[index + 1]));\n for (let x = left; x <= right; x += 1) {\n data[y * dimensions.width + x] = 1;\n }\n }\n }\n return data;\n }\n function createEmptyMask(dimensions) {\n if (!Number.isInteger(dimensions.width) ||\n dimensions.width <= 0 ||\n !Number.isInteger(dimensions.height) ||\n dimensions.height <= 0) {\n throw new Error(\"Media dimensions must be positive integers.\");\n }\n return new Uint8Array(dimensions.width * dimensions.height);\n }\n\n const MAX_ID_MASK_PALETTE_ENTRIES = 64;\n const MAX_ID_MASK_STROKE_WIDTH = 16;\n function createIdMaskFrame(instructions) {\n if (instructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...instructions.map(({ mask }) => mask.width));\n const height = Math.max(...instructions.map(({ mask }) => mask.height));\n const data = new Uint8Array(new ArrayBuffer(width * height));\n const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokeWidths = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4));\n let hasStroke = false;\n let maxStrokeWidth = 0;\n for (const instruction of instructions) {\n const detectionMaskId = instruction.detectionIndex + 1;\n if (detectionMaskId <= 0 ||\n detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {\n return undefined;\n }\n writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);\n if (instruction.stroke && instruction.stroke.width > 0) {\n const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);\n hasStroke = true;\n strokeWidths[detectionMaskId] = strokeWidth;\n maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);\n writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);\n }\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (decodedMask.data[maskOffset]) {\n data[y * width + x] = detectionMaskId;\n }\n }\n }\n }\n return {\n data,\n fillPalette,\n hasStroke,\n height,\n maxStrokeWidth,\n strokePalette,\n strokeWidths,\n width,\n };\n }\n function writePaletteEntry(palette, id, color, alpha) {\n const offset = id * 4;\n palette[offset] = ((color >> 16) & 0xff) / 255;\n palette[offset + 1] = ((color >> 8) & 0xff) / 255;\n palette[offset + 2] = (color & 0xff) / 255;\n palette[offset + 3] = Math.max(0, Math.min(alpha, 1));\n }\n\n var MediaRendererFit;\n (function (MediaRendererFit) {\n /**\n * Preserve media aspect ratio and fit the full frame inside the canvas.\n */\n MediaRendererFit[\"Contain\"] = \"contain\";\n /**\n * Preserve media aspect ratio and fill the canvas, cropping if necessary.\n */\n MediaRendererFit[\"Cover\"] = \"cover\";\n })(MediaRendererFit || (MediaRendererFit = {}));\n /**\n * Playback lifecycle state reported by a platform renderer.\n */\n var MediaRendererPlaybackState;\n (function (MediaRendererPlaybackState) {\n MediaRendererPlaybackState[\"Loading\"] = \"loading\";\n MediaRendererPlaybackState[\"Ready\"] = \"ready\";\n MediaRendererPlaybackState[\"Playing\"] = \"playing\";\n MediaRendererPlaybackState[\"Buffering\"] = \"buffering\";\n MediaRendererPlaybackState[\"Paused\"] = \"paused\";\n MediaRendererPlaybackState[\"Error\"] = \"error\";\n MediaRendererPlaybackState[\"Destroyed\"] = \"destroyed\";\n })(MediaRendererPlaybackState || (MediaRendererPlaybackState = {}));\n /**\n * Lower-level media source readiness.\n */\n var MediaSourceStatus;\n (function (MediaSourceStatus) {\n MediaSourceStatus[\"Loading\"] = \"loading\";\n MediaSourceStatus[\"Ready\"] = \"ready\";\n MediaSourceStatus[\"Error\"] = \"error\";\n MediaSourceStatus[\"Destroyed\"] = \"destroyed\";\n })(MediaSourceStatus || (MediaSourceStatus = {}));\n\n /**\n * Media-session operating mode.\n *\n * Core owns the names because file-like and stream-like lifecycle choices are\n * platform-neutral. Platform packages decide how these modes tune media,\n * storage, and renderer defaults.\n */\n var MediaSessionMode;\n (function (MediaSessionMode) {\n /**\n * Finite media. Defaults usually favor seek/replay and persistent detection\n * storage.\n */\n MediaSessionMode[\"File\"] = \"file\";\n /**\n * Live or append-only media. Defaults usually favor rolling windows and\n * bounded retention.\n */\n MediaSessionMode[\"Stream\"] = \"stream\";\n })(MediaSessionMode || (MediaSessionMode = {}));\n /**\n * Aggregate lifecycle state for a media session.\n */\n var MediaSessionStatus;\n (function (MediaSessionStatus) {\n MediaSessionStatus[\"Buffering\"] = \"buffering\";\n MediaSessionStatus[\"Destroyed\"] = \"destroyed\";\n MediaSessionStatus[\"Error\"] = \"error\";\n MediaSessionStatus[\"Loading\"] = \"loading\";\n MediaSessionStatus[\"Paused\"] = \"paused\";\n MediaSessionStatus[\"Playing\"] = \"playing\";\n MediaSessionStatus[\"Processing\"] = \"processing\";\n MediaSessionStatus[\"Ready\"] = \"ready\";\n })(MediaSessionStatus || (MediaSessionStatus = {}));\n /**\n * Subsystem currently affecting session readiness or presentation.\n */\n var MediaSessionActivityKind;\n (function (MediaSessionActivityKind) {\n MediaSessionActivityKind[\"DetectionsBuffering\"] = \"detectionsBuffering\";\n MediaSessionActivityKind[\"DetectionsLoading\"] = \"detectionsLoading\";\n MediaSessionActivityKind[\"Error\"] = \"error\";\n MediaSessionActivityKind[\"MediaNormalizing\"] = \"mediaNormalizing\";\n MediaSessionActivityKind[\"MediaOpening\"] = \"mediaOpening\";\n MediaSessionActivityKind[\"PlaybackBuffering\"] = \"playbackBuffering\";\n MediaSessionActivityKind[\"RenderPreparing\"] = \"renderPreparing\";\n })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));\n /**\n * State of one session activity.\n */\n var MediaSessionActivityStatus;\n (function (MediaSessionActivityStatus) {\n MediaSessionActivityStatus[\"Error\"] = \"error\";\n MediaSessionActivityStatus[\"Running\"] = \"running\";\n MediaSessionActivityStatus[\"Waiting\"] = \"waiting\";\n })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));\n\n const PNG_SIGNATURE = new Uint8Array([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n ]);\n function compositeMaskFrame(instructions) {\n const maskInstructions = materializeMaskInstructions(instructions);\n if (maskInstructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...maskInstructions.map(({ mask }) => mask.width));\n const height = Math.max(...maskInstructions.map(({ mask }) => mask.height));\n const data = new Uint8ClampedArray(new ArrayBuffer(width * height * 4));\n for (const instruction of maskInstructions) {\n compositeInstruction(data, width, instruction);\n }\n return { data, height, width };\n }\n async function createPngIdMaskFrame(instructions) {\n const frame = createIdMaskFrame(materializeMaskInstructions(instructions));\n if (!frame) {\n return undefined;\n }\n return {\n ...frame,\n png: await encodeGrayscalePng({\n height: frame.height,\n pixels: frame.data,\n width: frame.width,\n }),\n };\n }\n function compositeInstruction(rgba, canvasWidth, instruction) {\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n const fill = resolveRgbaColor(instruction.color, instruction.alpha);\n compositeMaskFill(rgba, canvasWidth, decodedMask, fill);\n if (instruction.stroke) {\n compositeMaskStroke(rgba, canvasWidth, decodedMask, instruction.stroke);\n }\n }\n function materializeMaskInstructions(instructions) {\n return instructions.map((instruction) => {\n if (instruction.mask) {\n return instruction;\n }\n const { height, points, width } = instruction.polygon;\n return {\n alpha: instruction.alpha,\n color: instruction.color,\n detectionIndex: instruction.detectionIndex,\n mask: encodeBinaryMask(rasterizePolygonToMask(points, { height, width }), width, height),\n stroke: instruction.stroke,\n };\n });\n }\n function compositeMaskFill(rgba, canvasWidth, decodedMask, fill) {\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (!decodedMask.data[maskOffset]) {\n continue;\n }\n writePixel(rgba, canvasWidth, x, y, fill);\n }\n }\n }\n function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {\n const width = Math.round(stroke.width);\n if (width <= 0) {\n return;\n }\n const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n if (!isMaskPixel(decodedMask, x, y) ||\n !isBoundaryPixel(decodedMask, x, y)) {\n continue;\n }\n for (let offsetY = -width; offsetY <= width; offsetY += 1) {\n for (let offsetX = -width; offsetX <= width; offsetX += 1) {\n const strokeX = x + offsetX;\n const strokeY = y + offsetY;\n if (isOutsideMaskBounds(decodedMask, strokeX, strokeY) ||\n isMaskPixel(decodedMask, strokeX, strokeY)) {\n continue;\n }\n writePixel(rgba, canvasWidth, strokeX, strokeY, strokeColor);\n }\n }\n }\n }\n }\n function isBoundaryPixel(mask, x, y) {\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if (offsetX === 0 && offsetY === 0) {\n continue;\n }\n const neighborX = x + offsetX;\n const neighborY = y + offsetY;\n if (isOutsideMaskBounds(mask, neighborX, neighborY) ||\n !isMaskPixel(mask, neighborX, neighborY)) {\n return true;\n }\n }\n }\n return false;\n }\n function isMaskPixel(mask, x, y) {\n return mask.data[y * mask.width + x] === 1;\n }\n function isOutsideMaskBounds(mask, x, y) {\n return x < 0 || y < 0 || x >= mask.width || y >= mask.height;\n }\n function resolveRgbaColor(color, alpha) {\n return {\n alpha: Math.round(Math.max(0, Math.min(alpha, 1)) * 255),\n blue: color & 0xff,\n green: (color >> 8) & 0xff,\n red: (color >> 16) & 0xff,\n };\n }\n function writePixel(rgba, canvasWidth, x, y, color) {\n const rgbaOffset = (y * canvasWidth + x) * 4;\n rgba[rgbaOffset] = color.red;\n rgba[rgbaOffset + 1] = color.green;\n rgba[rgbaOffset + 2] = color.blue;\n rgba[rgbaOffset + 3] = color.alpha;\n }\n async function encodeGrayscalePng(options) {\n if (typeof CompressionStream === \"undefined\") {\n throw new Error(\"CompressionStream is required to encode PNG ID masks.\");\n }\n const rawScanlines = createFilterlessPngScanlines(options);\n const ihdr = new Uint8Array(13);\n const ihdrView = new DataView(ihdr.buffer);\n ihdrView.setUint32(0, options.width);\n ihdrView.setUint32(4, options.height);\n ihdr[8] = 8;\n ihdr[9] = 0;\n ihdr[10] = 0;\n ihdr[11] = 0;\n ihdr[12] = 0;\n const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])\n .stream()\n .pipeThrough(new CompressionStream(\"deflate\"))).arrayBuffer());\n return concatUint8Arrays([\n PNG_SIGNATURE,\n createPngChunk(\"IHDR\", ihdr),\n createPngChunk(\"IDAT\", compressed),\n createPngChunk(\"IEND\", new Uint8Array(0)),\n ]);\n }\n function createFilterlessPngScanlines(options) {\n const rowStride = options.width + 1;\n const scanlines = new Uint8Array(rowStride * options.height);\n for (let y = 0; y < options.height; y += 1) {\n const sourceOffset = y * options.width;\n const targetOffset = y * rowStride;\n scanlines[targetOffset] = 0;\n scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);\n }\n return scanlines;\n }\n function createPngChunk(type, data) {\n const typeBytes = new TextEncoder().encode(type);\n const chunk = new Uint8Array(12 + data.length);\n const view = new DataView(chunk.buffer);\n view.setUint32(0, data.length);\n chunk.set(typeBytes, 4);\n chunk.set(data, 8);\n view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));\n return chunk;\n }\n const crc32Table = createCrc32Table();\n function createCrc32Table() {\n const table = new Uint32Array(256);\n for (let index = 0; index < table.length; index += 1) {\n let value = index;\n for (let bit = 0; bit < 8; bit += 1) {\n value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;\n }\n table[index] = value >>> 0;\n }\n return table;\n }\n function crc32(bytes) {\n let crc = 0xffffffff;\n for (const byte of bytes) {\n crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n }\n function concatUint8Arrays(chunks) {\n const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n }\n\n var PreparedMaskFrameKind;\n (function (PreparedMaskFrameKind) {\n PreparedMaskFrameKind[\"PngIdMask\"] = \"pngIdMask\";\n PreparedMaskFrameKind[\"RgbaImage\"] = \"rgbaImage\";\n })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));\n\n var MaskPreparationWorkerMessageType;\n (function (MaskPreparationWorkerMessageType) {\n MaskPreparationWorkerMessageType[\"Complete\"] = \"complete\";\n MaskPreparationWorkerMessageType[\"Empty\"] = \"empty\";\n MaskPreparationWorkerMessageType[\"Error\"] = \"error\";\n MaskPreparationWorkerMessageType[\"Prepare\"] = \"prepare\";\n })(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));\n\n const workerScope = globalThis;\n workerScope.addEventListener(\"message\", (event) => {\n const message = event.data;\n if (message.type !== MaskPreparationWorkerMessageType.Prepare) {\n return;\n }\n void prepareMaskFrame(message);\n });\n async function prepareMaskFrame(message) {\n try {\n const pngIdMaskFrame = await createPngIdMaskWorkerResponse(message);\n if (pngIdMaskFrame) {\n workerScope.postMessage(pngIdMaskFrame, [\n pngIdMaskFrame.imageBitmap,\n pngIdMaskFrame.png.buffer,\n pngIdMaskFrame.fillPalette.buffer,\n pngIdMaskFrame.strokePalette.buffer,\n pngIdMaskFrame.strokeWidths.buffer,\n ]);\n return;\n }\n const compositedFrame = compositeMaskFrame(message.job.instructions);\n if (!compositedFrame) {\n workerScope.postMessage({\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Empty,\n });\n return;\n }\n const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);\n const imageBitmap = createImageBitmapFromImageData(imageData);\n if (imageBitmap) {\n workerScope.postMessage({\n imageBitmap,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [imageBitmap]);\n return;\n }\n workerScope.postMessage({\n imageData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [imageData.data.buffer]);\n }\n catch (error) {\n workerScope.postMessage({\n error: error instanceof Error\n ? error.message\n : \"Unable to prepare mask frame.\",\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Error,\n });\n }\n }\n async function createPngIdMaskWorkerResponse(message) {\n if (typeof Blob === \"undefined\" ||\n typeof createImageBitmap === \"undefined\" ||\n typeof CompressionStream === \"undefined\") {\n return undefined;\n }\n let frame;\n try {\n frame = await createPngIdMaskFrame(message.job.instructions);\n }\n catch {\n return undefined;\n }\n if (!frame) {\n return undefined;\n }\n let imageBitmap;\n try {\n imageBitmap = await createImageBitmap(new Blob([frame.png], { type: \"image/png\" }));\n }\n catch {\n return undefined;\n }\n return {\n artifactKind: PreparedMaskFrameKind.PngIdMask,\n fillPalette: frame.fillPalette,\n hasStroke: frame.hasStroke,\n imageBitmap,\n key: message.job.key,\n maxStrokeWidth: frame.maxStrokeWidth,\n png: frame.png,\n requestId: message.requestId,\n strokePalette: frame.strokePalette,\n strokeWidths: frame.strokeWidths,\n type: MaskPreparationWorkerMessageType.Complete,\n };\n }\n function createImageBitmapFromImageData(imageData) {\n if (typeof OffscreenCanvas === \"undefined\") {\n return null;\n }\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const context = canvas.getContext(\"2d\");\n if (!context) {\n return null;\n }\n context.putImageData(imageData, 0, 0);\n return canvas.transferToImageBitmap();\n }\n\n})();";
5476
+ const EMBEDDED_MASK_PREPARATION_WORKER_SOURCE = "(function () {\n 'use strict';\n\n /** COCO-compatible keypoint visibility values. */\n var KeypointVisibility;\n (function (KeypointVisibility) {\n KeypointVisibility[KeypointVisibility[\"NotLabeled\"] = 0] = \"NotLabeled\";\n KeypointVisibility[KeypointVisibility[\"Occluded\"] = 1] = \"Occluded\";\n KeypointVisibility[KeypointVisibility[\"Visible\"] = 2] = \"Visible\";\n })(KeypointVisibility || (KeypointVisibility = {}));\n var DetectionMaskEncoding;\n (function (DetectionMaskEncoding) {\n DetectionMaskEncoding[\"CompressedRle\"] = \"compressedRle\";\n })(DetectionMaskEncoding || (DetectionMaskEncoding = {}));\n\n var DetectionBufferStatus;\n (function (DetectionBufferStatus) {\n DetectionBufferStatus[\"Idle\"] = \"idle\";\n DetectionBufferStatus[\"Loading\"] = \"loading\";\n DetectionBufferStatus[\"Ready\"] = \"ready\";\n DetectionBufferStatus[\"Error\"] = \"error\";\n DetectionBufferStatus[\"Destroyed\"] = \"destroyed\";\n })(DetectionBufferStatus || (DetectionBufferStatus = {}));\n /**\n * How the renderer selects the active detection frame for a media timestamp.\n */\n var DetectionFrameSelectionMode;\n (function (DetectionFrameSelectionMode) {\n /**\n * Select the frame whose `[mediaTime, endTime)` interval contains the media\n * time. This is the default for interval annotations and timestamped sources.\n */\n DetectionFrameSelectionMode[\"Interval\"] = \"interval\";\n /**\n * Select from a known inference frame grid using `frameIndex`, `frameRate`,\n * and optional `frameIndexOriginTime`. This is useful when inference was run\n * on normalized frames and playback should snap detections to that grid.\n */\n DetectionFrameSelectionMode[\"NearestFrameIndex\"] = \"nearestFrameIndex\";\n })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));\n var DetectionFrameRetentionMode;\n (function (DetectionFrameRetentionMode) {\n /**\n * Keep writable detections only in an in-memory store. Useful for ephemeral\n * live streams where old predictions should disappear when evicted.\n */\n DetectionFrameRetentionMode[\"MemoryOnly\"] = \"memoryOnly\";\n /**\n * Persist every written detection frame. Useful for finite media where seek\n * and replay should not require recomputing detections.\n */\n DetectionFrameRetentionMode[\"PersistAll\"] = \"persistAll\";\n /**\n * Persist only the most recent retention window. Useful for long-running\n * streams where replay is bounded to a recent time horizon.\n */\n DetectionFrameRetentionMode[\"PersistWindow\"] = \"persistWindow\";\n })(DetectionFrameRetentionMode || (DetectionFrameRetentionMode = {}));\n function decodeCompressedRleMask(mask) {\n if (mask.encoding !== DetectionMaskEncoding.CompressedRle) {\n throw new Error(`Unsupported detection mask encoding: ${mask.encoding}`);\n }\n const data = new Uint8Array(mask.width * mask.height);\n const counts = decodeCompressedRleCounts(mask.counts);\n let offset = 0;\n for (let index = 0; index < counts.length; index += 1) {\n const runLength = counts[index] ?? 0;\n const isForeground = index % 2 === 1;\n if (isForeground) {\n for (let runOffset = 0; runOffset < runLength; runOffset += 1) {\n const maskOffset = offset + runOffset;\n const x = Math.floor(maskOffset / mask.height);\n const y = maskOffset % mask.height;\n const rowMajorOffset = y * mask.width + x;\n if (rowMajorOffset < data.length) {\n data[rowMajorOffset] = 1;\n }\n }\n }\n offset += runLength;\n }\n return {\n data,\n height: mask.height,\n width: mask.width,\n };\n }\n function decodeCompressedRleCounts(counts) {\n const decoded = [];\n let index = 0;\n while (index < counts.length) {\n let value = 0;\n let shift = 0;\n let charCode;\n do {\n charCode = counts.charCodeAt(index) - 48;\n index += 1;\n value |= (charCode & 0x1f) << shift;\n shift += 5;\n } while (charCode & 0x20);\n if (charCode & 0x10) {\n value |= -1 << shift;\n }\n if (decoded.length > 2) {\n value += decoded[decoded.length - 2] ?? 0;\n }\n decoded.push(value);\n }\n return decoded;\n }\n function encodeCompressedRleCounts(counts) {\n return counts\n .map((count, index) => {\n let value = index > 2 ? count - counts[index - 2] : count;\n let encoded = \"\";\n let more = true;\n while (more) {\n let charCode = value & 0x1f;\n value >>= 5;\n more = !((value === 0 && (charCode & 0x10) === 0) ||\n (value === -1 && (charCode & 0x10) !== 0));\n if (more) {\n charCode |= 0x20;\n }\n encoded += String.fromCharCode(charCode + 48);\n }\n return encoded;\n })\n .join(\"\");\n }\n\n var AnnotationFrameMutationKind;\n (function (AnnotationFrameMutationKind) {\n AnnotationFrameMutationKind[\"Add\"] = \"add\";\n AnnotationFrameMutationKind[\"Remove\"] = \"remove\";\n AnnotationFrameMutationKind[\"Replace\"] = \"replace\";\n AnnotationFrameMutationKind[\"Transact\"] = \"transact\";\n AnnotationFrameMutationKind[\"Update\"] = \"update\";\n })(AnnotationFrameMutationKind || (AnnotationFrameMutationKind = {}));\n\n var DetectionPickTarget;\n (function (DetectionPickTarget) {\n DetectionPickTarget[\"Box\"] = \"box\";\n DetectionPickTarget[\"Edge\"] = \"edge\";\n DetectionPickTarget[\"Keypoint\"] = \"keypoint\";\n DetectionPickTarget[\"Label\"] = \"label\";\n DetectionPickTarget[\"Mask\"] = \"mask\";\n DetectionPickTarget[\"Polygon\"] = \"polygon\";\n DetectionPickTarget[\"Polyline\"] = \"polyline\";\n })(DetectionPickTarget || (DetectionPickTarget = {}));\n var MediaInteractionMode;\n (function (MediaInteractionMode) {\n MediaInteractionMode[\"Always\"] = \"always\";\n MediaInteractionMode[\"Disabled\"] = \"disabled\";\n MediaInteractionMode[\"PausedOnly\"] = \"pausedOnly\";\n })(MediaInteractionMode || (MediaInteractionMode = {}));\n\n function centerRectToTopLeftRect(rect) {\n return {\n height: rect.height,\n width: rect.width,\n x: rect.x - rect.width / 2,\n y: rect.y - rect.height / 2,\n };\n }\n function getPointsRect(points) {\n if (points.length === 0) {\n return undefined;\n }\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n for (const point of points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n return {\n height: maxY - minY,\n width: maxX - minX,\n x: (minX + maxX) / 2,\n y: (minY + maxY) / 2,\n };\n }\n\n var AnnotationGeometryKind;\n (function (AnnotationGeometryKind) {\n AnnotationGeometryKind[\"Box\"] = \"box\";\n AnnotationGeometryKind[\"Polygon\"] = \"polygon\";\n AnnotationGeometryKind[\"Polyline\"] = \"polyline\";\n AnnotationGeometryKind[\"Keypoints\"] = \"keypoints\";\n AnnotationGeometryKind[\"Mask\"] = \"mask\";\n })(AnnotationGeometryKind || (AnnotationGeometryKind = {}));\n var AnnotationGestureStateKind;\n (function (AnnotationGestureStateKind) {\n AnnotationGestureStateKind[\"Idle\"] = \"idle\";\n AnnotationGestureStateKind[\"Creating\"] = \"creating\";\n AnnotationGestureStateKind[\"Moving\"] = \"moving\";\n AnnotationGestureStateKind[\"Resizing\"] = \"resizing\";\n AnnotationGestureStateKind[\"DragSelecting\"] = \"dragSelecting\";\n })(AnnotationGestureStateKind || (AnnotationGestureStateKind = {}));\n var AnnotationHandleKind;\n (function (AnnotationHandleKind) {\n AnnotationHandleKind[\"Resize\"] = \"resize\";\n AnnotationHandleKind[\"Vertex\"] = \"vertex\";\n AnnotationHandleKind[\"AddVertex\"] = \"addVertex\";\n AnnotationHandleKind[\"Keypoint\"] = \"keypoint\";\n })(AnnotationHandleKind || (AnnotationHandleKind = {}));\n\n /** Alignment for strokes applied to closed rendered geometry. */\n var StrokeAlignment;\n (function (StrokeAlignment) {\n StrokeAlignment[\"Inside\"] = \"inside\";\n StrokeAlignment[\"Center\"] = \"center\";\n StrokeAlignment[\"Outside\"] = \"outside\";\n })(StrokeAlignment || (StrokeAlignment = {}));\n\n var BoxShape;\n (function (BoxShape) {\n BoxShape[\"Rect\"] = \"rect\";\n BoxShape[\"RoundedRect\"] = \"roundedRect\";\n })(BoxShape || (BoxShape = {}));\n\n var FocusTargetMode;\n (function (FocusTargetMode) {\n FocusTargetMode[\"Hovered\"] = \"hovered\";\n FocusTargetMode[\"Selected\"] = \"selected\";\n FocusTargetMode[\"HoveredAndSelected\"] = \"hoveredAndSelected\";\n FocusTargetMode[\"Ambient\"] = \"ambient\";\n })(FocusTargetMode || (FocusTargetMode = {}));\n\n var DetectionInteractionState;\n (function (DetectionInteractionState) {\n DetectionInteractionState[\"Hovered\"] = \"hovered\";\n DetectionInteractionState[\"Selected\"] = \"selected\";\n })(DetectionInteractionState || (DetectionInteractionState = {}));\n\n var LabelPlacement;\n (function (LabelPlacement) {\n LabelPlacement[\"Top\"] = \"top\";\n LabelPlacement[\"Bottom\"] = \"bottom\";\n LabelPlacement[\"InsideTop\"] = \"insideTop\";\n LabelPlacement[\"InsideBottom\"] = \"insideBottom\";\n LabelPlacement[\"Center\"] = \"center\";\n })(LabelPlacement || (LabelPlacement = {}));\n var LabelVisibilityMode;\n (function (LabelVisibilityMode) {\n LabelVisibilityMode[\"Always\"] = \"always\";\n LabelVisibilityMode[\"HoveredOnly\"] = \"hoveredOnly\";\n })(LabelVisibilityMode || (LabelVisibilityMode = {}));\n\n var MaskRenderMode;\n (function (MaskRenderMode) {\n MaskRenderMode[\"FillAndStroke\"] = \"fillAndStroke\";\n MaskRenderMode[\"FillOnly\"] = \"fillOnly\";\n MaskRenderMode[\"StrokeOnly\"] = \"strokeOnly\";\n })(MaskRenderMode || (MaskRenderMode = {}));\n\n /**\n * Discriminator for renderer-neutral shape draw instructions.\n */\n var ShapeInstructionKind;\n (function (ShapeInstructionKind) {\n ShapeInstructionKind[\"Ellipse\"] = \"ellipse\";\n ShapeInstructionKind[\"Marker\"] = \"marker\";\n ShapeInstructionKind[\"Path\"] = \"path\";\n })(ShapeInstructionKind || (ShapeInstructionKind = {}));\n var MarkerShape;\n (function (MarkerShape) {\n MarkerShape[\"Circle\"] = \"circle\";\n MarkerShape[\"Cross\"] = \"cross\";\n MarkerShape[\"Square\"] = \"square\";\n MarkerShape[\"Triangle\"] = \"triangle\";\n })(MarkerShape || (MarkerShape = {}));\n /**\n * Coordinate space for marker sizing. Media sizes scale with the viewport;\n * screen sizes stay constant on screen, matching stroke-width semantics.\n */\n var MarkerSizeSpace;\n (function (MarkerSizeSpace) {\n MarkerSizeSpace[\"Media\"] = \"media\";\n MarkerSizeSpace[\"Screen\"] = \"screen\";\n })(MarkerSizeSpace || (MarkerSizeSpace = {}));\n\n var KeypointMarkerShape;\n (function (KeypointMarkerShape) {\n KeypointMarkerShape[\"Circle\"] = \"circle\";\n KeypointMarkerShape[\"Cross\"] = \"cross\";\n })(KeypointMarkerShape || (KeypointMarkerShape = {}));\n\n var DetectionMaskPayloadFormat;\n (function (DetectionMaskPayloadFormat) {\n DetectionMaskPayloadFormat[\"RawCocoRle\"] = \"rawCocoRle\";\n DetectionMaskPayloadFormat[\"DeflatedBase64\"] = \"deflatedBase64\";\n })(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));\n function encodeBinaryMask(data, width, height) {\n assertMaskDimensions(data, width, height);\n const runs = [];\n let currentValue = 0;\n let runLength = 0;\n for (let x = 0; x < width; x += 1) {\n for (let y = 0; y < height; y += 1) {\n const value = data[y * width + x] ? 1 : 0;\n if (value === currentValue) {\n runLength += 1;\n }\n else {\n runs.push(runLength);\n currentValue = value;\n runLength = 1;\n }\n }\n }\n runs.push(runLength);\n return {\n counts: encodeCompressedRleCounts(runs),\n encoding: DetectionMaskEncoding.CompressedRle,\n height,\n width,\n };\n }\n function assertMaskDimensions(data, width, height) {\n if (!Number.isInteger(width) || width <= 0) {\n throw new Error(\"Mask width must be a positive integer.\");\n }\n if (!Number.isInteger(height) || height <= 0) {\n throw new Error(\"Mask height must be a positive integer.\");\n }\n if (data.length !== width * height) {\n throw new Error(\"Mask data length must equal width * height.\");\n }\n }\n function rasterizePolygonToMask(points, dimensions) {\n const data = createEmptyMask(dimensions);\n if (points.length < 3) {\n return data;\n }\n const bounds = centerRectToTopLeftRect(getPointsRect(points));\n const startY = Math.max(0, Math.floor(bounds.y));\n const endY = Math.min(dimensions.height - 1, Math.ceil(bounds.y + bounds.height));\n for (let y = startY; y <= endY; y += 1) {\n const scanY = y + 0.5;\n const intersections = [];\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index];\n const next = points[(index + 1) % points.length];\n if ((current.y <= scanY && next.y > scanY) ||\n (next.y <= scanY && current.y > scanY)) {\n const ratio = (scanY - current.y) / (next.y - current.y);\n intersections.push(current.x + ratio * (next.x - current.x));\n }\n }\n intersections.sort((left, right) => left - right);\n for (let index = 0; index < intersections.length - 1; index += 2) {\n const left = Math.max(0, Math.ceil(intersections[index]));\n const right = Math.min(dimensions.width - 1, Math.floor(intersections[index + 1]));\n for (let x = left; x <= right; x += 1) {\n data[y * dimensions.width + x] = 1;\n }\n }\n }\n return data;\n }\n function createEmptyMask(dimensions) {\n if (!Number.isInteger(dimensions.width) ||\n dimensions.width <= 0 ||\n !Number.isInteger(dimensions.height) ||\n dimensions.height <= 0) {\n throw new Error(\"Media dimensions must be positive integers.\");\n }\n return new Uint8Array(dimensions.width * dimensions.height);\n }\n\n const MAX_ID_MASK_PALETTE_ENTRIES = 64;\n const MAX_ID_MASK_STROKE_WIDTH = 16;\n function createIdMaskFrame(instructions) {\n if (instructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...instructions.map(({ mask }) => mask.width));\n const height = Math.max(...instructions.map(({ mask }) => mask.height));\n const data = new Uint8Array(new ArrayBuffer(width * height));\n const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokeWidths = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4));\n let hasStroke = false;\n let maxStrokeWidth = 0;\n for (const instruction of instructions) {\n const detectionMaskId = instruction.detectionIndex + 1;\n if (detectionMaskId <= 0 ||\n detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {\n return undefined;\n }\n writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);\n if (instruction.stroke && instruction.stroke.width > 0) {\n const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);\n hasStroke = true;\n strokeWidths[detectionMaskId] = strokeWidth;\n maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);\n writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);\n }\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (decodedMask.data[maskOffset]) {\n data[y * width + x] = detectionMaskId;\n }\n }\n }\n }\n return {\n data,\n fillPalette,\n hasStroke,\n height,\n maxStrokeWidth,\n strokePalette,\n strokeWidths,\n width,\n };\n }\n function writePaletteEntry(palette, id, color, alpha) {\n const offset = id * 4;\n palette[offset] = ((color >> 16) & 0xff) / 255;\n palette[offset + 1] = ((color >> 8) & 0xff) / 255;\n palette[offset + 2] = (color & 0xff) / 255;\n palette[offset + 3] = Math.max(0, Math.min(alpha, 1));\n }\n\n var MediaRendererFit;\n (function (MediaRendererFit) {\n /**\n * Preserve media aspect ratio and fit the full frame inside the canvas.\n */\n MediaRendererFit[\"Contain\"] = \"contain\";\n /**\n * Preserve media aspect ratio and fill the canvas, cropping if necessary.\n */\n MediaRendererFit[\"Cover\"] = \"cover\";\n })(MediaRendererFit || (MediaRendererFit = {}));\n /**\n * Playback lifecycle state reported by a platform renderer.\n */\n var MediaRendererPlaybackState;\n (function (MediaRendererPlaybackState) {\n MediaRendererPlaybackState[\"Loading\"] = \"loading\";\n MediaRendererPlaybackState[\"Ready\"] = \"ready\";\n MediaRendererPlaybackState[\"Playing\"] = \"playing\";\n MediaRendererPlaybackState[\"Buffering\"] = \"buffering\";\n MediaRendererPlaybackState[\"Paused\"] = \"paused\";\n MediaRendererPlaybackState[\"Error\"] = \"error\";\n MediaRendererPlaybackState[\"Destroyed\"] = \"destroyed\";\n })(MediaRendererPlaybackState || (MediaRendererPlaybackState = {}));\n /**\n * Lower-level media source readiness.\n */\n var MediaSourceStatus;\n (function (MediaSourceStatus) {\n MediaSourceStatus[\"Loading\"] = \"loading\";\n MediaSourceStatus[\"Ready\"] = \"ready\";\n MediaSourceStatus[\"Error\"] = \"error\";\n MediaSourceStatus[\"Destroyed\"] = \"destroyed\";\n })(MediaSourceStatus || (MediaSourceStatus = {}));\n\n /**\n * Media-session operating mode.\n *\n * Core owns the names because file-like and stream-like lifecycle choices are\n * platform-neutral. Platform packages decide how these modes tune media,\n * storage, and renderer defaults.\n */\n var MediaSessionMode;\n (function (MediaSessionMode) {\n /**\n * Finite media. Defaults usually favor seek/replay and persistent detection\n * storage.\n */\n MediaSessionMode[\"File\"] = \"file\";\n /**\n * Live or append-only media. Defaults usually favor rolling windows and\n * bounded retention.\n */\n MediaSessionMode[\"Stream\"] = \"stream\";\n })(MediaSessionMode || (MediaSessionMode = {}));\n /**\n * Aggregate lifecycle state for a media session.\n */\n var MediaSessionStatus;\n (function (MediaSessionStatus) {\n MediaSessionStatus[\"Buffering\"] = \"buffering\";\n MediaSessionStatus[\"Destroyed\"] = \"destroyed\";\n MediaSessionStatus[\"Error\"] = \"error\";\n MediaSessionStatus[\"Loading\"] = \"loading\";\n MediaSessionStatus[\"Paused\"] = \"paused\";\n MediaSessionStatus[\"Playing\"] = \"playing\";\n MediaSessionStatus[\"Processing\"] = \"processing\";\n MediaSessionStatus[\"Ready\"] = \"ready\";\n })(MediaSessionStatus || (MediaSessionStatus = {}));\n /**\n * Subsystem currently affecting session readiness or presentation.\n */\n var MediaSessionActivityKind;\n (function (MediaSessionActivityKind) {\n MediaSessionActivityKind[\"DetectionsBuffering\"] = \"detectionsBuffering\";\n MediaSessionActivityKind[\"DetectionsLoading\"] = \"detectionsLoading\";\n MediaSessionActivityKind[\"Error\"] = \"error\";\n MediaSessionActivityKind[\"MediaNormalizing\"] = \"mediaNormalizing\";\n MediaSessionActivityKind[\"MediaOpening\"] = \"mediaOpening\";\n MediaSessionActivityKind[\"PlaybackBuffering\"] = \"playbackBuffering\";\n MediaSessionActivityKind[\"RenderPreparing\"] = \"renderPreparing\";\n })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));\n /**\n * State of one session activity.\n */\n var MediaSessionActivityStatus;\n (function (MediaSessionActivityStatus) {\n MediaSessionActivityStatus[\"Error\"] = \"error\";\n MediaSessionActivityStatus[\"Running\"] = \"running\";\n MediaSessionActivityStatus[\"Waiting\"] = \"waiting\";\n })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));\n\n const PNG_SIGNATURE = new Uint8Array([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n ]);\n function compositeMaskFrame(instructions) {\n const maskInstructions = materializeMaskInstructions(instructions);\n if (maskInstructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...maskInstructions.map(({ mask }) => mask.width));\n const height = Math.max(...maskInstructions.map(({ mask }) => mask.height));\n const data = new Uint8ClampedArray(new ArrayBuffer(width * height * 4));\n for (const instruction of maskInstructions) {\n compositeInstruction(data, width, instruction);\n }\n return { data, height, width };\n }\n function createMaskIdFrame(instructions) {\n return createIdMaskFrame(materializeMaskInstructions(instructions));\n }\n async function createPngIdMaskFrame(instructions) {\n const frame = createMaskIdFrame(instructions);\n if (!frame) {\n return undefined;\n }\n return {\n ...frame,\n png: await encodeGrayscalePng({\n height: frame.height,\n pixels: frame.data,\n width: frame.width,\n }),\n };\n }\n function compositeInstruction(rgba, canvasWidth, instruction) {\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n const fill = resolveRgbaColor(instruction.color, instruction.alpha);\n compositeMaskFill(rgba, canvasWidth, decodedMask, fill);\n if (instruction.stroke) {\n compositeMaskStroke(rgba, canvasWidth, decodedMask, instruction.stroke);\n }\n }\n function materializeMaskInstructions(instructions) {\n return instructions.map((instruction) => {\n if (instruction.mask) {\n return instruction;\n }\n const { height, points, width } = instruction.polygon;\n return {\n alpha: instruction.alpha,\n color: instruction.color,\n detectionIndex: instruction.detectionIndex,\n mask: encodeBinaryMask(rasterizePolygonToMask(points, { height, width }), width, height),\n stroke: instruction.stroke,\n };\n });\n }\n function compositeMaskFill(rgba, canvasWidth, decodedMask, fill) {\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (!decodedMask.data[maskOffset]) {\n continue;\n }\n writePixel(rgba, canvasWidth, x, y, fill);\n }\n }\n }\n function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {\n const width = Math.round(stroke.width);\n if (width <= 0) {\n return;\n }\n const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n if (!isMaskPixel(decodedMask, x, y) ||\n !isBoundaryPixel(decodedMask, x, y)) {\n continue;\n }\n for (let offsetY = -width; offsetY <= width; offsetY += 1) {\n for (let offsetX = -width; offsetX <= width; offsetX += 1) {\n const strokeX = x + offsetX;\n const strokeY = y + offsetY;\n if (isOutsideMaskBounds(decodedMask, strokeX, strokeY) ||\n isMaskPixel(decodedMask, strokeX, strokeY)) {\n continue;\n }\n writePixel(rgba, canvasWidth, strokeX, strokeY, strokeColor);\n }\n }\n }\n }\n }\n function isBoundaryPixel(mask, x, y) {\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if (offsetX === 0 && offsetY === 0) {\n continue;\n }\n const neighborX = x + offsetX;\n const neighborY = y + offsetY;\n if (isOutsideMaskBounds(mask, neighborX, neighborY) ||\n !isMaskPixel(mask, neighborX, neighborY)) {\n return true;\n }\n }\n }\n return false;\n }\n function isMaskPixel(mask, x, y) {\n return mask.data[y * mask.width + x] === 1;\n }\n function isOutsideMaskBounds(mask, x, y) {\n return x < 0 || y < 0 || x >= mask.width || y >= mask.height;\n }\n function resolveRgbaColor(color, alpha) {\n return {\n alpha: Math.round(Math.max(0, Math.min(alpha, 1)) * 255),\n blue: color & 0xff,\n green: (color >> 8) & 0xff,\n red: (color >> 16) & 0xff,\n };\n }\n function writePixel(rgba, canvasWidth, x, y, color) {\n const rgbaOffset = (y * canvasWidth + x) * 4;\n rgba[rgbaOffset] = color.red;\n rgba[rgbaOffset + 1] = color.green;\n rgba[rgbaOffset + 2] = color.blue;\n rgba[rgbaOffset + 3] = color.alpha;\n }\n async function encodeGrayscalePng(options) {\n if (typeof CompressionStream === \"undefined\") {\n throw new Error(\"CompressionStream is required to encode PNG ID masks.\");\n }\n const rawScanlines = createFilterlessPngScanlines(options);\n const ihdr = new Uint8Array(13);\n const ihdrView = new DataView(ihdr.buffer);\n ihdrView.setUint32(0, options.width);\n ihdrView.setUint32(4, options.height);\n ihdr[8] = 8;\n ihdr[9] = 0;\n ihdr[10] = 0;\n ihdr[11] = 0;\n ihdr[12] = 0;\n const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])\n .stream()\n .pipeThrough(new CompressionStream(\"deflate\"))).arrayBuffer());\n return concatUint8Arrays([\n PNG_SIGNATURE,\n createPngChunk(\"IHDR\", ihdr),\n createPngChunk(\"IDAT\", compressed),\n createPngChunk(\"IEND\", new Uint8Array(0)),\n ]);\n }\n function createFilterlessPngScanlines(options) {\n const rowStride = options.width + 1;\n const scanlines = new Uint8Array(rowStride * options.height);\n for (let y = 0; y < options.height; y += 1) {\n const sourceOffset = y * options.width;\n const targetOffset = y * rowStride;\n scanlines[targetOffset] = 0;\n scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);\n }\n return scanlines;\n }\n function createPngChunk(type, data) {\n const typeBytes = new TextEncoder().encode(type);\n const chunk = new Uint8Array(12 + data.length);\n const view = new DataView(chunk.buffer);\n view.setUint32(0, data.length);\n chunk.set(typeBytes, 4);\n chunk.set(data, 8);\n view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));\n return chunk;\n }\n const crc32Table = createCrc32Table();\n function createCrc32Table() {\n const table = new Uint32Array(256);\n for (let index = 0; index < table.length; index += 1) {\n let value = index;\n for (let bit = 0; bit < 8; bit += 1) {\n value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;\n }\n table[index] = value >>> 0;\n }\n return table;\n }\n function crc32(bytes) {\n let crc = 0xffffffff;\n for (const byte of bytes) {\n crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n }\n function concatUint8Arrays(chunks) {\n const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n }\n\n var PreparedMaskFrameKind;\n (function (PreparedMaskFrameKind) {\n PreparedMaskFrameKind[\"PngIdMask\"] = \"pngIdMask\";\n PreparedMaskFrameKind[\"RgbaImage\"] = \"rgbaImage\";\n })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));\n\n var MaskPreparationWorkerMessageType;\n (function (MaskPreparationWorkerMessageType) {\n MaskPreparationWorkerMessageType[\"Complete\"] = \"complete\";\n MaskPreparationWorkerMessageType[\"Empty\"] = \"empty\";\n MaskPreparationWorkerMessageType[\"Error\"] = \"error\";\n MaskPreparationWorkerMessageType[\"Prepare\"] = \"prepare\";\n })(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));\n\n const workerScope = globalThis;\n workerScope.addEventListener(\"message\", (event) => {\n const message = event.data;\n if (message.type !== MaskPreparationWorkerMessageType.Prepare) {\n return;\n }\n void prepareMaskFrame(message);\n });\n async function prepareMaskFrame(message) {\n try {\n const pngIdMaskFrame = await createPngIdMaskWorkerResponse(message);\n if (pngIdMaskFrame) {\n workerScope.postMessage(pngIdMaskFrame, [\n pngIdMaskFrame.imageBitmap,\n pngIdMaskFrame.png.buffer,\n pngIdMaskFrame.fillPalette.buffer,\n pngIdMaskFrame.strokePalette.buffer,\n pngIdMaskFrame.strokeWidths.buffer,\n ]);\n return;\n }\n const compositedFrame = compositeMaskFrame(message.job.instructions);\n if (!compositedFrame) {\n workerScope.postMessage({\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Empty,\n });\n return;\n }\n const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);\n const imageBitmap = createImageBitmapFromImageData(imageData);\n const idMaskData = createMaskIdFrame(message.job.instructions)?.data;\n if (imageBitmap) {\n workerScope.postMessage({\n imageBitmap,\n idMaskData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [...(idMaskData ? [idMaskData.buffer] : []), imageBitmap]);\n return;\n }\n workerScope.postMessage({\n idMaskData,\n imageData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [...(idMaskData ? [idMaskData.buffer] : []), imageData.data.buffer]);\n }\n catch (error) {\n workerScope.postMessage({\n error: error instanceof Error\n ? error.message\n : \"Unable to prepare mask frame.\",\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Error,\n });\n }\n }\n async function createPngIdMaskWorkerResponse(message) {\n if (typeof Blob === \"undefined\" ||\n typeof createImageBitmap === \"undefined\" ||\n typeof CompressionStream === \"undefined\") {\n return undefined;\n }\n let frame;\n try {\n frame = await createPngIdMaskFrame(message.job.instructions);\n }\n catch {\n return undefined;\n }\n if (!frame) {\n return undefined;\n }\n let imageBitmap;\n try {\n imageBitmap = await createImageBitmap(new Blob([frame.png], { type: \"image/png\" }));\n }\n catch {\n return undefined;\n }\n return {\n artifactKind: PreparedMaskFrameKind.PngIdMask,\n fillPalette: frame.fillPalette,\n hasStroke: frame.hasStroke,\n imageBitmap,\n key: message.job.key,\n maxStrokeWidth: frame.maxStrokeWidth,\n png: frame.png,\n requestId: message.requestId,\n strokePalette: frame.strokePalette,\n strokeWidths: frame.strokeWidths,\n type: MaskPreparationWorkerMessageType.Complete,\n };\n }\n function createImageBitmapFromImageData(imageData) {\n if (typeof OffscreenCanvas === \"undefined\") {\n return null;\n }\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const context = canvas.getContext(\"2d\");\n if (!context) {\n return null;\n }\n context.putImageData(imageData, 0, 0);\n return canvas.transferToImageBitmap();\n }\n\n})();";
5302
5477
 
5303
5478
  const DEFAULT_WORKER_NAME = "supervision-js-render-preparation";
5304
5479
  let defaultWorkerUrl;
@@ -5573,6 +5748,7 @@ function createMainThreadMaskFramePreparer(workerStatus, message = null) {
5573
5748
  canvas.height = 0;
5574
5749
  },
5575
5750
  height: compositedFrame.height,
5751
+ idMaskData: createMaskIdFrame(job.instructions)?.data,
5576
5752
  key: job.key,
5577
5753
  kind: PreparedMaskFrameKind.RgbaImage,
5578
5754
  source: canvas,
@@ -5747,6 +5923,7 @@ function createPreparedFrameFromWorkerResponse(message) {
5747
5923
  message.imageBitmap?.close();
5748
5924
  },
5749
5925
  height: message.imageBitmap.height,
5926
+ idMaskData: message.idMaskData,
5750
5927
  key: message.key,
5751
5928
  kind: PreparedMaskFrameKind.RgbaImage,
5752
5929
  source: message.imageBitmap,
@@ -5770,6 +5947,7 @@ function createPreparedFrameFromWorkerResponse(message) {
5770
5947
  canvas.height = 0;
5771
5948
  },
5772
5949
  height: message.imageData.height,
5950
+ idMaskData: message.idMaskData,
5773
5951
  key: message.key,
5774
5952
  kind: PreparedMaskFrameKind.RgbaImage,
5775
5953
  source: canvas,
@@ -6638,7 +6816,7 @@ function createPixiIdMaskShaderRenderer(options) {
6638
6816
  autoGenerateMipmaps: false,
6639
6817
  dynamic: false,
6640
6818
  height: 1,
6641
- resource: createPlaceholderCanvas(),
6819
+ resource: createPlaceholderCanvas$1(),
6642
6820
  scaleMode: "nearest",
6643
6821
  width: 1,
6644
6822
  });
@@ -6728,7 +6906,7 @@ function createPixiIdMaskShaderRenderer(options) {
6728
6906
  mesh.shader = shader;
6729
6907
  }
6730
6908
  }
6731
- function createPlaceholderCanvas() {
6909
+ function createPlaceholderCanvas$1() {
6732
6910
  const canvas = document.createElement("canvas");
6733
6911
  canvas.height = 1;
6734
6912
  canvas.width = 1;
@@ -6878,15 +7056,219 @@ void main(void) {
6878
7056
  }
6879
7057
  `;
6880
7058
 
7059
+ /**
7060
+ * Renders blurred, per-detection-colored copies of the prepared id mask
7061
+ * beneath the mask layer. Each pass maps mask ids through a halo palette and
7062
+ * a GPU blur filter turns that coverage into a smooth glow following the
7063
+ * exact mask silhouette, reusing the already-prepared mask texture. Passes
7064
+ * are pooled and one runs per distinct spread.
7065
+ */
7066
+ function createPixiMaskHaloRenderer(options) {
7067
+ const display = new options.Container();
7068
+ const placeholderSource = new options.ImageSource({
7069
+ autoGenerateMipmaps: false,
7070
+ dynamic: false,
7071
+ height: 1,
7072
+ resource: createPlaceholderCanvas(),
7073
+ scaleMode: "nearest",
7074
+ width: 1,
7075
+ });
7076
+ const geometry = new options.MeshGeometry({
7077
+ indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
7078
+ positions: new Float32Array([
7079
+ 0,
7080
+ 0,
7081
+ options.mediaWidth,
7082
+ 0,
7083
+ options.mediaWidth,
7084
+ options.mediaHeight,
7085
+ 0,
7086
+ options.mediaHeight,
7087
+ ]),
7088
+ shrinkBuffersToFit: true,
7089
+ topology: "triangle-list",
7090
+ uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
7091
+ });
7092
+ const passes = [];
7093
+ display.visible = false;
7094
+ return {
7095
+ destroy() {
7096
+ for (const pass of passes) {
7097
+ pass.mesh.destroy();
7098
+ pass.shader.destroy(true);
7099
+ }
7100
+ passes.length = 0;
7101
+ display.destroy();
7102
+ geometry.destroy();
7103
+ placeholderSource.destroy();
7104
+ },
7105
+ display,
7106
+ hide() {
7107
+ display.visible = false;
7108
+ },
7109
+ render(frame, texture, groups) {
7110
+ if (groups.length === 0) {
7111
+ display.visible = false;
7112
+ return;
7113
+ }
7114
+ for (const [index, group] of groups.entries()) {
7115
+ const pass = ensurePass(index);
7116
+ bindTexture(pass, texture.source);
7117
+ pass.uniforms.uniforms.uHaloPalette = group.palette;
7118
+ pass.uniforms.update();
7119
+ pass.blurFilter.strength = Math.max(group.spread, 0);
7120
+ pass.mesh.visible = true;
7121
+ }
7122
+ for (const pass of passes.slice(groups.length)) {
7123
+ pass.mesh.visible = false;
7124
+ }
7125
+ display.visible = true;
7126
+ },
7127
+ setOpacity(opacity) {
7128
+ display.alpha = opacity;
7129
+ },
7130
+ };
7131
+ function ensurePass(index) {
7132
+ const existing = passes[index];
7133
+ if (existing) {
7134
+ return existing;
7135
+ }
7136
+ const uniforms = new options.UniformGroup({
7137
+ uHaloPalette: {
7138
+ size: MAX_ID_MASK_PALETTE_ENTRIES,
7139
+ type: "vec4<f32>",
7140
+ value: new Float32Array(MAX_ID_MASK_PALETTE_ENTRIES * 4),
7141
+ },
7142
+ });
7143
+ const shader = createShader(uniforms);
7144
+ const mesh = new options.Mesh({ geometry, shader });
7145
+ const blurFilter = new options.BlurFilter({ quality: 4, strength: 8 });
7146
+ // Filters size their working texture from measured bounds, and a
7147
+ // custom-geometry mesh measures empty; an explicit bounds area keeps the
7148
+ // blur pass covering the full media rect.
7149
+ mesh.boundsArea = new options.Rectangle(0, 0, options.mediaWidth, options.mediaHeight);
7150
+ mesh.filters = [blurFilter];
7151
+ display.addChild(mesh);
7152
+ const pass = { blurFilter, mesh, shader, uniforms };
7153
+ passes.push(pass);
7154
+ return pass;
7155
+ }
7156
+ function bindTexture(pass, source) {
7157
+ try {
7158
+ pass.shader.resources.uTexture = source;
7159
+ pass.shader.resources.uSampler = source.style;
7160
+ }
7161
+ catch {
7162
+ try {
7163
+ pass.shader.destroy(true);
7164
+ }
7165
+ catch {
7166
+ // Pixi has already invalidated this shader's resource group.
7167
+ }
7168
+ pass.shader = createShader(pass.uniforms);
7169
+ pass.mesh.shader = pass.shader;
7170
+ pass.shader.resources.uTexture = source;
7171
+ pass.shader.resources.uSampler = source.style;
7172
+ }
7173
+ }
7174
+ function createShader(uniforms) {
7175
+ return options.Shader.from({
7176
+ gl: {
7177
+ fragment: maskHaloFragmentShader,
7178
+ vertex: maskHaloVertexShader,
7179
+ },
7180
+ resources: {
7181
+ haloUniforms: uniforms,
7182
+ uSampler: placeholderSource.style,
7183
+ uTexture: placeholderSource,
7184
+ },
7185
+ });
7186
+ }
7187
+ }
7188
+ /**
7189
+ * Builds one halo palette uniform: premultiplied halo color and alpha per
7190
+ * mask id, transparent for ids outside the group.
7191
+ */
7192
+ function buildMaskHaloPalette(halos) {
7193
+ const palette = new Float32Array(MAX_ID_MASK_PALETTE_ENTRIES * 4);
7194
+ for (const [maskId, halo] of halos) {
7195
+ if (maskId <= 0 || maskId >= MAX_ID_MASK_PALETTE_ENTRIES) {
7196
+ continue;
7197
+ }
7198
+ const alpha = Math.max(0, Math.min(halo.alpha, 1));
7199
+ const offset = maskId * 4;
7200
+ palette[offset] = (((halo.color >> 16) & 0xff) / 255) * alpha;
7201
+ palette[offset + 1] = (((halo.color >> 8) & 0xff) / 255) * alpha;
7202
+ palette[offset + 2] = ((halo.color & 0xff) / 255) * alpha;
7203
+ palette[offset + 3] = alpha;
7204
+ }
7205
+ return palette;
7206
+ }
7207
+ function createPlaceholderCanvas() {
7208
+ const canvas = document.createElement("canvas");
7209
+ canvas.height = 1;
7210
+ canvas.width = 1;
7211
+ return canvas;
7212
+ }
7213
+ const maskHaloVertexShader = `#version 300 es
7214
+ precision highp float;
7215
+
7216
+ in vec2 aPosition;
7217
+ in vec2 aUV;
7218
+
7219
+ uniform mat3 uProjectionMatrix;
7220
+ uniform mat3 uWorldTransformMatrix;
7221
+ uniform vec4 uWorldColorAlpha;
7222
+ uniform mat3 uTransformMatrix;
7223
+ uniform vec4 uColor;
7224
+
7225
+ out vec2 vUV;
7226
+ out vec4 vColor;
7227
+
7228
+ void main(void) {
7229
+ mat3 modelViewProjectionMatrix =
7230
+ uProjectionMatrix * uWorldTransformMatrix * uTransformMatrix;
7231
+
7232
+ gl_Position =
7233
+ vec4((modelViewProjectionMatrix * vec3(aPosition, 1.0)).xy, 0.0, 1.0);
7234
+ vUV = aUV;
7235
+ vColor = uWorldColorAlpha * uColor;
7236
+ }
7237
+ `;
7238
+ const maskHaloFragmentShader = `#version 300 es
7239
+ precision highp float;
7240
+ precision highp int;
7241
+
7242
+ in vec2 vUV;
7243
+ in vec4 vColor;
7244
+
7245
+ uniform sampler2D uTexture;
7246
+ uniform vec4 uHaloPalette[${MAX_ID_MASK_PALETTE_ENTRIES}];
7247
+
7248
+ out vec4 finalColor;
7249
+
7250
+ void main(void) {
7251
+ float maskId = floor(texture(uTexture, vUV).r * 255.0 + 0.5);
7252
+ int paletteIndex =
7253
+ int(clamp(maskId, 0.0, float(${MAX_ID_MASK_PALETTE_ENTRIES - 1})));
7254
+
7255
+ finalColor = uHaloPalette[paletteIndex] * vColor;
7256
+ }
7257
+ `;
7258
+
6881
7259
  const MAX_PENDING_MASK_HOLD_SECONDS = 0.05;
6882
7260
  function createPixiMaskLayer(options) {
6883
7261
  let mediaHeight = 0;
6884
7262
  let mediaWidth = 0;
6885
7263
  let idMaskRenderer;
7264
+ let haloRenderer;
7265
+ let currentMaskHaloStyle = options.maskHaloStyle ?? null;
7266
+ options.maskStyle;
6886
7267
  let maskSprite;
6887
7268
  let activeFrameKey = null;
6888
7269
  let activeFrameMediaTime = null;
6889
7270
  let activeIdMaskFrame = null;
7271
+ let activeRgbaMaskFrame = null;
6890
7272
  let maskOpacity = resolveMaskStyleOpacity(options.maskStyle);
6891
7273
  let maskPickCanvas;
6892
7274
  let maskPickContext;
@@ -6894,6 +7276,7 @@ function createPixiMaskLayer(options) {
6894
7276
  let visibleMaskMediaTime = null;
6895
7277
  let isDestroyed = false;
6896
7278
  const maskTextures = new Map();
7279
+ const haloTextures = new Map();
6897
7280
  const preparedRenderWindow = createPreparedRenderWindow({
6898
7281
  artifactKind: options.artifactKind,
6899
7282
  detectionTimeline: options.detectionTimeline,
@@ -6939,10 +7322,19 @@ function createPixiMaskLayer(options) {
6939
7322
  maskSprite.height = mediaHeight;
6940
7323
  idMaskRenderer = createIdMaskRenderer();
6941
7324
  idMaskRenderer?.setOpacity(maskOpacity);
7325
+ haloRenderer = createHaloRenderer();
7326
+ // Halo alpha is resolved from MaskHaloStyle per detection. It must not
7327
+ // inherit the independent mask renderer's global opacity.
7328
+ haloRenderer?.setOpacity(1);
6942
7329
  if (!idMaskRenderer || !options.Container) {
6943
7330
  return maskSprite;
6944
7331
  }
6945
7332
  const maskContainer = new options.Container();
7333
+ // The halo draws beneath the mask so the glow bleeds outward from the
7334
+ // silhouette while fills and borders stay crisp on top.
7335
+ if (haloRenderer) {
7336
+ maskContainer.addChild(haloRenderer.display);
7337
+ }
6946
7338
  maskContainer.addChild(maskSprite, idMaskRenderer.mesh);
6947
7339
  return maskContainer;
6948
7340
  },
@@ -6952,6 +7344,7 @@ function createPixiMaskLayer(options) {
6952
7344
  activeFrameKey = preparedFrame?.key ?? null;
6953
7345
  activeFrameMediaTime = preparedFrame?.detectionFrame.mediaTime ?? null;
6954
7346
  activeIdMaskFrame = null;
7347
+ activeRgbaMaskFrame = null;
6955
7348
  hideSprite();
6956
7349
  return;
6957
7350
  }
@@ -6964,6 +7357,7 @@ function createPixiMaskLayer(options) {
6964
7357
  if (preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Empty ||
6965
7358
  preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Disabled) {
6966
7359
  activeIdMaskFrame = null;
7360
+ activeRgbaMaskFrame = null;
6967
7361
  hideSprite();
6968
7362
  return;
6969
7363
  }
@@ -7016,6 +7410,14 @@ function createPixiMaskLayer(options) {
7016
7410
  applyMaskOpacity();
7017
7411
  }
7018
7412
  preparedRenderWindow.setMaskStyle(nextMaskStyle);
7413
+ refreshHalo();
7414
+ },
7415
+ setMaskHaloStyle(nextMaskHaloStyle) {
7416
+ if (nextMaskHaloStyle === undefined) {
7417
+ return;
7418
+ }
7419
+ currentMaskHaloStyle = nextMaskHaloStyle;
7420
+ refreshHalo();
7019
7421
  },
7020
7422
  destroy() {
7021
7423
  if (isDestroyed) {
@@ -7026,17 +7428,22 @@ function createPixiMaskLayer(options) {
7026
7428
  destroyTextures();
7027
7429
  resetMaskPickCanvas();
7028
7430
  idMaskRenderer?.destroy();
7431
+ haloRenderer?.destroy();
7029
7432
  },
7030
7433
  };
7031
7434
  function showMaskFrame(maskFrame, mediaTime) {
7032
7435
  visibleMaskMediaTime = mediaTime;
7033
7436
  activeIdMaskFrame =
7034
7437
  maskFrame.kind === PreparedMaskFrameKind.PngIdMask ? maskFrame : null;
7438
+ activeRgbaMaskFrame =
7439
+ maskFrame.kind === PreparedMaskFrameKind.RgbaImage ? maskFrame : null;
7035
7440
  if (maskFrame.kind === PreparedMaskFrameKind.PngIdMask && idMaskRenderer) {
7036
7441
  showIdMaskFrame(maskFrame);
7037
7442
  return;
7038
7443
  }
7039
- showRgbaMaskFrame(maskFrame);
7444
+ if (maskFrame.kind === PreparedMaskFrameKind.RgbaImage) {
7445
+ showRgbaMaskFrame(maskFrame);
7446
+ }
7040
7447
  }
7041
7448
  function getTexture(maskFrame) {
7042
7449
  const existingTexture = maskTextures.get(maskFrame.key);
@@ -7060,6 +7467,40 @@ function createPixiMaskLayer(options) {
7060
7467
  maskTextures.set(maskFrame.key, texture);
7061
7468
  return texture;
7062
7469
  }
7470
+ function getHaloTexture(maskFrame) {
7471
+ const existingTexture = haloTextures.get(maskFrame.key);
7472
+ if (existingTexture) {
7473
+ return existingTexture;
7474
+ }
7475
+ if (!maskFrame.idMaskData || typeof document === "undefined") {
7476
+ return undefined;
7477
+ }
7478
+ const canvas = document.createElement("canvas");
7479
+ const context = canvas.getContext("2d");
7480
+ if (!context || typeof context.createImageData !== "function") {
7481
+ return undefined;
7482
+ }
7483
+ canvas.width = maskFrame.width;
7484
+ canvas.height = maskFrame.height;
7485
+ const imageData = context.createImageData(maskFrame.width, maskFrame.height);
7486
+ for (let index = 0; index < maskFrame.idMaskData.length; index += 1) {
7487
+ const offset = index * 4;
7488
+ imageData.data[offset] = maskFrame.idMaskData[index];
7489
+ imageData.data[offset + 3] = 255;
7490
+ }
7491
+ context.putImageData(imageData, 0, 0);
7492
+ const source = new options.ImageSource({
7493
+ autoGenerateMipmaps: false,
7494
+ dynamic: false,
7495
+ height: maskFrame.height,
7496
+ resource: canvas,
7497
+ scaleMode: "nearest",
7498
+ width: maskFrame.width,
7499
+ });
7500
+ const texture = new options.Texture({ dynamic: false, source });
7501
+ haloTextures.set(maskFrame.key, texture);
7502
+ return texture;
7503
+ }
7063
7504
  function showRgbaMaskFrame(maskFrame) {
7064
7505
  if (!maskSprite) {
7065
7506
  return;
@@ -7071,6 +7512,13 @@ function createPixiMaskLayer(options) {
7071
7512
  maskSprite.height = mediaHeight;
7072
7513
  maskSprite.visible = true;
7073
7514
  idMaskRenderer?.hide();
7515
+ const haloTexture = getHaloTexture(maskFrame);
7516
+ if (haloTexture) {
7517
+ renderHalo(maskFrame, haloTexture);
7518
+ }
7519
+ else {
7520
+ haloRenderer?.hide();
7521
+ }
7074
7522
  }
7075
7523
  function showIdMaskFrame(maskFrame) {
7076
7524
  if (!idMaskRenderer || !maskSprite) {
@@ -7078,14 +7526,82 @@ function createPixiMaskLayer(options) {
7078
7526
  }
7079
7527
  maskSprite.visible = false;
7080
7528
  idMaskRenderer.render(maskFrame, getTexture(maskFrame));
7529
+ renderHalo(maskFrame, getTexture(maskFrame));
7530
+ }
7531
+ function renderHalo(maskFrame, texture) {
7532
+ if (!haloRenderer) {
7533
+ return;
7534
+ }
7535
+ const groups = resolveFrameHaloGroups();
7536
+ if (groups.length === 0) {
7537
+ haloRenderer.hide();
7538
+ return;
7539
+ }
7540
+ haloRenderer.render(maskFrame, texture, groups);
7541
+ }
7542
+ function refreshHalo() {
7543
+ if (activeIdMaskFrame) {
7544
+ renderHalo(activeIdMaskFrame, getTexture(activeIdMaskFrame));
7545
+ return;
7546
+ }
7547
+ if (activeRgbaMaskFrame) {
7548
+ const texture = getHaloTexture(activeRgbaMaskFrame);
7549
+ if (texture) {
7550
+ renderHalo(activeRgbaMaskFrame, texture);
7551
+ }
7552
+ }
7553
+ }
7554
+ /**
7555
+ * Halo instructions resolve per frame from the live halo style rather than
7556
+ * being baked into the prepared artifact, so halo edits never invalidate
7557
+ * prepared mask pixels. Detections are grouped by their requested spread
7558
+ * and every group renders in its own blur pass, preserving each
7559
+ * detection's public spread contract.
7560
+ */
7561
+ function resolveFrameHaloGroups() {
7562
+ if (visibleMaskMediaTime === null || !currentMaskHaloStyle) {
7563
+ return [];
7564
+ }
7565
+ const frame = options.detectionTimeline.selectFrame(visibleMaskMediaTime);
7566
+ if (!frame) {
7567
+ return [];
7568
+ }
7569
+ const bySpread = new Map();
7570
+ for (const [detectionIndex, detection] of frame.detections.entries()) {
7571
+ if (!detection.mask) {
7572
+ continue;
7573
+ }
7574
+ const halo = currentMaskHaloStyle.resolve(detection, {
7575
+ detectionIndex,
7576
+ frame,
7577
+ mediaTime: visibleMaskMediaTime,
7578
+ });
7579
+ if (!halo || halo.alpha <= 0 || halo.spread <= 0) {
7580
+ continue;
7581
+ }
7582
+ let group = bySpread.get(halo.spread);
7583
+ if (!group) {
7584
+ group = new Map();
7585
+ bySpread.set(halo.spread, group);
7586
+ }
7587
+ group.set(detectionIndex + 1, { alpha: halo.alpha, color: halo.color });
7588
+ }
7589
+ return [...bySpread.entries()]
7590
+ .sort(([left], [right]) => left - right)
7591
+ .map(([spread, halos]) => ({
7592
+ palette: buildMaskHaloPalette(halos),
7593
+ spread,
7594
+ }));
7081
7595
  }
7082
7596
  function hideSprite() {
7083
7597
  visibleMaskMediaTime = null;
7084
7598
  activeIdMaskFrame = null;
7599
+ activeRgbaMaskFrame = null;
7085
7600
  if (maskSprite) {
7086
7601
  maskSprite.visible = false;
7087
7602
  }
7088
7603
  idMaskRenderer?.hide();
7604
+ haloRenderer?.hide();
7089
7605
  }
7090
7606
  function canHoldVisibleMaskFor(mediaTime) {
7091
7607
  return (visibleMaskMediaTime !== null &&
@@ -7101,11 +7617,21 @@ function createPixiMaskLayer(options) {
7101
7617
  function destroyTexture(key) {
7102
7618
  const texture = maskTextures.get(key);
7103
7619
  if (!texture) {
7620
+ destroyHaloTexture(key);
7104
7621
  return;
7105
7622
  }
7106
7623
  releaseTextureBindings(key, texture);
7107
7624
  maskTextures.delete(key);
7108
7625
  texture.destroy(true);
7626
+ destroyHaloTexture(key);
7627
+ }
7628
+ function destroyHaloTexture(key) {
7629
+ const texture = haloTextures.get(key);
7630
+ if (!texture) {
7631
+ return;
7632
+ }
7633
+ haloTextures.delete(key);
7634
+ texture.destroy(true);
7109
7635
  }
7110
7636
  function destroyTextures() {
7111
7637
  releaseTextureBindings();
@@ -7113,6 +7639,10 @@ function createPixiMaskLayer(options) {
7113
7639
  texture.destroy(true);
7114
7640
  }
7115
7641
  maskTextures.clear();
7642
+ for (const texture of haloTextures.values()) {
7643
+ texture.destroy(true);
7644
+ }
7645
+ haloTextures.clear();
7116
7646
  }
7117
7647
  function releaseTextureBindings(key, texture) {
7118
7648
  if (!texture || maskSprite?.texture === texture) {
@@ -7124,6 +7654,31 @@ function createPixiMaskLayer(options) {
7124
7654
  idMaskRenderer?.clearTexture();
7125
7655
  }
7126
7656
  }
7657
+ function createHaloRenderer() {
7658
+ if (!options.BlurFilter ||
7659
+ !options.Container ||
7660
+ !options.Rectangle ||
7661
+ !options.Mesh ||
7662
+ !options.MeshGeometry ||
7663
+ !options.Shader ||
7664
+ !options.UniformGroup ||
7665
+ mediaWidth <= 0 ||
7666
+ mediaHeight <= 0) {
7667
+ return undefined;
7668
+ }
7669
+ return createPixiMaskHaloRenderer({
7670
+ BlurFilter: options.BlurFilter,
7671
+ Container: options.Container,
7672
+ Rectangle: options.Rectangle,
7673
+ ImageSource: options.ImageSource,
7674
+ Mesh: options.Mesh,
7675
+ MeshGeometry: options.MeshGeometry,
7676
+ Shader: options.Shader,
7677
+ UniformGroup: options.UniformGroup,
7678
+ mediaHeight,
7679
+ mediaWidth,
7680
+ });
7681
+ }
7127
7682
  function createIdMaskRenderer() {
7128
7683
  if (!options.Mesh ||
7129
7684
  !options.MeshGeometry ||
@@ -7324,6 +7879,9 @@ function canPreparePolygonInstruction(instruction) {
7324
7879
  return (instruction.points.length >= 3 &&
7325
7880
  Boolean(instruction.fill || stroke) &&
7326
7881
  !stroke?.dash?.length &&
7882
+ stroke?.cap === undefined &&
7883
+ stroke?.join === undefined &&
7884
+ stroke?.miterLimit === undefined &&
7327
7885
  (stroke?.alignment === undefined ||
7328
7886
  stroke.alignment === BoxStrokeAlignment.Center));
7329
7887
  }
@@ -7368,6 +7926,367 @@ function resolvePreparedPolygonInstructions(options) {
7368
7926
  return instructions;
7369
7927
  }
7370
7928
 
7929
+ const sharedAssets = new WeakMap();
7930
+ /** Browser implementation for asset-backed region renderer descriptors. */
7931
+ function createPixiRegionLayer(options) {
7932
+ let container;
7933
+ let currentFrame;
7934
+ let currentMediaTime = 0;
7935
+ let currentViewportScale = 1;
7936
+ let destroyed = false;
7937
+ let renderers = [...options.regionRenderers];
7938
+ const assets = new Map();
7939
+ const entries = new Map();
7940
+ const pools = new Map();
7941
+ syncAssets();
7942
+ return {
7943
+ createContainer() {
7944
+ if (!container) {
7945
+ container = new options.Container();
7946
+ container.sortableChildren = true;
7947
+ }
7948
+ return container;
7949
+ },
7950
+ drawFrame(mediaTime, viewportScale = 1) {
7951
+ currentMediaTime = mediaTime;
7952
+ currentViewportScale = viewportScale;
7953
+ currentFrame = options.detectionTimeline.selectFrame(mediaTime);
7954
+ const activeKeys = new Set();
7955
+ const activeDetectionIndexes = new Set();
7956
+ for (const entry of entries.values())
7957
+ entry.active = false;
7958
+ if (currentFrame) {
7959
+ for (const [rendererIndex, renderer] of renderers.entries()) {
7960
+ const asset = assets.get(renderer.id);
7961
+ if (!asset?.asset)
7962
+ continue;
7963
+ for (const [detectionIndex, detection,] of currentFrame.detections.entries()) {
7964
+ const context = {
7965
+ detectionIndex,
7966
+ frame: currentFrame,
7967
+ mediaTime,
7968
+ viewportScale,
7969
+ ...options.resolveContextState?.(detection),
7970
+ };
7971
+ if (context.hidden ||
7972
+ !matchesTarget(renderer, detection, context)) {
7973
+ continue;
7974
+ }
7975
+ const region = resolveRegion(renderer, detection);
7976
+ if (!region)
7977
+ continue;
7978
+ const key = resolveSpriteKey(renderer.id, detection, detectionIndex);
7979
+ const entry = ensureEntry(key, renderer.id, asset.src, asset.asset, detection.id);
7980
+ const position = positionSprite(entry.display, renderer, region, asset.asset);
7981
+ entry.baseX = position.x;
7982
+ entry.baseY = position.y;
7983
+ entry.detectionId = detection.id;
7984
+ entry.display.zIndex =
7985
+ (renderer.compose?.zIndex ?? 0) * 1_000_000 +
7986
+ rendererIndex * 10_000 +
7987
+ (detection.zIndex ?? detectionIndex);
7988
+ entry.display.visible = true;
7989
+ entry.active = true;
7990
+ activeKeys.add(key);
7991
+ activeDetectionIndexes.add(detectionIndex);
7992
+ }
7993
+ }
7994
+ }
7995
+ for (const [key, entry] of entries) {
7996
+ if (activeKeys.has(key))
7997
+ continue;
7998
+ entry.display.visible = false;
7999
+ pauseAnimatedDisplay(entry.display);
8000
+ entries.delete(key);
8001
+ const poolKey = resolvePoolKey(entry.rendererId, entry.src);
8002
+ const pool = pools.get(poolKey) ?? [];
8003
+ pool.push(entry);
8004
+ pools.set(poolKey, pool);
8005
+ }
8006
+ return { activeDetectionIndexes: [...activeDetectionIndexes] };
8007
+ },
8008
+ setRenderers(nextRenderers) {
8009
+ renderers = [...nextRenderers];
8010
+ syncAssets();
8011
+ this.drawFrame(currentMediaTime, currentViewportScale);
8012
+ },
8013
+ translateDetection(id, x, y) {
8014
+ let translated = false;
8015
+ for (const entry of entries.values()) {
8016
+ if (!entry.active || entry.detectionId !== id)
8017
+ continue;
8018
+ entry.display.position.set(entry.baseX + x, entry.baseY + y);
8019
+ translated = true;
8020
+ }
8021
+ return translated;
8022
+ },
8023
+ destroy() {
8024
+ destroyed = true;
8025
+ destroyAllDisplays();
8026
+ for (const asset of assets.values())
8027
+ asset.lease.release();
8028
+ assets.clear();
8029
+ container = undefined;
8030
+ currentFrame = undefined;
8031
+ },
8032
+ };
8033
+ function syncAssets() {
8034
+ const desired = new Map(renderers.map((renderer) => [renderer.id, renderer.source.asset.src]));
8035
+ for (const [id, asset] of assets) {
8036
+ if (desired.get(id) === asset.src)
8037
+ continue;
8038
+ destroyRendererDisplays(id);
8039
+ asset.lease.release();
8040
+ assets.delete(id);
8041
+ }
8042
+ for (const [id, src] of desired) {
8043
+ if (assets.has(id))
8044
+ continue;
8045
+ const lease = acquireAsset(options.Assets, src);
8046
+ const asset = { lease, src };
8047
+ assets.set(id, asset);
8048
+ void lease.asset.then((loadedAsset) => {
8049
+ if (destroyed || assets.get(id) !== asset)
8050
+ return;
8051
+ asset.asset = loadedAsset;
8052
+ options.onInvalidate?.();
8053
+ }, (error) => {
8054
+ // A failed asset is omitted. Replacing the descriptor retries it.
8055
+ options.onAssetError?.({ error, rendererId: id, src });
8056
+ });
8057
+ }
8058
+ }
8059
+ function ensureEntry(key, rendererId, src, asset, detectionId) {
8060
+ let entry = entries.get(key);
8061
+ if (entry)
8062
+ return entry;
8063
+ const poolKey = resolvePoolKey(rendererId, src);
8064
+ const pool = pools.get(poolKey);
8065
+ entry = pool?.pop();
8066
+ if (pool?.length === 0)
8067
+ pools.delete(poolKey);
8068
+ if (entry) {
8069
+ entry.detectionId = detectionId;
8070
+ resumeAnimatedDisplay(entry.display);
8071
+ }
8072
+ else {
8073
+ const display = isGifSource(asset)
8074
+ ? new options.GifSprite({ autoPlay: true, loop: true, source: asset })
8075
+ : new options.Sprite({ texture: asset });
8076
+ display.anchor.set(0.5);
8077
+ container?.addChild(display);
8078
+ entry = {
8079
+ active: false,
8080
+ baseX: 0,
8081
+ baseY: 0,
8082
+ detectionId,
8083
+ display,
8084
+ rendererId,
8085
+ src,
8086
+ };
8087
+ }
8088
+ entries.set(key, entry);
8089
+ return entry;
8090
+ }
8091
+ function destroyRendererDisplays(rendererId) {
8092
+ for (const [key, entry] of entries) {
8093
+ if (entry.rendererId !== rendererId)
8094
+ continue;
8095
+ destroyDisplay(entry.display);
8096
+ entries.delete(key);
8097
+ }
8098
+ for (const [key, pool] of pools) {
8099
+ const retained = pool.filter((entry) => {
8100
+ if (entry.rendererId !== rendererId)
8101
+ return true;
8102
+ destroyDisplay(entry.display);
8103
+ return false;
8104
+ });
8105
+ if (retained.length > 0)
8106
+ pools.set(key, retained);
8107
+ else
8108
+ pools.delete(key);
8109
+ }
8110
+ }
8111
+ function destroyAllDisplays() {
8112
+ for (const entry of entries.values())
8113
+ destroyDisplay(entry.display);
8114
+ for (const pool of pools.values()) {
8115
+ for (const entry of pool)
8116
+ destroyDisplay(entry.display);
8117
+ }
8118
+ entries.clear();
8119
+ pools.clear();
8120
+ }
8121
+ }
8122
+ function destroyDisplay(display) {
8123
+ display.removeFromParent?.();
8124
+ // GifSprite sources are shared and released through Assets.unload().
8125
+ display.destroy();
8126
+ }
8127
+ function pauseAnimatedDisplay(display) {
8128
+ if ("stop" in display && typeof display.stop === "function")
8129
+ display.stop();
8130
+ }
8131
+ function resumeAnimatedDisplay(display) {
8132
+ if ("play" in display && typeof display.play === "function")
8133
+ display.play();
8134
+ }
8135
+ function matchesTarget(renderer, detection, context) {
8136
+ const { target } = renderer;
8137
+ return (matchesValue(target.id, detection.id) &&
8138
+ matchesValue(target.className, detection.className) &&
8139
+ matchesValue(target.sourceId, detection.sourceId) &&
8140
+ (target.resolve?.(detection, context) ?? true));
8141
+ }
8142
+ function matchesValue(configured, actual) {
8143
+ if (configured === undefined)
8144
+ return true;
8145
+ if (actual === undefined)
8146
+ return false;
8147
+ return Array.isArray(configured)
8148
+ ? configured.includes(actual)
8149
+ : configured === actual;
8150
+ }
8151
+ function resolveRegion(renderer, detection) {
8152
+ if (renderer.region.kind === "bounds")
8153
+ return detection.rect;
8154
+ const fallbackSize = detection.rect
8155
+ ? Math.max(1, detection.rect.width * 0.4)
8156
+ : undefined;
8157
+ if (renderer.region.anchor === "head") {
8158
+ const facePoints = detection.keypoints?.points
8159
+ .slice(0, 5)
8160
+ .filter((_, index) => (detection.keypoints?.visibility?.[index] ?? 2) > 0);
8161
+ if (facePoints && facePoints.length > 0) {
8162
+ const xs = facePoints.map(({ x }) => x);
8163
+ const ys = facePoints.map(({ y }) => y);
8164
+ const width = Math.max(fallbackSize ?? 1, (Math.max(...xs) - Math.min(...xs)) * 2);
8165
+ return {
8166
+ height: width,
8167
+ width,
8168
+ x: xs.reduce((sum, x) => sum + x, 0) / xs.length,
8169
+ y: ys.reduce((sum, y) => sum + y, 0) / ys.length,
8170
+ };
8171
+ }
8172
+ if (!detection.rect || fallbackSize === undefined)
8173
+ return undefined;
8174
+ return {
8175
+ height: fallbackSize,
8176
+ width: fallbackSize,
8177
+ x: detection.rect.x,
8178
+ y: detection.rect.y - detection.rect.height / 2,
8179
+ };
8180
+ }
8181
+ const point = detection.keypoints?.points[renderer.region.anchor];
8182
+ const visibility = detection.keypoints?.visibility?.[renderer.region.anchor] ?? 2;
8183
+ if (!point || visibility <= 0 || fallbackSize === undefined)
8184
+ return undefined;
8185
+ return { height: fallbackSize, width: fallbackSize, x: point.x, y: point.y };
8186
+ }
8187
+ function positionSprite(sprite, renderer, region, asset) {
8188
+ const scale = finiteOr(renderer.transform?.scale, 1);
8189
+ const opacity = Math.min(1, Math.max(0, finiteOr(renderer.transform?.opacity, 1)));
8190
+ const sourceWidth = Math.max(1, asset.width);
8191
+ const sourceHeight = Math.max(1, asset.height);
8192
+ const containScale = Math.min(region.width / sourceWidth, region.height / sourceHeight);
8193
+ const offset = renderer.transform?.offset;
8194
+ sprite.alpha = opacity;
8195
+ sprite.width = sourceWidth * containScale * scale;
8196
+ sprite.height = sourceHeight * containScale * scale;
8197
+ const position = {
8198
+ x: region.x + finiteOr(offset?.x, 0) * region.width,
8199
+ y: region.y + finiteOr(offset?.y, 0) * region.height,
8200
+ };
8201
+ sprite.position.set(position.x, position.y);
8202
+ sprite.rotation = finiteOr(renderer.transform?.rotation, 0);
8203
+ return position;
8204
+ }
8205
+ function isGifSource(asset) {
8206
+ return ("totalFrames" in asset && "frames" in asset && Array.isArray(asset.frames));
8207
+ }
8208
+ function resolvePoolKey(rendererId, src) {
8209
+ return JSON.stringify([rendererId, src]);
8210
+ }
8211
+ function resolveSpriteKey(rendererId, detection, detectionIndex) {
8212
+ return JSON.stringify([rendererId, detection.id ?? null, detectionIndex]);
8213
+ }
8214
+ function finiteOr(value, fallback) {
8215
+ return value !== undefined && Number.isFinite(value) ? value : fallback;
8216
+ }
8217
+ function acquireAsset(loader, src) {
8218
+ let cache = sharedAssets.get(loader);
8219
+ if (!cache) {
8220
+ cache = new Map();
8221
+ sharedAssets.set(loader, cache);
8222
+ }
8223
+ let entry = cache.get(src);
8224
+ if (!entry) {
8225
+ entry = { asset: loader.load(src), references: 0 };
8226
+ cache.set(src, entry);
8227
+ }
8228
+ entry.references += 1;
8229
+ let released = false;
8230
+ return {
8231
+ release() {
8232
+ if (released)
8233
+ return;
8234
+ released = true;
8235
+ entry.references -= 1;
8236
+ if (entry.references > 0)
8237
+ return;
8238
+ cache.delete(src);
8239
+ void entry.asset.then(() => loader.unload(src), () => undefined);
8240
+ },
8241
+ asset: entry.asset,
8242
+ };
8243
+ }
8244
+
8245
+ /**
8246
+ * Bridges public annotation renderer kinds onto the internal vector-layer
8247
+ * shape pipeline.
8248
+ *
8249
+ * Each shape-backed renderer kind resolves its own public instruction per
8250
+ * detection; this adapter lowers those instructions into the internal
8251
+ * renderer-neutral shape vocabulary and concatenates them into the single
8252
+ * shape style the vector layer consumes. Kinds stay independent: a kind that
8253
+ * resolves nothing for a detection simply contributes no instructions.
8254
+ */
8255
+ function resolveAnnotationShapeStyle(styles) {
8256
+ const boxCornerStyle = styles.boxCornerStyle ?? null;
8257
+ const ellipseStyle = styles.ellipseStyle ?? null;
8258
+ const markerStyle = styles.markerStyle ?? null;
8259
+ if (!boxCornerStyle && !ellipseStyle && !markerStyle) {
8260
+ return null;
8261
+ }
8262
+ return {
8263
+ resolve(detection, context) {
8264
+ const instructions = [];
8265
+ const boxCorners = boxCornerStyle?.resolve(detection, context);
8266
+ if (boxCorners) {
8267
+ instructions.push({
8268
+ closed: false,
8269
+ kind: ShapeInstructionKind.Path,
8270
+ segments: boxCorners.segments,
8271
+ stroke: boxCorners.stroke,
8272
+ });
8273
+ }
8274
+ const marker = markerStyle?.resolve(detection, context);
8275
+ if (marker) {
8276
+ instructions.push({ kind: ShapeInstructionKind.Marker, ...marker });
8277
+ }
8278
+ const ellipse = ellipseStyle?.resolve(detection, context);
8279
+ if (ellipse) {
8280
+ instructions.push(lowerEllipseInstruction(ellipse));
8281
+ }
8282
+ return instructions.length > 0 ? instructions : undefined;
8283
+ },
8284
+ };
8285
+ }
8286
+ function lowerEllipseInstruction(instruction) {
8287
+ return { ...instruction, kind: ShapeInstructionKind.Ellipse };
8288
+ }
8289
+
7371
8290
  function createPixiAnnotationOverlayLayer(editingEngine, initialStyle) {
7372
8291
  let graphics;
7373
8292
  let style = resolveAnnotationOverlayStyle(initialStyle);
@@ -7600,8 +8519,11 @@ function drawLoading(graphics, context, style) {
7600
8519
  const loading = values instanceof Set ? values : new Set(values);
7601
8520
  const angle = (context.now / 500) % (Math.PI * 2);
7602
8521
  for (const detection of context.frame?.detections ?? []) {
7603
- if (detection.id === undefined || !loading.has(detection.id))
8522
+ if (detection.id === undefined ||
8523
+ !loading.has(detection.id) ||
8524
+ resolveAnnotationStyleState(detection, context.visibility).hidden) {
7604
8525
  continue;
8526
+ }
7605
8527
  const rect = getDetectionRect(detection);
7606
8528
  if (!rect)
7607
8529
  continue;
@@ -7676,6 +8598,7 @@ var PixiSceneLayerKind;
7676
8598
  PixiSceneLayerKind["Mask"] = "mask";
7677
8599
  PixiSceneLayerKind["Box"] = "box";
7678
8600
  PixiSceneLayerKind["Vector"] = "vector";
8601
+ PixiSceneLayerKind["Region"] = "region";
7679
8602
  PixiSceneLayerKind["Focus"] = "focus";
7680
8603
  PixiSceneLayerKind["Preview"] = "preview";
7681
8604
  PixiSceneLayerKind["Guide"] = "guide";
@@ -7720,6 +8643,8 @@ function defaultLayerOrder(kind) {
7720
8643
  return 200;
7721
8644
  case PixiSceneLayerKind.Vector:
7722
8645
  return 250;
8646
+ case PixiSceneLayerKind.Region:
8647
+ return 275;
7723
8648
  case PixiSceneLayerKind.Focus:
7724
8649
  return 300;
7725
8650
  case PixiSceneLayerKind.Preview:
@@ -7775,14 +8700,42 @@ function observePixiContainerResize(container, resize) {
7775
8700
  return () => observer.disconnect();
7776
8701
  }
7777
8702
  async function createPixiMediaScene(options) {
7778
- const { Application, CanvasSource, Container, Graphics, ImageSource, Mesh, MeshGeometry, Rectangle, Sprite, Shader, Text, Texture, UniformGroup, } = await import('pixi.js');
8703
+ const { Application, Assets, BlurFilter, CanvasSource, Container, Graphics, ImageSource, Mesh, MeshGeometry, Rectangle, Sprite, Shader, Text, Texture, UniformGroup, } = await import('pixi.js');
8704
+ const { GifSprite } = await import('pixi.js/gif');
7779
8705
  const app = new Application();
8706
+ let currentEllipseStyle = options.ellipseStyle ?? null;
8707
+ let currentBoxCornerStyle = options.boxCornerStyle ?? null;
7780
8708
  let currentLabelStyle = options.labelStyle ?? null;
7781
8709
  let currentMaskStyle = options.maskStyle ?? null;
8710
+ let currentMarkerStyle = options.markerStyle ?? null;
8711
+ let currentMaskHaloStyle = options.maskHaloStyle ?? null;
8712
+ // Halo-only presentations still need prepared id-mask artifacts; this
8713
+ // invisible style drives preparation without drawing any fill or border.
8714
+ const haloOnlyMaskStyle = {
8715
+ artifactKey: "mask-halo-only:v1",
8716
+ resolve: (detection) => detection.mask
8717
+ ? { alpha: 0, color: 0x000000, mask: detection.mask }
8718
+ : undefined,
8719
+ };
8720
+ const createVisibilityMaskHaloStyle = (style) => ({
8721
+ resolve(detection, context) {
8722
+ const state = resolveContextState(detection);
8723
+ if (state.hidden) {
8724
+ return undefined;
8725
+ }
8726
+ return style.resolve(detection, { ...context, ...state });
8727
+ },
8728
+ });
8729
+ const resolveArtifactMaskStyle = () => currentMaskStyle ?? (currentMaskHaloStyle ? haloOnlyMaskStyle : null);
7782
8730
  let currentPolygonStyle = options.polygonStyle === undefined
7783
8731
  ? new BasePolygonStyle()
7784
8732
  : options.polygonStyle;
7785
8733
  let currentVisibility = options.visibility;
8734
+ let currentMediaTime = 0;
8735
+ let viewportScale = 1;
8736
+ let hasPresentedSample = false;
8737
+ let mediaHeight = 0;
8738
+ let mediaWidth = 0;
7786
8739
  let visibilityVersion = 0;
7787
8740
  let visibilityMaskStyleCache = null;
7788
8741
  const resolveContextState = (detection) => resolveAnnotationStyleState(detection, currentVisibility);
@@ -7791,6 +8744,28 @@ async function createPixiMediaScene(options) {
7791
8744
  hidden: currentVisibility?.labelsHidden === true ||
7792
8745
  resolveContextState(detection).hidden,
7793
8746
  });
8747
+ // The vector layer consumes one shape style; renderer kinds that lower to
8748
+ // shape instructions compose with the internal hook here.
8749
+ function resolveVectorShapeStyle() {
8750
+ const kindShapeStyle = resolveAnnotationShapeStyle({
8751
+ boxCornerStyle: currentBoxCornerStyle,
8752
+ ellipseStyle: currentEllipseStyle,
8753
+ markerStyle: currentMarkerStyle,
8754
+ });
8755
+ const baseShapeStyle = options.shapeStyle ?? null;
8756
+ if (!kindShapeStyle || !baseShapeStyle) {
8757
+ return kindShapeStyle ?? baseShapeStyle;
8758
+ }
8759
+ return {
8760
+ resolve(detection, context) {
8761
+ const combined = [
8762
+ ...(baseShapeStyle.resolve(detection, context) ?? []),
8763
+ ...(kindShapeStyle.resolve(detection, context) ?? []),
8764
+ ];
8765
+ return combined.length > 0 ? combined : undefined;
8766
+ },
8767
+ };
8768
+ }
7794
8769
  const boxLayer = createPixiBoxLayer({
7795
8770
  boxStyle: options.boxStyle,
7796
8771
  Container: options.editingEngine ? Container : undefined,
@@ -7823,6 +8798,24 @@ async function createPixiMediaScene(options) {
7823
8798
  polygonStyle: polygonLayer?.getVectorFallbackStyle() ?? currentPolygonStyle,
7824
8799
  polylineStyle: options.polylineStyle,
7825
8800
  keypointStyle: options.keypointStyle,
8801
+ shapeStyle: resolveVectorShapeStyle(),
8802
+ resolveContextState,
8803
+ });
8804
+ const regionLayer = createPixiRegionLayer({
8805
+ Assets,
8806
+ Container,
8807
+ GifSprite,
8808
+ Sprite,
8809
+ detectionTimeline: options.detectionTimeline,
8810
+ onInvalidate: () => {
8811
+ if (!hasPresentedSample || mediaWidth <= 0 || mediaHeight <= 0)
8812
+ return;
8813
+ const boxState = boxLayer.drawFrame(currentMediaTime, viewportScale);
8814
+ const regionState = regionLayer.drawFrame(currentMediaTime, viewportScale);
8815
+ options.onPresentationUpdate?.(createPresentedSampleState(currentMediaTime, boxState, regionState));
8816
+ },
8817
+ onAssetError: options.diagnostics?.onAssetError,
8818
+ regionRenderers: options.regionRenderers,
7826
8819
  resolveContextState,
7827
8820
  });
7828
8821
  const annotationOverlayLayer = createPixiAnnotationOverlayLayer(options.editingEngine, options.annotationOverlayStyle);
@@ -7836,9 +8829,11 @@ async function createPixiMediaScene(options) {
7836
8829
  preview: options.maskBrush,
7837
8830
  })
7838
8831
  : undefined;
7839
- let maskLayer = options.maskStyle
8832
+ let maskLayer = resolveArtifactMaskStyle()
7840
8833
  ? createPixiMaskLayer({
8834
+ BlurFilter,
7841
8835
  Container,
8836
+ Rectangle,
7842
8837
  ImageSource,
7843
8838
  Mesh,
7844
8839
  MeshGeometry,
@@ -7847,7 +8842,10 @@ async function createPixiMediaScene(options) {
7847
8842
  Texture,
7848
8843
  UniformGroup,
7849
8844
  detectionTimeline: options.detectionTimeline,
7850
- maskStyle: createVisibilityMaskStyle(options.maskStyle),
8845
+ maskHaloStyle: currentMaskHaloStyle
8846
+ ? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
8847
+ : null,
8848
+ maskStyle: createVisibilityMaskStyle(resolveArtifactMaskStyle()),
7851
8849
  onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
7852
8850
  renderPreparation: options.renderPreparation,
7853
8851
  })
@@ -7879,8 +8877,6 @@ async function createPixiMediaScene(options) {
7879
8877
  if (!stagingContext) {
7880
8878
  throw new Error("Unable to create staging canvas context.");
7881
8879
  }
7882
- let mediaHeight = 0;
7883
- let mediaWidth = 0;
7884
8880
  let mediaScene;
7885
8881
  let vectorDisplay;
7886
8882
  let polygonDisplay;
@@ -7889,6 +8885,7 @@ async function createPixiMediaScene(options) {
7889
8885
  const maskSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Mask);
7890
8886
  const boxSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Box);
7891
8887
  const vectorSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Vector);
8888
+ const regionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Region);
7892
8889
  const focusSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Focus);
7893
8890
  const previewSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Preview);
7894
8891
  const interactionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Interaction);
@@ -7899,16 +8896,14 @@ async function createPixiMediaScene(options) {
7899
8896
  maskSlot,
7900
8897
  boxSlot,
7901
8898
  vectorSlot,
8899
+ regionSlot,
7902
8900
  focusSlot,
7903
8901
  previewSlot,
7904
8902
  handleSlot,
7905
8903
  interactionSlot,
7906
8904
  labelSlot,
7907
8905
  ];
7908
- let currentMediaTime = 0;
7909
- let viewportScale = 1;
7910
8906
  const viewport = createViewportController({ scale: 1 });
7911
- let hasPresentedSample = false;
7912
8907
  /**
7913
8908
  * Timestamp of the sample whose pixels are on the staging canvas. Unlike
7914
8909
  * `currentMediaTime`, presentation and selection updates never move it.
@@ -7924,6 +8919,7 @@ async function createPixiMediaScene(options) {
7924
8919
  interaction: options.interaction ?? {
7925
8920
  mode: MediaInteractionMode.Always,
7926
8921
  },
8922
+ canPickDetection: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
7927
8923
  editingEngine: options.editingEngine,
7928
8924
  capturePointer: (pointerId) => {
7929
8925
  if (!rendererCanvas.hasPointerCapture?.(pointerId)) {
@@ -7960,6 +8956,7 @@ async function createPixiMediaScene(options) {
7960
8956
  Text,
7961
8957
  UniformGroup,
7962
8958
  interactionStyle: options.interactionStyle,
8959
+ isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
7963
8960
  })
7964
8961
  : undefined;
7965
8962
  let fastTranslatedDetectionId = null;
@@ -7967,6 +8964,7 @@ async function createPixiMediaScene(options) {
7967
8964
  fastTranslatedDetectionId = id;
7968
8965
  boxLayer.translateDetection(id, dx, dy);
7969
8966
  vectorLayer.translateDetection(id, dx, dy);
8967
+ regionLayer.translateDetection(id, dx, dy);
7970
8968
  labelLayer?.translateDetection(id, dx, dy);
7971
8969
  });
7972
8970
  const unsubscribeEditingState = options.editingEngine?.subscribe((state) => {
@@ -7978,11 +8976,13 @@ async function createPixiMediaScene(options) {
7978
8976
  fastTranslatedDetectionId = null;
7979
8977
  boxLayer.translateDetection(id, 0, 0);
7980
8978
  vectorLayer.translateDetection(id, 0, 0);
8979
+ regionLayer.translateDetection(id, 0, 0);
7981
8980
  labelLayer?.translateDetection(id, 0, 0);
7982
8981
  boxLayer.invalidateDetection(id);
7983
8982
  vectorLayer.invalidateDetection(id);
7984
8983
  boxLayer.drawFrame(currentMediaTime, viewportScale);
7985
8984
  vectorLayer.drawFrame(currentMediaTime, viewportScale);
8985
+ regionLayer.drawFrame(currentMediaTime, viewportScale);
7986
8986
  labelLayer?.drawFrame(currentMediaTime, viewportScale);
7987
8987
  });
7988
8988
  const handleKeyDown = (event) => {
@@ -8017,6 +9017,7 @@ async function createPixiMediaScene(options) {
8017
9017
  Shader,
8018
9018
  UniformGroup,
8019
9019
  focusStyle: options.focusStyle,
9020
+ isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
8020
9021
  })
8021
9022
  : undefined;
8022
9023
  let mediaSprite;
@@ -8126,6 +9127,7 @@ async function createPixiMediaScene(options) {
8126
9127
  attachPolygonLayerDisplay();
8127
9128
  vectorDisplay.addChild(vectorLayer.createContainer());
8128
9129
  vectorSlot.setDisplay(vectorDisplay);
9130
+ regionSlot.setDisplay(regionLayer.createContainer());
8129
9131
  previewSlot.setDisplay(maskBrushPreview?.display);
8130
9132
  if (annotationOverlay) {
8131
9133
  annotationOverlayLayer.attachGraphics(annotationOverlay);
@@ -8179,13 +9181,14 @@ async function createPixiMediaScene(options) {
8179
9181
  const boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
8180
9182
  polygonLayer?.drawFrame(sample.timestamp, viewportScale);
8181
9183
  vectorLayer.drawFrame(sample.timestamp, viewportScale);
9184
+ const regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
8182
9185
  interactionLayer?.drawFrame(sample.timestamp);
8183
9186
  drawFocusLayer(sample.timestamp);
8184
9187
  drawInteractionPresentationLayer(sample.timestamp);
8185
9188
  labelLayer?.drawFrame(sample.timestamp, viewportScale);
8186
9189
  updateMediaSceneFit();
8187
9190
  return {
8188
- ...createPresentedSampleState(sample.timestamp, boxState),
9191
+ ...createPresentedSampleState(sample.timestamp, boxState, regionState),
8189
9192
  duration: sample.duration,
8190
9193
  };
8191
9194
  }
@@ -8207,10 +9210,12 @@ async function createPixiMediaScene(options) {
8207
9210
  maskLayer?.drawFrame(sample.timestamp);
8208
9211
  });
8209
9212
  let boxState;
9213
+ let regionState;
8210
9214
  boxMs = measure(() => {
8211
9215
  boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
8212
9216
  polygonLayer?.drawFrame(sample.timestamp, viewportScale);
8213
9217
  vectorLayer.drawFrame(sample.timestamp, viewportScale);
9218
+ regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
8214
9219
  });
8215
9220
  interactionMs = measure(() => {
8216
9221
  interactionLayer?.drawFrame(sample.timestamp);
@@ -8237,7 +9242,7 @@ async function createPixiMediaScene(options) {
8237
9242
  totalMs: elapsedSince(totalStart),
8238
9243
  };
8239
9244
  return {
8240
- ...createPresentedSampleState(sample.timestamp, boxState),
9245
+ ...createPresentedSampleState(sample.timestamp, boxState, regionState),
8241
9246
  duration: sample.duration,
8242
9247
  renderTimings,
8243
9248
  };
@@ -8386,6 +9391,7 @@ async function createPixiMediaScene(options) {
8386
9391
  visibilityVersion += 1;
8387
9392
  maskLayer?.setMaskStyle(createVisibilityMaskStyle(currentMaskStyle));
8388
9393
  }
9394
+ interactionLayer?.drawFrame(mediaTime);
8389
9395
  }
8390
9396
  boxLayer.setBoxStyle(presentation.boxStyle);
8391
9397
  if (presentation.polygonStyle !== undefined) {
@@ -8406,15 +9412,38 @@ async function createPixiMediaScene(options) {
8406
9412
  polylineStyle: presentation.polylineStyle,
8407
9413
  keypointStyle: presentation.keypointStyle,
8408
9414
  });
8409
- if (presentation.maskStyle !== undefined) {
8410
- currentMaskStyle = presentation.maskStyle;
8411
- const nextMaskLayer = presentation.maskStyle
8412
- ? ensureMaskLayer(presentation.maskStyle)
9415
+ regionLayer.setRenderers(presentation.renderers?.filter((renderer) => renderer.kind === "region") ?? []);
9416
+ if (presentation.boxCornerStyle !== undefined ||
9417
+ presentation.ellipseStyle !== undefined ||
9418
+ presentation.markerStyle !== undefined) {
9419
+ if (presentation.boxCornerStyle !== undefined) {
9420
+ currentBoxCornerStyle = presentation.boxCornerStyle;
9421
+ }
9422
+ if (presentation.ellipseStyle !== undefined) {
9423
+ currentEllipseStyle = presentation.ellipseStyle;
9424
+ }
9425
+ if (presentation.markerStyle !== undefined) {
9426
+ currentMarkerStyle = presentation.markerStyle;
9427
+ }
9428
+ vectorLayer.setStyles({ shapeStyle: resolveVectorShapeStyle() });
9429
+ }
9430
+ if (presentation.maskStyle !== undefined ||
9431
+ presentation.maskHaloStyle !== undefined) {
9432
+ if (presentation.maskStyle !== undefined) {
9433
+ currentMaskStyle = presentation.maskStyle;
9434
+ }
9435
+ if (presentation.maskHaloStyle !== undefined) {
9436
+ currentMaskHaloStyle = presentation.maskHaloStyle;
9437
+ }
9438
+ const artifactStyle = resolveArtifactMaskStyle();
9439
+ const nextMaskLayer = artifactStyle
9440
+ ? ensureMaskLayer(artifactStyle)
8413
9441
  : maskLayer;
8414
- nextMaskLayer?.setMaskStyle(presentation.maskStyle
8415
- ? createVisibilityMaskStyle(presentation.maskStyle)
8416
- : presentation.maskStyle);
8417
- if (presentation.maskStyle) {
9442
+ nextMaskLayer?.setMaskStyle(artifactStyle ? createVisibilityMaskStyle(artifactStyle) : null);
9443
+ nextMaskLayer?.setMaskHaloStyle(currentMaskHaloStyle
9444
+ ? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
9445
+ : null);
9446
+ if (artifactStyle) {
8418
9447
  attachMaskLayerDisplay();
8419
9448
  syncSceneChildren();
8420
9449
  }
@@ -8448,11 +9477,12 @@ async function createPixiMediaScene(options) {
8448
9477
  const boxState = boxLayer.drawFrame(mediaTime, viewportScale);
8449
9478
  polygonLayer?.drawFrame(mediaTime, viewportScale);
8450
9479
  vectorLayer.drawFrame(mediaTime, viewportScale);
9480
+ const regionState = regionLayer.drawFrame(mediaTime, viewportScale);
8451
9481
  interactionLayer?.drawFrame(mediaTime);
8452
9482
  drawFocusLayer(mediaTime);
8453
9483
  drawInteractionPresentationLayer(mediaTime);
8454
9484
  labelLayer?.drawFrame(mediaTime, viewportScale);
8455
- return createPresentedSampleState(mediaTime, boxState);
9485
+ return createPresentedSampleState(mediaTime, boxState, regionState);
8456
9486
  },
8457
9487
  setSelectedDetection(selection, mediaTime) {
8458
9488
  currentMediaTime = selection?.mediaTime ?? mediaTime;
@@ -8479,6 +9509,7 @@ async function createPixiMediaScene(options) {
8479
9509
  polygonLayer?.destroy();
8480
9510
  labelLayer?.destroy();
8481
9511
  vectorLayer.destroy();
9512
+ regionLayer.destroy();
8482
9513
  maskBrushPreview?.destroy();
8483
9514
  unsubscribeFastTranslate?.();
8484
9515
  unsubscribeEditingState?.();
@@ -8495,21 +9526,24 @@ async function createPixiMediaScene(options) {
8495
9526
  });
8496
9527
  },
8497
9528
  };
8498
- function createPresentedSampleState(mediaTime, boxState) {
9529
+ function createPresentedSampleState(mediaTime, boxState, regionState) {
8499
9530
  const detectionFrame = options.detectionTimeline.selectFrame(mediaTime);
8500
9531
  return {
8501
- activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState),
9532
+ activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState, regionState),
8502
9533
  activeDetectionFrameIndex: detectionFrame?.frameIndex ?? null,
8503
9534
  activeDetectionFrameTime: detectionFrame?.mediaTime ?? null,
8504
9535
  detectionBuffer: options.detectionTimeline.getState(),
8505
9536
  mediaTime,
8506
9537
  };
8507
9538
  }
8508
- function countPresentedDetections(frame, mediaTime, boxState) {
9539
+ function countPresentedDetections(frame, mediaTime, boxState, regionState) {
8509
9540
  if (!frame) {
8510
9541
  return 0;
8511
9542
  }
8512
9543
  const visibleDetectionIndexes = new Set(boxState.activeDetectionIndexes);
9544
+ for (const detectionIndex of regionState?.activeDetectionIndexes ?? []) {
9545
+ visibleDetectionIndexes.add(detectionIndex);
9546
+ }
8513
9547
  for (const [detectionIndex, detection] of frame.detections.entries()) {
8514
9548
  if (visibleDetectionIndexes.has(detectionIndex)) {
8515
9549
  continue;
@@ -8536,7 +9570,9 @@ async function createPixiMediaScene(options) {
8536
9570
  function ensureMaskLayer(maskStyle) {
8537
9571
  if (!maskLayer) {
8538
9572
  maskLayer = createPixiMaskLayer({
9573
+ BlurFilter,
8539
9574
  Container,
9575
+ Rectangle,
8540
9576
  ImageSource,
8541
9577
  Mesh,
8542
9578
  MeshGeometry,
@@ -8545,6 +9581,9 @@ async function createPixiMediaScene(options) {
8545
9581
  Texture,
8546
9582
  UniformGroup,
8547
9583
  detectionTimeline: options.detectionTimeline,
9584
+ maskHaloStyle: currentMaskHaloStyle
9585
+ ? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
9586
+ : null,
8548
9587
  maskStyle: createVisibilityMaskStyle(maskStyle),
8549
9588
  onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
8550
9589
  renderPreparation: options.renderPreparation,
@@ -8622,6 +9661,7 @@ async function createPixiMediaScene(options) {
8622
9661
  Shader,
8623
9662
  UniformGroup,
8624
9663
  focusStyle,
9664
+ isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
8625
9665
  });
8626
9666
  }
8627
9667
  return focusLayer;
@@ -8657,10 +9697,10 @@ async function createPixiMediaScene(options) {
8657
9697
  const interactionState = interactionLayer?.getState();
8658
9698
  focusLayer.drawFrame({
8659
9699
  frame,
8660
- hoveredPick: interactionState?.hoveredPick ?? null,
9700
+ hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
8661
9701
  idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
8662
9702
  mediaTime,
8663
- selectedPick: interactionState?.selectedPick ?? null,
9703
+ selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
8664
9704
  viewportScale,
8665
9705
  });
8666
9706
  }
@@ -8672,11 +9712,11 @@ async function createPixiMediaScene(options) {
8672
9712
  const interactionState = interactionLayer?.getState();
8673
9713
  interactionPresentationLayer.drawFrame({
8674
9714
  frame,
8675
- hoveredPick: interactionState?.hoveredPick ?? null,
9715
+ hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
8676
9716
  idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
8677
9717
  mediaTime,
8678
- selectedPick: interactionState?.selectedPick ?? null,
8679
- selectedPicks: interactionState?.selectedPicks,
9718
+ selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
9719
+ selectedPicks: interactionState?.selectedPicks.filter(({ detection }) => !resolveAnnotationStyleState(detection, currentVisibility).hidden),
8680
9720
  viewportScale,
8681
9721
  });
8682
9722
  }
@@ -8747,6 +9787,12 @@ async function createPixiMediaScene(options) {
8747
9787
  };
8748
9788
  return visibilityMaskStyle;
8749
9789
  }
9790
+ function filterVisiblePick(pick) {
9791
+ return pick &&
9792
+ !resolveAnnotationStyleState(pick.detection, currentVisibility).hidden
9793
+ ? pick
9794
+ : null;
9795
+ }
8750
9796
  }
8751
9797
  function canReuseMaskVisibilityArtifacts(previous, next) {
8752
9798
  return ((previous?.annotationsHidden === true) ===
@@ -9393,10 +10439,20 @@ async function createMediaSession(options) {
9393
10439
  ? undefined
9394
10440
  : { enabledRendererKinds: renderers.map(({ kind }) => kind) });
9395
10441
  };
9396
- const resolveRendererPresentation = (presentation) => ({
9397
- ...resolvePresentation(presentation),
9398
- renderers: undefined,
9399
- });
10442
+ const resolveRendererPresentation = (presentation) => {
10443
+ const resolved = resolvePresentation(presentation);
10444
+ return {
10445
+ ...resolved,
10446
+ // Style-backed renderers have already been lowered into their
10447
+ // specialized fields, including source overrides. Keep their kinds in
10448
+ // the authoritative list so the renderer core does not clear those
10449
+ // fields when direct renderers are also present, but let the resolved
10450
+ // fields win over the descriptors' original global styles.
10451
+ renderers: resolved.renderers?.map((renderer) => renderer.kind === "region"
10452
+ ? renderer
10453
+ : { ...renderer, style: undefined }),
10454
+ };
10455
+ };
9400
10456
  const initialPresentation = resolveRendererPresentation(currentPresentation);
9401
10457
  const renderer = await createMediaRenderer({
9402
10458
  ...options.renderer,
@@ -9404,6 +10460,8 @@ async function createMediaSession(options) {
9404
10460
  backgroundColor: initialPresentation.backgroundColor,
9405
10461
  annotationOverlayStyle: initialPresentation.annotationOverlayStyle,
9406
10462
  boxStyle: initialPresentation.boxStyle,
10463
+ boxCornerStyle: initialPresentation.boxCornerStyle,
10464
+ ellipseStyle: initialPresentation.ellipseStyle,
9407
10465
  container: options.container,
9408
10466
  detectionBuffer: sessionDefaults.detectionBuffer,
9409
10467
  detectionFrames: sessionDetections.detectionFrames,
@@ -9412,9 +10470,12 @@ async function createMediaSession(options) {
9412
10470
  focusStyle: initialPresentation.focusStyle,
9413
10471
  interactionStyle: initialPresentation.interactionStyle,
9414
10472
  labelStyle: initialPresentation.labelStyle,
10473
+ maskHaloStyle: initialPresentation.maskHaloStyle,
9415
10474
  maskStyle: initialPresentation.maskStyle,
10475
+ markerStyle: initialPresentation.markerStyle,
9416
10476
  polygonStyle: initialPresentation.polygonStyle,
9417
10477
  polylineStyle: initialPresentation.polylineStyle,
10478
+ renderers: initialPresentation.renderers,
9418
10479
  keypointStyle: initialPresentation.keypointStyle,
9419
10480
  visibility: initialPresentation.visibility,
9420
10481
  onState(state) {