react-native-image-stitcher 0.5.0 → 0.6.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 +200 -8
- package/android/src/main/java/io/imagestitcher/rn/CvFlowGateFrameProcessor.kt +2 -2
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +120 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +266 -385
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +6 -3
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +17 -30
- package/dist/camera/Camera.d.ts +29 -27
- package/dist/camera/Camera.js +48 -79
- package/dist/index.d.ts +0 -2
- package/dist/index.js +4 -6
- package/dist/stitching/incremental.d.ts +10 -11
- package/dist/stitching/useFrameProcessorDriver.d.ts +7 -6
- package/dist/stitching/useFrameProcessorDriver.js +12 -11
- package/ios/Package.swift +35 -21
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +85 -206
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +0 -8
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +6 -126
- package/ios/Sources/RNImageStitcher/KeyframeGateFrameProcessor.mm +6 -6
- package/package.json +1 -1
- package/src/camera/Camera.tsx +83 -107
- package/src/index.ts +3 -8
- package/src/stitching/incremental.ts +10 -11
- package/src/stitching/useFrameProcessorDriver.ts +12 -11
- package/dist/stitching/useIncrementalJSDriver.d.ts +0 -74
- package/dist/stitching/useIncrementalJSDriver.js +0 -220
- package/src/stitching/useIncrementalJSDriver.ts +0 -297
package/src/camera/Camera.tsx
CHANGED
|
@@ -89,7 +89,6 @@ import {
|
|
|
89
89
|
subscribeIncrementalState,
|
|
90
90
|
type IncrementalState,
|
|
91
91
|
} from '../stitching/incremental';
|
|
92
|
-
import { useIncrementalJSDriver } from '../stitching/useIncrementalJSDriver';
|
|
93
92
|
import { useFrameProcessorDriver } from '../stitching/useFrameProcessorDriver';
|
|
94
93
|
import { useIncrementalStitcher } from '../stitching/useIncrementalStitcher';
|
|
95
94
|
import { useIMUTranslationGate } from '../sensors/useIMUTranslationGate';
|
|
@@ -235,6 +234,29 @@ export interface CameraProps {
|
|
|
235
234
|
showSettingsButton?: boolean;
|
|
236
235
|
style?: StyleProp<ViewStyle>;
|
|
237
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Which incremental stitcher engine to drive. Default
|
|
239
|
+
* `'batch-keyframe'` — collects accepted JPEGs and runs
|
|
240
|
+
* `cv::Stitcher` once at finalize time. This is the v0.4+
|
|
241
|
+
* production default and what the v0.5 Frame Processor migration
|
|
242
|
+
* exercises.
|
|
243
|
+
*
|
|
244
|
+
* Switch to a live engine (`'firstwins-rectilinear'` or
|
|
245
|
+
* `'hybrid'`) for low-latency in-flight stitching. Live engines
|
|
246
|
+
* exercise the F8.6 pixel-buffer ingest path (skipping the JPEG
|
|
247
|
+
* encode/decode round-trip; ~30–50 ms saved per accept) when the
|
|
248
|
+
* Frame Processor driver is active.
|
|
249
|
+
*
|
|
250
|
+
* See `docs/f8-frame-processor-plan.md` and the v0.5.0
|
|
251
|
+
* CHANGELOG for the trade-offs between batch-keyframe and live
|
|
252
|
+
* engines.
|
|
253
|
+
*/
|
|
254
|
+
engine?: 'batch-keyframe'
|
|
255
|
+
| 'hybrid'
|
|
256
|
+
| 'slitscan-rotate' | 'slitscan-both'
|
|
257
|
+
| 'firstwins' | 'firstwins-zoomed' | 'firstwins-rectilinear'
|
|
258
|
+
| 'slitscan';
|
|
259
|
+
|
|
238
260
|
/**
|
|
239
261
|
* Optional destination directory for captures. When set, the lib
|
|
240
262
|
* lands tap-photos at `${outputDir}/photo-${ts}.jpg` and panoramas
|
|
@@ -280,37 +302,20 @@ export interface CameraProps {
|
|
|
280
302
|
* Introduced for F8 (FrameProcessor port) — see
|
|
281
303
|
* `docs/f8-frame-processor-plan.md`.
|
|
282
304
|
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
* one-time console.warn — supplying your own worklet would race
|
|
289
|
-
* with the SDK's pixel-buffer feed.
|
|
305
|
+
* The SDK installs its own frame processor via
|
|
306
|
+
* `useFrameProcessorDriver`. Setting this prop is ignored with
|
|
307
|
+
* a one-time `console.warn` — supplying a host worklet would
|
|
308
|
+
* race with the SDK's pixel-buffer feed. Either remove the prop
|
|
309
|
+
* or fork the SDK if you genuinely need a custom worklet.
|
|
290
310
|
*
|
|
291
|
-
*
|
|
292
|
-
* * Default (modern non-AR): SDK owns the worklet, this prop
|
|
293
|
-
* is ignored.
|
|
294
|
-
* * `legacyDriver={true}`: SDK uses the old `useIncrementalJSDriver`
|
|
295
|
-
* (takeSnapshot path). Honoured for diagnostics or as an
|
|
296
|
-
* escape hatch.
|
|
297
|
-
* * AR mode: vision-camera Camera isn't mounted, this prop is
|
|
298
|
-
* irrelevant.
|
|
299
|
-
*/
|
|
300
|
-
frameProcessor?: ReadonlyFrameProcessor | DrawableFrameProcessor;
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Opt back into the legacy `useIncrementalJSDriver` for non-AR
|
|
304
|
-
* captures (the v0.4 path: `takeSnapshot` → JPEG → cache file →
|
|
305
|
-
* `IncrementalStitcher.processFrameAtPath`).
|
|
311
|
+
* AR mode is irrelevant: vision-camera's Camera isn't mounted.
|
|
306
312
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* specific issue with the new driver and need to ship a fix.
|
|
313
|
+
* (v0.5 had a `legacyDriver` escape hatch that routed back to
|
|
314
|
+
* `useIncrementalJSDriver`. That hook + prop were removed in
|
|
315
|
+
* v0.6 per the deprecation timeline announced in the v0.5.0
|
|
316
|
+
* CHANGELOG.)
|
|
312
317
|
*/
|
|
313
|
-
|
|
318
|
+
frameProcessor?: ReadonlyFrameProcessor | DrawableFrameProcessor;
|
|
314
319
|
}
|
|
315
320
|
|
|
316
321
|
|
|
@@ -586,7 +591,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
586
591
|
onFramesDropped,
|
|
587
592
|
onError,
|
|
588
593
|
frameProcessor: hostFrameProcessor,
|
|
589
|
-
|
|
594
|
+
engine = 'batch-keyframe',
|
|
590
595
|
} = props;
|
|
591
596
|
|
|
592
597
|
const insets = useSafeAreaInsets();
|
|
@@ -766,63 +771,49 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
766
771
|
},
|
|
767
772
|
});
|
|
768
773
|
|
|
769
|
-
//
|
|
770
|
-
// engine consumes frames from the ARSession stream
|
|
771
|
-
// hook stays idle.
|
|
774
|
+
// Frame Processor driver for non-AR captures (iOS + Android).
|
|
775
|
+
// In AR mode the engine consumes frames from the ARSession stream
|
|
776
|
+
// natively, so this hook stays idle.
|
|
772
777
|
//
|
|
773
778
|
// IMPORTANT: start()/stop() are called imperatively from the hold
|
|
774
779
|
// handlers below — NOT from a useEffect driven by statusPhase. The
|
|
775
780
|
// hook returns a fresh object identity on every render, and during
|
|
776
781
|
// a recording the engine emits IncrementalStateUpdate events that
|
|
777
|
-
// cause re-renders multiple times per second. An effect with
|
|
778
|
-
//
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
//
|
|
782
|
-
//
|
|
783
|
-
//
|
|
784
|
-
// imperative pattern (start on hold-start, stop on hold-end) avoids
|
|
785
|
-
// the re-render churn entirely.
|
|
786
|
-
const jsDriver = useIncrementalJSDriver();
|
|
787
|
-
// F8.3 — vision-camera Frame Processor variant. Always
|
|
788
|
-
// instantiated so we don't have conditional hook calls; only one
|
|
789
|
-
// of the two drivers actually .start()s per capture. Stop() on
|
|
790
|
-
// an idle driver is a no-op.
|
|
782
|
+
// cause re-renders multiple times per second. An effect with the
|
|
783
|
+
// driver in its deps would teardown + restart on every event,
|
|
784
|
+
// resetting the gyro accumulator (yaw/pitch) to zero each cycle.
|
|
785
|
+
// User-visible symptom: "only the first keyframe is accepted, every
|
|
786
|
+
// subsequent ingest sees pose=(0,0) and is rejected as a duplicate".
|
|
787
|
+
// The imperative pattern (start on hold-start, stop on hold-end)
|
|
788
|
+
// avoids the re-render churn entirely.
|
|
791
789
|
const fpDriver = useFrameProcessorDriver();
|
|
792
|
-
// Safety:
|
|
793
|
-
// mid-recording. Empty deps so this only fires on unmount.
|
|
790
|
+
// Safety: stop the driver if the component unmounts mid-recording.
|
|
794
791
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
795
|
-
useEffect(() => () => {
|
|
796
|
-
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
792
|
+
useEffect(() => () => { fpDriver.stop(); }, []);
|
|
793
|
+
|
|
794
|
+
// One-shot deprecation warning when the host supplies their own
|
|
795
|
+
// `frameProcessor` prop. Two worklets racing on the same
|
|
796
|
+
// producer thread would corrupt the engine's workQueue ordering,
|
|
797
|
+
// so the SDK's own worklet wins and the host's is silently
|
|
798
|
+
// ignored. (v0.5 had a `legacyDriver` opt-out for hosts that
|
|
799
|
+
// wanted to route around the SDK driver; that was removed in
|
|
800
|
+
// v0.6 along with `useIncrementalJSDriver`.)
|
|
804
801
|
const hostFrameProcessorIgnoredWarnedRef = useRef(false);
|
|
805
802
|
if (
|
|
806
803
|
hostFrameProcessor != null
|
|
807
|
-
&& !legacyDriver
|
|
808
804
|
&& !hostFrameProcessorIgnoredWarnedRef.current
|
|
809
805
|
) {
|
|
810
806
|
hostFrameProcessorIgnoredWarnedRef.current = true;
|
|
811
807
|
// eslint-disable-next-line no-console
|
|
812
808
|
console.warn(
|
|
813
809
|
'[react-native-image-stitcher] The `frameProcessor` prop on '
|
|
814
|
-
+ '<Camera> is ignored
|
|
815
|
-
+ '
|
|
816
|
-
+ '
|
|
810
|
+
+ '<Camera> is ignored — the SDK installs its own worklet '
|
|
811
|
+
+ 'via useFrameProcessorDriver. Remove the prop, or fork '
|
|
812
|
+
+ 'the SDK if you genuinely need a custom worklet.',
|
|
817
813
|
);
|
|
818
814
|
}
|
|
819
|
-
// The Frame Processor worklet
|
|
820
|
-
|
|
821
|
-
// 1. Legacy mode: honor the host's prop (or null).
|
|
822
|
-
// 2. Modern mode: SDK driver's worklet, regardless of host's prop.
|
|
823
|
-
const effectiveFrameProcessor = legacyDriver
|
|
824
|
-
? (hostFrameProcessor ?? null)
|
|
825
|
-
: fpDriver.frameProcessor;
|
|
815
|
+
// The Frame Processor worklet bound to vision-camera's Camera.
|
|
816
|
+
const effectiveFrameProcessor = fpDriver.frameProcessor;
|
|
826
817
|
|
|
827
818
|
// ── Subscribe to engine state for live keyframe thumbs ──────────
|
|
828
819
|
useEffect(() => {
|
|
@@ -879,17 +870,17 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
879
870
|
const accepted = incrementalState?.acceptedCount ?? 0;
|
|
880
871
|
if (accepted > lastAcceptedCountRef.current) {
|
|
881
872
|
lastAcceptedCountRef.current = accepted;
|
|
882
|
-
// F8.3 review-of-review (M3 revert):
|
|
883
|
-
// `legacyDriver` because the Frame
|
|
884
|
-
// consult `imuGate` for its own pose
|
|
885
|
-
// load-bearing side effect:
|
|
886
|
-
// IIR-integrator drift
|
|
887
|
-
// `imuGate.getTotalAbsMetres()` is read
|
|
888
|
-
//
|
|
873
|
+
// F8.3 review-of-review (M3 revert): an earlier draft gated
|
|
874
|
+
// this on the pre-v0.6 `legacyDriver` prop because the Frame
|
|
875
|
+
// Processor driver doesn't consult `imuGate` for its own pose
|
|
876
|
+
// synthesis. That ignored a load-bearing side effect:
|
|
877
|
+
// `imuGate.resetAnchor()` bounds the IIR-integrator drift
|
|
878
|
+
// window per-accept, and `imuGate.getTotalAbsMetres()` is read
|
|
879
|
+
// at finalize time as `imuTranslationMetres` into the native
|
|
889
880
|
// stitchMode auto-resolver (PANORAMA vs SCANS). Without the
|
|
890
881
|
// per-accept reset, long FP-driver captures let IIR drift
|
|
891
|
-
// compound → inflated metres → biased toward SCANS.
|
|
892
|
-
//
|
|
882
|
+
// compound → inflated metres → biased toward SCANS. Now fires
|
|
883
|
+
// for ALL non-AR captures (the only non-AR driver post-v0.6).
|
|
893
884
|
if (isNonAR) {
|
|
894
885
|
imuGate.resetAnchor();
|
|
895
886
|
}
|
|
@@ -1020,18 +1011,16 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1020
1011
|
snapshotEveryNAccepts: 1,
|
|
1021
1012
|
frameRotationDegrees: orientationRotation,
|
|
1022
1013
|
captureOrientation: deviceOrientation,
|
|
1023
|
-
//
|
|
1024
|
-
//
|
|
1025
|
-
//
|
|
1026
|
-
// ARSession-driven path.
|
|
1027
|
-
frameSourceMode: isNonAR
|
|
1028
|
-
? (legacyDriver ? 'jsDriver' : 'frameProcessor')
|
|
1029
|
-
: 'arSession',
|
|
1014
|
+
// Non-AR captures use the Frame Processor driver
|
|
1015
|
+
// (vision-camera producer-thread worklet → cv_flow_gate
|
|
1016
|
+
// plugin → IncrementalStitcher.consumeFrame). AR captures
|
|
1017
|
+
// use the ARSession-driven path.
|
|
1018
|
+
frameSourceMode: isNonAR ? 'frameProcessor' : 'arSession',
|
|
1030
1019
|
composeWidth: 1920,
|
|
1031
1020
|
composeHeight: 1080,
|
|
1032
1021
|
canvasWidth: 5000,
|
|
1033
1022
|
canvasHeight: 5000,
|
|
1034
|
-
engine
|
|
1023
|
+
engine,
|
|
1035
1024
|
config: panoramaSettingsToNativeConfig({
|
|
1036
1025
|
...settings,
|
|
1037
1026
|
captureSource: effectiveCaptureSource,
|
|
@@ -1042,21 +1031,13 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1042
1031
|
// matching comment on the per-accept reset useEffect above).
|
|
1043
1032
|
// Keep firing it on every capture start, not just legacy mode.
|
|
1044
1033
|
imuGate.resetAnchor();
|
|
1045
|
-
// Start the non-AR
|
|
1046
|
-
// ARSession so
|
|
1047
|
-
//
|
|
1048
|
-
//
|
|
1049
|
-
//
|
|
1050
|
-
// * Legacy: JS driver — `takeSnapshot` + `processFrameAtPath`
|
|
1051
|
-
// via the cameraRef.
|
|
1052
|
-
// Imperative-pattern rationale: see the useIncrementalJSDriver
|
|
1053
|
-
// comment above re. why this isn't a useEffect.
|
|
1034
|
+
// Start the Frame Processor driver for non-AR captures. AR
|
|
1035
|
+
// mode feeds natively from ARSession so the driver stays idle.
|
|
1036
|
+
// Imperative pattern (vs useEffect) because the driver's start
|
|
1037
|
+
// resets pose accumulators that should only fire at the
|
|
1038
|
+
// hold-start moment, not on every re-render.
|
|
1054
1039
|
if (isNonAR) {
|
|
1055
|
-
|
|
1056
|
-
jsDriver.start(visionCameraRef);
|
|
1057
|
-
} else {
|
|
1058
|
-
fpDriver.start();
|
|
1059
|
-
}
|
|
1040
|
+
fpDriver.start();
|
|
1060
1041
|
}
|
|
1061
1042
|
} catch (err) {
|
|
1062
1043
|
setStatusPhase('idle');
|
|
@@ -1076,9 +1057,8 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1076
1057
|
settings,
|
|
1077
1058
|
effectiveCaptureSource,
|
|
1078
1059
|
imuGate,
|
|
1079
|
-
jsDriver,
|
|
1080
1060
|
fpDriver,
|
|
1081
|
-
|
|
1061
|
+
engine,
|
|
1082
1062
|
onError,
|
|
1083
1063
|
]);
|
|
1084
1064
|
|
|
@@ -1087,10 +1067,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1087
1067
|
setStatusPhase('stitching');
|
|
1088
1068
|
// Stop pumping new frames before finalizing so the engine isn't
|
|
1089
1069
|
// racing the final cv::Stitcher pass against late-arriving
|
|
1090
|
-
// keyframes.
|
|
1091
|
-
// corresponding driver wasn't started (AR mode, or the inactive
|
|
1092
|
-
// driver in non-AR mode).
|
|
1093
|
-
jsDriver.stop();
|
|
1070
|
+
// keyframes. No-op in AR mode (the driver was never started).
|
|
1094
1071
|
fpDriver.stop();
|
|
1095
1072
|
try {
|
|
1096
1073
|
// Compose the panorama output path: host-controlled if
|
|
@@ -1168,7 +1145,6 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1168
1145
|
onFramesDropped,
|
|
1169
1146
|
onError,
|
|
1170
1147
|
recordingStartedAt,
|
|
1171
|
-
jsDriver,
|
|
1172
1148
|
fpDriver,
|
|
1173
1149
|
// F10 Phase 2 review N1 — these four were missing pre-fix. The
|
|
1174
1150
|
// callback reads `settings.debug` (to gate the stitchToast),
|
package/src/index.ts
CHANGED
|
@@ -177,14 +177,9 @@ export {
|
|
|
177
177
|
} from './stitching/incremental';
|
|
178
178
|
export type { IncrementalState } from './stitching/incremental';
|
|
179
179
|
export { useIncrementalStitcher } from './stitching/useIncrementalStitcher';
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
IncrementalJSDriverHandle,
|
|
184
|
-
} from './stitching/useIncrementalJSDriver';
|
|
185
|
-
// F8.3 — vision-camera Frame Processor variant of the non-AR
|
|
186
|
-
// driver. Preferred over `useIncrementalJSDriver` in v0.5+; the
|
|
187
|
-
// JS driver stays exported as a deprecated fallback until v0.6.
|
|
180
|
+
// vision-camera Frame Processor driver for non-AR captures. As
|
|
181
|
+
// of v0.6 the only non-AR driver exported (the legacy
|
|
182
|
+
// `useIncrementalJSDriver` was removed; was deprecated in v0.5).
|
|
188
183
|
export { useFrameProcessorDriver } from './stitching/useFrameProcessorDriver';
|
|
189
184
|
export type {
|
|
190
185
|
UseFrameProcessorDriverOptions,
|
|
@@ -200,23 +200,22 @@ export interface IncrementalStartOptions {
|
|
|
200
200
|
* bridge.start() requires `RNSARSession.start()` to
|
|
201
201
|
* have already been called.
|
|
202
202
|
*
|
|
203
|
-
* - 'jsDriver' — engine skips AR-session registration; JS
|
|
204
|
-
* feeds frames via `processFrameAtPath`. Use in iOS non-AR
|
|
205
|
-
* captures (vision-camera + gyro). No AR session required.
|
|
206
|
-
* LEGACY; deprecated in v0.5, removed in v0.6.
|
|
207
|
-
*
|
|
208
203
|
* - 'frameProcessor' (F8.3 iOS / F8.4 Android, v0.5+) — engine
|
|
209
204
|
* flips on `frameProcessorIngestEnabled` so the vision-camera
|
|
210
205
|
* Frame Processor plugin (`cv_flow_gate_process_frame`) can
|
|
211
206
|
* feed pixel data directly into the engine's gate path. iOS
|
|
212
207
|
* passes the `CVPixelBuffer` straight to `consumeFrame`;
|
|
213
|
-
* Android extracts the Y plane to a ByteArray and
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* captures driven by `useFrameProcessorDriver`. Pairs
|
|
217
|
-
* `Camera`'s default driver mode.
|
|
208
|
+
* Android extracts the Y plane to a ByteArray and (since
|
|
209
|
+
* F8.6, v0.5.1) routes live-engine ingest through
|
|
210
|
+
* `addFramePixelData` without a JPEG round-trip. Use in
|
|
211
|
+
* non-AR captures driven by `useFrameProcessorDriver`. Pairs
|
|
212
|
+
* with `Camera`'s default driver mode.
|
|
213
|
+
*
|
|
214
|
+
* `'jsDriver'` was removed in v0.6 (deprecated in v0.5). Hosts
|
|
215
|
+
* that used it should switch to `useFrameProcessorDriver` (or
|
|
216
|
+
* just let `<Camera>` use its default).
|
|
218
217
|
*/
|
|
219
|
-
frameSourceMode?: 'arSession' | '
|
|
218
|
+
frameSourceMode?: 'arSession' | 'frameProcessor';
|
|
220
219
|
/** Compose-resolution width in pixels (default 720 for portrait, 960 for landscape). */
|
|
221
220
|
composeWidth?: number;
|
|
222
221
|
/** Compose-resolution height in pixels (default 960 for portrait, 720 for landscape). */
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
/**
|
|
3
3
|
* useFrameProcessorDriver — vision-camera Frame Processor + gyro
|
|
4
|
-
* driver for the incremental panorama engine.
|
|
5
|
-
* `useIncrementalJSDriver`
|
|
4
|
+
* driver for the incremental panorama engine. Sole non-AR driver
|
|
5
|
+
* from v0.6 onward (replaced the deprecated `useIncrementalJSDriver`
|
|
6
|
+
* hook, which was removed in v0.6).
|
|
6
7
|
*
|
|
7
|
-
* Why this exists (vs the JS-driver predecessor)
|
|
8
|
+
* Why this exists (vs the pre-v0.6 JS-driver predecessor)
|
|
8
9
|
*
|
|
9
|
-
* The JS driver
|
|
10
|
-
* path to `IncrementalStitcher.processFrameAtPath
|
|
11
|
-
*
|
|
10
|
+
* The old JS driver took a JPEG snapshot every ~250 ms and fed the
|
|
11
|
+
* path to `IncrementalStitcher.processFrameAtPath` (both removed in
|
|
12
|
+
* v0.6). That path had three costs:
|
|
12
13
|
*
|
|
13
14
|
* 1. JPEG encode (`takeSnapshot` ≈ 30–80 ms on iPhone 16 Pro)
|
|
14
15
|
* 2. Disk write of the JPEG
|
|
@@ -303,9 +304,9 @@ export function useFrameProcessorDriver(
|
|
|
303
304
|
// y = horizontal pan (yaw, about world-Y)
|
|
304
305
|
// x = vertical tilt (pitch, about world-X)
|
|
305
306
|
// z = wrist-twist roll (about world-Z, normal to the screen)
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
307
|
+
// Right-hand-rule convention throughout — same signs the pre-v0.6
|
|
308
|
+
// `useIncrementalJSDriver` produced. If field captures show
|
|
309
|
+
// inverted roll, flip the sign on `z * dt` below.
|
|
309
310
|
setUpdateIntervalForType(SensorTypes.gyroscope, gyroIntervalMs);
|
|
310
311
|
gyroSubRef.current = gyroscope.subscribe({
|
|
311
312
|
next: ({ x, y, z }) => {
|
|
@@ -382,8 +383,8 @@ export function useFrameProcessorDriver(
|
|
|
382
383
|
imageWidth: w, imageHeight: h,
|
|
383
384
|
timestampMs: 0,
|
|
384
385
|
// 2 == RNSARTrackingState.tracking — we always claim "good
|
|
385
|
-
// tracking" because there's no ARKit signal to differentiate
|
|
386
|
-
// (
|
|
386
|
+
// tracking" because there's no ARKit signal to differentiate.
|
|
387
|
+
// (Same contract as the pre-v0.6 useIncrementalJSDriver.)
|
|
387
388
|
trackingStateRaw: 2,
|
|
388
389
|
});
|
|
389
390
|
// Deps array intentionally minimal: only `plugin` actually
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useIncrementalJSDriver — vision-camera + gyro frame driver for
|
|
3
|
-
* the incremental panorama engine, used in non-AR captures on both
|
|
4
|
-
* iOS and Android.
|
|
5
|
-
*
|
|
6
|
-
* History: previously called `useIncrementalAndroidDriver` because
|
|
7
|
-
* it was Android-only. As of 2026-05-17 (Issue #2), the native
|
|
8
|
-
* `processFrameAtPath` entry point exists on both platforms and the
|
|
9
|
-
* hook drives non-AR on iOS too; renamed 2026-05-19 to reflect
|
|
10
|
-
* that.
|
|
11
|
-
*
|
|
12
|
-
* Why this exists
|
|
13
|
-
* In AR captures the engine consumes frames from the ARSession
|
|
14
|
-
* stream natively (60 Hz pose + image delivery, zero JS
|
|
15
|
-
* involvement once started). In NON-AR captures there is no AR
|
|
16
|
-
* session — vision-camera owns the camera — so the engine needs
|
|
17
|
-
* another frame source. This hook fills the gap:
|
|
18
|
-
*
|
|
19
|
-
* - vision-camera keeps the camera viewport
|
|
20
|
-
* - `takeSnapshot()` runs at ~250 ms intervals during press-hold
|
|
21
|
-
* - `react-native-sensors` gyroscope is integrated to estimate
|
|
22
|
-
* cumulative yaw/pitch (drives the FoV-overlap gate)
|
|
23
|
-
* - Each snapshot path + integrated pose is fed to
|
|
24
|
-
* `IncrementalStitcher.processFrameAtPath()`
|
|
25
|
-
*
|
|
26
|
-
* Trade-off vs the AR path
|
|
27
|
-
* Gyro integration drifts ~1–2° per minute. Acceptable for the
|
|
28
|
-
* typical 5–15 s shelf pan; not great for ambitious 360° captures.
|
|
29
|
-
* Snapshot rate is ~4 Hz (vs 60 Hz in AR mode). Pose drives
|
|
30
|
-
* frame-selection only — the actual image alignment is feature-
|
|
31
|
-
* matched + RANSAC-fit, so quality of the panorama itself isn't
|
|
32
|
-
* bounded by gyro accuracy.
|
|
33
|
-
*
|
|
34
|
-
* Lifecycle
|
|
35
|
-
* `start({ cameraRef })` enables the loop; `stop()` tears down.
|
|
36
|
-
* Both should be called by the host's hold-start / hold-complete
|
|
37
|
-
* handlers. Safe to call on either platform; the hook only
|
|
38
|
-
* activates inside the start/stop block.
|
|
39
|
-
*/
|
|
40
|
-
import type { Camera } from 'react-native-vision-camera';
|
|
41
|
-
export interface UseIncrementalJSDriverOptions {
|
|
42
|
-
/**
|
|
43
|
-
* Snapshot interval in ms. Default 250 (≈ 4 Hz). Lower = more
|
|
44
|
-
* candidate frames + more disk I/O. Don't go below 200 — vision-
|
|
45
|
-
* camera's snapshot pipeline can't keep up reliably below that.
|
|
46
|
-
*/
|
|
47
|
-
snapshotIntervalMs?: number;
|
|
48
|
-
/**
|
|
49
|
-
* Gyro sample rate in ms (~30 Hz default matches the existing
|
|
50
|
-
* `PanoramaGuidance` cadence). Used for pose integration only —
|
|
51
|
-
* not the snapshot rate.
|
|
52
|
-
*/
|
|
53
|
-
gyroIntervalMs?: number;
|
|
54
|
-
/**
|
|
55
|
-
* Approximate horizontal FoV of the device camera. Drives the
|
|
56
|
-
* overlap-percent calculation in the native engine. Default 65°
|
|
57
|
-
* is a reasonable mid-tier smartphone average.
|
|
58
|
-
*/
|
|
59
|
-
fovHorizDegrees?: number;
|
|
60
|
-
/**
|
|
61
|
-
* Approximate vertical FoV of the device camera. Default 50° for
|
|
62
|
-
* typical 4:3 phone cameras. When ARCore-driven path is in use
|
|
63
|
-
* the engine receives both FoVs straight from intrinsics; the
|
|
64
|
-
* gyro driver is a fallback so the defaults are good enough.
|
|
65
|
-
*/
|
|
66
|
-
fovVertDegrees?: number;
|
|
67
|
-
}
|
|
68
|
-
export interface IncrementalJSDriverHandle {
|
|
69
|
-
start: (cameraRef: React.RefObject<Camera | null>) => void;
|
|
70
|
-
stop: () => void;
|
|
71
|
-
isRunning: boolean;
|
|
72
|
-
}
|
|
73
|
-
export declare function useIncrementalJSDriver(options?: UseIncrementalJSDriverOptions): IncrementalJSDriverHandle;
|
|
74
|
-
//# sourceMappingURL=useIncrementalJSDriver.d.ts.map
|