react-native-image-stitcher 0.1.0
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/CHANGELOG.md +96 -0
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +189 -0
- package/RNImageStitcher.podspec +76 -0
- package/android/build.gradle +224 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +124 -0
- package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
- package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
- package/cpp/ar_frame_pose.h +63 -0
- package/cpp/keyframe_gate.cpp +927 -0
- package/cpp/keyframe_gate.hpp +240 -0
- package/cpp/stitcher.cpp +2207 -0
- package/cpp/stitcher.hpp +275 -0
- package/dist/ar/useARSession.d.ts +102 -0
- package/dist/ar/useARSession.js +133 -0
- package/dist/camera/ARCameraView.d.ts +93 -0
- package/dist/camera/ARCameraView.js +170 -0
- package/dist/camera/Camera.d.ts +134 -0
- package/dist/camera/Camera.js +688 -0
- package/dist/camera/CameraShutter.d.ts +80 -0
- package/dist/camera/CameraShutter.js +237 -0
- package/dist/camera/CameraView.d.ts +65 -0
- package/dist/camera/CameraView.js +117 -0
- package/dist/camera/CaptureControlsBar.d.ts +87 -0
- package/dist/camera/CaptureControlsBar.js +82 -0
- package/dist/camera/CaptureHeader.d.ts +62 -0
- package/dist/camera/CaptureHeader.js +81 -0
- package/dist/camera/CapturePreview.d.ts +70 -0
- package/dist/camera/CapturePreview.js +188 -0
- package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
- package/dist/camera/CaptureStatusOverlay.js +326 -0
- package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
- package/dist/camera/CaptureThumbnailStrip.js +177 -0
- package/dist/camera/IncrementalPanGuide.d.ts +83 -0
- package/dist/camera/IncrementalPanGuide.js +267 -0
- package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
- package/dist/camera/PanoramaBandOverlay.js +399 -0
- package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
- package/dist/camera/PanoramaConfirmModal.js +128 -0
- package/dist/camera/PanoramaGuidance.d.ts +79 -0
- package/dist/camera/PanoramaGuidance.js +246 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
- package/dist/camera/PanoramaSettingsModal.js +611 -0
- package/dist/camera/ViewportCropOverlay.d.ts +46 -0
- package/dist/camera/ViewportCropOverlay.js +67 -0
- package/dist/camera/useCapture.d.ts +111 -0
- package/dist/camera/useCapture.js +160 -0
- package/dist/camera/useDeviceOrientation.d.ts +48 -0
- package/dist/camera/useDeviceOrientation.js +131 -0
- package/dist/camera/useVideoCapture.d.ts +79 -0
- package/dist/camera/useVideoCapture.js +151 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +39 -0
- package/dist/quality/normaliseOrientation.d.ts +36 -0
- package/dist/quality/normaliseOrientation.js +62 -0
- package/dist/quality/runQualityCheck.d.ts +41 -0
- package/dist/quality/runQualityCheck.js +98 -0
- package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
- package/dist/sensors/useIMUTranslationGate.js +235 -0
- package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
- package/dist/stitching/IncrementalStitcherView.js +157 -0
- package/dist/stitching/incremental.d.ts +930 -0
- package/dist/stitching/incremental.js +133 -0
- package/dist/stitching/stitchFrames.d.ts +55 -0
- package/dist/stitching/stitchFrames.js +56 -0
- package/dist/stitching/stitchVideo.d.ts +119 -0
- package/dist/stitching/stitchVideo.js +57 -0
- package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
- package/dist/stitching/useIncrementalJSDriver.js +199 -0
- package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
- package/dist/stitching/useIncrementalStitcher.js +172 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +15 -0
- package/ios/Package.swift +72 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
- package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
- package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
- package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
- package/package.json +73 -0
- package/react-native.config.js +34 -0
- package/scripts/opencv-version.txt +1 -0
- package/scripts/postinstall-fetch-binaries.js +286 -0
- package/src/ar/useARSession.ts +210 -0
- package/src/camera/.gitkeep +0 -0
- package/src/camera/ARCameraView.tsx +256 -0
- package/src/camera/Camera.tsx +1053 -0
- package/src/camera/CameraShutter.tsx +292 -0
- package/src/camera/CameraView.tsx +157 -0
- package/src/camera/CaptureControlsBar.tsx +204 -0
- package/src/camera/CaptureHeader.tsx +184 -0
- package/src/camera/CapturePreview.tsx +318 -0
- package/src/camera/CaptureStatusOverlay.tsx +391 -0
- package/src/camera/CaptureThumbnailStrip.tsx +277 -0
- package/src/camera/IncrementalPanGuide.tsx +328 -0
- package/src/camera/PanoramaBandOverlay.tsx +498 -0
- package/src/camera/PanoramaConfirmModal.tsx +206 -0
- package/src/camera/PanoramaGuidance.tsx +327 -0
- package/src/camera/PanoramaSettingsModal.tsx +1357 -0
- package/src/camera/ViewportCropOverlay.tsx +81 -0
- package/src/camera/useCapture.ts +279 -0
- package/src/camera/useDeviceOrientation.ts +140 -0
- package/src/camera/useVideoCapture.ts +236 -0
- package/src/index.ts +53 -0
- package/src/quality/.gitkeep +0 -0
- package/src/quality/normaliseOrientation.ts +79 -0
- package/src/quality/runQualityCheck.ts +131 -0
- package/src/sensors/useIMUTranslationGate.ts +347 -0
- package/src/stitching/.gitkeep +0 -0
- package/src/stitching/IncrementalStitcherView.tsx +198 -0
- package/src/stitching/incremental.ts +1021 -0
- package/src/stitching/stitchFrames.ts +88 -0
- package/src/stitching/stitchVideo.ts +153 -0
- package/src/stitching/useIncrementalJSDriver.ts +273 -0
- package/src/stitching/useIncrementalStitcher.ts +252 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// keyframe_gate.hpp — shared C++ port of KeyframeGate.swift.
|
|
4
|
+
//
|
|
5
|
+
// Why a shared port:
|
|
6
|
+
// The Swift KeyframeGate has been the production-quality V16-Phase-0
|
|
7
|
+
// gate on iOS for months. The Android side was running a frame-
|
|
8
|
+
// counter MVP placeholder, producing different keyframe sets and
|
|
9
|
+
// therefore different panoramas across platforms. Porting to
|
|
10
|
+
// shared C++ that both iOS (via Obj-C++ bridge) and Android (via
|
|
11
|
+
// JNI) call into eliminates the divergence and makes panorama
|
|
12
|
+
// composition platform-identical.
|
|
13
|
+
//
|
|
14
|
+
// Algorithm summary (1:1 with KeyframeGate.swift comments):
|
|
15
|
+
// For each candidate frame, project its 4 image corners onto the
|
|
16
|
+
// latched ARKit/ARCore plane via ray-plane intersection. Compute
|
|
17
|
+
// convex-polygon overlap with the previous accepted keyframe's
|
|
18
|
+
// plane-projected polygon via Sutherland-Hodgman clipping.
|
|
19
|
+
// new_content_fraction = 1 − intersection_area / current_frame_area.
|
|
20
|
+
// Accept iff new_content_fraction ≥ overlapThreshold (default 0.4)
|
|
21
|
+
// and acceptedCount < maxCount (default 6).
|
|
22
|
+
//
|
|
23
|
+
// No-plane fallback:
|
|
24
|
+
// When the host can't supply a plane (planeSource=Disabled, or
|
|
25
|
+
// plane lock hasn't latched yet), the gate falls back to comparing
|
|
26
|
+
// the camera-forward angular delta from the last accepted keyframe.
|
|
27
|
+
// new_content = angularDelta / min(fovH, fovV). Same accept rule.
|
|
28
|
+
//
|
|
29
|
+
// First/last frames:
|
|
30
|
+
// - First frame is always accepted (anchor).
|
|
31
|
+
// - markNextFrameAsLast() arms a one-shot "next frame is the
|
|
32
|
+
// trailing keyframe, force-accept". Set on shutter-release path
|
|
33
|
+
// so we don't truncate the right edge of the scan.
|
|
34
|
+
//
|
|
35
|
+
// Threading: NOT thread-safe. Caller must serialise evaluate() /
|
|
36
|
+
// reset() / markNextFrameAsLast() / setters. On both platforms this
|
|
37
|
+
// is already guaranteed by the engine's work queue serial dispatch.
|
|
38
|
+
|
|
39
|
+
#pragma once
|
|
40
|
+
|
|
41
|
+
#include <cstdint>
|
|
42
|
+
#include "ar_frame_pose.h"
|
|
43
|
+
|
|
44
|
+
namespace retailens {
|
|
45
|
+
|
|
46
|
+
/// Strategy selector — chooses how the gate measures "new content" for
|
|
47
|
+
/// the accept decision. Set via `setStrategy(...)` between captures;
|
|
48
|
+
/// not safe to flip mid-capture.
|
|
49
|
+
///
|
|
50
|
+
/// Pose — the original V16-Phase-0 algorithm: project frame corners
|
|
51
|
+
/// onto the latched plane, compare polygon overlap. Falls
|
|
52
|
+
/// back to camera-forward angular delta when projection
|
|
53
|
+
/// degenerates (no plane / behind-camera intersection).
|
|
54
|
+
/// Cheap but oversensitive when the latched plane covers a
|
|
55
|
+
/// small fraction of the visible frame: 6 cm of physical
|
|
56
|
+
/// motion at 2.7 m perpDist on a 0.4×1.6 m plane produced 6
|
|
57
|
+
/// accepts in 1 s (Ram report 2026-05-13).
|
|
58
|
+
///
|
|
59
|
+
/// Flow — V16 fix-attempt-8/A2: sparse Lucas-Kanade optical flow.
|
|
60
|
+
/// Detect Shi-Tomasi corners once per accepted keyframe;
|
|
61
|
+
/// track them into each incoming frame with
|
|
62
|
+
/// `calcOpticalFlowPyrLK`; accept when the median pan-axis
|
|
63
|
+
/// displacement crosses `overlapThreshold * frame_dim` (the
|
|
64
|
+
/// same 0.40 threshold as Pose, with directly-translatable
|
|
65
|
+
/// semantics: 40 % of frame dim = 40 % new content). Costs
|
|
66
|
+
/// one detect (~15–25 ms) per accept + one track (~1–3 ms)
|
|
67
|
+
/// per frame. Scale-invariant — independent of plane size.
|
|
68
|
+
/// Falls back to angular delta when feature tracking fails
|
|
69
|
+
/// (texture-poor scene / motion exceeds pyramid window).
|
|
70
|
+
///
|
|
71
|
+
/// Default is `Pose` to keep behaviour unchanged when this field
|
|
72
|
+
/// arrives unset. The TS/host side flips to `Flow` via settings in a
|
|
73
|
+
/// follow-up commit.
|
|
74
|
+
enum class GateStrategy : int32_t {
|
|
75
|
+
Pose = 0,
|
|
76
|
+
Flow = 1,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/// 1:1 with KeyframeGate.swift's `reason` strings. An int enum
|
|
80
|
+
/// crosses the bridge cleanly; iOS/Android wrappers map back to
|
|
81
|
+
/// strings for telemetry.
|
|
82
|
+
enum class KeyframeGateDecisionReason : int32_t {
|
|
83
|
+
// Accept reasons
|
|
84
|
+
AcceptDisabled = 0, // "gate-disabled" — pass-through when !enabled
|
|
85
|
+
AcceptForceLast = 1, // "force-last" — shutter-release force-accept
|
|
86
|
+
AcceptFirstOnPlane = 2, // "first-anchored-on-plane"
|
|
87
|
+
AcceptFirstNoPlane = 3, // "first-no-plane"
|
|
88
|
+
AcceptOk = 4, // "ok" — plane path
|
|
89
|
+
AcceptOkAngular = 5, // "ok-angular" — no-plane fallback
|
|
90
|
+
AcceptProjectionDegenerate = 6, // "projection-degenerate"
|
|
91
|
+
AcceptCurrentAreaZero = 7, // "current-area-zero"
|
|
92
|
+
AcceptNoPoseYet = 8, // "no-pose-yet" — defensive
|
|
93
|
+
// Reject reasons
|
|
94
|
+
RejectMaxReached = 9, // "max-reached"
|
|
95
|
+
RejectOverlapTooHigh = 10, // "overlap-too-high"
|
|
96
|
+
RejectOverlapTooHighAngular = 11, // "overlap-too-high (angular)"
|
|
97
|
+
// Flow strategy reasons (V16 A2)
|
|
98
|
+
AcceptOkFlow = 12, // "ok-flow" — flow displacement-percentile crossed threshold
|
|
99
|
+
AcceptFirstFlow = 13, // "first-flow" — first frame under flow strategy
|
|
100
|
+
RejectOverlapTooHighFlow = 14, // "overlap-too-high (flow)"
|
|
101
|
+
AcceptFlowTranslation = 15, // "ok-flow-translation" — translation since last accept exceeded flowMaxTranslationM (force-accept even when novelty < threshold)
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
struct KeyframeGateDecision {
|
|
105
|
+
bool accept;
|
|
106
|
+
KeyframeGateDecisionReason reason;
|
|
107
|
+
double newContentFraction; // -1.0 when not computed (disabled / first / force-last)
|
|
108
|
+
int32_t acceptedCount;
|
|
109
|
+
int32_t maxCount;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/// Opaque handle — implementation kept in keyframe_gate.cpp via pImpl.
|
|
113
|
+
/// Lifetime is owned by the host wrapper (Obj-C++ object on iOS, JNI
|
|
114
|
+
/// `Long` handle on Android).
|
|
115
|
+
class KeyframeGate {
|
|
116
|
+
public:
|
|
117
|
+
KeyframeGate();
|
|
118
|
+
~KeyframeGate();
|
|
119
|
+
|
|
120
|
+
// Non-copyable, non-movable — the pImpl is heap-owned and the
|
|
121
|
+
// bridges manage lifetime explicitly.
|
|
122
|
+
KeyframeGate(const KeyframeGate&) = delete;
|
|
123
|
+
KeyframeGate& operator=(const KeyframeGate&) = delete;
|
|
124
|
+
KeyframeGate(KeyframeGate&&) = delete;
|
|
125
|
+
KeyframeGate& operator=(KeyframeGate&&) = delete;
|
|
126
|
+
|
|
127
|
+
// ── Settings (called between captures, not per-frame) ─────────
|
|
128
|
+
void setEnabled(bool enabled);
|
|
129
|
+
void setOverlapThreshold(double threshold); // [0, 1]; default 0.4
|
|
130
|
+
void setMaxCount(int32_t maxCount); // ≥ 1; default 6
|
|
131
|
+
void markNextFrameAsLast(); // one-shot, consumed by next evaluate()
|
|
132
|
+
void reset(); // clears acceptedCount, lastCorners, planeCached AND flow state
|
|
133
|
+
|
|
134
|
+
// ── Strategy selector + Flow params (V16 A2) ──────────────────
|
|
135
|
+
// Flow params are only consulted when strategy == Flow. Safe to
|
|
136
|
+
// set when strategy == Pose; they'll be live the moment strategy
|
|
137
|
+
// flips. Defaults below are stable on iPhone 13/14/15 testing.
|
|
138
|
+
void setStrategy(GateStrategy strategy);
|
|
139
|
+
GateStrategy getStrategy() const;
|
|
140
|
+
void setFlowMaxCorners(int32_t maxCorners); // ≥ 30; default 150
|
|
141
|
+
void setFlowQualityLevel(double quality); // (0, 1]; default 0.01
|
|
142
|
+
void setFlowMinDistance(double minDistance); // ≥ 1.0; default 10.0 (working-resolution pixels)
|
|
143
|
+
/// V16 — translation budget for the Flow strategy. When the camera's
|
|
144
|
+
/// 3D Euclidean translation since the last accepted keyframe exceeds
|
|
145
|
+
/// this value (metres), the gate force-accepts the current frame
|
|
146
|
+
/// even if novelty < `overlapThreshold`. Purpose: prevent the
|
|
147
|
+
/// upstream stitcher's matcher from being fed two views with so
|
|
148
|
+
/// much parallax that even an affine match-confidence collapses
|
|
149
|
+
/// (Ram report 2026-05-13: captures with 25-60 cm of camera
|
|
150
|
+
/// translation between keyframes produced validPairs=0 even after
|
|
151
|
+
/// the matcher swap to AffineBestOf2NearestMatcher). Default
|
|
152
|
+
/// 0.0 = disabled (back-compat). Sensible production setting:
|
|
153
|
+
/// 0.08 (8 cm). Clamped to ≥ 0.0.
|
|
154
|
+
void setFlowMaxTranslationM(double metres);
|
|
155
|
+
/// V16 — percentile (in [0.5, 0.99]) used to aggregate the tracked
|
|
156
|
+
/// features' absolute displacements into a per-axis novelty estimate.
|
|
157
|
+
/// Default 0.85. Pre-V16 used median (0.50); the median under-
|
|
158
|
+
/// reports novelty when the user has rotated the camera enough that
|
|
159
|
+
/// the LEADING EDGE of new content is visible but most-existing-
|
|
160
|
+
/// features have moved less than half a frame. 85th-percentile picks
|
|
161
|
+
/// up the leading-edge motion sooner and lines up better with the
|
|
162
|
+
/// user's visual perception of "new content visible". Clamped to
|
|
163
|
+
/// [0.5, 0.99].
|
|
164
|
+
void setFlowNoveltyPercentile(double percentile);
|
|
165
|
+
|
|
166
|
+
/// 2026-05-14 — disable the angular-delta fallback that the gate
|
|
167
|
+
/// otherwise uses when (a) the pose-strategy's plane-projection
|
|
168
|
+
/// is unavailable / degenerate, or (b) the flow-strategy's KLT
|
|
169
|
+
/// tracking fails. When `true`, every angular-fallback path
|
|
170
|
+
/// returns `RejectOverlapTooHighAngular` regardless of the actual
|
|
171
|
+
/// pose, so the only path that can accept a frame is the strategy's
|
|
172
|
+
/// primary signal (plane-overlap for Pose, flow-displacement for
|
|
173
|
+
/// Flow).
|
|
174
|
+
///
|
|
175
|
+
/// Set this to `true` in non-AR mode (captureSource ∈ {wide,
|
|
176
|
+
/// ultrawide}) where pose data is missing / IMU-derived — the
|
|
177
|
+
/// angular calc would produce nonsense in that environment.
|
|
178
|
+
/// Default `false` (back-compat — AR mode uses the fallback).
|
|
179
|
+
void setDisableAngularFallback(bool disabled);
|
|
180
|
+
|
|
181
|
+
// ── Per-frame evaluation ──────────────────────────────────────
|
|
182
|
+
//
|
|
183
|
+
// Two overloads:
|
|
184
|
+
//
|
|
185
|
+
// evaluate(pose, plane)
|
|
186
|
+
// Backward-compat entry point. Used by callers that don't
|
|
187
|
+
// (yet) supply per-frame image data. Always runs the Pose
|
|
188
|
+
// strategy regardless of `getStrategy()` — Flow needs the
|
|
189
|
+
// image to compute novelty, so Pose is the only thing it
|
|
190
|
+
// CAN do here. Android JNI today calls this; the iOS side
|
|
191
|
+
// moves to `evaluateWithFrame` in commit 2.
|
|
192
|
+
//
|
|
193
|
+
// evaluateWithFrame(pose, plane, grayData, width, height, stride)
|
|
194
|
+
// Strategy-aware entry point. When strategy == Flow, runs
|
|
195
|
+
// sparse-flow novelty on the supplied grayscale frame.
|
|
196
|
+
// When strategy == Pose, behaves identically to `evaluate`
|
|
197
|
+
// (the frame data is ignored — no extra cost beyond the
|
|
198
|
+
// caller's pixel-buffer → grayscale conversion, which the
|
|
199
|
+
// caller can elide by checking strategy first).
|
|
200
|
+
//
|
|
201
|
+
// @param pose camera pose + intrinsics for the frame
|
|
202
|
+
// @param latchedPlane optional plane transform (column-major 4×4
|
|
203
|
+
// matching ARKit ARPlaneAnchor convention).
|
|
204
|
+
// Pass nullptr if no plane is latched →
|
|
205
|
+
// gate uses angular-delta fallback.
|
|
206
|
+
// @param grayData pointer to grayscale 8-bit pixel data.
|
|
207
|
+
// Non-owning; data only needs to be valid
|
|
208
|
+
// for the duration of this call.
|
|
209
|
+
// @param width/height frame dimensions in pixels.
|
|
210
|
+
// @param stride bytes per row (usually equal to width;
|
|
211
|
+
// larger when the underlying buffer is
|
|
212
|
+
// padded).
|
|
213
|
+
KeyframeGateDecision evaluate(const Pose& pose,
|
|
214
|
+
const PlaneTransform* latchedPlane);
|
|
215
|
+
KeyframeGateDecision evaluateWithFrame(const Pose& pose,
|
|
216
|
+
const PlaneTransform* latchedPlane,
|
|
217
|
+
const uint8_t* grayData,
|
|
218
|
+
int32_t width,
|
|
219
|
+
int32_t height,
|
|
220
|
+
int32_t stride);
|
|
221
|
+
|
|
222
|
+
// ── State accessors (read-only, post-evaluate) ────────────────
|
|
223
|
+
int32_t getAcceptedCount() const;
|
|
224
|
+
int32_t getMaxCount() const;
|
|
225
|
+
bool isEnabled() const;
|
|
226
|
+
|
|
227
|
+
private:
|
|
228
|
+
struct Impl;
|
|
229
|
+
Impl* pImpl_;
|
|
230
|
+
|
|
231
|
+
// Shared angular-delta evaluation path. Used by §4 (no plane was
|
|
232
|
+
// ever latched) and §5's degenerate branches (V16 Phase 2 fix —
|
|
233
|
+
// projection-degenerate / current-area-zero fall back here rather
|
|
234
|
+
// than accepting blindly, which used to burst-accept every frame
|
|
235
|
+
// and corrupt the gate cap).
|
|
236
|
+
static KeyframeGateDecision evaluateAngularFallback(
|
|
237
|
+
Impl& s, const Pose& pose);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
} // namespace retailens
|