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.
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1158 -97
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +43 -9
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/render-preparation/mask-frame-artifact.d.ts +6 -0
- package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-compositor.d.ts +1 -0
- package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +1 -0
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
- package/dist/renderers/annotation-shape-styles.d.ts +17 -0
- package/dist/renderers/annotation-shape-styles.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +17 -2
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-annotation-overlay-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-focus-layer.d.ts +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts +2 -0
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts +2 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-halo.d.ts +94 -0
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -0
- package/dist/renderers/pixi-mask-layer.d.ts +10 -1
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-path.d.ts +13 -7
- package/dist/renderers/pixi-path.d.ts.map +1 -1
- package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-region-layer.d.ts +42 -0
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
- package/dist/renderers/pixi-vector-layer.d.ts +3 -1
- package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
- package/dist/sessions/media-session.d.ts.map +1 -1
- package/dist/types/media-renderer.d.ts +1 -1
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +17 -3
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +708 -74
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts +18 -0
- package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts +13 -6
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/box-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts +19 -0
- package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/keypoint-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/polygon-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts +8 -0
- package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts +12 -0
- package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +124 -4
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts +31 -0
- package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/box-style.d.ts +4 -17
- package/node_modules/supervision-js-core/dist/types/box-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts +30 -0
- package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts +12 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/marker-style.d.ts +46 -0
- package/node_modules/supervision-js-core/dist/types/marker-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts +29 -0
- package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +18 -2
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/paint-style.d.ts +36 -0
- package/node_modules/supervision-js-core/dist/types/paint-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/shape-style.d.ts +112 -0
- package/node_modules/supervision-js-core/dist/types/shape-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts +50 -0
- package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts.map +1 -0
- 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,
|
|
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
|
-
|
|
2796
|
-
appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
|
|
2797
|
-
|
|
2798
|
-
|
|
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 ===
|
|
2842
|
+
alignment: stroke.alignment === StrokeAlignment.Inside
|
|
2820
2843
|
? 1
|
|
2821
|
-
: stroke.alignment ===
|
|
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
|
-
|
|
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
|
|
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$
|
|
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$
|
|
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 (
|
|
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
|
-
|
|
3612
|
+
const visibleDetections = activeFrame?.detections.flatMap((detection, detectionIndex) => canPickDetection(detection) ? [{ detection, detectionIndex }] : []);
|
|
3613
|
+
if (!activeFrame || !visibleDetections?.length)
|
|
3567
3614
|
return null;
|
|
3568
|
-
const
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
const detection =
|
|
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
|
|
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
|
|
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 ||
|
|
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
|
-
|
|
3778
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
3786
|
-
|
|
3845
|
+
followSelectedPicks(frame);
|
|
3846
|
+
}
|
|
3847
|
+
function followSelectedPicks(frame) {
|
|
3848
|
+
if (selectedPicks.length === 0) {
|
|
3849
|
+
return;
|
|
3787
3850
|
}
|
|
3788
|
-
|
|
3789
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
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
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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 ||
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
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(
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
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
|
-
|
|
9398
|
-
|
|
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) {
|