supervision 0.2.7 → 0.2.8
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.js +203 -45
- package/dist/index.js.map +1 -1
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +6 -0
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts +5 -0
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-region-layer.d.ts +2 -0
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -1
- package/dist/types/media-renderer.d.ts +7 -6
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts +7 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copySortedDetectionFrames, detectionFrameOverlapsRange, sortedDetectionFrames, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, MediaErrorKind, includeDefined, createDepthColormapLut, parseDepthManifest, validateDepthMap, resolveDepthFrameFile, PLAYHEAD_QUANTIZATION_TOLERANCE_SECONDS, decodeCompressedRleMask, decodeCompressedRleCounts, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, resolveIdMaskPaletteId, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createProjectedDetectionFrameSource, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, PlaybackGateReach, createIdleDetectionBufferState, MediaInteractionMode, MAX_ID_MASK_PALETTE_ENTRIES, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, depthPreviewTopCode, resolveDepthColorMapping, resolveDepthQuantity, computeDepthPercentileRange, BaseFocusStyle, extractDetectionMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget,
|
|
1
|
+
import { copySortedDetectionFrames, detectionFrameOverlapsRange, sortedDetectionFrames, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, MediaErrorKind, includeDefined, createDepthColormapLut, parseDepthManifest, validateDepthMap, resolveDepthFrameFile, PLAYHEAD_QUANTIZATION_TOLERANCE_SECONDS, decodeCompressedRleMask, decodeCompressedRleCounts, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, resolveIdMaskPaletteId, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createProjectedDetectionFrameSource, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, PlaybackGateReach, createIdleDetectionBufferState, MediaInteractionMode, MAX_ID_MASK_PALETTE_ENTRIES, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, depthPreviewTopCode, resolveDepthColorMapping, resolveDepthQuantity, computeDepthPercentileRange, BaseFocusStyle, extractDetectionMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, rebaseDetectionPickToFrame, pickDetectionAtPoint, createDetectionPickKey, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, MAX_ID_MASK_STROKE_WIDTH, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, writeIdMaskPaletteEntry, resolveIdMaskStrokeTexels, DetectionBufferStatus, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, RegionRendererMediaEffectKind, RegionRendererSourceKind, RegionRendererCoverageKind, RegionRendererSizeSpace, resolveStyleValue, resolveAnnotationStyleState, lightenColor, resolveAnnotationRendererStyleFields, annotationRendererKinds, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation, projectDetectionFrames, DetectionFrameSelectionMode } from 'supervision-js-core';
|
|
2
2
|
export { BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BaseOrientedBoxStyle, BasePercentageBarStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DepthColormap, DepthMapKind, DepthPreviewLevels, DepthQuantity, DepthSampling, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaErrorKind, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, PercentageBarPlacement, PlaybackGateReach, RegionRendererComposeMode, RegionRendererCoverageKind, RegionRendererMediaEffectKind, RegionRendererRegionKind, RegionRendererSizeSpace, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, TrackingGeometry, annotationRendererKinds, annotationRenderers, computeDepthPercentileRange, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createByteTrackTracker, createCBIoUTracker, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createOCSortTracker, createProjectedDetectionFrameSource, createSortTracker, createWritableDetectionFrameSource, depthColormapColors, detectionPostProcessors, normalizeDetectionClassName, parseDepthManifest, pickDetectionAtPoint, projectDetectionFrame, projectDetectionFrameForTracking, projectDetectionFrames, readDepthAt, resolveDetectionClassColorStyle } from 'supervision-js-core';
|
|
3
3
|
import { c as createMediaFrameClock, M as MediaSourceError, t as toMediaSourceError, r as resolveDisplayPixelRatio } from './video-engine-media-source-DOFsuGS4.js';
|
|
4
4
|
export { a as createWebVideoEngineMediaRendererSource, g as getMediaErrorKind, i as isMediaSourceError, o as openWebVideoEngineMediaSource } from './video-engine-media-source-DOFsuGS4.js';
|
|
@@ -10132,6 +10132,9 @@ async function createMediaRendererCore(options, providers) {
|
|
|
10132
10132
|
polygonStyle: currentPresentation.polygonStyle,
|
|
10133
10133
|
polylineStyle: currentPresentation.polylineStyle,
|
|
10134
10134
|
presentedFrames: protectedPresentedFrames?.source,
|
|
10135
|
+
// A source with no duration, such as a still image, has one frame to
|
|
10136
|
+
// show and nothing to play.
|
|
10137
|
+
stillFrame: metadata.duration === 0,
|
|
10135
10138
|
regionRenderers: resolveRegionRenderers(currentPresentation),
|
|
10136
10139
|
heatmapRenderers: resolveHeatmapRenderers(currentPresentation),
|
|
10137
10140
|
depthRenderers: resolveDepthRenderers(currentPresentation),
|
|
@@ -13562,7 +13565,6 @@ function createPixiInteractionLayer(options) {
|
|
|
13562
13565
|
let hoveredPick = null;
|
|
13563
13566
|
let hoveredPickKey = null;
|
|
13564
13567
|
let selectedPick = null;
|
|
13565
|
-
let selectedPickKey = null;
|
|
13566
13568
|
let selectedPicks = [];
|
|
13567
13569
|
let marqueeStart = null;
|
|
13568
13570
|
let marqueeRect = null;
|
|
@@ -13615,9 +13617,19 @@ function createPixiInteractionLayer(options) {
|
|
|
13615
13617
|
setSelectedPick(null);
|
|
13616
13618
|
return null;
|
|
13617
13619
|
}
|
|
13618
|
-
const
|
|
13619
|
-
|
|
13620
|
-
|
|
13620
|
+
const nextPicks = selection.detectionIds
|
|
13621
|
+
? [...new Set(selection.detectionIds)].flatMap((detectionId) => {
|
|
13622
|
+
const pick = createPickFromSelection({
|
|
13623
|
+
detectionId,
|
|
13624
|
+
mediaTime: selection.mediaTime,
|
|
13625
|
+
point: selection.point,
|
|
13626
|
+
target: selection.target,
|
|
13627
|
+
});
|
|
13628
|
+
return pick ? [pick] : [];
|
|
13629
|
+
})
|
|
13630
|
+
: [createPickFromSelection(selection)].filter((pick) => pick !== null);
|
|
13631
|
+
setSelectedPicks(nextPicks);
|
|
13632
|
+
return nextPicks.at(-1) ?? null;
|
|
13621
13633
|
},
|
|
13622
13634
|
getState() {
|
|
13623
13635
|
return createState();
|
|
@@ -13744,6 +13756,18 @@ function createPixiInteractionLayer(options) {
|
|
|
13744
13756
|
if (event.button !== undefined && event.button !== 0) {
|
|
13745
13757
|
return;
|
|
13746
13758
|
}
|
|
13759
|
+
if (options.interaction.multiSelect &&
|
|
13760
|
+
event.shiftKey &&
|
|
13761
|
+
pick &&
|
|
13762
|
+
!options.editingEngine.hasCreationTool() &&
|
|
13763
|
+
options.editingEngine.getState().kind ===
|
|
13764
|
+
AnnotationGestureStateKind.Idle) {
|
|
13765
|
+
// Selection modifiers must run before the editing engine captures a
|
|
13766
|
+
// move. The following pointertap belongs to this same toggle.
|
|
13767
|
+
toggleSelectedPick(pick);
|
|
13768
|
+
suppressNextTap = true;
|
|
13769
|
+
return;
|
|
13770
|
+
}
|
|
13747
13771
|
// Keep selection owned by this layer. The editing engine deliberately
|
|
13748
13772
|
// treats a click as a cancelled move, so it cannot establish selection
|
|
13749
13773
|
// itself; selecting here also makes resize handles available on the next
|
|
@@ -13783,7 +13807,7 @@ function createPixiInteractionLayer(options) {
|
|
|
13783
13807
|
if (!marqueeStart)
|
|
13784
13808
|
return;
|
|
13785
13809
|
if (didDragMarquee && marqueeRect && activeFrame) {
|
|
13786
|
-
|
|
13810
|
+
const nextPicks = activeFrame.detections.flatMap((detection, detectionIndex) => {
|
|
13787
13811
|
if (!canPickDetection(detection))
|
|
13788
13812
|
return [];
|
|
13789
13813
|
const rect = getDetectionRect(detection);
|
|
@@ -13804,10 +13828,7 @@ function createPixiInteractionLayer(options) {
|
|
|
13804
13828
|
},
|
|
13805
13829
|
];
|
|
13806
13830
|
});
|
|
13807
|
-
|
|
13808
|
-
selectedPickKey = createDetectionPickKey(selectedPick);
|
|
13809
|
-
options.interaction.onSelect?.(selectedPick);
|
|
13810
|
-
options.interaction.onSelectionChange?.(selectedPicks);
|
|
13831
|
+
setSelectedPicks(nextPicks);
|
|
13811
13832
|
}
|
|
13812
13833
|
marqueeStart = null;
|
|
13813
13834
|
marqueeRect = null;
|
|
@@ -13903,7 +13924,6 @@ function createPixiInteractionLayer(options) {
|
|
|
13903
13924
|
if (nextKeys === currentKeys) {
|
|
13904
13925
|
return;
|
|
13905
13926
|
}
|
|
13906
|
-
selectedPickKey = createDetectionPickKey(selectedPick);
|
|
13907
13927
|
// A followed pick re-bases onto every new frame, so its full pick key
|
|
13908
13928
|
// changes at playback rate. The scene's normal frame path consumes this
|
|
13909
13929
|
// refreshed state immediately; emitting onStateChange here would redraw
|
|
@@ -13935,15 +13955,17 @@ function createPixiInteractionLayer(options) {
|
|
|
13935
13955
|
return true;
|
|
13936
13956
|
}
|
|
13937
13957
|
function setSelectedPick(nextPick) {
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13958
|
+
setSelectedPicks(nextPick ? [nextPick] : []);
|
|
13959
|
+
}
|
|
13960
|
+
function setSelectedPicks(nextPicks) {
|
|
13961
|
+
const unchanged = selectedPicks.length === nextPicks.length &&
|
|
13962
|
+
selectedPicks.every((pick, index) => createDetectionPickKey(pick) ===
|
|
13963
|
+
createDetectionPickKey(nextPicks[index]));
|
|
13964
|
+
selectedPicks = nextPicks;
|
|
13965
|
+
selectedPick = nextPicks.at(-1) ?? null;
|
|
13966
|
+
if (unchanged)
|
|
13941
13967
|
return;
|
|
13942
|
-
|
|
13943
|
-
selectedPick = nextPick;
|
|
13944
|
-
selectedPicks = nextPick ? [nextPick] : [];
|
|
13945
|
-
selectedPickKey = nextKey;
|
|
13946
|
-
options.interaction.onSelect?.(nextPick);
|
|
13968
|
+
options.interaction.onSelect?.(selectedPick);
|
|
13947
13969
|
options.interaction.onSelectionChange?.(selectedPicks);
|
|
13948
13970
|
notifyStateChange();
|
|
13949
13971
|
}
|
|
@@ -13962,17 +13984,20 @@ function createPixiInteractionLayer(options) {
|
|
|
13962
13984
|
function toggleSelectedPick(pick) {
|
|
13963
13985
|
if (!pick)
|
|
13964
13986
|
return;
|
|
13965
|
-
const key =
|
|
13966
|
-
const index = selectedPicks.findIndex((candidate) =>
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
|
|
13974
|
-
|
|
13975
|
-
|
|
13987
|
+
const key = createSelectionKey(pick);
|
|
13988
|
+
const index = selectedPicks.findIndex((candidate) => createSelectionKey(candidate) === key);
|
|
13989
|
+
setSelectedPicks(index >= 0
|
|
13990
|
+
? selectedPicks.filter((_, candidateIndex) => candidateIndex !== index)
|
|
13991
|
+
: [...selectedPicks, pick]);
|
|
13992
|
+
}
|
|
13993
|
+
function createSelectionKey(pick) {
|
|
13994
|
+
// Selection toggles the detection, regardless of whether its body, label,
|
|
13995
|
+
// or keypoint was hit. Keep the picker's frame/index fallback for duplicate ids.
|
|
13996
|
+
return createDetectionPickKey({
|
|
13997
|
+
...pick,
|
|
13998
|
+
geometryIndex: undefined,
|
|
13999
|
+
target: DetectionPickTarget.Box,
|
|
14000
|
+
});
|
|
13976
14001
|
}
|
|
13977
14002
|
function getPoint(event) {
|
|
13978
14003
|
if (!container)
|
|
@@ -17438,6 +17463,13 @@ function createPixiRegionLayer(options) {
|
|
|
17438
17463
|
}
|
|
17439
17464
|
return { activeDetectionIndexes: [...activeDetectionIndexes] };
|
|
17440
17465
|
},
|
|
17466
|
+
hasAnimatedDisplay() {
|
|
17467
|
+
for (const entry of entries.values()) {
|
|
17468
|
+
if (isAnimatedDisplay(entry.display))
|
|
17469
|
+
return true;
|
|
17470
|
+
}
|
|
17471
|
+
return false;
|
|
17472
|
+
},
|
|
17441
17473
|
releaseMaskTexture(texture) {
|
|
17442
17474
|
if (destroyed)
|
|
17443
17475
|
return;
|
|
@@ -17760,12 +17792,15 @@ function destroyEntry(entry) {
|
|
|
17760
17792
|
// lightweight crop texture; the scene destroys the shared source.
|
|
17761
17793
|
entry.ownedTexture?.destroy(false);
|
|
17762
17794
|
}
|
|
17795
|
+
function isAnimatedDisplay(display) {
|
|
17796
|
+
return "play" in display && typeof display.play === "function";
|
|
17797
|
+
}
|
|
17763
17798
|
function pauseAnimatedDisplay(display) {
|
|
17764
17799
|
if ("stop" in display && typeof display.stop === "function")
|
|
17765
17800
|
display.stop();
|
|
17766
17801
|
}
|
|
17767
17802
|
function resumeAnimatedDisplay(display) {
|
|
17768
|
-
if (
|
|
17803
|
+
if (isAnimatedDisplay(display))
|
|
17769
17804
|
display.play();
|
|
17770
17805
|
}
|
|
17771
17806
|
function matchesTarget(renderer, detection, context) {
|
|
@@ -18466,6 +18501,11 @@ function observePixiContainerResize(container, resize) {
|
|
|
18466
18501
|
* sample it wants, hands it to `presentSample`, and Pixi's ticker paints the
|
|
18467
18502
|
* scene every frame.
|
|
18468
18503
|
*
|
|
18504
|
+
* A still frame is pulled once and then renders on change the way push
|
|
18505
|
+
* presentation does, with the sample it presents as one more change. The
|
|
18506
|
+
* ticker runs only while a looping GIF region or a loading spinner is on the
|
|
18507
|
+
* frame.
|
|
18508
|
+
*
|
|
18469
18509
|
* Push presentation engages when the opened source carries a presented-frame
|
|
18470
18510
|
* channel. The producer then decides which frame is on screen and announces it,
|
|
18471
18511
|
* so the ticker paints nothing and every render is an explicit answer to a
|
|
@@ -18496,6 +18536,18 @@ async function createPixiMediaScene(options) {
|
|
|
18496
18536
|
let maxTextureSide;
|
|
18497
18537
|
const maxTextureSize = () => (maxTextureSide ??= queryMaxTextureSize(app.renderer));
|
|
18498
18538
|
const frameChannel = options.presentedFrames;
|
|
18539
|
+
/**
|
|
18540
|
+
* A still frame has nothing to play, so it renders on change exactly as push
|
|
18541
|
+
* presentation does. The ticker runs only while something on the frame
|
|
18542
|
+
* animates by the clock.
|
|
18543
|
+
*/
|
|
18544
|
+
const stillFrame = frameChannel === undefined && options.stillFrame === true;
|
|
18545
|
+
const rendersOnChange = frameChannel !== undefined || stillFrame;
|
|
18546
|
+
let stillFrameTickerRunning = false;
|
|
18547
|
+
/** The content of the `previewOverlay` answer the overlay on screen drew. */
|
|
18548
|
+
let drawnPreviewOverlayContent = null;
|
|
18549
|
+
/** The editing state the overlay on screen was drawn from. */
|
|
18550
|
+
let drawnEditingState;
|
|
18499
18551
|
let currentFocusStyle = options.focusStyle ?? null;
|
|
18500
18552
|
let currentLabelStyle = options.labelStyle ?? null;
|
|
18501
18553
|
let currentMaskStyle = options.maskStyle ?? null;
|
|
@@ -18736,7 +18788,7 @@ async function createPixiMediaScene(options) {
|
|
|
18736
18788
|
: undefined;
|
|
18737
18789
|
await app.init({
|
|
18738
18790
|
autoDensity: true,
|
|
18739
|
-
autoStart:
|
|
18791
|
+
autoStart: !rendersOnChange,
|
|
18740
18792
|
backgroundColor: options.backgroundColor ?? 0x111111,
|
|
18741
18793
|
/* Only the video-engine media path supplies a presented-frame channel, so
|
|
18742
18794
|
* which reader opens the clip is also what decides which backend draws. */
|
|
@@ -18755,7 +18807,10 @@ async function createPixiMediaScene(options) {
|
|
|
18755
18807
|
if (!stagingContext) {
|
|
18756
18808
|
throw new Error("Unable to create staging canvas context.");
|
|
18757
18809
|
}
|
|
18758
|
-
const renderScheduler = createSceneRenderScheduler(() =>
|
|
18810
|
+
const renderScheduler = createSceneRenderScheduler(() => {
|
|
18811
|
+
app.render();
|
|
18812
|
+
syncStillFrameTicker();
|
|
18813
|
+
});
|
|
18759
18814
|
const maskBrushPreview = options.maskBrush
|
|
18760
18815
|
? createPixiMaskBrushPreview({
|
|
18761
18816
|
CanvasSource,
|
|
@@ -18764,7 +18819,7 @@ async function createPixiMediaScene(options) {
|
|
|
18764
18819
|
Sprite,
|
|
18765
18820
|
Texture,
|
|
18766
18821
|
preview: options.maskBrush,
|
|
18767
|
-
onInvalidate:
|
|
18822
|
+
onInvalidate: rendersOnChange ? renderNow : undefined,
|
|
18768
18823
|
})
|
|
18769
18824
|
: undefined;
|
|
18770
18825
|
let appliedPresentation;
|
|
@@ -18933,6 +18988,17 @@ async function createPixiMediaScene(options) {
|
|
|
18933
18988
|
labelLayer?.drawFrame(currentMediaTime, viewportScale);
|
|
18934
18989
|
renderNow();
|
|
18935
18990
|
}
|
|
18991
|
+
// With no ticker to pick it up, an edit the host drives itself, such as a
|
|
18992
|
+
// cancel or a commit, reaches the screen through this notification. A
|
|
18993
|
+
// pointer gesture draws and renders its own state before the microtask
|
|
18994
|
+
// runs, which then finds nothing left to draw or render.
|
|
18995
|
+
if (rendersOnChange) {
|
|
18996
|
+
queueMicrotask(() => {
|
|
18997
|
+
if (isDestroyed || isPresenting)
|
|
18998
|
+
return;
|
|
18999
|
+
renderOnChange();
|
|
19000
|
+
});
|
|
19001
|
+
}
|
|
18936
19002
|
});
|
|
18937
19003
|
const handleKeyDown = (event) => {
|
|
18938
19004
|
if (event.key === "Tab" && interactionLayer) {
|
|
@@ -19122,15 +19188,17 @@ async function createPixiMediaScene(options) {
|
|
|
19122
19188
|
renderScene();
|
|
19123
19189
|
};
|
|
19124
19190
|
let unsubscribeDetectionTimeline;
|
|
19125
|
-
if (
|
|
19126
|
-
frameChannel.onPresentedFrame(handlePresentedFrame);
|
|
19191
|
+
if (rendersOnChange) {
|
|
19127
19192
|
document.addEventListener?.("visibilitychange", handleVisibilityChange);
|
|
19128
19193
|
// A detection load landing moves what the window covers exactly as a cook
|
|
19129
19194
|
// landing does. Nothing else brings that to a resting playhead: the layers
|
|
19130
19195
|
// read the timeline only while drawing, and this is what makes the draw.
|
|
19131
19196
|
unsubscribeDetectionTimeline = options.detectionTimeline.subscribe?.(handlePreparedWindowChange);
|
|
19132
19197
|
}
|
|
19133
|
-
|
|
19198
|
+
if (frameChannel) {
|
|
19199
|
+
frameChannel.onPresentedFrame(handlePresentedFrame);
|
|
19200
|
+
}
|
|
19201
|
+
else if (!stillFrame) {
|
|
19134
19202
|
app.ticker.add(updateMediaSceneFit);
|
|
19135
19203
|
app.ticker.add(drawAnnotationOverlayNow);
|
|
19136
19204
|
app.ticker.add(tickFocusLayer);
|
|
@@ -19200,7 +19268,9 @@ async function createPixiMediaScene(options) {
|
|
|
19200
19268
|
advanceFocus: () => undefined,
|
|
19201
19269
|
drawAnnotationOverlay: frameChannel
|
|
19202
19270
|
? presentLayers.drawAnnotationOverlay
|
|
19203
|
-
:
|
|
19271
|
+
: stillFrame
|
|
19272
|
+
? (mediaTime) => drawAnnotationOverlay(mediaTime, overlayNow())
|
|
19273
|
+
: () => undefined,
|
|
19204
19274
|
};
|
|
19205
19275
|
// A viewport-style redraw covers the layers whose geometry is a function of
|
|
19206
19276
|
// the viewport scale. Mask outlines reuse the artifact already on screen;
|
|
@@ -19250,7 +19320,7 @@ async function createPixiMediaScene(options) {
|
|
|
19250
19320
|
};
|
|
19251
19321
|
return {
|
|
19252
19322
|
getRenderCount() {
|
|
19253
|
-
return
|
|
19323
|
+
return rendersOnChange ? renderScheduler.getRenderCount() : null;
|
|
19254
19324
|
},
|
|
19255
19325
|
getPreparedAnnotationWindow() {
|
|
19256
19326
|
return frameChannel ? annotationWindow.getSnapshot() : null;
|
|
@@ -19363,6 +19433,7 @@ async function createPixiMediaScene(options) {
|
|
|
19363
19433
|
stagingTexture?.update();
|
|
19364
19434
|
const { boxState, regionState } = drawFramePresentLayers(annotationDrawLayers, sample.timestamp);
|
|
19365
19435
|
updateMediaSceneFit();
|
|
19436
|
+
renderOnChange();
|
|
19366
19437
|
return {
|
|
19367
19438
|
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
19368
19439
|
duration: sample.duration,
|
|
@@ -19378,6 +19449,7 @@ async function createPixiMediaScene(options) {
|
|
|
19378
19449
|
});
|
|
19379
19450
|
const { boxState, regionState } = drawFramePresentLayers(measuredAnnotationDrawLayers, sample.timestamp);
|
|
19380
19451
|
const fitMs = measure(updateMediaSceneFit);
|
|
19452
|
+
renderOnChange();
|
|
19381
19453
|
return {
|
|
19382
19454
|
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
19383
19455
|
duration: sample.duration,
|
|
@@ -20256,7 +20328,7 @@ async function createPixiMediaScene(options) {
|
|
|
20256
20328
|
return;
|
|
20257
20329
|
}
|
|
20258
20330
|
const { boxState, regionState } = drawAnnotationFrame(currentMediaTime);
|
|
20259
|
-
if (
|
|
20331
|
+
if (rendersOnChange) {
|
|
20260
20332
|
const signature = describeSceneRender();
|
|
20261
20333
|
if (forceRender)
|
|
20262
20334
|
renderScheduler.render(signature);
|
|
@@ -20313,6 +20385,13 @@ async function createPixiMediaScene(options) {
|
|
|
20313
20385
|
labelLayer?.drawCreationPreview(editingState?.kind === AnnotationGestureStateKind.Creating
|
|
20314
20386
|
? editingState.preview
|
|
20315
20387
|
: null, mediaTime, viewportScale);
|
|
20388
|
+
const previewOverlay = options.previewOverlay?.() ?? null;
|
|
20389
|
+
drawnEditingState = editingState;
|
|
20390
|
+
// Only a scene that renders on change compares signatures.
|
|
20391
|
+
if (rendersOnChange) {
|
|
20392
|
+
drawnPreviewOverlayContent =
|
|
20393
|
+
describePreviewOverlayContent(previewOverlay);
|
|
20394
|
+
}
|
|
20316
20395
|
annotationOverlayLayer.draw({
|
|
20317
20396
|
frame,
|
|
20318
20397
|
marquee: interactionState?.marqueeRect ?? null,
|
|
@@ -20320,7 +20399,7 @@ async function createPixiMediaScene(options) {
|
|
|
20320
20399
|
mediaWidth,
|
|
20321
20400
|
now: overlayNowMs,
|
|
20322
20401
|
pointer: interactionState?.pointerPoint ?? null,
|
|
20323
|
-
previewOverlay
|
|
20402
|
+
previewOverlay,
|
|
20324
20403
|
selectedDetectionIds: (interactionState?.selectedPicks ?? []).flatMap(({ detection }) => (detection.id === undefined ? [] : [detection.id])),
|
|
20325
20404
|
viewportScale,
|
|
20326
20405
|
visibility: currentVisibility,
|
|
@@ -20328,8 +20407,9 @@ async function createPixiMediaScene(options) {
|
|
|
20328
20407
|
}
|
|
20329
20408
|
/**
|
|
20330
20409
|
* Milliseconds for animations that are not annotation geometry. A pull scene
|
|
20331
|
-
* has a ticker and reads the wall clock
|
|
20332
|
-
*
|
|
20410
|
+
* has a ticker and reads the wall clock, as does a still frame while its
|
|
20411
|
+
* ticker runs; a push scene has the presented media time and nothing else,
|
|
20412
|
+
* so its animations move only when the media does.
|
|
20333
20413
|
*/
|
|
20334
20414
|
function overlayNow() {
|
|
20335
20415
|
return frameChannel ? currentMediaTime * MILLISECONDS_PER_SECOND : now();
|
|
@@ -20378,8 +20458,21 @@ async function createPixiMediaScene(options) {
|
|
|
20378
20458
|
focusLayer.tick(focusAnimationTimeMs);
|
|
20379
20459
|
}
|
|
20380
20460
|
function renderScene() {
|
|
20461
|
+
drawOverlayForEditingState();
|
|
20381
20462
|
renderScheduler.render(describeSceneRender());
|
|
20382
20463
|
}
|
|
20464
|
+
/**
|
|
20465
|
+
* The render signature names the editing state, so a render must not record
|
|
20466
|
+
* a state the overlay has not drawn: the redraw that follows would match the
|
|
20467
|
+
* recorded signature and be skipped, leaving the old preview on screen.
|
|
20468
|
+
*/
|
|
20469
|
+
function drawOverlayForEditingState() {
|
|
20470
|
+
const editingState = options.editingEngine?.getState();
|
|
20471
|
+
if (editingState === undefined || editingState === drawnEditingState) {
|
|
20472
|
+
return;
|
|
20473
|
+
}
|
|
20474
|
+
drawAnnotationOverlay(currentMediaTime, overlayNow());
|
|
20475
|
+
}
|
|
20383
20476
|
/**
|
|
20384
20477
|
* Renders a present at most once per display refresh. Frames arrive as
|
|
20385
20478
|
* messages from the producer's own thread, so a main thread that falls
|
|
@@ -20405,15 +20498,46 @@ async function createPixiMediaScene(options) {
|
|
|
20405
20498
|
presentFrameNow(deferred);
|
|
20406
20499
|
}
|
|
20407
20500
|
function renderNow() {
|
|
20408
|
-
if (
|
|
20501
|
+
if (rendersOnChange)
|
|
20409
20502
|
renderScene();
|
|
20410
20503
|
}
|
|
20411
20504
|
function renderOnChange() {
|
|
20412
|
-
if (!
|
|
20505
|
+
if (!rendersOnChange)
|
|
20413
20506
|
return;
|
|
20414
20507
|
settleFocusAnimation();
|
|
20508
|
+
drawOverlayForEditingState();
|
|
20415
20509
|
renderScheduler.renderOnChange(describeSceneRender());
|
|
20416
20510
|
}
|
|
20511
|
+
/**
|
|
20512
|
+
* Runs the ticker on a still frame for as long as something on it moves by
|
|
20513
|
+
* the clock: a looping GIF region or a loading spinner. Pixi renders on
|
|
20514
|
+
* every tick it runs, and the overlay redraws so the spinner turns. Each
|
|
20515
|
+
* render checks again, so the ticker stops with the render that removes the
|
|
20516
|
+
* last of them.
|
|
20517
|
+
*/
|
|
20518
|
+
function syncStillFrameTicker() {
|
|
20519
|
+
if (!stillFrame || isDestroyed)
|
|
20520
|
+
return;
|
|
20521
|
+
const animating = hasPresentedSample &&
|
|
20522
|
+
(regionLayer.hasAnimatedDisplay() || hasLoadingDetections());
|
|
20523
|
+
if (animating === stillFrameTickerRunning)
|
|
20524
|
+
return;
|
|
20525
|
+
stillFrameTickerRunning = animating;
|
|
20526
|
+
if (animating) {
|
|
20527
|
+
app.ticker.add(drawAnnotationOverlayNow);
|
|
20528
|
+
app.ticker.start();
|
|
20529
|
+
}
|
|
20530
|
+
else {
|
|
20531
|
+
app.ticker.stop();
|
|
20532
|
+
app.ticker.remove(drawAnnotationOverlayNow);
|
|
20533
|
+
}
|
|
20534
|
+
}
|
|
20535
|
+
function hasLoadingDetections() {
|
|
20536
|
+
const loading = currentVisibility?.loadingDetectionIds;
|
|
20537
|
+
if (!loading)
|
|
20538
|
+
return false;
|
|
20539
|
+
return "size" in loading ? loading.size > 0 : loading.length > 0;
|
|
20540
|
+
}
|
|
20417
20541
|
/**
|
|
20418
20542
|
* Everything a render would put on screen, as values that can be compared.
|
|
20419
20543
|
* Styles are opaque resolvers, so they are compared by identity: handing the
|
|
@@ -20451,6 +20575,9 @@ async function createPixiMediaScene(options) {
|
|
|
20451
20575
|
interactionState?.marqueeRect,
|
|
20452
20576
|
options.editingEngine?.getState(),
|
|
20453
20577
|
options.editingEngine ? interactionState?.pointerPoint : null,
|
|
20578
|
+
// The host's preview is read when the overlay draws, and a host may edit
|
|
20579
|
+
// its arrays in place, so its content is what tells a redraw it changed.
|
|
20580
|
+
drawnPreviewOverlayContent,
|
|
20454
20581
|
];
|
|
20455
20582
|
}
|
|
20456
20583
|
/**
|
|
@@ -20789,6 +20916,37 @@ function elapsedSince(start) {
|
|
|
20789
20916
|
function now() {
|
|
20790
20917
|
return globalThis.performance?.now?.() ?? Date.now();
|
|
20791
20918
|
}
|
|
20919
|
+
/**
|
|
20920
|
+
* A key for what a preview overlay draws, hashed from its coordinates rather
|
|
20921
|
+
* than its arrays, so a preview the host edits in place still changes it.
|
|
20922
|
+
* Coordinates count to a 256th of a media pixel, finer than anything drawn.
|
|
20923
|
+
*/
|
|
20924
|
+
function describePreviewOverlayContent(overlay) {
|
|
20925
|
+
if (!overlay)
|
|
20926
|
+
return null;
|
|
20927
|
+
let hash = 0x811c9dc5;
|
|
20928
|
+
const mix = (value) => {
|
|
20929
|
+
hash = Math.imul(hash ^ value, 0x01000193);
|
|
20930
|
+
};
|
|
20931
|
+
const mixPoint = (point) => {
|
|
20932
|
+
mix(Math.round(point.x * 256));
|
|
20933
|
+
mix(Math.round(point.y * 256));
|
|
20934
|
+
};
|
|
20935
|
+
for (const polygons of [overlay.hoverPolygons, overlay.draftPolygons]) {
|
|
20936
|
+
mix(polygons?.length ?? -1);
|
|
20937
|
+
for (const polygon of polygons ?? []) {
|
|
20938
|
+
mix(polygon.length);
|
|
20939
|
+
for (const point of polygon)
|
|
20940
|
+
mixPoint(point);
|
|
20941
|
+
}
|
|
20942
|
+
}
|
|
20943
|
+
mix(overlay.points?.length ?? -1);
|
|
20944
|
+
for (const point of overlay.points ?? []) {
|
|
20945
|
+
mixPoint(point);
|
|
20946
|
+
mix(point.positive ? 1 : 0);
|
|
20947
|
+
}
|
|
20948
|
+
return hash;
|
|
20949
|
+
}
|
|
20792
20950
|
/**
|
|
20793
20951
|
* Fills the container and paints the margin around the canvas in the colour the
|
|
20794
20952
|
* renderer clears to. The scene owns this element so the canvas is positioned
|