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,252 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* useIncrementalStitcher — React hook driving the live panorama
|
|
4
|
+
* engine.
|
|
5
|
+
*
|
|
6
|
+
* Lifecycle:
|
|
7
|
+
* 1. Host calls `useARSession().start()` to put the AR session in
|
|
8
|
+
* tracking mode. (Works for AR-supported devices only — non-AR
|
|
9
|
+
* fallback comes in a later phase.)
|
|
10
|
+
* 2. Host calls `start()` from this hook. The native engine
|
|
11
|
+
* registers itself as the AR session's frame consumer.
|
|
12
|
+
* 3. Native emits a state event for every ARFrame the engine
|
|
13
|
+
* processes (~60 Hz, mostly trivially-skipped). The hook
|
|
14
|
+
* mirrors this into React state so a `<IncrementalStitcherView>`
|
|
15
|
+
* or any other consumer can render the live panorama + UX hints.
|
|
16
|
+
* 4. Host calls `finalize(outputPath)` when the user releases the
|
|
17
|
+
* shutter; resolves with the final panorama path + stats.
|
|
18
|
+
* 5. Host calls `cancel()` if the user dismisses the capture.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
22
|
+
import {
|
|
23
|
+
getIncrementalNativeModule,
|
|
24
|
+
incrementalStitcherIsAvailable,
|
|
25
|
+
subscribeIncrementalState,
|
|
26
|
+
IncrementalOutcome,
|
|
27
|
+
type IncrementalState,
|
|
28
|
+
type IncrementalStartOptions,
|
|
29
|
+
type IncrementalFinalizeResult,
|
|
30
|
+
} from './incremental';
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export type IncrementalHint =
|
|
34
|
+
| 'slow-down'
|
|
35
|
+
| 'scene-uniform'
|
|
36
|
+
| 'alignment-lost'
|
|
37
|
+
| 'tracking-poor'
|
|
38
|
+
| null;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export interface UseIncrementalStitcherReturn {
|
|
42
|
+
/** Whether the native engine is registered. False = no fallback wiring. */
|
|
43
|
+
isAvailable: boolean;
|
|
44
|
+
/** True between successful `start()` and `finalize()`/`cancel()`. */
|
|
45
|
+
isRunning: boolean;
|
|
46
|
+
/** Latest state pushed by the native engine, or null pre-start. */
|
|
47
|
+
state: IncrementalState | null;
|
|
48
|
+
/**
|
|
49
|
+
* Convenience: which UX hint to show, derived from the latest
|
|
50
|
+
* state.outcome. null when nothing should be shown (silent
|
|
51
|
+
* accepts, skips inside the overlap window).
|
|
52
|
+
*/
|
|
53
|
+
hint: IncrementalHint;
|
|
54
|
+
/**
|
|
55
|
+
* Convenience: 'high' | 'medium' | null based on the last accept.
|
|
56
|
+
* Drives confidence-ring rendering in the live preview.
|
|
57
|
+
*/
|
|
58
|
+
confidenceLevel: 'high' | 'medium' | null;
|
|
59
|
+
/** Begin a new capture. Throws if the AR session isn't running. */
|
|
60
|
+
start: (options?: IncrementalStartOptions) => Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* End the capture and write the final panorama. When `outputPath`
|
|
63
|
+
* is omitted or empty, the native side picks a path under the
|
|
64
|
+
* app's tmp directory and returns it in the result.
|
|
65
|
+
*
|
|
66
|
+
* `captureOrientation` (optional) — pass the user's CURRENT
|
|
67
|
+
* device orientation at finalize time. The engine prefers this
|
|
68
|
+
* value over the start-time snapshot for the bake-rotation pass,
|
|
69
|
+
* so cross-orientation captures (user opened screen in portrait,
|
|
70
|
+
* captured in landscape) bake correctly. Omit to keep the legacy
|
|
71
|
+
* behaviour (start-time orientation).
|
|
72
|
+
*/
|
|
73
|
+
finalize: (
|
|
74
|
+
outputPath?: string,
|
|
75
|
+
quality?: number,
|
|
76
|
+
captureOrientation?: string,
|
|
77
|
+
) => Promise<IncrementalFinalizeResult>;
|
|
78
|
+
/** Abort the capture without producing output. */
|
|
79
|
+
cancel: () => Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Map raw outcome → user-facing hint string. null = no banner.
|
|
85
|
+
*/
|
|
86
|
+
function outcomeToHint(outcome: IncrementalOutcome): IncrementalHint {
|
|
87
|
+
switch (outcome) {
|
|
88
|
+
case IncrementalOutcome.RejectedTooFar:
|
|
89
|
+
return 'slow-down';
|
|
90
|
+
case IncrementalOutcome.RejectedSceneUniform:
|
|
91
|
+
return 'scene-uniform';
|
|
92
|
+
case IncrementalOutcome.RejectedAlignmentLost:
|
|
93
|
+
return 'alignment-lost';
|
|
94
|
+
case IncrementalOutcome.SkippedTrackingPoor:
|
|
95
|
+
return 'tracking-poor';
|
|
96
|
+
case IncrementalOutcome.AcceptedHigh:
|
|
97
|
+
case IncrementalOutcome.AcceptedMedium:
|
|
98
|
+
case IncrementalOutcome.SkippedTooClose:
|
|
99
|
+
default:
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
function outcomeToConfidence(
|
|
106
|
+
outcome: IncrementalOutcome,
|
|
107
|
+
): 'high' | 'medium' | null {
|
|
108
|
+
if (outcome === IncrementalOutcome.AcceptedHigh) return 'high';
|
|
109
|
+
if (outcome === IncrementalOutcome.AcceptedMedium) return 'medium';
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
export function useIncrementalStitcher(): UseIncrementalStitcherReturn {
|
|
115
|
+
const native = getIncrementalNativeModule();
|
|
116
|
+
const isAvailable = incrementalStitcherIsAvailable();
|
|
117
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
118
|
+
const [state, setState] = useState<IncrementalState | null>(null);
|
|
119
|
+
|
|
120
|
+
// Keep the latest hint/confidence sticky for a few frames after a
|
|
121
|
+
// skip — otherwise the UI flickers since SkippedTooClose returns
|
|
122
|
+
// a "silent" outcome between every accept. We collapse this by
|
|
123
|
+
// only updating hint when the new outcome is itself a hint or an
|
|
124
|
+
// accept, leaving non-hint skips alone.
|
|
125
|
+
const lastHintRef = useRef<IncrementalHint>(null);
|
|
126
|
+
|
|
127
|
+
// Subscribe to native events on mount. The subscription itself
|
|
128
|
+
// is cheap; the native side gates `hasListeners` so events are
|
|
129
|
+
// only emitted when JS is listening.
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!native) return undefined;
|
|
132
|
+
const sub = subscribeIncrementalState((nextState) => {
|
|
133
|
+
// Sticky-snapshot merge: the native side emits a state event
|
|
134
|
+
// for EVERY ARFrame the engine processes (~60 Hz), most of
|
|
135
|
+
// which are SkippedTooClose with NO snapshot path. A naive
|
|
136
|
+
// `setState(nextState)` would wipe the panoramaPath to null
|
|
137
|
+
// 60 times per second, blanking the live preview between
|
|
138
|
+
// accepts. Keep the last-good snapshot fields so the PiP
|
|
139
|
+
// shows the most recent panorama continuously between accepts;
|
|
140
|
+
// other fields (outcome, confidence, hint) update normally.
|
|
141
|
+
setState((prev) => {
|
|
142
|
+
if (!nextState.panoramaPath && prev?.panoramaPath) {
|
|
143
|
+
return {
|
|
144
|
+
...nextState,
|
|
145
|
+
panoramaPath: prev.panoramaPath,
|
|
146
|
+
width: prev.width,
|
|
147
|
+
height: prev.height,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return nextState;
|
|
151
|
+
});
|
|
152
|
+
const newHint = outcomeToHint(nextState.outcome);
|
|
153
|
+
if (newHint !== null) {
|
|
154
|
+
lastHintRef.current = newHint;
|
|
155
|
+
} else if (
|
|
156
|
+
nextState.outcome === IncrementalOutcome.AcceptedHigh ||
|
|
157
|
+
nextState.outcome === IncrementalOutcome.AcceptedMedium
|
|
158
|
+
) {
|
|
159
|
+
// An accept clears any prior hint — operator's back on track.
|
|
160
|
+
lastHintRef.current = null;
|
|
161
|
+
}
|
|
162
|
+
// Else: SkippedTooClose etc. — leave the hint alone.
|
|
163
|
+
});
|
|
164
|
+
return () => sub?.remove();
|
|
165
|
+
}, [native]);
|
|
166
|
+
|
|
167
|
+
const start = useCallback(
|
|
168
|
+
async (options: IncrementalStartOptions = {}) => {
|
|
169
|
+
if (!native) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
'useIncrementalStitcher: IncrementalStitcher native '
|
|
172
|
+
+ 'module is not registered. Ensure the SDK pod has been '
|
|
173
|
+
+ 'rebuilt against the host app.',
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
await native.start(options);
|
|
177
|
+
setIsRunning(true);
|
|
178
|
+
setState(null);
|
|
179
|
+
lastHintRef.current = null;
|
|
180
|
+
},
|
|
181
|
+
[native],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const finalize = useCallback(
|
|
185
|
+
async (
|
|
186
|
+
outputPath?: string,
|
|
187
|
+
quality = 90,
|
|
188
|
+
captureOrientation?: string,
|
|
189
|
+
): Promise<IncrementalFinalizeResult> => {
|
|
190
|
+
if (!native) {
|
|
191
|
+
throw new Error('useIncrementalStitcher: native module unavailable');
|
|
192
|
+
}
|
|
193
|
+
const result = await native.finalize({
|
|
194
|
+
outputPath: outputPath ?? '',
|
|
195
|
+
quality,
|
|
196
|
+
// 2026-05-18 (iOS cross-orientation fix) — fresh orientation
|
|
197
|
+
// at finalize time. Engine uses this for bake-rotation
|
|
198
|
+
// instead of the start-time snapshot. Undefined = keep
|
|
199
|
+
// legacy start-time behaviour.
|
|
200
|
+
captureOrientation,
|
|
201
|
+
});
|
|
202
|
+
setIsRunning(false);
|
|
203
|
+
// Clear React state on finalize so the next start doesn't
|
|
204
|
+
// briefly show stale frame counts / hint banners from the
|
|
205
|
+
// previous capture. Without this, the IncrementalStitcherView
|
|
206
|
+
// displayed acceptedCount from the prior pan if a late event
|
|
207
|
+
// had already updated state.
|
|
208
|
+
setState(null);
|
|
209
|
+
lastHintRef.current = null;
|
|
210
|
+
return result;
|
|
211
|
+
},
|
|
212
|
+
[native],
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const cancel = useCallback(async () => {
|
|
216
|
+
if (!native) return;
|
|
217
|
+
await native.cancel();
|
|
218
|
+
setIsRunning(false);
|
|
219
|
+
setState(null);
|
|
220
|
+
lastHintRef.current = null;
|
|
221
|
+
}, [native]);
|
|
222
|
+
|
|
223
|
+
// Cleanup-on-unmount that actually works. The previous version
|
|
224
|
+
// captured `isRunning` from the initial render (false), so the
|
|
225
|
+
// cancel never fired. Reading from a ref keeps the latest
|
|
226
|
+
// value visible at unmount time.
|
|
227
|
+
const isRunningRef = useRef(false);
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
isRunningRef.current = isRunning;
|
|
230
|
+
}, [isRunning]);
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
return () => {
|
|
233
|
+
if (native && isRunningRef.current) {
|
|
234
|
+
native.cancel().catch(() => undefined);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
|
+
}, []);
|
|
239
|
+
|
|
240
|
+
const confidenceLevel = state ? outcomeToConfidence(state.outcome) : null;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
isAvailable,
|
|
244
|
+
isRunning,
|
|
245
|
+
state,
|
|
246
|
+
hint: lastHintRef.current,
|
|
247
|
+
confidenceLevel,
|
|
248
|
+
start,
|
|
249
|
+
finalize,
|
|
250
|
+
cancel,
|
|
251
|
+
};
|
|
252
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Internal type definitions for `react-native-image-stitcher`.
|
|
4
|
+
*
|
|
5
|
+
* These are NOT re-exported from `src/index.ts` — they are consumed
|
|
6
|
+
* by internal modules (`useCapture`, `runQualityCheck`) and adapted
|
|
7
|
+
* to the public surface (e.g., `CameraCaptureResult` in
|
|
8
|
+
* `src/camera/Camera.tsx`) before reaching consumers.
|
|
9
|
+
*
|
|
10
|
+
* If something here needs to become public, expose it deliberately
|
|
11
|
+
* from `src/index.ts` rather than encouraging deep imports.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ── Quality-check result types ────────────────────────────────────────────
|
|
15
|
+
// These are used by `quality/runQualityCheck.ts` and the internal
|
|
16
|
+
// `useCapture` hook. Algorithm details: Laplacian variance for blur,
|
|
17
|
+
// mean luminance for brightness.
|
|
18
|
+
|
|
19
|
+
export interface QualityThresholds {
|
|
20
|
+
/** Minimum Laplacian variance for blur detection */
|
|
21
|
+
minBlurScore: number;
|
|
22
|
+
/** Minimum brightness (0-255) */
|
|
23
|
+
minBrightness: number;
|
|
24
|
+
/** Maximum brightness (0-255) */
|
|
25
|
+
maxBrightness: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface QualityReport {
|
|
29
|
+
passed: boolean;
|
|
30
|
+
blurScore: number;
|
|
31
|
+
brightnessScore: number;
|
|
32
|
+
issues: QualityIssue[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface QualityIssue {
|
|
36
|
+
type: 'blur' | 'brightness_low' | 'brightness_high' | 'framing';
|
|
37
|
+
message: string;
|
|
38
|
+
severity: 'warning' | 'error';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Device metadata captured at takePhoto time ────────────────────────────
|
|
42
|
+
// Internal-only. `useCapture` populates this from native side; the
|
|
43
|
+
// public `CameraCaptureResult` (in Camera.tsx) doesn't include it
|
|
44
|
+
// because most public consumers don't want it and shouldn't pay for
|
|
45
|
+
// the round-trip-to-native cost in their type contract.
|
|
46
|
+
|
|
47
|
+
export interface DeviceMetadata {
|
|
48
|
+
platform: 'ios' | 'android';
|
|
49
|
+
osVersion: string;
|
|
50
|
+
deviceModel: string;
|
|
51
|
+
cameraId: string;
|
|
52
|
+
flashEnabled: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Internal CaptureResult shape returned by useCapture.takePhoto ─────────
|
|
56
|
+
// `Camera.tsx` adapts this into the public `CameraCaptureResult` (a
|
|
57
|
+
// discriminated union of photo + panorama) before emitting `onCapture`.
|
|
58
|
+
|
|
59
|
+
export interface CaptureResult {
|
|
60
|
+
/** Unique device-generated UUID */
|
|
61
|
+
deviceUuid: string;
|
|
62
|
+
/** Local file path to compressed image */
|
|
63
|
+
compressedUri: string;
|
|
64
|
+
/** Local file path to original image (if retained) */
|
|
65
|
+
originalUri?: string;
|
|
66
|
+
/** Image width in pixels, after EXIF orientation correction. */
|
|
67
|
+
width: number;
|
|
68
|
+
/** Image height in pixels, after EXIF orientation correction. */
|
|
69
|
+
height: number;
|
|
70
|
+
/** Whether this is a stitched panoramic image */
|
|
71
|
+
isStitched: boolean;
|
|
72
|
+
/** Capture timestamp (ISO 8601) */
|
|
73
|
+
capturedAt: string;
|
|
74
|
+
/** Quality check results (if enabled) */
|
|
75
|
+
qualityReport?: QualityReport;
|
|
76
|
+
/** Device metadata at capture time */
|
|
77
|
+
deviceMetadata: DeviceMetadata;
|
|
78
|
+
}
|