supervision 0.1.2 → 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 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1232 -107
- 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 +19 -3
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +944 -157
- 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 +9 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts +47 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -0
- 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 +38 -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 +18 -2
- 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 +197 -0
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -0
- 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 +26 -1
- 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, 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, 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;
|
|
@@ -2231,7 +2231,7 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2231
2231
|
throw new RangeError("playbackRate must be a finite number greater than zero.");
|
|
2232
2232
|
}
|
|
2233
2233
|
const defaultPresentation = createDefaultAnnotationPresentation();
|
|
2234
|
-
const resolvePresentation = (presentation) => ({
|
|
2234
|
+
const resolvePresentation = (presentation) => resolveAnnotationRendererPresentation({
|
|
2235
2235
|
...defaultPresentation,
|
|
2236
2236
|
...presentation,
|
|
2237
2237
|
});
|
|
@@ -2239,13 +2239,18 @@ 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,
|
|
2253
|
+
renderers: options.renderers,
|
|
2249
2254
|
visibility: options.visibility,
|
|
2250
2255
|
});
|
|
2251
2256
|
let detectionTimeline;
|
|
@@ -2537,6 +2542,8 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2537
2542
|
annotationOverlayStyle: currentPresentation.annotationOverlayStyle,
|
|
2538
2543
|
backgroundColor: currentPresentation.backgroundColor,
|
|
2539
2544
|
boxStyle: currentPresentation.boxStyle,
|
|
2545
|
+
boxCornerStyle: currentPresentation.boxCornerStyle,
|
|
2546
|
+
ellipseStyle: currentPresentation.ellipseStyle,
|
|
2540
2547
|
canInteract: () => canInteract(options, runtimeState.isPlaybackActive()),
|
|
2541
2548
|
container: options.container,
|
|
2542
2549
|
detectionTimeline,
|
|
@@ -2549,10 +2556,18 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2549
2556
|
keypointStyle: currentPresentation.keypointStyle,
|
|
2550
2557
|
labelStyle: currentPresentation.labelStyle,
|
|
2551
2558
|
maskBrush: options.createMaskBrush?.(mediaDimensions) ?? options.maskBrush,
|
|
2559
|
+
maskHaloStyle: currentPresentation.maskHaloStyle,
|
|
2552
2560
|
maskStyle: currentPresentation.maskStyle,
|
|
2561
|
+
markerStyle: currentPresentation.markerStyle,
|
|
2553
2562
|
maxDevicePixelRatio: options.maxDevicePixelRatio,
|
|
2563
|
+
onPresentationUpdate(presentedSample) {
|
|
2564
|
+
if (!runtimeState.isDestroyed()) {
|
|
2565
|
+
runtimeState.recordPresentationUpdate(presentedSample);
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2554
2568
|
polygonStyle: currentPresentation.polygonStyle,
|
|
2555
2569
|
polylineStyle: currentPresentation.polylineStyle,
|
|
2570
|
+
regionRenderers: resolveRegionRenderers(currentPresentation),
|
|
2556
2571
|
previewOverlay: options.previewOverlay,
|
|
2557
2572
|
renderPreparation: options.renderPreparation
|
|
2558
2573
|
? {
|
|
@@ -2698,6 +2713,10 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2698
2713
|
}
|
|
2699
2714
|
}
|
|
2700
2715
|
}
|
|
2716
|
+
function resolveRegionRenderers(presentation) {
|
|
2717
|
+
return (presentation.renderers?.filter((renderer) => renderer.kind === "region") ??
|
|
2718
|
+
[]);
|
|
2719
|
+
}
|
|
2701
2720
|
async function openRendererMediaSource(options, providers) {
|
|
2702
2721
|
if (options.src !== undefined && options.source !== undefined) {
|
|
2703
2722
|
throw new Error("Provide either src or source, not both.");
|
|
@@ -2791,10 +2810,10 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
|
|
|
2791
2810
|
if (points.length < 2) {
|
|
2792
2811
|
return;
|
|
2793
2812
|
}
|
|
2794
|
-
|
|
2795
|
-
appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
|
|
2796
|
-
|
|
2797
|
-
|
|
2813
|
+
const appendedDash = stroke.dash?.length
|
|
2814
|
+
? appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
|
|
2815
|
+
: false;
|
|
2816
|
+
if (!appendedDash) {
|
|
2798
2817
|
graphics.moveTo(points[0].x, points[0].y);
|
|
2799
2818
|
for (let index = 1; index < points.length; index += 1) {
|
|
2800
2819
|
graphics.lineTo(points[index].x, points[index].y);
|
|
@@ -2808,16 +2827,21 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
|
|
|
2808
2827
|
function resolvePixiStroke(stroke, viewportScale) {
|
|
2809
2828
|
const pixiStroke = {
|
|
2810
2829
|
alpha: stroke.alpha,
|
|
2830
|
+
...(stroke.cap === undefined ? {} : { cap: stroke.cap }),
|
|
2811
2831
|
color: stroke.color,
|
|
2832
|
+
...(stroke.join === undefined ? {} : { join: stroke.join }),
|
|
2833
|
+
...(stroke.miterLimit === undefined
|
|
2834
|
+
? {}
|
|
2835
|
+
: { miterLimit: stroke.miterLimit }),
|
|
2812
2836
|
width: resolveScreenLength(stroke.width, viewportScale),
|
|
2813
2837
|
};
|
|
2814
2838
|
if (stroke.alignment === undefined)
|
|
2815
2839
|
return pixiStroke;
|
|
2816
2840
|
return {
|
|
2817
2841
|
...pixiStroke,
|
|
2818
|
-
alignment: stroke.alignment ===
|
|
2842
|
+
alignment: stroke.alignment === StrokeAlignment.Inside
|
|
2819
2843
|
? 1
|
|
2820
|
-
: stroke.alignment ===
|
|
2844
|
+
: stroke.alignment === StrokeAlignment.Outside
|
|
2821
2845
|
? 0
|
|
2822
2846
|
: 0.5,
|
|
2823
2847
|
};
|
|
@@ -2827,7 +2851,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2827
2851
|
.filter((value) => Number.isFinite(value) && value > 0)
|
|
2828
2852
|
.map((value) => resolveScreenLength(value, viewportScale));
|
|
2829
2853
|
if (pattern.length === 0) {
|
|
2830
|
-
return;
|
|
2854
|
+
return false;
|
|
2831
2855
|
}
|
|
2832
2856
|
if (pattern.length % 2 === 1) {
|
|
2833
2857
|
pattern.push(...pattern);
|
|
@@ -2835,6 +2859,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2835
2859
|
let patternIndex = 0;
|
|
2836
2860
|
let patternRemaining = pattern[0];
|
|
2837
2861
|
let drawing = true;
|
|
2862
|
+
let penDown = false;
|
|
2838
2863
|
const segmentCount = closed ? points.length : points.length - 1;
|
|
2839
2864
|
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
|
|
2840
2865
|
const start = points[segmentIndex];
|
|
@@ -2855,8 +2880,14 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2855
2880
|
const x2 = start.x + dx * endRatio;
|
|
2856
2881
|
const y2 = start.y + dy * endRatio;
|
|
2857
2882
|
if (drawing) {
|
|
2858
|
-
|
|
2883
|
+
if (!penDown) {
|
|
2884
|
+
graphics.moveTo(x1, y1);
|
|
2885
|
+
}
|
|
2859
2886
|
graphics.lineTo(x2, y2);
|
|
2887
|
+
penDown = true;
|
|
2888
|
+
}
|
|
2889
|
+
else {
|
|
2890
|
+
penDown = false;
|
|
2860
2891
|
}
|
|
2861
2892
|
offset += step;
|
|
2862
2893
|
patternRemaining -= step;
|
|
@@ -2864,9 +2895,13 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2864
2895
|
patternIndex = (patternIndex + 1) % pattern.length;
|
|
2865
2896
|
patternRemaining = pattern[patternIndex];
|
|
2866
2897
|
drawing = !drawing;
|
|
2898
|
+
if (!drawing) {
|
|
2899
|
+
penDown = false;
|
|
2900
|
+
}
|
|
2867
2901
|
}
|
|
2868
2902
|
}
|
|
2869
2903
|
}
|
|
2904
|
+
return true;
|
|
2870
2905
|
}
|
|
2871
2906
|
|
|
2872
2907
|
function createPixiBoxLayer(options) {
|
|
@@ -3055,6 +3090,10 @@ function createPixiFocusLayer(options) {
|
|
|
3055
3090
|
let targetAlpha = 0;
|
|
3056
3091
|
let lastTick = null;
|
|
3057
3092
|
let isDestroyed = false;
|
|
3093
|
+
// A frame can fall back to a composited RGBA texture when its colored ID-mask
|
|
3094
|
+
// palette is exhausted. Keep decoded row runs by the immutable mask payload so
|
|
3095
|
+
// that fallback focus still cuts out the actual mask, not its bounding rect.
|
|
3096
|
+
const maskCutoutRuns = new WeakMap();
|
|
3058
3097
|
return {
|
|
3059
3098
|
createDisplay({ width, height }) {
|
|
3060
3099
|
mediaWidth = width;
|
|
@@ -3099,13 +3138,19 @@ function createPixiFocusLayer(options) {
|
|
|
3099
3138
|
hide();
|
|
3100
3139
|
return;
|
|
3101
3140
|
}
|
|
3102
|
-
const
|
|
3141
|
+
const resolvedInstruction = focusStyle.resolve({
|
|
3103
3142
|
frame: context.frame,
|
|
3104
3143
|
hoveredPick: context.hoveredPick,
|
|
3105
3144
|
mediaTime: context.mediaTime,
|
|
3106
3145
|
selectedPick: context.selectedPick,
|
|
3107
3146
|
viewportScale: context.viewportScale,
|
|
3108
3147
|
});
|
|
3148
|
+
const instruction = resolvedInstruction
|
|
3149
|
+
? {
|
|
3150
|
+
...resolvedInstruction,
|
|
3151
|
+
targets: resolvedInstruction.targets.filter(({ detection }) => options.isDetectionVisible?.(detection) ?? true),
|
|
3152
|
+
}
|
|
3153
|
+
: undefined;
|
|
3109
3154
|
if (!instruction || instruction.targets.length === 0) {
|
|
3110
3155
|
transitionToHidden();
|
|
3111
3156
|
return;
|
|
@@ -3163,8 +3208,10 @@ function createPixiFocusLayer(options) {
|
|
|
3163
3208
|
if (!focusGraphics) {
|
|
3164
3209
|
return;
|
|
3165
3210
|
}
|
|
3166
|
-
const
|
|
3167
|
-
|
|
3211
|
+
const targetsWithGeometry = instruction.targets.filter((target) => target.detection.mask ||
|
|
3212
|
+
target.detection.rect ||
|
|
3213
|
+
target.detection.polygon);
|
|
3214
|
+
if (targetsWithGeometry.length === 0) {
|
|
3168
3215
|
hideVectorFocus();
|
|
3169
3216
|
return;
|
|
3170
3217
|
}
|
|
@@ -3175,24 +3222,30 @@ function createPixiFocusLayer(options) {
|
|
|
3175
3222
|
if (focusMaskGraphics) {
|
|
3176
3223
|
focusMaskGraphics.visible = true;
|
|
3177
3224
|
focusMaskGraphics.clear();
|
|
3178
|
-
for (const target of
|
|
3179
|
-
drawCutoutShape(focusMaskGraphics, target, instruction)
|
|
3180
|
-
|
|
3225
|
+
for (const target of targetsWithGeometry) {
|
|
3226
|
+
if (drawCutoutShape(focusMaskGraphics, target, instruction) === "drawn") {
|
|
3227
|
+
focusMaskGraphics.fill({ alpha: 1, color: 0xffffff });
|
|
3228
|
+
}
|
|
3181
3229
|
}
|
|
3182
3230
|
return;
|
|
3183
3231
|
}
|
|
3184
|
-
for (const target of
|
|
3185
|
-
drawCutoutShape(focusGraphics, target, instruction)
|
|
3186
|
-
|
|
3232
|
+
for (const target of targetsWithGeometry) {
|
|
3233
|
+
if (drawCutoutShape(focusGraphics, target, instruction) === "drawn") {
|
|
3234
|
+
focusGraphics.cut();
|
|
3235
|
+
}
|
|
3187
3236
|
}
|
|
3188
3237
|
}
|
|
3189
3238
|
function drawCutoutShape(graphics, target, instruction) {
|
|
3239
|
+
const maskCutout = drawMaskCutout(graphics, target);
|
|
3240
|
+
if (maskCutout) {
|
|
3241
|
+
return maskCutout;
|
|
3242
|
+
}
|
|
3190
3243
|
if (target.detection.polygon?.points.length && graphics.poly) {
|
|
3191
3244
|
graphics.poly(target.detection.polygon.points.flatMap(({ x, y }) => [x, y]), true);
|
|
3192
|
-
return;
|
|
3245
|
+
return "drawn";
|
|
3193
3246
|
}
|
|
3194
3247
|
if (!target.detection.rect) {
|
|
3195
|
-
return;
|
|
3248
|
+
return "empty";
|
|
3196
3249
|
}
|
|
3197
3250
|
const { rect } = target.detection;
|
|
3198
3251
|
const fallback = instruction.fallback;
|
|
@@ -3203,6 +3256,44 @@ function createPixiFocusLayer(options) {
|
|
|
3203
3256
|
else {
|
|
3204
3257
|
graphics.rect(left, top, rect.width, rect.height);
|
|
3205
3258
|
}
|
|
3259
|
+
return "drawn";
|
|
3260
|
+
}
|
|
3261
|
+
function drawMaskCutout(graphics, target) {
|
|
3262
|
+
const mask = target.detection.mask;
|
|
3263
|
+
if (!mask) {
|
|
3264
|
+
return undefined;
|
|
3265
|
+
}
|
|
3266
|
+
let runs;
|
|
3267
|
+
if (!maskCutoutRuns.has(mask)) {
|
|
3268
|
+
try {
|
|
3269
|
+
const decoded = decodeCompressedRleMask(mask);
|
|
3270
|
+
runs = {
|
|
3271
|
+
height: decoded.height,
|
|
3272
|
+
runs: extractMaskRectRuns(decoded.data, decoded.width, decoded.height) ??
|
|
3273
|
+
[],
|
|
3274
|
+
width: decoded.width,
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
catch {
|
|
3278
|
+
// Preserve the documented rectangle fallback for malformed masks. A
|
|
3279
|
+
// valid mask must never lose its shape merely because ID-mask rendering
|
|
3280
|
+
// is unavailable, but an unreadable payload still has only bounds.
|
|
3281
|
+
runs = null;
|
|
3282
|
+
}
|
|
3283
|
+
maskCutoutRuns.set(mask, runs);
|
|
3284
|
+
}
|
|
3285
|
+
else {
|
|
3286
|
+
runs = maskCutoutRuns.get(mask);
|
|
3287
|
+
}
|
|
3288
|
+
if (runs === null) {
|
|
3289
|
+
return undefined;
|
|
3290
|
+
}
|
|
3291
|
+
const horizontalScale = mediaWidth / runs.width;
|
|
3292
|
+
const verticalScale = mediaHeight / runs.height;
|
|
3293
|
+
for (const run of runs.runs) {
|
|
3294
|
+
graphics.rect(run.x * horizontalScale, run.y * verticalScale, run.width * horizontalScale, run.height * verticalScale);
|
|
3295
|
+
}
|
|
3296
|
+
return runs.runs.length === 0 ? "empty" : "drawn";
|
|
3206
3297
|
}
|
|
3207
3298
|
function hide() {
|
|
3208
3299
|
hideVectorFocus();
|
|
@@ -3271,7 +3362,7 @@ function createFocusIdMaskRenderer(options) {
|
|
|
3271
3362
|
autoGenerateMipmaps: false,
|
|
3272
3363
|
dynamic: false,
|
|
3273
3364
|
height: 1,
|
|
3274
|
-
resource: createPlaceholderCanvas$
|
|
3365
|
+
resource: createPlaceholderCanvas$3(),
|
|
3275
3366
|
scaleMode: "nearest",
|
|
3276
3367
|
width: 1,
|
|
3277
3368
|
});
|
|
@@ -3363,7 +3454,7 @@ function createPremultipliedColor(fill) {
|
|
|
3363
3454
|
alpha,
|
|
3364
3455
|
]);
|
|
3365
3456
|
}
|
|
3366
|
-
function createPlaceholderCanvas$
|
|
3457
|
+
function createPlaceholderCanvas$3() {
|
|
3367
3458
|
const canvas = document.createElement("canvas");
|
|
3368
3459
|
canvas.height = 1;
|
|
3369
3460
|
canvas.width = 1;
|
|
@@ -3485,15 +3576,21 @@ function createPixiInteractionLayer(options) {
|
|
|
3485
3576
|
drawFrame(mediaTime) {
|
|
3486
3577
|
currentMediaTime = mediaTime;
|
|
3487
3578
|
activeFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
3488
|
-
if (
|
|
3489
|
-
pointerPoint = null;
|
|
3490
|
-
marqueeRect = null;
|
|
3491
|
-
setHoveredPick(null);
|
|
3492
|
-
setSelectedPick(null);
|
|
3493
|
-
}
|
|
3494
|
-
else {
|
|
3579
|
+
if (canHandleInteraction()) {
|
|
3495
3580
|
clearStalePicks(activeFrame);
|
|
3581
|
+
return;
|
|
3496
3582
|
}
|
|
3583
|
+
pointerPoint = null;
|
|
3584
|
+
marqueeRect = null;
|
|
3585
|
+
setHoveredPick(null);
|
|
3586
|
+
if (isDestroyed || mode === MediaInteractionMode.Disabled) {
|
|
3587
|
+
setSelectedPick(null);
|
|
3588
|
+
return;
|
|
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);
|
|
3497
3594
|
},
|
|
3498
3595
|
setSelectedDetection(selection) {
|
|
3499
3596
|
if (selection === null) {
|
|
@@ -3512,14 +3609,15 @@ function createPixiInteractionLayer(options) {
|
|
|
3512
3609
|
return createState();
|
|
3513
3610
|
},
|
|
3514
3611
|
cycleSelection(direction = 1) {
|
|
3515
|
-
|
|
3612
|
+
const visibleDetections = activeFrame?.detections.flatMap((detection, detectionIndex) => canPickDetection(detection) ? [{ detection, detectionIndex }] : []);
|
|
3613
|
+
if (!activeFrame || !visibleDetections?.length)
|
|
3516
3614
|
return null;
|
|
3517
|
-
const
|
|
3518
|
-
const
|
|
3519
|
-
|
|
3520
|
-
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];
|
|
3521
3619
|
const next = createPickFromSelection({
|
|
3522
|
-
detectionIndex
|
|
3620
|
+
detectionIndex,
|
|
3523
3621
|
point: getDetectionCenter(detection),
|
|
3524
3622
|
});
|
|
3525
3623
|
setSelectedPick(next);
|
|
@@ -3648,6 +3746,8 @@ function createPixiInteractionLayer(options) {
|
|
|
3648
3746
|
return;
|
|
3649
3747
|
if (didDragMarquee && marqueeRect && activeFrame) {
|
|
3650
3748
|
selectedPicks = activeFrame.detections.flatMap((detection, detectionIndex) => {
|
|
3749
|
+
if (!canPickDetection(detection))
|
|
3750
|
+
return [];
|
|
3651
3751
|
const rect = getDetectionRect(detection);
|
|
3652
3752
|
if (!rect || !intersects(rect, marqueeRect))
|
|
3653
3753
|
return [];
|
|
@@ -3684,10 +3784,13 @@ function createPixiInteractionLayer(options) {
|
|
|
3684
3784
|
const pickPoint = { x: point.x, y: point.y };
|
|
3685
3785
|
const labelPick = options.pickLabelDetectionAtPoint?.(pickPoint, currentMediaTime);
|
|
3686
3786
|
const activeLabelPick = rebaseDetectionPickToFrame(labelPick ?? null, activeFrame);
|
|
3687
|
-
if (activeLabelPick)
|
|
3787
|
+
if (activeLabelPick && canPickDetection(activeLabelPick.detection)) {
|
|
3688
3788
|
return activeLabelPick;
|
|
3789
|
+
}
|
|
3689
3790
|
const geometryPick = pickDetectionAtPoint(activeFrame, pickPoint, {
|
|
3690
3791
|
...options.interaction,
|
|
3792
|
+
filter: (detection, detectionIndex) => canPickDetection(detection) &&
|
|
3793
|
+
(options.interaction.filter?.(detection, detectionIndex) ?? true),
|
|
3691
3794
|
includeMasks: options.pickMaskDetectionAtPoint === undefined,
|
|
3692
3795
|
maskMediaDimensions: options.getMediaDimensions?.(),
|
|
3693
3796
|
padding: pickPadding,
|
|
@@ -3697,7 +3800,9 @@ function createPixiInteractionLayer(options) {
|
|
|
3697
3800
|
}
|
|
3698
3801
|
const maskPick = options.pickMaskDetectionAtPoint?.(pickPoint, currentMediaTime);
|
|
3699
3802
|
const activeMaskPick = rebaseDetectionPickToFrame(maskPick ?? null, activeFrame);
|
|
3700
|
-
return activeMaskPick
|
|
3803
|
+
return activeMaskPick && canPickDetection(activeMaskPick.detection)
|
|
3804
|
+
? activeMaskPick
|
|
3805
|
+
: geometryPick;
|
|
3701
3806
|
}
|
|
3702
3807
|
function createPickFromSelection(selection) {
|
|
3703
3808
|
const frame = selection.mediaTime === undefined
|
|
@@ -3709,7 +3814,10 @@ function createPixiInteractionLayer(options) {
|
|
|
3709
3814
|
const detection = detectionIndex === undefined || detectionIndex < 0
|
|
3710
3815
|
? undefined
|
|
3711
3816
|
: frame?.detections[detectionIndex];
|
|
3712
|
-
if (!frame ||
|
|
3817
|
+
if (!frame ||
|
|
3818
|
+
detectionIndex === undefined ||
|
|
3819
|
+
!detection ||
|
|
3820
|
+
!canPickDetection(detection)) {
|
|
3713
3821
|
return null;
|
|
3714
3822
|
}
|
|
3715
3823
|
return {
|
|
@@ -3723,19 +3831,44 @@ function createPixiInteractionLayer(options) {
|
|
|
3723
3831
|
};
|
|
3724
3832
|
}
|
|
3725
3833
|
function clearStalePicks(frame) {
|
|
3726
|
-
|
|
3727
|
-
const
|
|
3834
|
+
// Hover stays bound to the pointer, so it keeps a per-frame lifetime.
|
|
3835
|
+
const nextHoveredPick = filterPick(rebaseDetectionPickToFrame(hoveredPick, frame));
|
|
3728
3836
|
if (hoveredPick && !nextHoveredPick) {
|
|
3729
3837
|
setHoveredPick(null);
|
|
3730
3838
|
}
|
|
3731
3839
|
else if (nextHoveredPick && nextHoveredPick !== hoveredPick) {
|
|
3732
|
-
|
|
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);
|
|
3733
3844
|
}
|
|
3734
|
-
|
|
3735
|
-
|
|
3845
|
+
followSelectedPicks(frame);
|
|
3846
|
+
}
|
|
3847
|
+
function followSelectedPicks(frame) {
|
|
3848
|
+
if (selectedPicks.length === 0) {
|
|
3849
|
+
return;
|
|
3850
|
+
}
|
|
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;
|
|
3736
3862
|
}
|
|
3737
|
-
|
|
3738
|
-
|
|
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);
|
|
3739
3872
|
}
|
|
3740
3873
|
}
|
|
3741
3874
|
function setHoveredPick(nextPick) {
|
|
@@ -3844,6 +3977,12 @@ function createPixiInteractionLayer(options) {
|
|
|
3844
3977
|
}
|
|
3845
3978
|
return options.canInteract();
|
|
3846
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
|
+
}
|
|
3847
3986
|
}
|
|
3848
3987
|
function getDetectionCenter(detection) {
|
|
3849
3988
|
const rect = getDetectionRect(detection);
|
|
@@ -3889,8 +4028,11 @@ function compositeMaskFrame(instructions) {
|
|
|
3889
4028
|
}
|
|
3890
4029
|
return { data, height, width };
|
|
3891
4030
|
}
|
|
4031
|
+
function createMaskIdFrame(instructions) {
|
|
4032
|
+
return createIdMaskFrame(materializeMaskInstructions(instructions));
|
|
4033
|
+
}
|
|
3892
4034
|
async function createPngIdMaskFrame(instructions) {
|
|
3893
|
-
const frame =
|
|
4035
|
+
const frame = createMaskIdFrame(instructions);
|
|
3894
4036
|
if (!frame) {
|
|
3895
4037
|
return undefined;
|
|
3896
4038
|
}
|
|
@@ -4429,6 +4571,9 @@ function createPixiVectorLayer(options) {
|
|
|
4429
4571
|
let keypointStyle = options.keypointStyle === undefined
|
|
4430
4572
|
? new BaseKeypointStyle()
|
|
4431
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;
|
|
4432
4577
|
let styleVersion = 0;
|
|
4433
4578
|
let drawnStyleVersion = -1;
|
|
4434
4579
|
// A versioned source can replace a frame without changing its timeline key.
|
|
@@ -4465,7 +4610,8 @@ function createPixiVectorLayer(options) {
|
|
|
4465
4610
|
const resolvedDetections = frame.detections.flatMap((detection, detectionIndex) => {
|
|
4466
4611
|
if (!detection.polygon &&
|
|
4467
4612
|
!detection.polyline &&
|
|
4468
|
-
!detection.keypoints
|
|
4613
|
+
!detection.keypoints &&
|
|
4614
|
+
!shapeStyle) {
|
|
4469
4615
|
return [];
|
|
4470
4616
|
}
|
|
4471
4617
|
const resolved = resolveDetection(detection, detectionIndex, frame, mediaTime, viewportScale);
|
|
@@ -4500,6 +4646,8 @@ function createPixiVectorLayer(options) {
|
|
|
4500
4646
|
polylineStyle = styles.polylineStyle;
|
|
4501
4647
|
if (styles.keypointStyle !== undefined)
|
|
4502
4648
|
keypointStyle = styles.keypointStyle;
|
|
4649
|
+
if (styles.shapeStyle !== undefined)
|
|
4650
|
+
shapeStyle = styles.shapeStyle;
|
|
4503
4651
|
styleVersion += 1;
|
|
4504
4652
|
},
|
|
4505
4653
|
destroy() {
|
|
@@ -4544,13 +4692,16 @@ function createPixiVectorLayer(options) {
|
|
|
4544
4692
|
const polygon = polygonStyle?.resolve(detection, context);
|
|
4545
4693
|
const polyline = polylineStyle?.resolve(detection, context);
|
|
4546
4694
|
const keypoints = keypointStyle?.resolve(detection, context);
|
|
4547
|
-
|
|
4695
|
+
const shapes = shapeStyle?.resolve(detection, context);
|
|
4696
|
+
if (!polygon && !polyline && !keypoints && !shapes?.length) {
|
|
4548
4697
|
return undefined;
|
|
4698
|
+
}
|
|
4549
4699
|
return {
|
|
4550
4700
|
key: detectionKey(detection, detectionIndex),
|
|
4551
4701
|
keypoints,
|
|
4552
4702
|
polygon,
|
|
4553
4703
|
polyline,
|
|
4704
|
+
shapes,
|
|
4554
4705
|
zIndex: detection.zIndex ?? detectionIndex,
|
|
4555
4706
|
};
|
|
4556
4707
|
}
|
|
@@ -4559,7 +4710,10 @@ function createPixiVectorLayer(options) {
|
|
|
4559
4710
|
if (!entry.cleared)
|
|
4560
4711
|
graphics.clear();
|
|
4561
4712
|
entry.cleared = false;
|
|
4562
|
-
const { keypoints, polygon, polyline } = detection;
|
|
4713
|
+
const { keypoints, polygon, polyline, shapes } = detection;
|
|
4714
|
+
for (const shape of shapes ?? []) {
|
|
4715
|
+
drawShapeInstruction(graphics, shape, viewportScale);
|
|
4716
|
+
}
|
|
4563
4717
|
if (polygon) {
|
|
4564
4718
|
graphics.poly(polygon.points.flatMap(({ x, y }) => [x, y]), true);
|
|
4565
4719
|
if (polygon.fill)
|
|
@@ -4577,31 +4731,102 @@ function createPixiVectorLayer(options) {
|
|
|
4577
4731
|
drawPixiPath(graphics, [edge.from, edge.to], false, edge.stroke, viewportScale);
|
|
4578
4732
|
}
|
|
4579
4733
|
for (const marker of keypoints.markers) {
|
|
4580
|
-
const radius = resolveScreenLength(marker.radius, viewportScale);
|
|
4581
4734
|
if (marker.shape === KeypointMarkerShape.Cross) {
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
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);
|
|
4590
4743
|
}
|
|
4591
4744
|
else {
|
|
4592
|
-
graphics
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
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);
|
|
4601
4754
|
}
|
|
4602
4755
|
}
|
|
4603
4756
|
}
|
|
4604
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));
|
|
4803
|
+
}
|
|
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
|
+
}
|
|
4815
|
+
}
|
|
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
|
+
}
|
|
4605
4830
|
function detectionKey(detection, detectionIndex) {
|
|
4606
4831
|
return detection.id === undefined
|
|
4607
4832
|
? `index:${detectionIndex}`
|
|
@@ -4731,7 +4956,8 @@ function createPixiInteractionPresentationLayer(options) {
|
|
|
4731
4956
|
}
|
|
4732
4957
|
function createActivePick(pick, frame, mediaTime, state) {
|
|
4733
4958
|
const activePick = rebaseDetectionPickToFrame(pick, frame);
|
|
4734
|
-
if (!activePick
|
|
4959
|
+
if (!activePick ||
|
|
4960
|
+
options.isDetectionVisible?.(activePick.detection) === false) {
|
|
4735
4961
|
return null;
|
|
4736
4962
|
}
|
|
4737
4963
|
const context = {
|
|
@@ -4988,7 +5214,7 @@ function createInteractionMaskRenderer(options) {
|
|
|
4988
5214
|
autoGenerateMipmaps: false,
|
|
4989
5215
|
dynamic: false,
|
|
4990
5216
|
height: 1,
|
|
4991
|
-
resource: createPlaceholderCanvas$
|
|
5217
|
+
resource: createPlaceholderCanvas$2(),
|
|
4992
5218
|
scaleMode: "nearest",
|
|
4993
5219
|
width: 1,
|
|
4994
5220
|
});
|
|
@@ -5099,7 +5325,7 @@ function writePaletteEntry(palette, maskId, color, alpha) {
|
|
|
5099
5325
|
palette[offset + 2] = (color & 0xff) / 255;
|
|
5100
5326
|
palette[offset + 3] = clampedAlpha;
|
|
5101
5327
|
}
|
|
5102
|
-
function createPlaceholderCanvas$
|
|
5328
|
+
function createPlaceholderCanvas$2() {
|
|
5103
5329
|
const canvas = document.createElement("canvas");
|
|
5104
5330
|
canvas.height = 1;
|
|
5105
5331
|
canvas.width = 1;
|
|
@@ -5247,7 +5473,7 @@ void main(void) {
|
|
|
5247
5473
|
}
|
|
5248
5474
|
`;
|
|
5249
5475
|
|
|
5250
|
-
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})();";
|
|
5251
5477
|
|
|
5252
5478
|
const DEFAULT_WORKER_NAME = "supervision-js-render-preparation";
|
|
5253
5479
|
let defaultWorkerUrl;
|
|
@@ -5522,6 +5748,7 @@ function createMainThreadMaskFramePreparer(workerStatus, message = null) {
|
|
|
5522
5748
|
canvas.height = 0;
|
|
5523
5749
|
},
|
|
5524
5750
|
height: compositedFrame.height,
|
|
5751
|
+
idMaskData: createMaskIdFrame(job.instructions)?.data,
|
|
5525
5752
|
key: job.key,
|
|
5526
5753
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5527
5754
|
source: canvas,
|
|
@@ -5696,6 +5923,7 @@ function createPreparedFrameFromWorkerResponse(message) {
|
|
|
5696
5923
|
message.imageBitmap?.close();
|
|
5697
5924
|
},
|
|
5698
5925
|
height: message.imageBitmap.height,
|
|
5926
|
+
idMaskData: message.idMaskData,
|
|
5699
5927
|
key: message.key,
|
|
5700
5928
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5701
5929
|
source: message.imageBitmap,
|
|
@@ -5719,6 +5947,7 @@ function createPreparedFrameFromWorkerResponse(message) {
|
|
|
5719
5947
|
canvas.height = 0;
|
|
5720
5948
|
},
|
|
5721
5949
|
height: message.imageData.height,
|
|
5950
|
+
idMaskData: message.idMaskData,
|
|
5722
5951
|
key: message.key,
|
|
5723
5952
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5724
5953
|
source: canvas,
|
|
@@ -6587,7 +6816,7 @@ function createPixiIdMaskShaderRenderer(options) {
|
|
|
6587
6816
|
autoGenerateMipmaps: false,
|
|
6588
6817
|
dynamic: false,
|
|
6589
6818
|
height: 1,
|
|
6590
|
-
resource: createPlaceholderCanvas(),
|
|
6819
|
+
resource: createPlaceholderCanvas$1(),
|
|
6591
6820
|
scaleMode: "nearest",
|
|
6592
6821
|
width: 1,
|
|
6593
6822
|
});
|
|
@@ -6677,7 +6906,7 @@ function createPixiIdMaskShaderRenderer(options) {
|
|
|
6677
6906
|
mesh.shader = shader;
|
|
6678
6907
|
}
|
|
6679
6908
|
}
|
|
6680
|
-
function createPlaceholderCanvas() {
|
|
6909
|
+
function createPlaceholderCanvas$1() {
|
|
6681
6910
|
const canvas = document.createElement("canvas");
|
|
6682
6911
|
canvas.height = 1;
|
|
6683
6912
|
canvas.width = 1;
|
|
@@ -6827,15 +7056,219 @@ void main(void) {
|
|
|
6827
7056
|
}
|
|
6828
7057
|
`;
|
|
6829
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
|
+
|
|
6830
7259
|
const MAX_PENDING_MASK_HOLD_SECONDS = 0.05;
|
|
6831
7260
|
function createPixiMaskLayer(options) {
|
|
6832
7261
|
let mediaHeight = 0;
|
|
6833
7262
|
let mediaWidth = 0;
|
|
6834
7263
|
let idMaskRenderer;
|
|
7264
|
+
let haloRenderer;
|
|
7265
|
+
let currentMaskHaloStyle = options.maskHaloStyle ?? null;
|
|
7266
|
+
options.maskStyle;
|
|
6835
7267
|
let maskSprite;
|
|
6836
7268
|
let activeFrameKey = null;
|
|
6837
7269
|
let activeFrameMediaTime = null;
|
|
6838
7270
|
let activeIdMaskFrame = null;
|
|
7271
|
+
let activeRgbaMaskFrame = null;
|
|
6839
7272
|
let maskOpacity = resolveMaskStyleOpacity(options.maskStyle);
|
|
6840
7273
|
let maskPickCanvas;
|
|
6841
7274
|
let maskPickContext;
|
|
@@ -6843,6 +7276,7 @@ function createPixiMaskLayer(options) {
|
|
|
6843
7276
|
let visibleMaskMediaTime = null;
|
|
6844
7277
|
let isDestroyed = false;
|
|
6845
7278
|
const maskTextures = new Map();
|
|
7279
|
+
const haloTextures = new Map();
|
|
6846
7280
|
const preparedRenderWindow = createPreparedRenderWindow({
|
|
6847
7281
|
artifactKind: options.artifactKind,
|
|
6848
7282
|
detectionTimeline: options.detectionTimeline,
|
|
@@ -6888,10 +7322,19 @@ function createPixiMaskLayer(options) {
|
|
|
6888
7322
|
maskSprite.height = mediaHeight;
|
|
6889
7323
|
idMaskRenderer = createIdMaskRenderer();
|
|
6890
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);
|
|
6891
7329
|
if (!idMaskRenderer || !options.Container) {
|
|
6892
7330
|
return maskSprite;
|
|
6893
7331
|
}
|
|
6894
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
|
+
}
|
|
6895
7338
|
maskContainer.addChild(maskSprite, idMaskRenderer.mesh);
|
|
6896
7339
|
return maskContainer;
|
|
6897
7340
|
},
|
|
@@ -6901,6 +7344,7 @@ function createPixiMaskLayer(options) {
|
|
|
6901
7344
|
activeFrameKey = preparedFrame?.key ?? null;
|
|
6902
7345
|
activeFrameMediaTime = preparedFrame?.detectionFrame.mediaTime ?? null;
|
|
6903
7346
|
activeIdMaskFrame = null;
|
|
7347
|
+
activeRgbaMaskFrame = null;
|
|
6904
7348
|
hideSprite();
|
|
6905
7349
|
return;
|
|
6906
7350
|
}
|
|
@@ -6913,6 +7357,7 @@ function createPixiMaskLayer(options) {
|
|
|
6913
7357
|
if (preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Empty ||
|
|
6914
7358
|
preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Disabled) {
|
|
6915
7359
|
activeIdMaskFrame = null;
|
|
7360
|
+
activeRgbaMaskFrame = null;
|
|
6916
7361
|
hideSprite();
|
|
6917
7362
|
return;
|
|
6918
7363
|
}
|
|
@@ -6965,6 +7410,14 @@ function createPixiMaskLayer(options) {
|
|
|
6965
7410
|
applyMaskOpacity();
|
|
6966
7411
|
}
|
|
6967
7412
|
preparedRenderWindow.setMaskStyle(nextMaskStyle);
|
|
7413
|
+
refreshHalo();
|
|
7414
|
+
},
|
|
7415
|
+
setMaskHaloStyle(nextMaskHaloStyle) {
|
|
7416
|
+
if (nextMaskHaloStyle === undefined) {
|
|
7417
|
+
return;
|
|
7418
|
+
}
|
|
7419
|
+
currentMaskHaloStyle = nextMaskHaloStyle;
|
|
7420
|
+
refreshHalo();
|
|
6968
7421
|
},
|
|
6969
7422
|
destroy() {
|
|
6970
7423
|
if (isDestroyed) {
|
|
@@ -6975,17 +7428,22 @@ function createPixiMaskLayer(options) {
|
|
|
6975
7428
|
destroyTextures();
|
|
6976
7429
|
resetMaskPickCanvas();
|
|
6977
7430
|
idMaskRenderer?.destroy();
|
|
7431
|
+
haloRenderer?.destroy();
|
|
6978
7432
|
},
|
|
6979
7433
|
};
|
|
6980
7434
|
function showMaskFrame(maskFrame, mediaTime) {
|
|
6981
7435
|
visibleMaskMediaTime = mediaTime;
|
|
6982
7436
|
activeIdMaskFrame =
|
|
6983
7437
|
maskFrame.kind === PreparedMaskFrameKind.PngIdMask ? maskFrame : null;
|
|
7438
|
+
activeRgbaMaskFrame =
|
|
7439
|
+
maskFrame.kind === PreparedMaskFrameKind.RgbaImage ? maskFrame : null;
|
|
6984
7440
|
if (maskFrame.kind === PreparedMaskFrameKind.PngIdMask && idMaskRenderer) {
|
|
6985
7441
|
showIdMaskFrame(maskFrame);
|
|
6986
7442
|
return;
|
|
6987
7443
|
}
|
|
6988
|
-
|
|
7444
|
+
if (maskFrame.kind === PreparedMaskFrameKind.RgbaImage) {
|
|
7445
|
+
showRgbaMaskFrame(maskFrame);
|
|
7446
|
+
}
|
|
6989
7447
|
}
|
|
6990
7448
|
function getTexture(maskFrame) {
|
|
6991
7449
|
const existingTexture = maskTextures.get(maskFrame.key);
|
|
@@ -7009,6 +7467,40 @@ function createPixiMaskLayer(options) {
|
|
|
7009
7467
|
maskTextures.set(maskFrame.key, texture);
|
|
7010
7468
|
return texture;
|
|
7011
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
|
+
}
|
|
7012
7504
|
function showRgbaMaskFrame(maskFrame) {
|
|
7013
7505
|
if (!maskSprite) {
|
|
7014
7506
|
return;
|
|
@@ -7020,6 +7512,13 @@ function createPixiMaskLayer(options) {
|
|
|
7020
7512
|
maskSprite.height = mediaHeight;
|
|
7021
7513
|
maskSprite.visible = true;
|
|
7022
7514
|
idMaskRenderer?.hide();
|
|
7515
|
+
const haloTexture = getHaloTexture(maskFrame);
|
|
7516
|
+
if (haloTexture) {
|
|
7517
|
+
renderHalo(maskFrame, haloTexture);
|
|
7518
|
+
}
|
|
7519
|
+
else {
|
|
7520
|
+
haloRenderer?.hide();
|
|
7521
|
+
}
|
|
7023
7522
|
}
|
|
7024
7523
|
function showIdMaskFrame(maskFrame) {
|
|
7025
7524
|
if (!idMaskRenderer || !maskSprite) {
|
|
@@ -7027,14 +7526,82 @@ function createPixiMaskLayer(options) {
|
|
|
7027
7526
|
}
|
|
7028
7527
|
maskSprite.visible = false;
|
|
7029
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
|
+
}));
|
|
7030
7595
|
}
|
|
7031
7596
|
function hideSprite() {
|
|
7032
7597
|
visibleMaskMediaTime = null;
|
|
7033
7598
|
activeIdMaskFrame = null;
|
|
7599
|
+
activeRgbaMaskFrame = null;
|
|
7034
7600
|
if (maskSprite) {
|
|
7035
7601
|
maskSprite.visible = false;
|
|
7036
7602
|
}
|
|
7037
7603
|
idMaskRenderer?.hide();
|
|
7604
|
+
haloRenderer?.hide();
|
|
7038
7605
|
}
|
|
7039
7606
|
function canHoldVisibleMaskFor(mediaTime) {
|
|
7040
7607
|
return (visibleMaskMediaTime !== null &&
|
|
@@ -7050,11 +7617,21 @@ function createPixiMaskLayer(options) {
|
|
|
7050
7617
|
function destroyTexture(key) {
|
|
7051
7618
|
const texture = maskTextures.get(key);
|
|
7052
7619
|
if (!texture) {
|
|
7620
|
+
destroyHaloTexture(key);
|
|
7053
7621
|
return;
|
|
7054
7622
|
}
|
|
7055
7623
|
releaseTextureBindings(key, texture);
|
|
7056
7624
|
maskTextures.delete(key);
|
|
7057
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);
|
|
7058
7635
|
}
|
|
7059
7636
|
function destroyTextures() {
|
|
7060
7637
|
releaseTextureBindings();
|
|
@@ -7062,6 +7639,10 @@ function createPixiMaskLayer(options) {
|
|
|
7062
7639
|
texture.destroy(true);
|
|
7063
7640
|
}
|
|
7064
7641
|
maskTextures.clear();
|
|
7642
|
+
for (const texture of haloTextures.values()) {
|
|
7643
|
+
texture.destroy(true);
|
|
7644
|
+
}
|
|
7645
|
+
haloTextures.clear();
|
|
7065
7646
|
}
|
|
7066
7647
|
function releaseTextureBindings(key, texture) {
|
|
7067
7648
|
if (!texture || maskSprite?.texture === texture) {
|
|
@@ -7073,6 +7654,31 @@ function createPixiMaskLayer(options) {
|
|
|
7073
7654
|
idMaskRenderer?.clearTexture();
|
|
7074
7655
|
}
|
|
7075
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
|
+
}
|
|
7076
7682
|
function createIdMaskRenderer() {
|
|
7077
7683
|
if (!options.Mesh ||
|
|
7078
7684
|
!options.MeshGeometry ||
|
|
@@ -7273,6 +7879,9 @@ function canPreparePolygonInstruction(instruction) {
|
|
|
7273
7879
|
return (instruction.points.length >= 3 &&
|
|
7274
7880
|
Boolean(instruction.fill || stroke) &&
|
|
7275
7881
|
!stroke?.dash?.length &&
|
|
7882
|
+
stroke?.cap === undefined &&
|
|
7883
|
+
stroke?.join === undefined &&
|
|
7884
|
+
stroke?.miterLimit === undefined &&
|
|
7276
7885
|
(stroke?.alignment === undefined ||
|
|
7277
7886
|
stroke.alignment === BoxStrokeAlignment.Center));
|
|
7278
7887
|
}
|
|
@@ -7317,6 +7926,367 @@ function resolvePreparedPolygonInstructions(options) {
|
|
|
7317
7926
|
return instructions;
|
|
7318
7927
|
}
|
|
7319
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
|
+
|
|
7320
8290
|
function createPixiAnnotationOverlayLayer(editingEngine, initialStyle) {
|
|
7321
8291
|
let graphics;
|
|
7322
8292
|
let style = resolveAnnotationOverlayStyle(initialStyle);
|
|
@@ -7549,8 +8519,11 @@ function drawLoading(graphics, context, style) {
|
|
|
7549
8519
|
const loading = values instanceof Set ? values : new Set(values);
|
|
7550
8520
|
const angle = (context.now / 500) % (Math.PI * 2);
|
|
7551
8521
|
for (const detection of context.frame?.detections ?? []) {
|
|
7552
|
-
if (detection.id === undefined ||
|
|
8522
|
+
if (detection.id === undefined ||
|
|
8523
|
+
!loading.has(detection.id) ||
|
|
8524
|
+
resolveAnnotationStyleState(detection, context.visibility).hidden) {
|
|
7553
8525
|
continue;
|
|
8526
|
+
}
|
|
7554
8527
|
const rect = getDetectionRect(detection);
|
|
7555
8528
|
if (!rect)
|
|
7556
8529
|
continue;
|
|
@@ -7625,6 +8598,7 @@ var PixiSceneLayerKind;
|
|
|
7625
8598
|
PixiSceneLayerKind["Mask"] = "mask";
|
|
7626
8599
|
PixiSceneLayerKind["Box"] = "box";
|
|
7627
8600
|
PixiSceneLayerKind["Vector"] = "vector";
|
|
8601
|
+
PixiSceneLayerKind["Region"] = "region";
|
|
7628
8602
|
PixiSceneLayerKind["Focus"] = "focus";
|
|
7629
8603
|
PixiSceneLayerKind["Preview"] = "preview";
|
|
7630
8604
|
PixiSceneLayerKind["Guide"] = "guide";
|
|
@@ -7669,6 +8643,8 @@ function defaultLayerOrder(kind) {
|
|
|
7669
8643
|
return 200;
|
|
7670
8644
|
case PixiSceneLayerKind.Vector:
|
|
7671
8645
|
return 250;
|
|
8646
|
+
case PixiSceneLayerKind.Region:
|
|
8647
|
+
return 275;
|
|
7672
8648
|
case PixiSceneLayerKind.Focus:
|
|
7673
8649
|
return 300;
|
|
7674
8650
|
case PixiSceneLayerKind.Preview:
|
|
@@ -7724,14 +8700,42 @@ function observePixiContainerResize(container, resize) {
|
|
|
7724
8700
|
return () => observer.disconnect();
|
|
7725
8701
|
}
|
|
7726
8702
|
async function createPixiMediaScene(options) {
|
|
7727
|
-
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');
|
|
7728
8705
|
const app = new Application();
|
|
8706
|
+
let currentEllipseStyle = options.ellipseStyle ?? null;
|
|
8707
|
+
let currentBoxCornerStyle = options.boxCornerStyle ?? null;
|
|
7729
8708
|
let currentLabelStyle = options.labelStyle ?? null;
|
|
7730
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);
|
|
7731
8730
|
let currentPolygonStyle = options.polygonStyle === undefined
|
|
7732
8731
|
? new BasePolygonStyle()
|
|
7733
8732
|
: options.polygonStyle;
|
|
7734
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;
|
|
7735
8739
|
let visibilityVersion = 0;
|
|
7736
8740
|
let visibilityMaskStyleCache = null;
|
|
7737
8741
|
const resolveContextState = (detection) => resolveAnnotationStyleState(detection, currentVisibility);
|
|
@@ -7740,6 +8744,28 @@ async function createPixiMediaScene(options) {
|
|
|
7740
8744
|
hidden: currentVisibility?.labelsHidden === true ||
|
|
7741
8745
|
resolveContextState(detection).hidden,
|
|
7742
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
|
+
}
|
|
7743
8769
|
const boxLayer = createPixiBoxLayer({
|
|
7744
8770
|
boxStyle: options.boxStyle,
|
|
7745
8771
|
Container: options.editingEngine ? Container : undefined,
|
|
@@ -7772,6 +8798,24 @@ async function createPixiMediaScene(options) {
|
|
|
7772
8798
|
polygonStyle: polygonLayer?.getVectorFallbackStyle() ?? currentPolygonStyle,
|
|
7773
8799
|
polylineStyle: options.polylineStyle,
|
|
7774
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,
|
|
7775
8819
|
resolveContextState,
|
|
7776
8820
|
});
|
|
7777
8821
|
const annotationOverlayLayer = createPixiAnnotationOverlayLayer(options.editingEngine, options.annotationOverlayStyle);
|
|
@@ -7785,9 +8829,11 @@ async function createPixiMediaScene(options) {
|
|
|
7785
8829
|
preview: options.maskBrush,
|
|
7786
8830
|
})
|
|
7787
8831
|
: undefined;
|
|
7788
|
-
let maskLayer =
|
|
8832
|
+
let maskLayer = resolveArtifactMaskStyle()
|
|
7789
8833
|
? createPixiMaskLayer({
|
|
8834
|
+
BlurFilter,
|
|
7790
8835
|
Container,
|
|
8836
|
+
Rectangle,
|
|
7791
8837
|
ImageSource,
|
|
7792
8838
|
Mesh,
|
|
7793
8839
|
MeshGeometry,
|
|
@@ -7796,7 +8842,10 @@ async function createPixiMediaScene(options) {
|
|
|
7796
8842
|
Texture,
|
|
7797
8843
|
UniformGroup,
|
|
7798
8844
|
detectionTimeline: options.detectionTimeline,
|
|
7799
|
-
|
|
8845
|
+
maskHaloStyle: currentMaskHaloStyle
|
|
8846
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
8847
|
+
: null,
|
|
8848
|
+
maskStyle: createVisibilityMaskStyle(resolveArtifactMaskStyle()),
|
|
7800
8849
|
onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
|
|
7801
8850
|
renderPreparation: options.renderPreparation,
|
|
7802
8851
|
})
|
|
@@ -7828,8 +8877,6 @@ async function createPixiMediaScene(options) {
|
|
|
7828
8877
|
if (!stagingContext) {
|
|
7829
8878
|
throw new Error("Unable to create staging canvas context.");
|
|
7830
8879
|
}
|
|
7831
|
-
let mediaHeight = 0;
|
|
7832
|
-
let mediaWidth = 0;
|
|
7833
8880
|
let mediaScene;
|
|
7834
8881
|
let vectorDisplay;
|
|
7835
8882
|
let polygonDisplay;
|
|
@@ -7838,6 +8885,7 @@ async function createPixiMediaScene(options) {
|
|
|
7838
8885
|
const maskSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Mask);
|
|
7839
8886
|
const boxSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Box);
|
|
7840
8887
|
const vectorSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Vector);
|
|
8888
|
+
const regionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Region);
|
|
7841
8889
|
const focusSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Focus);
|
|
7842
8890
|
const previewSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Preview);
|
|
7843
8891
|
const interactionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Interaction);
|
|
@@ -7848,16 +8896,14 @@ async function createPixiMediaScene(options) {
|
|
|
7848
8896
|
maskSlot,
|
|
7849
8897
|
boxSlot,
|
|
7850
8898
|
vectorSlot,
|
|
8899
|
+
regionSlot,
|
|
7851
8900
|
focusSlot,
|
|
7852
8901
|
previewSlot,
|
|
7853
8902
|
handleSlot,
|
|
7854
8903
|
interactionSlot,
|
|
7855
8904
|
labelSlot,
|
|
7856
8905
|
];
|
|
7857
|
-
let currentMediaTime = 0;
|
|
7858
|
-
let viewportScale = 1;
|
|
7859
8906
|
const viewport = createViewportController({ scale: 1 });
|
|
7860
|
-
let hasPresentedSample = false;
|
|
7861
8907
|
/**
|
|
7862
8908
|
* Timestamp of the sample whose pixels are on the staging canvas. Unlike
|
|
7863
8909
|
* `currentMediaTime`, presentation and selection updates never move it.
|
|
@@ -7873,6 +8919,7 @@ async function createPixiMediaScene(options) {
|
|
|
7873
8919
|
interaction: options.interaction ?? {
|
|
7874
8920
|
mode: MediaInteractionMode.Always,
|
|
7875
8921
|
},
|
|
8922
|
+
canPickDetection: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
7876
8923
|
editingEngine: options.editingEngine,
|
|
7877
8924
|
capturePointer: (pointerId) => {
|
|
7878
8925
|
if (!rendererCanvas.hasPointerCapture?.(pointerId)) {
|
|
@@ -7909,6 +8956,7 @@ async function createPixiMediaScene(options) {
|
|
|
7909
8956
|
Text,
|
|
7910
8957
|
UniformGroup,
|
|
7911
8958
|
interactionStyle: options.interactionStyle,
|
|
8959
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
7912
8960
|
})
|
|
7913
8961
|
: undefined;
|
|
7914
8962
|
let fastTranslatedDetectionId = null;
|
|
@@ -7916,6 +8964,7 @@ async function createPixiMediaScene(options) {
|
|
|
7916
8964
|
fastTranslatedDetectionId = id;
|
|
7917
8965
|
boxLayer.translateDetection(id, dx, dy);
|
|
7918
8966
|
vectorLayer.translateDetection(id, dx, dy);
|
|
8967
|
+
regionLayer.translateDetection(id, dx, dy);
|
|
7919
8968
|
labelLayer?.translateDetection(id, dx, dy);
|
|
7920
8969
|
});
|
|
7921
8970
|
const unsubscribeEditingState = options.editingEngine?.subscribe((state) => {
|
|
@@ -7927,11 +8976,13 @@ async function createPixiMediaScene(options) {
|
|
|
7927
8976
|
fastTranslatedDetectionId = null;
|
|
7928
8977
|
boxLayer.translateDetection(id, 0, 0);
|
|
7929
8978
|
vectorLayer.translateDetection(id, 0, 0);
|
|
8979
|
+
regionLayer.translateDetection(id, 0, 0);
|
|
7930
8980
|
labelLayer?.translateDetection(id, 0, 0);
|
|
7931
8981
|
boxLayer.invalidateDetection(id);
|
|
7932
8982
|
vectorLayer.invalidateDetection(id);
|
|
7933
8983
|
boxLayer.drawFrame(currentMediaTime, viewportScale);
|
|
7934
8984
|
vectorLayer.drawFrame(currentMediaTime, viewportScale);
|
|
8985
|
+
regionLayer.drawFrame(currentMediaTime, viewportScale);
|
|
7935
8986
|
labelLayer?.drawFrame(currentMediaTime, viewportScale);
|
|
7936
8987
|
});
|
|
7937
8988
|
const handleKeyDown = (event) => {
|
|
@@ -7966,6 +9017,7 @@ async function createPixiMediaScene(options) {
|
|
|
7966
9017
|
Shader,
|
|
7967
9018
|
UniformGroup,
|
|
7968
9019
|
focusStyle: options.focusStyle,
|
|
9020
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
7969
9021
|
})
|
|
7970
9022
|
: undefined;
|
|
7971
9023
|
let mediaSprite;
|
|
@@ -8075,6 +9127,7 @@ async function createPixiMediaScene(options) {
|
|
|
8075
9127
|
attachPolygonLayerDisplay();
|
|
8076
9128
|
vectorDisplay.addChild(vectorLayer.createContainer());
|
|
8077
9129
|
vectorSlot.setDisplay(vectorDisplay);
|
|
9130
|
+
regionSlot.setDisplay(regionLayer.createContainer());
|
|
8078
9131
|
previewSlot.setDisplay(maskBrushPreview?.display);
|
|
8079
9132
|
if (annotationOverlay) {
|
|
8080
9133
|
annotationOverlayLayer.attachGraphics(annotationOverlay);
|
|
@@ -8128,13 +9181,14 @@ async function createPixiMediaScene(options) {
|
|
|
8128
9181
|
const boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8129
9182
|
polygonLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8130
9183
|
vectorLayer.drawFrame(sample.timestamp, viewportScale);
|
|
9184
|
+
const regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8131
9185
|
interactionLayer?.drawFrame(sample.timestamp);
|
|
8132
9186
|
drawFocusLayer(sample.timestamp);
|
|
8133
9187
|
drawInteractionPresentationLayer(sample.timestamp);
|
|
8134
9188
|
labelLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8135
9189
|
updateMediaSceneFit();
|
|
8136
9190
|
return {
|
|
8137
|
-
...createPresentedSampleState(sample.timestamp, boxState),
|
|
9191
|
+
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
8138
9192
|
duration: sample.duration,
|
|
8139
9193
|
};
|
|
8140
9194
|
}
|
|
@@ -8156,10 +9210,12 @@ async function createPixiMediaScene(options) {
|
|
|
8156
9210
|
maskLayer?.drawFrame(sample.timestamp);
|
|
8157
9211
|
});
|
|
8158
9212
|
let boxState;
|
|
9213
|
+
let regionState;
|
|
8159
9214
|
boxMs = measure(() => {
|
|
8160
9215
|
boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8161
9216
|
polygonLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8162
9217
|
vectorLayer.drawFrame(sample.timestamp, viewportScale);
|
|
9218
|
+
regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8163
9219
|
});
|
|
8164
9220
|
interactionMs = measure(() => {
|
|
8165
9221
|
interactionLayer?.drawFrame(sample.timestamp);
|
|
@@ -8186,7 +9242,7 @@ async function createPixiMediaScene(options) {
|
|
|
8186
9242
|
totalMs: elapsedSince(totalStart),
|
|
8187
9243
|
};
|
|
8188
9244
|
return {
|
|
8189
|
-
...createPresentedSampleState(sample.timestamp, boxState),
|
|
9245
|
+
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
8190
9246
|
duration: sample.duration,
|
|
8191
9247
|
renderTimings,
|
|
8192
9248
|
};
|
|
@@ -8335,6 +9391,7 @@ async function createPixiMediaScene(options) {
|
|
|
8335
9391
|
visibilityVersion += 1;
|
|
8336
9392
|
maskLayer?.setMaskStyle(createVisibilityMaskStyle(currentMaskStyle));
|
|
8337
9393
|
}
|
|
9394
|
+
interactionLayer?.drawFrame(mediaTime);
|
|
8338
9395
|
}
|
|
8339
9396
|
boxLayer.setBoxStyle(presentation.boxStyle);
|
|
8340
9397
|
if (presentation.polygonStyle !== undefined) {
|
|
@@ -8355,15 +9412,38 @@ async function createPixiMediaScene(options) {
|
|
|
8355
9412
|
polylineStyle: presentation.polylineStyle,
|
|
8356
9413
|
keypointStyle: presentation.keypointStyle,
|
|
8357
9414
|
});
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
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)
|
|
8362
9441
|
: maskLayer;
|
|
8363
|
-
nextMaskLayer?.setMaskStyle(
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
9442
|
+
nextMaskLayer?.setMaskStyle(artifactStyle ? createVisibilityMaskStyle(artifactStyle) : null);
|
|
9443
|
+
nextMaskLayer?.setMaskHaloStyle(currentMaskHaloStyle
|
|
9444
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
9445
|
+
: null);
|
|
9446
|
+
if (artifactStyle) {
|
|
8367
9447
|
attachMaskLayerDisplay();
|
|
8368
9448
|
syncSceneChildren();
|
|
8369
9449
|
}
|
|
@@ -8397,11 +9477,12 @@ async function createPixiMediaScene(options) {
|
|
|
8397
9477
|
const boxState = boxLayer.drawFrame(mediaTime, viewportScale);
|
|
8398
9478
|
polygonLayer?.drawFrame(mediaTime, viewportScale);
|
|
8399
9479
|
vectorLayer.drawFrame(mediaTime, viewportScale);
|
|
9480
|
+
const regionState = regionLayer.drawFrame(mediaTime, viewportScale);
|
|
8400
9481
|
interactionLayer?.drawFrame(mediaTime);
|
|
8401
9482
|
drawFocusLayer(mediaTime);
|
|
8402
9483
|
drawInteractionPresentationLayer(mediaTime);
|
|
8403
9484
|
labelLayer?.drawFrame(mediaTime, viewportScale);
|
|
8404
|
-
return createPresentedSampleState(mediaTime, boxState);
|
|
9485
|
+
return createPresentedSampleState(mediaTime, boxState, regionState);
|
|
8405
9486
|
},
|
|
8406
9487
|
setSelectedDetection(selection, mediaTime) {
|
|
8407
9488
|
currentMediaTime = selection?.mediaTime ?? mediaTime;
|
|
@@ -8428,6 +9509,7 @@ async function createPixiMediaScene(options) {
|
|
|
8428
9509
|
polygonLayer?.destroy();
|
|
8429
9510
|
labelLayer?.destroy();
|
|
8430
9511
|
vectorLayer.destroy();
|
|
9512
|
+
regionLayer.destroy();
|
|
8431
9513
|
maskBrushPreview?.destroy();
|
|
8432
9514
|
unsubscribeFastTranslate?.();
|
|
8433
9515
|
unsubscribeEditingState?.();
|
|
@@ -8444,21 +9526,24 @@ async function createPixiMediaScene(options) {
|
|
|
8444
9526
|
});
|
|
8445
9527
|
},
|
|
8446
9528
|
};
|
|
8447
|
-
function createPresentedSampleState(mediaTime, boxState) {
|
|
9529
|
+
function createPresentedSampleState(mediaTime, boxState, regionState) {
|
|
8448
9530
|
const detectionFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
8449
9531
|
return {
|
|
8450
|
-
activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState),
|
|
9532
|
+
activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState, regionState),
|
|
8451
9533
|
activeDetectionFrameIndex: detectionFrame?.frameIndex ?? null,
|
|
8452
9534
|
activeDetectionFrameTime: detectionFrame?.mediaTime ?? null,
|
|
8453
9535
|
detectionBuffer: options.detectionTimeline.getState(),
|
|
8454
9536
|
mediaTime,
|
|
8455
9537
|
};
|
|
8456
9538
|
}
|
|
8457
|
-
function countPresentedDetections(frame, mediaTime, boxState) {
|
|
9539
|
+
function countPresentedDetections(frame, mediaTime, boxState, regionState) {
|
|
8458
9540
|
if (!frame) {
|
|
8459
9541
|
return 0;
|
|
8460
9542
|
}
|
|
8461
9543
|
const visibleDetectionIndexes = new Set(boxState.activeDetectionIndexes);
|
|
9544
|
+
for (const detectionIndex of regionState?.activeDetectionIndexes ?? []) {
|
|
9545
|
+
visibleDetectionIndexes.add(detectionIndex);
|
|
9546
|
+
}
|
|
8462
9547
|
for (const [detectionIndex, detection] of frame.detections.entries()) {
|
|
8463
9548
|
if (visibleDetectionIndexes.has(detectionIndex)) {
|
|
8464
9549
|
continue;
|
|
@@ -8485,7 +9570,9 @@ async function createPixiMediaScene(options) {
|
|
|
8485
9570
|
function ensureMaskLayer(maskStyle) {
|
|
8486
9571
|
if (!maskLayer) {
|
|
8487
9572
|
maskLayer = createPixiMaskLayer({
|
|
9573
|
+
BlurFilter,
|
|
8488
9574
|
Container,
|
|
9575
|
+
Rectangle,
|
|
8489
9576
|
ImageSource,
|
|
8490
9577
|
Mesh,
|
|
8491
9578
|
MeshGeometry,
|
|
@@ -8494,6 +9581,9 @@ async function createPixiMediaScene(options) {
|
|
|
8494
9581
|
Texture,
|
|
8495
9582
|
UniformGroup,
|
|
8496
9583
|
detectionTimeline: options.detectionTimeline,
|
|
9584
|
+
maskHaloStyle: currentMaskHaloStyle
|
|
9585
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
9586
|
+
: null,
|
|
8497
9587
|
maskStyle: createVisibilityMaskStyle(maskStyle),
|
|
8498
9588
|
onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
|
|
8499
9589
|
renderPreparation: options.renderPreparation,
|
|
@@ -8571,6 +9661,7 @@ async function createPixiMediaScene(options) {
|
|
|
8571
9661
|
Shader,
|
|
8572
9662
|
UniformGroup,
|
|
8573
9663
|
focusStyle,
|
|
9664
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
8574
9665
|
});
|
|
8575
9666
|
}
|
|
8576
9667
|
return focusLayer;
|
|
@@ -8606,10 +9697,10 @@ async function createPixiMediaScene(options) {
|
|
|
8606
9697
|
const interactionState = interactionLayer?.getState();
|
|
8607
9698
|
focusLayer.drawFrame({
|
|
8608
9699
|
frame,
|
|
8609
|
-
hoveredPick: interactionState?.hoveredPick ?? null,
|
|
9700
|
+
hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
|
|
8610
9701
|
idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
|
|
8611
9702
|
mediaTime,
|
|
8612
|
-
selectedPick: interactionState?.selectedPick ?? null,
|
|
9703
|
+
selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
|
|
8613
9704
|
viewportScale,
|
|
8614
9705
|
});
|
|
8615
9706
|
}
|
|
@@ -8621,11 +9712,11 @@ async function createPixiMediaScene(options) {
|
|
|
8621
9712
|
const interactionState = interactionLayer?.getState();
|
|
8622
9713
|
interactionPresentationLayer.drawFrame({
|
|
8623
9714
|
frame,
|
|
8624
|
-
hoveredPick: interactionState?.hoveredPick ?? null,
|
|
9715
|
+
hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
|
|
8625
9716
|
idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
|
|
8626
9717
|
mediaTime,
|
|
8627
|
-
selectedPick: interactionState?.selectedPick ?? null,
|
|
8628
|
-
selectedPicks: interactionState?.selectedPicks,
|
|
9718
|
+
selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
|
|
9719
|
+
selectedPicks: interactionState?.selectedPicks.filter(({ detection }) => !resolveAnnotationStyleState(detection, currentVisibility).hidden),
|
|
8629
9720
|
viewportScale,
|
|
8630
9721
|
});
|
|
8631
9722
|
}
|
|
@@ -8696,6 +9787,12 @@ async function createPixiMediaScene(options) {
|
|
|
8696
9787
|
};
|
|
8697
9788
|
return visibilityMaskStyle;
|
|
8698
9789
|
}
|
|
9790
|
+
function filterVisiblePick(pick) {
|
|
9791
|
+
return pick &&
|
|
9792
|
+
!resolveAnnotationStyleState(pick.detection, currentVisibility).hidden
|
|
9793
|
+
? pick
|
|
9794
|
+
: null;
|
|
9795
|
+
}
|
|
8699
9796
|
}
|
|
8700
9797
|
function canReuseMaskVisibilityArtifacts(previous, next) {
|
|
8701
9798
|
return ((previous?.annotationsHidden === true) ===
|
|
@@ -9332,14 +10429,39 @@ async function createMediaSession(options) {
|
|
|
9332
10429
|
const sessionDetections = preparedDetections;
|
|
9333
10430
|
const defaultPresentation = createDefaultAnnotationPresentation();
|
|
9334
10431
|
let currentPresentation = options.presentation;
|
|
9335
|
-
const resolvePresentation = (presentation) =>
|
|
9336
|
-
|
|
10432
|
+
const resolvePresentation = (presentation) => {
|
|
10433
|
+
const renderers = presentation?.renderers;
|
|
10434
|
+
const resolvedPresentation = resolveAnnotationRendererPresentation({
|
|
10435
|
+
...defaultPresentation,
|
|
10436
|
+
...presentation,
|
|
10437
|
+
});
|
|
10438
|
+
return createSourceAwarePresentation(resolvedPresentation, sessionDetections.sourcePresentations, renderers === undefined
|
|
10439
|
+
? undefined
|
|
10440
|
+
: { enabledRendererKinds: renderers.map(({ kind }) => kind) });
|
|
10441
|
+
};
|
|
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
|
+
};
|
|
10456
|
+
const initialPresentation = resolveRendererPresentation(currentPresentation);
|
|
9337
10457
|
const renderer = await createMediaRenderer({
|
|
9338
10458
|
...options.renderer,
|
|
9339
10459
|
...sessionMedia.rendererSourceOption,
|
|
9340
10460
|
backgroundColor: initialPresentation.backgroundColor,
|
|
9341
10461
|
annotationOverlayStyle: initialPresentation.annotationOverlayStyle,
|
|
9342
10462
|
boxStyle: initialPresentation.boxStyle,
|
|
10463
|
+
boxCornerStyle: initialPresentation.boxCornerStyle,
|
|
10464
|
+
ellipseStyle: initialPresentation.ellipseStyle,
|
|
9343
10465
|
container: options.container,
|
|
9344
10466
|
detectionBuffer: sessionDefaults.detectionBuffer,
|
|
9345
10467
|
detectionFrames: sessionDetections.detectionFrames,
|
|
@@ -9348,9 +10470,12 @@ async function createMediaSession(options) {
|
|
|
9348
10470
|
focusStyle: initialPresentation.focusStyle,
|
|
9349
10471
|
interactionStyle: initialPresentation.interactionStyle,
|
|
9350
10472
|
labelStyle: initialPresentation.labelStyle,
|
|
10473
|
+
maskHaloStyle: initialPresentation.maskHaloStyle,
|
|
9351
10474
|
maskStyle: initialPresentation.maskStyle,
|
|
10475
|
+
markerStyle: initialPresentation.markerStyle,
|
|
9352
10476
|
polygonStyle: initialPresentation.polygonStyle,
|
|
9353
10477
|
polylineStyle: initialPresentation.polylineStyle,
|
|
10478
|
+
renderers: initialPresentation.renderers,
|
|
9354
10479
|
keypointStyle: initialPresentation.keypointStyle,
|
|
9355
10480
|
visibility: initialPresentation.visibility,
|
|
9356
10481
|
onState(state) {
|
|
@@ -9432,7 +10557,7 @@ async function createMediaSession(options) {
|
|
|
9432
10557
|
},
|
|
9433
10558
|
setPresentation(presentation) {
|
|
9434
10559
|
currentPresentation = presentation;
|
|
9435
|
-
renderer.setPresentation(
|
|
10560
|
+
renderer.setPresentation(resolveRendererPresentation(currentPresentation));
|
|
9436
10561
|
},
|
|
9437
10562
|
setRenderQuality(quality) {
|
|
9438
10563
|
renderer.setRenderQuality(quality);
|