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,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* IncrementalPanGuide — V12.11 Step 2 (item 2 of the four-step
|
|
5
|
+
* preview-UX overhaul).
|
|
6
|
+
*
|
|
7
|
+
* Apple-pano-style "keep the arrow on the line" pan guide for the
|
|
8
|
+
* incremental capture flow.
|
|
9
|
+
*
|
|
10
|
+
* ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
11
|
+
* ▲
|
|
12
|
+
* ● ← marker drifts perpendicular
|
|
13
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← guide line
|
|
14
|
+
* (along pan axis)
|
|
15
|
+
* ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
16
|
+
*
|
|
17
|
+
* The user is told (via the band overlay's caption) to pan along
|
|
18
|
+
* one axis. This guide gives them live feedback on how WELL they
|
|
19
|
+
* are obeying that instruction:
|
|
20
|
+
*
|
|
21
|
+
* • A solid GUIDE LINE runs along the intended pan axis (the
|
|
22
|
+
* "ideal" pan path) across the centre of the screen.
|
|
23
|
+
* • A circular MARKER sits on the line. As the user tilts the
|
|
24
|
+
* device perpendicular to the pan axis, the marker drifts
|
|
25
|
+
* OFF the line by an amount proportional to the integrated
|
|
26
|
+
* perpendicular rotation since capture started.
|
|
27
|
+
* • The marker's COLOUR signals how far off they've drifted —
|
|
28
|
+
* green (on track), amber (slight drift), red (significant
|
|
29
|
+
* drift). Same colour scale as PanoramaGuidance for
|
|
30
|
+
* consistency.
|
|
31
|
+
*
|
|
32
|
+
* Why integrate perpendicular rotation rather than absolute device
|
|
33
|
+
* angle? We don't care about the user's starting orientation (they
|
|
34
|
+
* may begin a horizontal pan with the phone tilted slightly down
|
|
35
|
+
* — that's fine). We care about CHANGE during the pan. So we
|
|
36
|
+
* reset the integral to 0 at `active=true` and accumulate the
|
|
37
|
+
* perpendicular gyro rate from there. Drift over a typical 5–10 s
|
|
38
|
+
* capture is well within tolerance (a few degrees max).
|
|
39
|
+
*
|
|
40
|
+
* Why not consume ARKit pose? Two reasons:
|
|
41
|
+
* 1. The vision-camera (non-AR) capture path doesn't have ARKit
|
|
42
|
+
* pose at all — gyro is the only common signal.
|
|
43
|
+
* 2. We want this guide to work the SAME way regardless of
|
|
44
|
+
* whether AR mode is on or off — gyro keeps the UX
|
|
45
|
+
* consistent.
|
|
46
|
+
*
|
|
47
|
+
* Performance: gyro at 30 Hz, integration is two multiplies per
|
|
48
|
+
* sample, marker position update via setState. Negligible.
|
|
49
|
+
*/
|
|
50
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
51
|
+
if (k2 === undefined) k2 = k;
|
|
52
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
53
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
54
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
55
|
+
}
|
|
56
|
+
Object.defineProperty(o, k2, desc);
|
|
57
|
+
}) : (function(o, m, k, k2) {
|
|
58
|
+
if (k2 === undefined) k2 = k;
|
|
59
|
+
o[k2] = m[k];
|
|
60
|
+
}));
|
|
61
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
62
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
63
|
+
}) : function(o, v) {
|
|
64
|
+
o["default"] = v;
|
|
65
|
+
});
|
|
66
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
67
|
+
var ownKeys = function(o) {
|
|
68
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
69
|
+
var ar = [];
|
|
70
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
71
|
+
return ar;
|
|
72
|
+
};
|
|
73
|
+
return ownKeys(o);
|
|
74
|
+
};
|
|
75
|
+
return function (mod) {
|
|
76
|
+
if (mod && mod.__esModule) return mod;
|
|
77
|
+
var result = {};
|
|
78
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
79
|
+
__setModuleDefault(result, mod);
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
})();
|
|
83
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
84
|
+
exports.IncrementalPanGuide = IncrementalPanGuide;
|
|
85
|
+
const react_1 = __importStar(require("react"));
|
|
86
|
+
const react_native_1 = require("react-native");
|
|
87
|
+
const react_native_sensors_1 = require("react-native-sensors");
|
|
88
|
+
const useDeviceOrientation_1 = require("./useDeviceOrientation");
|
|
89
|
+
const COLOR_GOOD = '#34C759';
|
|
90
|
+
const COLOR_WARN = '#FFCC00';
|
|
91
|
+
const COLOR_BAD = '#FF3B30';
|
|
92
|
+
function bucketFor(absRad, warn, bad) {
|
|
93
|
+
if (absRad <= warn)
|
|
94
|
+
return 'good';
|
|
95
|
+
if (absRad <= bad)
|
|
96
|
+
return 'warn';
|
|
97
|
+
return 'bad';
|
|
98
|
+
}
|
|
99
|
+
function colorFor(bucket) {
|
|
100
|
+
switch (bucket) {
|
|
101
|
+
case 'good': return COLOR_GOOD;
|
|
102
|
+
case 'warn': return COLOR_WARN;
|
|
103
|
+
case 'bad': return COLOR_BAD;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Pan axis vs orientation — same convention as
|
|
108
|
+
* PanoramaBandOverlay (and PanoramaGuidance):
|
|
109
|
+
* • portrait → pan horizontal → guide line is HORIZONTAL across
|
|
110
|
+
* the screen, marker drifts vertically.
|
|
111
|
+
* • landscape → pan vertical → guide line is VERTICAL down the
|
|
112
|
+
* screen, marker drifts horizontally.
|
|
113
|
+
*/
|
|
114
|
+
function panAxis(orientation) {
|
|
115
|
+
return orientation === 'landscape-left' || orientation === 'landscape-right'
|
|
116
|
+
? 'vertical'
|
|
117
|
+
: 'horizontal';
|
|
118
|
+
}
|
|
119
|
+
function IncrementalPanGuide({ active, pixelsPerRad = 600, maxTravelPx = 80, warnRad = 0.05, // ≈ 2.9°
|
|
120
|
+
badRad = 0.10, // ≈ 5.7°
|
|
121
|
+
style, }) {
|
|
122
|
+
const orientation = (0, useDeviceOrientation_1.useDeviceOrientation)();
|
|
123
|
+
const axis = panAxis(orientation);
|
|
124
|
+
// Integrated PERPENDICULAR rotation since `active` flipped true.
|
|
125
|
+
// Held in a ref so per-sample updates don't re-render. We re-
|
|
126
|
+
// render only when the displayed marker offset (rounded to int
|
|
127
|
+
// px) or colour bucket changes.
|
|
128
|
+
const perpIntegralRef = (0, react_1.useRef)(0);
|
|
129
|
+
const lastSampleTsRef = (0, react_1.useRef)(null);
|
|
130
|
+
// Displayed marker offset in px (signed; sign tells us which
|
|
131
|
+
// side of the line the user has drifted to). Capped to
|
|
132
|
+
// ±maxTravelPx. Rounded to int to keep React reconciliation
|
|
133
|
+
// cheap (one re-render per integer pixel of travel).
|
|
134
|
+
const [markerOffsetPx, setMarkerOffsetPx] = (0, react_1.useState)(0);
|
|
135
|
+
const [bucket, setBucket] = (0, react_1.useState)('good');
|
|
136
|
+
const lastBucketRef = (0, react_1.useRef)('good');
|
|
137
|
+
const lastOffsetRef = (0, react_1.useRef)(0);
|
|
138
|
+
(0, react_1.useEffect)(() => {
|
|
139
|
+
// Inactive: reset integral and on-screen state so the next
|
|
140
|
+
// capture starts at a clean zero.
|
|
141
|
+
if (!active) {
|
|
142
|
+
perpIntegralRef.current = 0;
|
|
143
|
+
lastSampleTsRef.current = null;
|
|
144
|
+
lastBucketRef.current = 'good';
|
|
145
|
+
lastOffsetRef.current = 0;
|
|
146
|
+
setMarkerOffsetPx(0);
|
|
147
|
+
setBucket('good');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
(0, react_native_sensors_1.setUpdateIntervalForType)(react_native_sensors_1.SensorTypes.gyroscope, 33);
|
|
151
|
+
let subscription = react_native_sensors_1.gyroscope.subscribe({
|
|
152
|
+
next: ({ x, y }) => {
|
|
153
|
+
// The PERPENDICULAR axis is the orthogonal one to the pan:
|
|
154
|
+
// portrait pans horizontally → pan axis = gyro Y →
|
|
155
|
+
// perp axis = gyro X (pitch — head up/down).
|
|
156
|
+
// landscape pans vertically → pan axis = gyro X →
|
|
157
|
+
// perp axis = gyro Y (yaw — head left/right).
|
|
158
|
+
const perpRate = axis === 'horizontal' ? x : y;
|
|
159
|
+
// Δt in seconds since the previous sample. Use Date.now()
|
|
160
|
+
// rather than the upstream's `timestamp` field because
|
|
161
|
+
// react-native-sensors emits seconds on iOS and ms on
|
|
162
|
+
// Android — Date.now() is unambiguous and reliable enough
|
|
163
|
+
// at 30 Hz integration.
|
|
164
|
+
const nowMs = Date.now();
|
|
165
|
+
const last = lastSampleTsRef.current ?? nowMs;
|
|
166
|
+
const dt = Math.min(0.1, Math.max(0, (nowMs - last) / 1000));
|
|
167
|
+
lastSampleTsRef.current = nowMs;
|
|
168
|
+
perpIntegralRef.current += perpRate * dt;
|
|
169
|
+
// Convert integral (radians) → px offset, clamp.
|
|
170
|
+
const rawPx = perpIntegralRef.current * pixelsPerRad;
|
|
171
|
+
const clampedPx = Math.max(-maxTravelPx, Math.min(maxTravelPx, rawPx));
|
|
172
|
+
const roundedPx = Math.round(clampedPx);
|
|
173
|
+
// setState only when the rounded px changed — keeps the
|
|
174
|
+
// re-render rate to ~max(60, gyro rate) fps and usually
|
|
175
|
+
// far less since drift is gradual.
|
|
176
|
+
if (roundedPx !== lastOffsetRef.current) {
|
|
177
|
+
lastOffsetRef.current = roundedPx;
|
|
178
|
+
setMarkerOffsetPx(roundedPx);
|
|
179
|
+
}
|
|
180
|
+
const nextBucket = bucketFor(Math.abs(perpIntegralRef.current), warnRad, badRad);
|
|
181
|
+
if (nextBucket !== lastBucketRef.current) {
|
|
182
|
+
lastBucketRef.current = nextBucket;
|
|
183
|
+
setBucket(nextBucket);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
error: (err) => {
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.warn('[IncrementalPanGuide] gyroscope error', err);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
return () => {
|
|
192
|
+
subscription?.unsubscribe();
|
|
193
|
+
subscription = null;
|
|
194
|
+
};
|
|
195
|
+
}, [active, axis, pixelsPerRad, maxTravelPx, warnRad, badRad]);
|
|
196
|
+
if (!active)
|
|
197
|
+
return null;
|
|
198
|
+
const markerColor = colorFor(bucket);
|
|
199
|
+
if (axis === 'horizontal') {
|
|
200
|
+
// Portrait: guide line spans horizontally across the centre,
|
|
201
|
+
// marker drifts vertically. Marker is a small circle outlined
|
|
202
|
+
// in the bucket colour.
|
|
203
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [styles.rootCentered, style] },
|
|
204
|
+
react_1.default.createElement(react_native_1.View, { style: styles.lineHorizontal }),
|
|
205
|
+
react_1.default.createElement(react_native_1.View, { style: [
|
|
206
|
+
styles.marker,
|
|
207
|
+
{ borderColor: markerColor },
|
|
208
|
+
// Vertical translation from the line. Negative px =
|
|
209
|
+
// device tilted UP from start → marker UP.
|
|
210
|
+
{ transform: [{ translateY: markerOffsetPx }] },
|
|
211
|
+
] })));
|
|
212
|
+
}
|
|
213
|
+
// Landscape: guide line is VERTICAL down the centre, marker
|
|
214
|
+
// drifts horizontally.
|
|
215
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [styles.rootCentered, style] },
|
|
216
|
+
react_1.default.createElement(react_native_1.View, { style: styles.lineVertical }),
|
|
217
|
+
react_1.default.createElement(react_native_1.View, { style: [
|
|
218
|
+
styles.marker,
|
|
219
|
+
{ borderColor: markerColor },
|
|
220
|
+
{ transform: [{ translateX: markerOffsetPx }] },
|
|
221
|
+
] })));
|
|
222
|
+
}
|
|
223
|
+
// Layout: position the entire guide as an absolute, full-screen
|
|
224
|
+
// overlay so the line spans the camera viewport edge-to-edge.
|
|
225
|
+
// The marker is rendered absolutely at the centre and translated
|
|
226
|
+
// by markerOffsetPx (signed) along the perpendicular axis.
|
|
227
|
+
const styles = react_native_1.StyleSheet.create({
|
|
228
|
+
rootCentered: {
|
|
229
|
+
position: 'absolute',
|
|
230
|
+
top: 0,
|
|
231
|
+
left: 0,
|
|
232
|
+
right: 0,
|
|
233
|
+
bottom: 0,
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
justifyContent: 'center',
|
|
236
|
+
},
|
|
237
|
+
// Horizontal line across the screen, centred vertically. Thin
|
|
238
|
+
// (1.5 px), white-translucent, dashed via repeating segments
|
|
239
|
+
// would be nicer but a flat translucent rectangle is sufficient
|
|
240
|
+
// for V12.11 Step 2 — leaves room for a polish pass later.
|
|
241
|
+
lineHorizontal: {
|
|
242
|
+
position: 'absolute',
|
|
243
|
+
left: 0,
|
|
244
|
+
right: 0,
|
|
245
|
+
height: 1.5,
|
|
246
|
+
backgroundColor: 'rgba(255, 255, 255, 0.55)',
|
|
247
|
+
},
|
|
248
|
+
// Vertical line down the screen, centred horizontally.
|
|
249
|
+
lineVertical: {
|
|
250
|
+
position: 'absolute',
|
|
251
|
+
top: 0,
|
|
252
|
+
bottom: 0,
|
|
253
|
+
width: 1.5,
|
|
254
|
+
backgroundColor: 'rgba(255, 255, 255, 0.55)',
|
|
255
|
+
},
|
|
256
|
+
// Marker — a 22 px ringed circle. Filled with a translucent
|
|
257
|
+
// dark so the bucket-coloured ring reads cleanly against any
|
|
258
|
+
// camera-feed background.
|
|
259
|
+
marker: {
|
|
260
|
+
width: 22,
|
|
261
|
+
height: 22,
|
|
262
|
+
borderRadius: 11,
|
|
263
|
+
borderWidth: 3,
|
|
264
|
+
backgroundColor: 'rgba(0, 0, 0, 0.35)',
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
//# sourceMappingURL=IncrementalPanGuide.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PanoramaBandOverlay — V16 Phase 2 (merged band + strip).
|
|
3
|
+
*
|
|
4
|
+
* SINGLE source of truth for the live "progress strip" that sits on
|
|
5
|
+
* top of the camera preview during a panorama hold. Replaces what
|
|
6
|
+
* was previously TWO components rendered side-by-side:
|
|
7
|
+
*
|
|
8
|
+
* 1. live per-keyframe thumbnail strip — fed by accepted-frame URIs
|
|
9
|
+
* (batch-keyframe engine) OR by
|
|
10
|
+
* periodic vision-camera snapshots.
|
|
11
|
+
* 2. <PanoramaBandOverlay /> — a single cumulative-panorama
|
|
12
|
+
* thumbnail with a "fill ratio"
|
|
13
|
+
* bar growing with the pan.
|
|
14
|
+
*
|
|
15
|
+
* The split made the UI visually noisy AND made it differ between
|
|
16
|
+
* platforms when one side emitted keyframe events and the other
|
|
17
|
+
* didn't. V16 Phase 2 collapses them into ONE component that:
|
|
18
|
+
*
|
|
19
|
+
* • Renders a horizontally-scrolling list of per-keyframe
|
|
20
|
+
* thumbnails when `frameUris` is non-empty (batch-keyframe
|
|
21
|
+
* mode). Each frame the KeyframeGate accepts shows up as a
|
|
22
|
+
* mini-thumb.
|
|
23
|
+
*
|
|
24
|
+
* • Falls back to a SINGLE cumulative-panorama thumbnail (the
|
|
25
|
+
* V12.14.9 fill-ratio behaviour) when `frameUris` is empty —
|
|
26
|
+
* i.e. the live-stitching engines that don't surface
|
|
27
|
+
* per-keyframe paths. This preserves the existing visual for
|
|
28
|
+
* hybrid / firstwins / firstwins-rectilinear engines.
|
|
29
|
+
*
|
|
30
|
+
* • Edge-pinned to the BOTTOM of the camera area in portrait, and
|
|
31
|
+
* to the user's RIGHT in landscape (which corresponds to
|
|
32
|
+
* JS-bottom under the app's portrait-lock). Both anchors keep
|
|
33
|
+
* the band out of the centre of the scene the operator is
|
|
34
|
+
* framing.
|
|
35
|
+
*
|
|
36
|
+
* • Trailing arrow points along the pan axis (→ in portrait, ← in
|
|
37
|
+
* landscape-left's user perception). Arrow always sits at the
|
|
38
|
+
* pan-END side, so the LATEST keyframe abuts the arrow.
|
|
39
|
+
*
|
|
40
|
+
* • Auto-scrolls a `<ScrollView>` so the latest keyframe stays
|
|
41
|
+
* visible regardless of how many frames have been accumulated.
|
|
42
|
+
*
|
|
43
|
+
* Empty-state intentional non-design:
|
|
44
|
+
* The KeyframeGate force-accepts the FIRST frame of every capture
|
|
45
|
+
* (see C++ `AcceptFirstAnchoredOnPlane` / `AcceptFirstNoPlane` in
|
|
46
|
+
* keyframe_gate.cpp). By the time the operator's perceived "the
|
|
47
|
+
* band appeared", we already have at least one thumb/snapshot in
|
|
48
|
+
* flight. We therefore don't render any "no frames yet"
|
|
49
|
+
* placeholder — the empty period is sub-perceptual.
|
|
50
|
+
*
|
|
51
|
+
* Why this component is in react-native-image-stitcher (not host):
|
|
52
|
+
* It's the same JSX shipped to iOS and Android. Differences in
|
|
53
|
+
* what shows up come only from native-emitted data
|
|
54
|
+
* (`state.batchKeyframeThumbnailPath` / `state.panoramaPath`),
|
|
55
|
+
* not from per-platform component code. That's exactly the parity
|
|
56
|
+
* property the user wants: "the UI should not differ between iOS
|
|
57
|
+
* and Android — it's the same UI reused".
|
|
58
|
+
*/
|
|
59
|
+
import React from 'react';
|
|
60
|
+
import type { IncrementalState } from '../stitching/incremental';
|
|
61
|
+
/**
|
|
62
|
+
* 2026-05-18 (Issue #3 fix) — 4-way capture orientation classifier.
|
|
63
|
+
* Replaces the 2-way `state.isLandscape` boolean which couldn't
|
|
64
|
+
* distinguish landscape-LEFT (home button on user's right) from
|
|
65
|
+
* landscape-RIGHT (home button on user's left). Required because
|
|
66
|
+
* the JS-coordinate mapping to user-perceived directions inverts
|
|
67
|
+
* between the two landscape rotations — `flexDirection: 'row'`
|
|
68
|
+
* gives oldest-at-user-top in landscape-LEFT but oldest-at-user-
|
|
69
|
+
* bottom in landscape-RIGHT, so we need to branch the layout.
|
|
70
|
+
*/
|
|
71
|
+
export type BandCaptureOrientation = 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';
|
|
72
|
+
export interface PanoramaBandOverlayProps {
|
|
73
|
+
/**
|
|
74
|
+
* Latest engine state. Pass `useIncrementalStitcher().state`.
|
|
75
|
+
* Used for single-thumb fallback URI and fill-ratio when no
|
|
76
|
+
* per-keyframe URIs are provided. `state.isLandscape` is now
|
|
77
|
+
* superseded by `captureOrientation` below for layout selection.
|
|
78
|
+
*/
|
|
79
|
+
state: IncrementalState | null;
|
|
80
|
+
/**
|
|
81
|
+
* Optional list of per-keyframe thumbnail URIs accumulated by the
|
|
82
|
+
* host as the native batch-keyframe engine emits
|
|
83
|
+
* `batchKeyframeThumbnailPath` events. When non-empty, the band
|
|
84
|
+
* renders these as a scrolling mini-thumb strip. When empty or
|
|
85
|
+
* undefined, the band falls back to the single cumulative-panorama
|
|
86
|
+
* thumbnail (legacy live-engine visual).
|
|
87
|
+
*
|
|
88
|
+
* Caller should cap the list length itself if needed (e.g. the
|
|
89
|
+
* AuditCaptureScreen already trims at 24 entries). This component
|
|
90
|
+
* applies an internal hard cap as a safety net so a runaway
|
|
91
|
+
* emission doesn't blow up the scroll view.
|
|
92
|
+
*/
|
|
93
|
+
frameUris?: string[];
|
|
94
|
+
/**
|
|
95
|
+
* 2026-05-18 (Issue #3) — capture orientation passed from the host.
|
|
96
|
+
* Drives a 4-way layout switch so the band reads correctly in
|
|
97
|
+
* either landscape rotation (the 2-way `state.isLandscape` boolean
|
|
98
|
+
* collapses landscape-LEFT and landscape-RIGHT to the same render
|
|
99
|
+
* path, which inverts the user's perceived "oldest-top, grows
|
|
100
|
+
* down" intent in one of them). Pass
|
|
101
|
+
* `panoramaSettings.captureOrientation` from the host. Defaults
|
|
102
|
+
* to `'portrait'` when omitted (back-compat).
|
|
103
|
+
*/
|
|
104
|
+
captureOrientation?: BandCaptureOrientation;
|
|
105
|
+
}
|
|
106
|
+
export declare function PanoramaBandOverlay({ state, frameUris, captureOrientation, }: PanoramaBandOverlayProps): React.JSX.Element | null;
|
|
107
|
+
//# sourceMappingURL=PanoramaBandOverlay.d.ts.map
|