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
@@ -1928,6 +1928,29 @@ function createDetectionPickKey(pick) {
1928
1928
  pick.geometryIndex ?? "geometry",
1929
1929
  ].join(":");
1930
1930
  }
1931
+ /**
1932
+ * Compares the frame-independent membership of two ordered selections.
1933
+ *
1934
+ * Detection ids retain their runtime type, so numeric `1` and string `"1"`
1935
+ * remain distinct identities. Anonymous detections keep their frame-local
1936
+ * index identity.
1937
+ */
1938
+ function haveSameDetectionPickIdentities(current, next) {
1939
+ if (current.length !== next.length) {
1940
+ return false;
1941
+ }
1942
+ return current.every((pick, index) => {
1943
+ const nextPick = next[index];
1944
+ if (!nextPick) {
1945
+ return false;
1946
+ }
1947
+ const id = pick.detection.id;
1948
+ const nextId = nextPick.detection.id;
1949
+ return id === undefined || nextId === undefined
1950
+ ? id === nextId && pick.detectionIndex === nextPick.detectionIndex
1951
+ : typeof id === typeof nextId && id === nextId;
1952
+ });
1953
+ }
1931
1954
  function rebaseDetectionPickToFrame(pick, frame) {
1932
1955
  if (!pick || !frame) {
1933
1956
  return null;
@@ -1953,13 +1976,55 @@ function rebaseDetectionPickToFrame(pick, frame) {
1953
1976
  ? rebasedPick
1954
1977
  : null;
1955
1978
  }
1979
+ /**
1980
+ * Carries a pick forward onto a different frame of the same timeline.
1981
+ *
1982
+ * Same-frame rebases behave exactly like `rebaseDetectionPickToFrame`. Across
1983
+ * frames, the pick follows its detection only when the detection id is defined
1984
+ * and unique in both frames, so anonymous and duplicate-id detections keep the
1985
+ * per-frame lifetime they have today. Returns `null` when the detection has no
1986
+ * followable identity or has left the frame.
1987
+ */
1988
+ function followDetectionPickAcrossFrames(pick, frame) {
1989
+ const rebasedPick = rebaseDetectionPickToFrame(pick, frame);
1990
+ if (rebasedPick || !pick || !frame) {
1991
+ return rebasedPick;
1992
+ }
1993
+ const id = pick.detection.id;
1994
+ if (id === undefined ||
1995
+ !hasUniqueDetectionId(pick.frame, id) ||
1996
+ !hasUniqueDetectionId(frame, id)) {
1997
+ return null;
1998
+ }
1999
+ const detectionIndex = frame.detections.findIndex((detection) => detection.id === id);
2000
+ const detection = frame.detections[detectionIndex];
2001
+ if (!detection) {
2002
+ return null;
2003
+ }
2004
+ return {
2005
+ detection,
2006
+ detectionIndex,
2007
+ frame,
2008
+ geometryIndex: pick.geometryIndex,
2009
+ mediaTime: frame.mediaTime,
2010
+ point: pick.point,
2011
+ target: pick.target,
2012
+ };
2013
+ }
1956
2014
  function detectionPickKey(pick) {
1957
2015
  if (pick.detection.id === undefined) {
1958
2016
  return ["anonymous", pick.detectionIndex];
1959
2017
  }
2018
+ const idType = typeof pick.detection.id;
1960
2019
  return hasUniqueDetectionId(pick.frame, pick.detection.id)
1961
- ? ["id", String(pick.detection.id)]
1962
- : ["duplicate-id", String(pick.detection.id), "index", pick.detectionIndex];
2020
+ ? ["id", idType, String(pick.detection.id)]
2021
+ : [
2022
+ "duplicate-id",
2023
+ idType,
2024
+ String(pick.detection.id),
2025
+ "index",
2026
+ pick.detectionIndex,
2027
+ ];
1963
2028
  }
1964
2029
  function hasUniqueDetectionId(frame, id) {
1965
2030
  let count = 0;
@@ -2652,17 +2717,46 @@ function resolveStyleValue(value, detection, context) {
2652
2717
  : value;
2653
2718
  }
2654
2719
 
2720
+ /**
2721
+ * Resolves the renderer-neutral stroke contract without dropping optional
2722
+ * geometry controls as styles move from public configuration to draw
2723
+ * instructions.
2724
+ *
2725
+ * Keep this as the single normalization point for every built-in style. That
2726
+ * makes additions to `StrokeStyle` deliberate instead of relying on each style
2727
+ * implementation to copy a growing property list correctly.
2728
+ */
2729
+ function resolveStrokeStyle(stroke, defaults) {
2730
+ const alignment = stroke?.alignment ?? defaults.alignment;
2731
+ const cap = stroke?.cap ?? defaults.cap;
2732
+ const dash = stroke?.dash ?? defaults.dash;
2733
+ const join = stroke?.join ?? defaults.join;
2734
+ const miterLimit = stroke?.miterLimit ?? defaults.miterLimit;
2735
+ return {
2736
+ alpha: stroke?.alpha ?? defaults.alpha,
2737
+ color: stroke?.color ?? defaults.color,
2738
+ width: stroke?.width ?? defaults.width,
2739
+ ...(alignment === undefined ? {} : { alignment }),
2740
+ ...(cap === undefined ? {} : { cap }),
2741
+ ...(dash === undefined ? {} : { dash }),
2742
+ ...(join === undefined ? {} : { join }),
2743
+ ...(miterLimit === undefined ? {} : { miterLimit }),
2744
+ };
2745
+ }
2746
+
2747
+ /** Alignment for strokes applied to closed rendered geometry. */
2748
+ var StrokeAlignment;
2749
+ (function (StrokeAlignment) {
2750
+ StrokeAlignment["Inside"] = "inside";
2751
+ StrokeAlignment["Center"] = "center";
2752
+ StrokeAlignment["Outside"] = "outside";
2753
+ })(StrokeAlignment || (StrokeAlignment = {}));
2754
+
2655
2755
  var BoxShape;
2656
2756
  (function (BoxShape) {
2657
2757
  BoxShape["Rect"] = "rect";
2658
2758
  BoxShape["RoundedRect"] = "roundedRect";
2659
2759
  })(BoxShape || (BoxShape = {}));
2660
- var BoxStrokeAlignment;
2661
- (function (BoxStrokeAlignment) {
2662
- BoxStrokeAlignment["Inside"] = "inside";
2663
- BoxStrokeAlignment["Center"] = "center";
2664
- BoxStrokeAlignment["Outside"] = "outside";
2665
- })(BoxStrokeAlignment || (BoxStrokeAlignment = {}));
2666
2760
 
2667
2761
  const DEFAULT_BOX_STROKE_ALPHA = 1;
2668
2762
  const DEFAULT_BOX_STROKE_COLOR = 0x00ff66;
@@ -2716,22 +2810,12 @@ class BaseBoxStyle {
2716
2810
  if (stroke === null) {
2717
2811
  return undefined;
2718
2812
  }
2719
- const resolvedStroke = {
2720
- alpha: stroke?.alpha ?? DEFAULT_BOX_STROKE_ALPHA,
2721
- color: stroke?.color ?? DEFAULT_BOX_STROKE_COLOR,
2722
- width: stroke?.width ?? DEFAULT_BOX_STROKE_WIDTH,
2723
- };
2724
- return {
2725
- ...resolvedStroke,
2726
- ...(stroke?.alignment === undefined
2727
- ? {}
2728
- : { alignment: stroke.alignment }),
2729
- ...(stroke?.dash === undefined
2730
- ? context.ephemeral
2731
- ? { dash: [6, 4] }
2732
- : {}
2733
- : { dash: stroke.dash }),
2734
- };
2813
+ return resolveStrokeStyle(stroke, {
2814
+ alpha: DEFAULT_BOX_STROKE_ALPHA,
2815
+ color: DEFAULT_BOX_STROKE_COLOR,
2816
+ ...(context.ephemeral ? { dash: [6, 4] } : {}),
2817
+ width: DEFAULT_BOX_STROKE_WIDTH,
2818
+ });
2735
2819
  }
2736
2820
  resolveFill(detection, context) {
2737
2821
  const fill = resolveStyleValue(this.options.fill, detection, context);
@@ -2745,6 +2829,83 @@ class BaseBoxStyle {
2745
2829
  }
2746
2830
  }
2747
2831
 
2832
+ const DEFAULT_CORNER_LENGTH = 20;
2833
+ const DEFAULT_CORNER_STROKE = {
2834
+ alpha: 1,
2835
+ cap: "round",
2836
+ color: 0x00ff66,
2837
+ join: "round",
2838
+ width: 2,
2839
+ };
2840
+ /**
2841
+ * Configurable four-corner decoration derived from a detection rectangle.
2842
+ *
2843
+ * This is the BoxCornerAnnotator-style facade: it keeps the rectangle semantic
2844
+ * and draws only its four open corner segments.
2845
+ */
2846
+ class BaseBoxCornerStyle {
2847
+ options;
2848
+ constructor(options = {}) {
2849
+ this.options = options;
2850
+ }
2851
+ resolve(detection, context) {
2852
+ if (!detection.rect ||
2853
+ context.hidden ||
2854
+ this.options.shouldRender?.(detection, context) === false) {
2855
+ return undefined;
2856
+ }
2857
+ const resolvedLength = resolveStyleValue(this.options.length, detection, context) ??
2858
+ DEFAULT_CORNER_LENGTH;
2859
+ const viewportScale = context.viewportScale && context.viewportScale > 0
2860
+ ? context.viewportScale
2861
+ : 1;
2862
+ const length = Math.min(Math.max(0, resolvedLength / viewportScale), detection.rect.width / 2, detection.rect.height / 2);
2863
+ const stroke = resolveStyleValue(this.options.stroke, detection, context);
2864
+ if (length <= 0 || stroke === null)
2865
+ return undefined;
2866
+ const { height, width, x, y } = detection.rect;
2867
+ const left = x - width / 2;
2868
+ const right = x + width / 2;
2869
+ const top = y - height / 2;
2870
+ const bottom = y + height / 2;
2871
+ const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_CORNER_STROKE);
2872
+ const openStroke = {
2873
+ alpha: resolvedStroke.alpha,
2874
+ cap: resolvedStroke.cap,
2875
+ color: resolvedStroke.color,
2876
+ dash: resolvedStroke.dash,
2877
+ join: resolvedStroke.join,
2878
+ miterLimit: resolvedStroke.miterLimit,
2879
+ width: resolvedStroke.width,
2880
+ };
2881
+ return {
2882
+ segments: [
2883
+ [
2884
+ { x: left + length, y: top },
2885
+ { x: left, y: top },
2886
+ { x: left, y: top + length },
2887
+ ],
2888
+ [
2889
+ { x: right - length, y: top },
2890
+ { x: right, y: top },
2891
+ { x: right, y: top + length },
2892
+ ],
2893
+ [
2894
+ { x: left + length, y: bottom },
2895
+ { x: left, y: bottom },
2896
+ { x: left, y: bottom - length },
2897
+ ],
2898
+ [
2899
+ { x: right - length, y: bottom },
2900
+ { x: right, y: bottom },
2901
+ { x: right, y: bottom - length },
2902
+ ],
2903
+ ],
2904
+ stroke: openStroke,
2905
+ };
2906
+ }
2907
+ }
2908
+
2748
2909
  var FocusTargetMode;
2749
2910
  (function (FocusTargetMode) {
2750
2911
  FocusTargetMode["Hovered"] = "hovered";
@@ -2943,18 +3104,11 @@ class BaseInteractionStyle {
2943
3104
  return undefined;
2944
3105
  }
2945
3106
  const defaults = getStateDefaults(context.state);
2946
- const resolvedStroke = {
2947
- alpha: stroke?.alpha ?? defaults.strokeAlpha,
2948
- color: stroke?.color ?? defaults.strokeColor,
2949
- width: stroke?.width ?? defaults.strokeWidth,
2950
- };
2951
- if (stroke?.alignment !== undefined) {
2952
- return {
2953
- ...resolvedStroke,
2954
- alignment: stroke.alignment,
2955
- };
2956
- }
2957
- return resolvedStroke;
3107
+ return resolveStrokeStyle(stroke, {
3108
+ alpha: defaults.strokeAlpha,
3109
+ color: defaults.strokeColor,
3110
+ width: defaults.strokeWidth,
3111
+ });
2958
3112
  }
2959
3113
  resolveFill(detection, context) {
2960
3114
  const fill = resolveStyleValue(this.options.fill, detection, context);
@@ -3206,6 +3360,115 @@ function createArtifactKey(options) {
3206
3360
  return `base:${color}:${fillAlpha}:${mode}:${serializeStroke(resolveStroke(options.stroke, color, mode))}`;
3207
3361
  }
3208
3362
 
3363
+ /**
3364
+ * Discriminator for renderer-neutral shape draw instructions.
3365
+ */
3366
+ var ShapeInstructionKind;
3367
+ (function (ShapeInstructionKind) {
3368
+ ShapeInstructionKind["Ellipse"] = "ellipse";
3369
+ ShapeInstructionKind["Marker"] = "marker";
3370
+ ShapeInstructionKind["Path"] = "path";
3371
+ })(ShapeInstructionKind || (ShapeInstructionKind = {}));
3372
+ var MarkerShape;
3373
+ (function (MarkerShape) {
3374
+ MarkerShape["Circle"] = "circle";
3375
+ MarkerShape["Cross"] = "cross";
3376
+ MarkerShape["Square"] = "square";
3377
+ MarkerShape["Triangle"] = "triangle";
3378
+ })(MarkerShape || (MarkerShape = {}));
3379
+ /**
3380
+ * Coordinate space for marker sizing. Media sizes scale with the viewport;
3381
+ * screen sizes stay constant on screen, matching stroke-width semantics.
3382
+ */
3383
+ var MarkerSizeSpace;
3384
+ (function (MarkerSizeSpace) {
3385
+ MarkerSizeSpace["Media"] = "media";
3386
+ MarkerSizeSpace["Screen"] = "screen";
3387
+ })(MarkerSizeSpace || (MarkerSizeSpace = {}));
3388
+
3389
+ const DEFAULT_FILL = { alpha: 1, color: 0x00ff66 };
3390
+ const DEFAULT_STROKE = { alpha: 1, color: 0xffffff, width: 1 };
3391
+ const DEFAULT_SIZE = 12;
3392
+ /**
3393
+ * Configurable marker renderer style anchored to semantic detection geometry.
3394
+ *
3395
+ * Use `center` for any domain-specific anchor (keypoint, centroid, or custom
3396
+ * metadata). Without it, the marker lands at the detection rectangle center.
3397
+ */
3398
+ class BaseMarkerStyle {
3399
+ options;
3400
+ constructor(options = {}) {
3401
+ this.options = options;
3402
+ }
3403
+ resolve(detection, context) {
3404
+ if (context.hidden ||
3405
+ this.options.shouldRender?.(detection, context) === false) {
3406
+ return undefined;
3407
+ }
3408
+ const center = resolveStyleValue(this.options.center, detection, context) ??
3409
+ (detection.rect
3410
+ ? { x: detection.rect.x, y: detection.rect.y }
3411
+ : undefined);
3412
+ const shape = resolveStyleValue(this.options.shape, detection, context) ??
3413
+ MarkerShape.Circle;
3414
+ const size = resolveStyleValue(this.options.size, detection, context) ?? DEFAULT_SIZE;
3415
+ const sizeSpace = resolveStyleValue(this.options.sizeSpace, detection, context) ??
3416
+ MarkerSizeSpace.Screen;
3417
+ const rotation = resolveStyleValue(this.options.rotation, detection, context);
3418
+ const stroke = resolveStyleValue(this.options.stroke, detection, context);
3419
+ if (!center || !Number.isFinite(size) || size <= 0) {
3420
+ return undefined;
3421
+ }
3422
+ if (shape === MarkerShape.Cross) {
3423
+ if (stroke === null) {
3424
+ return undefined;
3425
+ }
3426
+ const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_STROKE);
3427
+ const openStroke = {
3428
+ alpha: resolvedStroke.alpha,
3429
+ cap: resolvedStroke.cap,
3430
+ color: resolvedStroke.color,
3431
+ dash: resolvedStroke.dash,
3432
+ join: resolvedStroke.join,
3433
+ miterLimit: resolvedStroke.miterLimit,
3434
+ width: resolvedStroke.width,
3435
+ };
3436
+ return {
3437
+ center,
3438
+ shape,
3439
+ size,
3440
+ sizeSpace,
3441
+ ...(rotation === undefined ? {} : { rotation }),
3442
+ stroke: openStroke,
3443
+ };
3444
+ }
3445
+ const fill = resolveStyleValue(this.options.fill, detection, context);
3446
+ if (fill === null && stroke === null) {
3447
+ return undefined;
3448
+ }
3449
+ return {
3450
+ center,
3451
+ shape,
3452
+ size,
3453
+ sizeSpace,
3454
+ ...(rotation === undefined ? {} : { rotation }),
3455
+ ...(fill === null
3456
+ ? {}
3457
+ : {
3458
+ fill: {
3459
+ alpha: fill?.alpha ?? DEFAULT_FILL.alpha,
3460
+ color: fill?.color ?? DEFAULT_FILL.color,
3461
+ },
3462
+ }),
3463
+ ...(stroke === null
3464
+ ? {}
3465
+ : stroke === undefined
3466
+ ? { stroke: DEFAULT_STROKE }
3467
+ : { stroke: resolveStrokeStyle(stroke, DEFAULT_STROKE) }),
3468
+ };
3469
+ }
3470
+ }
3471
+
3209
3472
  class BasePolygonStyle {
3210
3473
  options;
3211
3474
  constructor(options = {}) {
@@ -3233,15 +3496,11 @@ class BasePolygonStyle {
3233
3496
  ...(stroke === null
3234
3497
  ? {}
3235
3498
  : {
3236
- stroke: {
3237
- alpha: stroke?.alpha ?? 1,
3238
- color: stroke?.color ?? 0x00ff66,
3239
- width: stroke?.width ?? 2,
3240
- ...(stroke?.alignment === undefined
3241
- ? {}
3242
- : { alignment: stroke.alignment }),
3243
- ...(stroke?.dash === undefined ? {} : { dash: stroke.dash }),
3244
- },
3499
+ stroke: resolveStrokeStyle(stroke, {
3500
+ alpha: 1,
3501
+ color: 0x00ff66,
3502
+ width: 2,
3503
+ }),
3245
3504
  }),
3246
3505
  };
3247
3506
  }
@@ -3265,15 +3524,11 @@ class BasePolylineStyle {
3265
3524
  }
3266
3525
  return {
3267
3526
  points: detection.polyline.points,
3268
- stroke: {
3269
- alpha: stroke?.alpha ?? 1,
3270
- color: stroke?.color ?? 0x00ff66,
3271
- width: stroke?.width ?? 2,
3272
- ...(stroke?.alignment === undefined
3273
- ? {}
3274
- : { alignment: stroke.alignment }),
3275
- ...(stroke?.dash === undefined ? {} : { dash: stroke.dash }),
3276
- },
3527
+ stroke: resolveStrokeStyle(stroke, {
3528
+ alpha: 1,
3529
+ color: 0x00ff66,
3530
+ width: 2,
3531
+ }),
3277
3532
  };
3278
3533
  }
3279
3534
  }
@@ -3307,19 +3562,21 @@ class BaseKeypointStyle {
3307
3562
  const edges = geometry.edges.map(([fromIndex, toIndex], edgeIndex) => ({
3308
3563
  from: geometry.points[fromIndex],
3309
3564
  to: geometry.points[toIndex],
3310
- stroke: {
3311
- alpha: edgeStroke?.alpha ?? 1,
3312
- color: definition?.edges[edgeIndex]?.color ?? edgeStroke?.color ?? 0x00ff66,
3313
- width: edgeStroke?.width ?? 2,
3314
- },
3565
+ stroke: resolveStrokeStyle(definition?.edges[edgeIndex]?.color === undefined
3566
+ ? edgeStroke
3567
+ : { ...edgeStroke, color: definition.edges[edgeIndex].color }, {
3568
+ alpha: 1,
3569
+ color: 0x00ff66,
3570
+ width: 2,
3571
+ }),
3315
3572
  ...(shadowStroke === null
3316
3573
  ? {}
3317
3574
  : {
3318
- shadowStroke: {
3319
- alpha: shadowStroke?.alpha ?? 0.65,
3320
- color: shadowStroke?.color ?? 0x000000,
3321
- width: shadowStroke?.width ?? 4,
3322
- },
3575
+ shadowStroke: resolveStrokeStyle(shadowStroke, {
3576
+ alpha: 0.65,
3577
+ color: 0x000000,
3578
+ width: 4,
3579
+ }),
3323
3580
  }),
3324
3581
  }));
3325
3582
  const markers = geometry.points.flatMap((point, index) => {
@@ -3341,11 +3598,11 @@ class BaseKeypointStyle {
3341
3598
  shape: visibility === KeypointVisibility.Occluded
3342
3599
  ? KeypointMarkerShape.Cross
3343
3600
  : KeypointMarkerShape.Circle,
3344
- stroke: {
3345
- alpha: markerStroke?.alpha ?? 1,
3346
- color: markerStroke?.color ?? 0xffffff,
3347
- width: markerStroke?.width ?? 2,
3348
- },
3601
+ stroke: resolveStrokeStyle(markerStroke, {
3602
+ alpha: 1,
3603
+ color: 0xffffff,
3604
+ width: 2,
3605
+ }),
3349
3606
  },
3350
3607
  ];
3351
3608
  });
@@ -3426,6 +3683,9 @@ function createClassColorStyle(fill, labelBackground, labelText, stroke) {
3426
3683
  }
3427
3684
 
3428
3685
  const DEFAULT_BOX_CORNER_RADIUS = 1;
3686
+ const DEFAULT_ELLIPSE_AXIS_RATIO = 0.35;
3687
+ const DEFAULT_ELLIPSE_START_ANGLE = (-45 * Math.PI) / 180;
3688
+ const DEFAULT_ELLIPSE_END_ANGLE = (235 * Math.PI) / 180;
3429
3689
  const DEFAULT_OUTLINE_WIDTH = 2;
3430
3690
  const DEFAULT_FILL_ALPHA = 0.08;
3431
3691
  const DEFAULT_MASK_FILL_ALPHA = 0.45;
@@ -3480,6 +3740,47 @@ function createDefaultBoxStyle(options = {}) {
3480
3740
  }),
3481
3741
  });
3482
3742
  }
3743
+ /** Canonical opt-in BoxCornerAnnotator-style presentation. */
3744
+ function createDefaultBoxCornerStyle(options = {}) {
3745
+ const getClassColor = createClassColorResolver(options);
3746
+ return new BaseBoxCornerStyle({
3747
+ stroke: (detection) => ({ color: getClassColor(detection) }),
3748
+ });
3749
+ }
3750
+ /**
3751
+ * Matches the Python Supervision EllipseAnnotator: an elliptical footprint
3752
+ * arc under the detection box, swept from -45deg to 235deg, in the class
3753
+ * color.
3754
+ */
3755
+ function createDefaultEllipseStyle(options = {}) {
3756
+ const getClassColor = createClassColorResolver(options);
3757
+ return {
3758
+ resolve(detection, context) {
3759
+ if (!detection.rect || context.hidden) {
3760
+ return undefined;
3761
+ }
3762
+ const radiusX = detection.rect.width / 2;
3763
+ const radiusY = radiusX * DEFAULT_ELLIPSE_AXIS_RATIO;
3764
+ return {
3765
+ center: {
3766
+ x: detection.rect.x,
3767
+ // Bottom-tangent so the arc hugs the detection instead of dipping
3768
+ // below its feet.
3769
+ y: detection.rect.y + detection.rect.height / 2 - radiusY,
3770
+ },
3771
+ endAngle: DEFAULT_ELLIPSE_END_ANGLE,
3772
+ radiusX,
3773
+ radiusY,
3774
+ startAngle: DEFAULT_ELLIPSE_START_ANGLE,
3775
+ stroke: {
3776
+ alpha: 1,
3777
+ color: getClassColor(detection),
3778
+ width: DEFAULT_OUTLINE_WIDTH,
3779
+ },
3780
+ };
3781
+ },
3782
+ };
3783
+ }
3483
3784
  function createDefaultKeypointStyle(options = {}) {
3484
3785
  const getClassColor = createClassColorResolver(options);
3485
3786
  return new BaseKeypointStyle({
@@ -3538,6 +3839,31 @@ function createDefaultMaskStyle(options = {}) {
3538
3839
  }),
3539
3840
  });
3540
3841
  }
3842
+ /** Canonical opt-in marker presentation. */
3843
+ function createDefaultMarkerStyle() {
3844
+ return new BaseMarkerStyle();
3845
+ }
3846
+ const DEFAULT_MASK_HALO_ALPHA = 0.6;
3847
+ const DEFAULT_MASK_HALO_SPREAD = 12;
3848
+ /**
3849
+ * Canonical mask halo: a class-colored glow that follows the mask silhouette.
3850
+ * Used when the `maskHalo` renderer is listed without an explicit style.
3851
+ */
3852
+ function createDefaultMaskHaloStyle(options = {}) {
3853
+ const getClassColor = createClassColorResolver(options);
3854
+ return {
3855
+ resolve(detection, context) {
3856
+ if (!detection.mask || context.hidden) {
3857
+ return undefined;
3858
+ }
3859
+ return {
3860
+ alpha: DEFAULT_MASK_HALO_ALPHA,
3861
+ color: getClassColor(detection),
3862
+ spread: DEFAULT_MASK_HALO_SPREAD,
3863
+ };
3864
+ },
3865
+ };
3866
+ }
3541
3867
  function createDefaultPolygonStyle(options = {}) {
3542
3868
  const getClassColor = createClassColorResolver(options);
3543
3869
  return new BasePolygonStyle({
@@ -3580,12 +3906,30 @@ function resolveDefaultClassColor(detection, getClassColor) {
3580
3906
  */
3581
3907
  const annotationRendererKinds = [
3582
3908
  "box",
3909
+ "box-corners",
3910
+ "ellipse",
3583
3911
  "keypoints",
3584
3912
  "label",
3585
3913
  "mask",
3914
+ "maskHalo",
3915
+ "marker",
3586
3916
  "polygon",
3587
3917
  "polyline",
3918
+ "region",
3588
3919
  ];
3920
+ /** Sources currently supported by a region renderer. */
3921
+ const RegionRendererSourceKind = {
3922
+ Asset: "asset",
3923
+ };
3924
+ /** Media-space regions currently supported by a region renderer. */
3925
+ const RegionRendererRegionKind = {
3926
+ Bounds: "bounds",
3927
+ KeypointAnchor: "keypoint-anchor",
3928
+ };
3929
+ /** Composition modes currently supported by a region renderer. */
3930
+ const RegionRendererComposeMode = {
3931
+ Over: "over",
3932
+ };
3589
3933
  /**
3590
3934
  * Creates built-in annotation renderer descriptors for
3591
3935
  * `MediaRendererPresentation.renderers`.
@@ -3596,11 +3940,16 @@ const annotationRendererKinds = [
3596
3940
  */
3597
3941
  const annotationRenderers = {
3598
3942
  box: (options) => createAnnotationRenderer("box", options),
3943
+ boxCorners: (options) => createAnnotationRenderer("box-corners", options),
3944
+ ellipse: (options) => createAnnotationRenderer("ellipse", options),
3599
3945
  keypoints: (options) => createAnnotationRenderer("keypoints", options),
3600
3946
  label: (options) => createAnnotationRenderer("label", options),
3601
3947
  mask: (options) => createAnnotationRenderer("mask", options),
3948
+ maskHalo: (options) => createAnnotationRenderer("maskHalo", options),
3949
+ marker: (options) => createAnnotationRenderer("marker", options),
3602
3950
  polygon: (options) => createAnnotationRenderer("polygon", options),
3603
3951
  polyline: (options) => createAnnotationRenderer("polyline", options),
3952
+ region: (options) => ({ kind: "region", ...options }),
3604
3953
  };
3605
3954
  function createAnnotationRenderer(kind, options) {
3606
3955
  // Every built-in renderer keeps its kind as its stable id. TypeScript cannot
@@ -3610,31 +3959,67 @@ function createAnnotationRenderer(kind, options) {
3610
3959
  }
3611
3960
 
3612
3961
  const annotationRendererRegistry = {
3613
- box: { createCanonicalStyle: createDefaultBoxStyle, styleField: "boxStyle" },
3962
+ box: {
3963
+ cardinality: "singleton",
3964
+ createCanonicalStyle: createDefaultBoxStyle,
3965
+ styleField: "boxStyle",
3966
+ },
3967
+ "box-corners": {
3968
+ cardinality: "singleton",
3969
+ createCanonicalStyle: createDefaultBoxCornerStyle,
3970
+ styleField: "boxCornerStyle",
3971
+ },
3972
+ ellipse: {
3973
+ cardinality: "singleton",
3974
+ createCanonicalStyle: createDefaultEllipseStyle,
3975
+ styleField: "ellipseStyle",
3976
+ },
3614
3977
  keypoints: {
3978
+ cardinality: "singleton",
3615
3979
  createCanonicalStyle: createDefaultKeypointStyle,
3616
3980
  styleField: "keypointStyle",
3617
3981
  },
3618
3982
  label: {
3983
+ cardinality: "singleton",
3619
3984
  createCanonicalStyle: createDefaultLabelStyle,
3620
3985
  styleField: "labelStyle",
3621
3986
  },
3622
3987
  mask: {
3988
+ cardinality: "singleton",
3623
3989
  createCanonicalStyle: createDefaultMaskStyle,
3624
3990
  styleField: "maskStyle",
3625
3991
  },
3992
+ maskHalo: {
3993
+ cardinality: "singleton",
3994
+ createCanonicalStyle: () => createDefaultMaskHaloStyle(),
3995
+ styleField: "maskHaloStyle",
3996
+ },
3997
+ marker: {
3998
+ cardinality: "singleton",
3999
+ createCanonicalStyle: createDefaultMarkerStyle,
4000
+ styleField: "markerStyle",
4001
+ },
3626
4002
  polygon: {
4003
+ cardinality: "singleton",
3627
4004
  createCanonicalStyle: createDefaultPolygonStyle,
3628
4005
  styleField: "polygonStyle",
3629
4006
  },
3630
4007
  polyline: {
4008
+ cardinality: "singleton",
3631
4009
  createCanonicalStyle: createDefaultPolylineStyle,
3632
4010
  styleField: "polylineStyle",
3633
4011
  },
4012
+ region: { cardinality: "multiple" },
3634
4013
  };
4014
+ function isStyleBackedAnnotationRendererKind(kind) {
4015
+ return annotationRendererRegistry[kind].cardinality === "singleton";
4016
+ }
3635
4017
  /** Presentation fields owned by the given renderer kinds, in the same order. */
3636
4018
  function resolveAnnotationRendererStyleFields(kinds) {
3637
- return kinds.map((kind) => annotationRendererRegistry[kind].styleField);
4019
+ return kinds.flatMap((kind) => {
4020
+ const metadata = annotationRendererRegistry[kind];
4021
+ return "styleField" in metadata ? [metadata.styleField] : [];
4022
+ });
3638
4023
  }
3639
4024
 
3640
4025
  /**
@@ -3650,9 +4035,13 @@ function resolveAnnotationRendererPresentation(presentation) {
3650
4035
  }
3651
4036
  const resolved = {
3652
4037
  boxStyle: null,
4038
+ boxCornerStyle: null,
4039
+ ellipseStyle: null,
3653
4040
  keypointStyle: null,
3654
4041
  labelStyle: null,
4042
+ maskHaloStyle: null,
3655
4043
  maskStyle: null,
4044
+ markerStyle: null,
3656
4045
  polygonStyle: null,
3657
4046
  polylineStyle: null,
3658
4047
  };
@@ -3663,17 +4052,23 @@ function resolveAnnotationRendererPresentation(presentation) {
3663
4052
  throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer id "${renderer.id}".`);
3664
4053
  }
3665
4054
  rendererIds.add(renderer.id);
3666
- if (rendererKinds.has(renderer.kind)) {
4055
+ if (annotationRendererRegistry[renderer.kind].cardinality === "singleton" &&
4056
+ rendererKinds.has(renderer.kind)) {
3667
4057
  throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer kind "${renderer.kind}".`);
3668
4058
  }
3669
4059
  rendererKinds.add(renderer.kind);
3670
- applyRendererStyle(resolved, presentation, renderer);
4060
+ if (isStyleBackedAnnotationRenderer(renderer)) {
4061
+ applyRendererStyle(resolved, presentation, renderer);
4062
+ }
3671
4063
  }
3672
4064
  return {
3673
4065
  ...presentation,
3674
4066
  ...resolved,
3675
4067
  };
3676
4068
  }
4069
+ function isStyleBackedAnnotationRenderer(renderer) {
4070
+ return isStyleBackedAnnotationRendererKind(renderer.kind);
4071
+ }
3677
4072
  function applyRendererStyle(resolved, configured, renderer) {
3678
4073
  const { createCanonicalStyle, styleField } = annotationRendererRegistry[renderer.kind];
3679
4074
  const configuredStyle = configured[styleField];
@@ -3703,12 +4098,24 @@ function createSourceAwarePresentation(globalPresentation = {}, sources, options
3703
4098
  boxStyle: shouldApplySourceStyle("boxStyle")
3704
4099
  ? new SourceAwareBoxStyle(normalizeGlobalBoxStyle(globalPresentation.boxStyle), sourcePresentations)
3705
4100
  : globalPresentation.boxStyle,
4101
+ boxCornerStyle: shouldApplySourceStyle("boxCornerStyle")
4102
+ ? new SourceAwareBoxCornerStyle(globalPresentation.boxCornerStyle ?? null, sourcePresentations)
4103
+ : globalPresentation.boxCornerStyle,
4104
+ ellipseStyle: shouldApplySourceStyle("ellipseStyle")
4105
+ ? new SourceAwareEllipseStyle(globalPresentation.ellipseStyle ?? null, sourcePresentations)
4106
+ : globalPresentation.ellipseStyle,
3706
4107
  labelStyle: shouldApplySourceStyle("labelStyle")
3707
4108
  ? new SourceAwareLabelStyle(normalizeGlobalLabelStyle(globalPresentation.labelStyle), sourcePresentations)
3708
4109
  : globalPresentation.labelStyle,
3709
4110
  maskStyle: shouldApplySourceStyle("maskStyle")
3710
4111
  ? new SourceAwareMaskStyle(normalizeGlobalMaskStyle(globalPresentation.maskStyle), sourcePresentations)
3711
4112
  : globalPresentation.maskStyle,
4113
+ markerStyle: shouldApplySourceStyle("markerStyle")
4114
+ ? new SourceAwareMarkerStyle(normalizeGlobalMarkerStyle(globalPresentation.markerStyle), sourcePresentations)
4115
+ : globalPresentation.markerStyle,
4116
+ maskHaloStyle: shouldApplySourceStyle("maskHaloStyle")
4117
+ ? new SourceAwareMaskHaloStyle(normalizeGlobalMaskHaloStyle(globalPresentation.maskHaloStyle), sourcePresentations)
4118
+ : globalPresentation.maskHaloStyle,
3712
4119
  polygonStyle: shouldApplySourceStyle("polygonStyle")
3713
4120
  ? new SourceAwarePolygonStyle(normalizeGlobalPolygonStyle(globalPresentation.polygonStyle), sourcePresentations)
3714
4121
  : globalPresentation.polygonStyle,
@@ -3720,6 +4127,17 @@ function createSourceAwarePresentation(globalPresentation = {}, sources, options
3720
4127
  : globalPresentation.keypointStyle,
3721
4128
  };
3722
4129
  }
4130
+ class SourceAwareBoxCornerStyle {
4131
+ globalStyle;
4132
+ sourcePresentations;
4133
+ constructor(globalStyle, sourcePresentations) {
4134
+ this.globalStyle = globalStyle;
4135
+ this.sourcePresentations = sourcePresentations;
4136
+ }
4137
+ resolve(detection, context) {
4138
+ return resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "boxCornerStyle")?.resolve(detection, context);
4139
+ }
4140
+ }
3723
4141
  class SourceAwareBoxStyle {
3724
4142
  globalStyle;
3725
4143
  sourcePresentations;
@@ -3732,6 +4150,18 @@ class SourceAwareBoxStyle {
3732
4150
  return style?.resolve(detection, context);
3733
4151
  }
3734
4152
  }
4153
+ class SourceAwareEllipseStyle {
4154
+ globalStyle;
4155
+ sourcePresentations;
4156
+ constructor(globalStyle, sourcePresentations) {
4157
+ this.globalStyle = globalStyle;
4158
+ this.sourcePresentations = sourcePresentations;
4159
+ }
4160
+ resolve(detection, context) {
4161
+ const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "ellipseStyle");
4162
+ return style?.resolve(detection, context);
4163
+ }
4164
+ }
3735
4165
  class SourceAwareLabelStyle {
3736
4166
  globalStyle;
3737
4167
  sourcePresentations;
@@ -3759,6 +4189,30 @@ class SourceAwareMaskStyle {
3759
4189
  return style?.resolve(detection, context);
3760
4190
  }
3761
4191
  }
4192
+ class SourceAwareMaskHaloStyle {
4193
+ globalStyle;
4194
+ sourcePresentations;
4195
+ constructor(globalStyle, sourcePresentations) {
4196
+ this.globalStyle = globalStyle;
4197
+ this.sourcePresentations = sourcePresentations;
4198
+ }
4199
+ resolve(detection, context) {
4200
+ const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "maskHaloStyle");
4201
+ return style?.resolve(detection, context);
4202
+ }
4203
+ }
4204
+ class SourceAwareMarkerStyle {
4205
+ globalStyle;
4206
+ sourcePresentations;
4207
+ constructor(globalStyle, sourcePresentations) {
4208
+ this.globalStyle = globalStyle;
4209
+ this.sourcePresentations = sourcePresentations;
4210
+ }
4211
+ resolve(detection, context) {
4212
+ const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "markerStyle");
4213
+ return style?.resolve(detection, context);
4214
+ }
4215
+ }
3762
4216
  class SourceAwarePolygonStyle {
3763
4217
  globalStyle;
3764
4218
  sourcePresentations;
@@ -3814,6 +4268,12 @@ function normalizeGlobalLabelStyle(style) {
3814
4268
  function normalizeGlobalMaskStyle(style) {
3815
4269
  return style === undefined ? new BaseMaskStyle() : style;
3816
4270
  }
4271
+ function normalizeGlobalMaskHaloStyle(style) {
4272
+ return style === undefined ? createDefaultMaskHaloStyle() : style;
4273
+ }
4274
+ function normalizeGlobalMarkerStyle(style) {
4275
+ return style === undefined ? new BaseMarkerStyle() : style;
4276
+ }
3817
4277
  function normalizeGlobalPolygonStyle(style) {
3818
4278
  return style === undefined ? new BasePolygonStyle() : style;
3819
4279
  }
@@ -4310,6 +4770,180 @@ function writePaletteEntry(palette, id, color, alpha) {
4310
4770
  palette[offset + 3] = Math.max(0, Math.min(alpha, 1));
4311
4771
  }
4312
4772
 
4773
+ const DEFAULT_ELLIPSE_SEGMENT_COUNT = 48;
4774
+ const FULL_TURN = Math.PI * 2;
4775
+ const GEOMETRY_EPSILON = 1e-9;
4776
+ const MIN_ELLIPSE_SEGMENT_COUNT = 4;
4777
+ const MAX_ELLIPSE_SEGMENT_COUNT = 512;
4778
+ /**
4779
+ * Samples an ellipse or elliptical arc into a deterministic polyline so every
4780
+ * backend rasterizes the same structural geometry, including dashed strokes.
4781
+ *
4782
+ * A closed ellipse returns `segmentCount` points without repeating the first
4783
+ * point; an open arc returns `segmentCount + 1` points including both
4784
+ * endpoints.
4785
+ */
4786
+ function sampleEllipseArc(ellipse, segmentCount = DEFAULT_ELLIPSE_SEGMENT_COUNT) {
4787
+ validateEllipseGeometry(ellipse);
4788
+ assertIntegerAtLeast(segmentCount, MIN_ELLIPSE_SEGMENT_COUNT, "segmentCount");
4789
+ const rotation = ellipse.rotation ?? 0;
4790
+ const startAngle = ellipse.startAngle ?? 0;
4791
+ const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
4792
+ const range = endAngle - startAngle;
4793
+ const closed = ellipse.startAngle === undefined;
4794
+ const pointCount = closed ? segmentCount : segmentCount + 1;
4795
+ const cosRotation = Math.cos(rotation);
4796
+ const sinRotation = Math.sin(rotation);
4797
+ const points = [];
4798
+ for (let index = 0; index < pointCount; index += 1) {
4799
+ const angle = startAngle + (range * index) / segmentCount;
4800
+ const localX = Math.cos(angle) * ellipse.radiusX;
4801
+ const localY = Math.sin(angle) * ellipse.radiusY;
4802
+ points.push({
4803
+ x: ellipse.center.x + localX * cosRotation - localY * sinRotation,
4804
+ y: ellipse.center.y + localX * sinRotation + localY * cosRotation,
4805
+ });
4806
+ }
4807
+ return { closed, points };
4808
+ }
4809
+ /**
4810
+ * Chooses a bounded segment count from the ellipse's on-screen radius. The
4811
+ * result targets sub-pixel chord error without making tiny markers expensive.
4812
+ */
4813
+ function resolveEllipseSegmentCount(ellipse, viewportScale = 1, maxErrorPixels = 0.5) {
4814
+ validateEllipseGeometry(ellipse);
4815
+ assertPositiveFinite(viewportScale, "viewportScale");
4816
+ assertPositiveFinite(maxErrorPixels, "maxErrorPixels");
4817
+ const screenRadius = Math.max(ellipse.radiusX, ellipse.radiusY) * viewportScale;
4818
+ assertPositiveFinite(screenRadius, "screenRadius");
4819
+ const clampedError = Math.min(maxErrorPixels, screenRadius);
4820
+ const maxStep = clampedError === screenRadius
4821
+ ? Math.PI / 2
4822
+ : 2 * Math.acos(1 - clampedError / screenRadius);
4823
+ const startAngle = ellipse.startAngle ?? 0;
4824
+ const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
4825
+ const arcLength = Math.abs(endAngle - startAngle);
4826
+ const segmentCount = Math.ceil(arcLength / maxStep);
4827
+ return Math.min(MAX_ELLIPSE_SEGMENT_COUNT, Math.max(MIN_ELLIPSE_SEGMENT_COUNT, segmentCount));
4828
+ }
4829
+ /**
4830
+ * Resolves a marker instruction into media-space geometry. Screen-space sizes
4831
+ * are divided by the viewport scale, matching stroke-width semantics.
4832
+ */
4833
+ function resolveMarkerGeometry(marker, viewportScale = 1) {
4834
+ assertFinitePoint(marker.center, "center");
4835
+ assertPositiveFinite(marker.size, "size");
4836
+ assertPositiveFinite(viewportScale, "viewportScale");
4837
+ if (marker.rotation !== undefined) {
4838
+ assertFinite(marker.rotation, "rotation");
4839
+ }
4840
+ const size = marker.sizeSpace === MarkerSizeSpace.Screen
4841
+ ? marker.size / viewportScale
4842
+ : marker.size;
4843
+ const radius = size / 2;
4844
+ const rotation = marker.rotation ?? 0;
4845
+ if (marker.shape === MarkerShape.Circle) {
4846
+ return { center: marker.center, kind: "circle", radius };
4847
+ }
4848
+ if (marker.shape === MarkerShape.Cross) {
4849
+ return {
4850
+ closed: false,
4851
+ kind: "subpaths",
4852
+ subpaths: [
4853
+ [
4854
+ rotatePoint(marker.center, -radius, -radius, rotation),
4855
+ rotatePoint(marker.center, radius, radius, rotation),
4856
+ ],
4857
+ [
4858
+ rotatePoint(marker.center, radius, -radius, rotation),
4859
+ rotatePoint(marker.center, -radius, radius, rotation),
4860
+ ],
4861
+ ],
4862
+ };
4863
+ }
4864
+ if (marker.shape === MarkerShape.Square) {
4865
+ return {
4866
+ closed: true,
4867
+ kind: "subpaths",
4868
+ subpaths: [
4869
+ [
4870
+ rotatePoint(marker.center, -radius, -radius, rotation),
4871
+ rotatePoint(marker.center, radius, -radius, rotation),
4872
+ rotatePoint(marker.center, radius, radius, rotation),
4873
+ rotatePoint(marker.center, -radius, radius, rotation),
4874
+ ],
4875
+ ],
4876
+ };
4877
+ }
4878
+ // Triangle: equilateral, inscribed in the marker circle, pointing toward
4879
+ // positive y at rotation 0.
4880
+ const baseOffsetY = -radius / 2;
4881
+ const baseOffsetX = radius * (Math.sqrt(3) / 2);
4882
+ return {
4883
+ closed: true,
4884
+ kind: "subpaths",
4885
+ subpaths: [
4886
+ [
4887
+ rotatePoint(marker.center, 0, radius, rotation),
4888
+ rotatePoint(marker.center, baseOffsetX, baseOffsetY, rotation),
4889
+ rotatePoint(marker.center, -baseOffsetX, baseOffsetY, rotation),
4890
+ ],
4891
+ ],
4892
+ };
4893
+ }
4894
+ function validateEllipseGeometry(ellipse) {
4895
+ assertFinitePoint(ellipse.center, "center");
4896
+ assertPositiveFinite(ellipse.radiusX, "radiusX");
4897
+ assertPositiveFinite(ellipse.radiusY, "radiusY");
4898
+ if (ellipse.rotation !== undefined) {
4899
+ assertFinite(ellipse.rotation, "rotation");
4900
+ }
4901
+ const hasStart = ellipse.startAngle !== undefined;
4902
+ const hasEnd = ellipse.endAngle !== undefined;
4903
+ if (hasStart !== hasEnd) {
4904
+ throw new RangeError("startAngle and endAngle must be provided together");
4905
+ }
4906
+ if (hasStart && hasEnd) {
4907
+ assertFinite(ellipse.startAngle, "startAngle");
4908
+ assertFinite(ellipse.endAngle, "endAngle");
4909
+ const range = Math.abs(ellipse.endAngle - ellipse.startAngle);
4910
+ if (range <= GEOMETRY_EPSILON || range >= FULL_TURN - GEOMETRY_EPSILON) {
4911
+ throw new RangeError("ellipse arc range must be greater than 0 and less than one full turn");
4912
+ }
4913
+ }
4914
+ }
4915
+ function assertFinitePoint(point, name) {
4916
+ assertFinite(point.x, `${name}.x`);
4917
+ assertFinite(point.y, `${name}.y`);
4918
+ }
4919
+ function assertFinite(value, name) {
4920
+ if (!Number.isFinite(value)) {
4921
+ throw new RangeError(`${name} must be finite`);
4922
+ }
4923
+ }
4924
+ function assertPositiveFinite(value, name) {
4925
+ assertFinite(value, name);
4926
+ if (value <= 0) {
4927
+ throw new RangeError(`${name} must be greater than 0`);
4928
+ }
4929
+ }
4930
+ function assertIntegerAtLeast(value, minimum, name) {
4931
+ if (!Number.isInteger(value) || value < minimum) {
4932
+ throw new RangeError(`${name} must be an integer of at least ${minimum}`);
4933
+ }
4934
+ }
4935
+ function rotatePoint(center, offsetX, offsetY, rotation) {
4936
+ if (rotation === 0) {
4937
+ return { x: center.x + offsetX, y: center.y + offsetY };
4938
+ }
4939
+ const cosRotation = Math.cos(rotation);
4940
+ const sinRotation = Math.sin(rotation);
4941
+ return {
4942
+ x: center.x + offsetX * cosRotation - offsetY * sinRotation,
4943
+ y: center.y + offsetX * sinRotation + offsetY * cosRotation,
4944
+ };
4945
+ }
4946
+
4313
4947
  function includeDefined(values) {
4314
4948
  return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
4315
4949
  }
@@ -4426,5 +5060,5 @@ var MediaSessionActivityStatus;
4426
5060
  MediaSessionActivityStatus["Waiting"] = "waiting";
4427
5061
  })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
4428
5062
 
4429
- export { AnnotationFrameMutationKind, AnnotationGeometryKind, AnnotationGestureStateKind, AnnotationHandleKind, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionMaskPayloadFormat, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MAX_ID_MASK_PALETTE_ENTRIES, MAX_ID_MASK_STROKE_WIDTH, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, SUPERVISION_ROBOFLOW_COLOR, annotationRendererKinds, annotationRenderers, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, distanceToSegment, encodeBinaryMask, encodeBinaryMaskWithBounds, encodeCompressedRleCounts, encodeDetectionMaskPayload, extractMaskContour, extractMaskRectRuns, filterDetectionFramesForRange, findClosestAnnotationSegment, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveMaskStyleOpacity, resolveStyleValue, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
5063
+ export { AnnotationFrameMutationKind, AnnotationGeometryKind, AnnotationGestureStateKind, AnnotationHandleKind, BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, StrokeAlignment as BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionMaskPayloadFormat, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MAX_ID_MASK_PALETTE_ENTRIES, MAX_ID_MASK_STROKE_WIDTH, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, ShapeInstructionKind, StrokeAlignment, annotationRendererKinds, annotationRenderers, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, distanceToSegment, encodeBinaryMask, encodeBinaryMaskWithBounds, encodeCompressedRleCounts, encodeDetectionMaskPayload, extractMaskContour, extractMaskRectRuns, filterDetectionFramesForRange, findClosestAnnotationSegment, followDetectionPickAcrossFrames, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, haveSameDetectionPickIdentities, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveEllipseSegmentCount, resolveMarkerGeometry, resolveMaskStyleOpacity, resolveStyleValue, sampleEllipseArc, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
4430
5064
  //# sourceMappingURL=index.js.map