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.
Files changed (151) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +21 -0
  4. package/README.md +189 -0
  5. package/RNImageStitcher.podspec +76 -0
  6. package/android/build.gradle +224 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/CMakeLists.txt +124 -0
  9. package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
  10. package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
  11. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
  12. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
  13. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
  14. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
  15. package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
  16. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
  17. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
  18. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
  19. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
  20. package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
  21. package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
  22. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
  23. package/cpp/ar_frame_pose.h +63 -0
  24. package/cpp/keyframe_gate.cpp +927 -0
  25. package/cpp/keyframe_gate.hpp +240 -0
  26. package/cpp/stitcher.cpp +2207 -0
  27. package/cpp/stitcher.hpp +275 -0
  28. package/dist/ar/useARSession.d.ts +102 -0
  29. package/dist/ar/useARSession.js +133 -0
  30. package/dist/camera/ARCameraView.d.ts +93 -0
  31. package/dist/camera/ARCameraView.js +170 -0
  32. package/dist/camera/Camera.d.ts +134 -0
  33. package/dist/camera/Camera.js +688 -0
  34. package/dist/camera/CameraShutter.d.ts +80 -0
  35. package/dist/camera/CameraShutter.js +237 -0
  36. package/dist/camera/CameraView.d.ts +65 -0
  37. package/dist/camera/CameraView.js +117 -0
  38. package/dist/camera/CaptureControlsBar.d.ts +87 -0
  39. package/dist/camera/CaptureControlsBar.js +82 -0
  40. package/dist/camera/CaptureHeader.d.ts +62 -0
  41. package/dist/camera/CaptureHeader.js +81 -0
  42. package/dist/camera/CapturePreview.d.ts +70 -0
  43. package/dist/camera/CapturePreview.js +188 -0
  44. package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
  45. package/dist/camera/CaptureStatusOverlay.js +326 -0
  46. package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
  47. package/dist/camera/CaptureThumbnailStrip.js +177 -0
  48. package/dist/camera/IncrementalPanGuide.d.ts +83 -0
  49. package/dist/camera/IncrementalPanGuide.js +267 -0
  50. package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
  51. package/dist/camera/PanoramaBandOverlay.js +399 -0
  52. package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
  53. package/dist/camera/PanoramaConfirmModal.js +128 -0
  54. package/dist/camera/PanoramaGuidance.d.ts +79 -0
  55. package/dist/camera/PanoramaGuidance.js +246 -0
  56. package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
  57. package/dist/camera/PanoramaSettingsModal.js +611 -0
  58. package/dist/camera/ViewportCropOverlay.d.ts +46 -0
  59. package/dist/camera/ViewportCropOverlay.js +67 -0
  60. package/dist/camera/useCapture.d.ts +111 -0
  61. package/dist/camera/useCapture.js +160 -0
  62. package/dist/camera/useDeviceOrientation.d.ts +48 -0
  63. package/dist/camera/useDeviceOrientation.js +131 -0
  64. package/dist/camera/useVideoCapture.d.ts +79 -0
  65. package/dist/camera/useVideoCapture.js +151 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.js +39 -0
  68. package/dist/quality/normaliseOrientation.d.ts +36 -0
  69. package/dist/quality/normaliseOrientation.js +62 -0
  70. package/dist/quality/runQualityCheck.d.ts +41 -0
  71. package/dist/quality/runQualityCheck.js +98 -0
  72. package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
  73. package/dist/sensors/useIMUTranslationGate.js +235 -0
  74. package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
  75. package/dist/stitching/IncrementalStitcherView.js +157 -0
  76. package/dist/stitching/incremental.d.ts +930 -0
  77. package/dist/stitching/incremental.js +133 -0
  78. package/dist/stitching/stitchFrames.d.ts +55 -0
  79. package/dist/stitching/stitchFrames.js +56 -0
  80. package/dist/stitching/stitchVideo.d.ts +119 -0
  81. package/dist/stitching/stitchVideo.js +57 -0
  82. package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
  83. package/dist/stitching/useIncrementalJSDriver.js +199 -0
  84. package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
  85. package/dist/stitching/useIncrementalStitcher.js +172 -0
  86. package/dist/types.d.ts +58 -0
  87. package/dist/types.js +15 -0
  88. package/ios/Package.swift +72 -0
  89. package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
  90. package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
  91. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
  92. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
  93. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
  94. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
  95. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
  96. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
  97. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
  98. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
  99. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
  100. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
  101. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
  102. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
  103. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
  104. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
  105. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
  106. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
  107. package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
  108. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
  109. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
  110. package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
  111. package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
  112. package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
  113. package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
  114. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
  115. package/package.json +73 -0
  116. package/react-native.config.js +34 -0
  117. package/scripts/opencv-version.txt +1 -0
  118. package/scripts/postinstall-fetch-binaries.js +286 -0
  119. package/src/ar/useARSession.ts +210 -0
  120. package/src/camera/.gitkeep +0 -0
  121. package/src/camera/ARCameraView.tsx +256 -0
  122. package/src/camera/Camera.tsx +1053 -0
  123. package/src/camera/CameraShutter.tsx +292 -0
  124. package/src/camera/CameraView.tsx +157 -0
  125. package/src/camera/CaptureControlsBar.tsx +204 -0
  126. package/src/camera/CaptureHeader.tsx +184 -0
  127. package/src/camera/CapturePreview.tsx +318 -0
  128. package/src/camera/CaptureStatusOverlay.tsx +391 -0
  129. package/src/camera/CaptureThumbnailStrip.tsx +277 -0
  130. package/src/camera/IncrementalPanGuide.tsx +328 -0
  131. package/src/camera/PanoramaBandOverlay.tsx +498 -0
  132. package/src/camera/PanoramaConfirmModal.tsx +206 -0
  133. package/src/camera/PanoramaGuidance.tsx +327 -0
  134. package/src/camera/PanoramaSettingsModal.tsx +1357 -0
  135. package/src/camera/ViewportCropOverlay.tsx +81 -0
  136. package/src/camera/useCapture.ts +279 -0
  137. package/src/camera/useDeviceOrientation.ts +140 -0
  138. package/src/camera/useVideoCapture.ts +236 -0
  139. package/src/index.ts +53 -0
  140. package/src/quality/.gitkeep +0 -0
  141. package/src/quality/normaliseOrientation.ts +79 -0
  142. package/src/quality/runQualityCheck.ts +131 -0
  143. package/src/sensors/useIMUTranslationGate.ts +347 -0
  144. package/src/stitching/.gitkeep +0 -0
  145. package/src/stitching/IncrementalStitcherView.tsx +198 -0
  146. package/src/stitching/incremental.ts +1021 -0
  147. package/src/stitching/stitchFrames.ts +88 -0
  148. package/src/stitching/stitchVideo.ts +153 -0
  149. package/src/stitching/useIncrementalJSDriver.ts +273 -0
  150. package/src/stitching/useIncrementalStitcher.ts +252 -0
  151. package/src/types.ts +78 -0
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Incremental panorama-stitching native module bindings.
5
+ *
6
+ * See docs/site-content/design/2026-04-30-realtime-incremental-stitching.md
7
+ * for the architectural rationale. This file is the type-safe JS
8
+ * wrapper around the RN bridge; `useIncrementalStitcher` is the hook
9
+ * host code consumes; `IncrementalStitcherView` renders the live
10
+ * panorama preview.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.IncrementalOutcome = void 0;
14
+ exports.getIncrementalNativeModule = getIncrementalNativeModule;
15
+ exports.incrementalStitcherIsAvailable = incrementalStitcherIsAvailable;
16
+ exports.cleanupOldKeyframes = cleanupOldKeyframes;
17
+ exports.subscribeIncrementalState = subscribeIncrementalState;
18
+ const react_native_1 = require("react-native");
19
+ /**
20
+ * Per-frame outcome returned by the engine. Mirrors the iOS
21
+ * `RLISFrameOutcome` enum and the Android equivalent — numeric
22
+ * values are kept identical so the JS layer doesn't branch on
23
+ * platform.
24
+ */
25
+ var IncrementalOutcome;
26
+ (function (IncrementalOutcome) {
27
+ /** High-confidence accept — silent UX. */
28
+ IncrementalOutcome[IncrementalOutcome["AcceptedHigh"] = 0] = "AcceptedHigh";
29
+ /** Accept with marginal confidence — show subtle yellow ring. */
30
+ IncrementalOutcome[IncrementalOutcome["AcceptedMedium"] = 1] = "AcceptedMedium";
31
+ /** Frame too close to the previous accepted — wait for more pan. */
32
+ IncrementalOutcome[IncrementalOutcome["SkippedTooClose"] = 2] = "SkippedTooClose";
33
+ /** Frame too far past the overlap window — show "slow down" hint. */
34
+ IncrementalOutcome[IncrementalOutcome["RejectedTooFar"] = 3] = "RejectedTooFar";
35
+ /** Too few feature matches — show "scene too uniform" hint. */
36
+ IncrementalOutcome[IncrementalOutcome["RejectedSceneUniform"] = 4] = "RejectedSceneUniform";
37
+ /** RANSAC failed or homography degenerate — show "alignment lost". */
38
+ IncrementalOutcome[IncrementalOutcome["RejectedAlignmentLost"] = 5] = "RejectedAlignmentLost";
39
+ /** AR tracking quality was poor — skip silently. */
40
+ IncrementalOutcome[IncrementalOutcome["SkippedTrackingPoor"] = 6] = "SkippedTrackingPoor";
41
+ /**
42
+ * V12.11 Step D — operator panned BACKWARDS past the running
43
+ * max along the pan axis. Engine has SKIPPED the paste; host
44
+ * should auto-finalize the capture (the most useful pano is
45
+ * what we have so far at the high-water mark). Emitted by
46
+ * the rectilinear engine only — cylindrical engines tolerate
47
+ * reverse motion via their warp pipeline.
48
+ */
49
+ IncrementalOutcome[IncrementalOutcome["RejectedReverseDirection"] = 7] = "RejectedReverseDirection";
50
+ /**
51
+ * V16 — pose-driven keyframe gate rejected the frame because it
52
+ * overlapped >= (1 − overlapThreshold) with the last accepted
53
+ * keyframe. Host should keep showing the same status — user is
54
+ * mid-pan between two natural keyframe boundaries. No UX hint
55
+ * needed (this is the expected behaviour 90% of the time when
56
+ * pose-based selection is on).
57
+ */
58
+ IncrementalOutcome[IncrementalOutcome["SkippedKeyframeOverlap"] = 8] = "SkippedKeyframeOverlap";
59
+ /**
60
+ * V16 — pose-driven keyframe gate rejected the frame because the
61
+ * capture has hit `keyframeMaxCount` (default 6). Host should
62
+ * auto-finalize since no more frames will be accepted.
63
+ */
64
+ IncrementalOutcome[IncrementalOutcome["SkippedKeyframeMaxReached"] = 9] = "SkippedKeyframeMaxReached";
65
+ })(IncrementalOutcome || (exports.IncrementalOutcome = IncrementalOutcome = {}));
66
+ /**
67
+ * Lazy-resolve the native module. Returns null on platforms that
68
+ * don't have it registered yet (e.g. older builds without the new
69
+ * native code). Callers fall back to the batch stitcher in that
70
+ * case.
71
+ */
72
+ function getIncrementalNativeModule() {
73
+ const m = react_native_1.NativeModules['IncrementalStitcher'];
74
+ if (!m || typeof m !== 'object')
75
+ return null;
76
+ // The cast is safe — RN runtime sees only `Function` for each
77
+ // method but TypeScript's structural type system is happy with
78
+ // a record of any-callable.
79
+ return m;
80
+ }
81
+ /**
82
+ * Whether the native incremental stitcher is registered and ready.
83
+ * Equivalent to `getIncrementalNativeModule() !== null`; provided
84
+ * as a convenience export so host code reads cleanly.
85
+ */
86
+ function incrementalStitcherIsAvailable() {
87
+ return getIncrementalNativeModule() !== null;
88
+ }
89
+ /**
90
+ * 2026-05-18 (Iss 3) — host-callable helper to clean up old
91
+ * keyframe sessions. Wraps the native `cleanupKeyframes` with a
92
+ * sensible default (24 hours) and a noop fallback when the native
93
+ * method isn't implemented (older SDK builds).
94
+ *
95
+ * Typical use: call this in App.tsx's mount effect or from a
96
+ * background-task hook so storage stays bounded between captures.
97
+ *
98
+ * Resolves with the count of sessions deleted + bytes freed so the
99
+ * host can log / surface a "cleaned up X MB" message. Never
100
+ * rejects — filesystem failures (including ENOENT on the captures
101
+ * dir) resolve as `{ sessionsDeleted: 0, bytesFreed: 0 }`.
102
+ */
103
+ async function cleanupOldKeyframes(options) {
104
+ const native = getIncrementalNativeModule();
105
+ if (!native?.cleanupKeyframes) {
106
+ return { sessionsDeleted: 0, bytesFreed: 0 };
107
+ }
108
+ try {
109
+ const olderThanMs = options?.olderThanMs ?? 24 * 3600 * 1000;
110
+ return await native.cleanupKeyframes({ olderThanMs });
111
+ }
112
+ catch {
113
+ return { sessionsDeleted: 0, bytesFreed: 0 };
114
+ }
115
+ }
116
+ /**
117
+ * Subscribe to per-frame state updates emitted by the native engine.
118
+ * The returned `EmitterSubscription` MUST be removed when no longer
119
+ * needed (`subscription.remove()`); leaks here cause memory growth
120
+ * across captures.
121
+ */
122
+ function subscribeIncrementalState(listener) {
123
+ const native = getIncrementalNativeModule();
124
+ if (!native)
125
+ return null;
126
+ // Cast through the structural NativeModule type — the bridge
127
+ // module IS an RCTEventEmitter at runtime, which exposes
128
+ // addListener/removeListeners as part of the contract. TS just
129
+ // can't see the iOS side's class hierarchy.
130
+ const emitter = new react_native_1.NativeEventEmitter(react_native_1.NativeModules.IncrementalStitcher);
131
+ return emitter.addListener('IncrementalStateUpdate', listener);
132
+ }
133
+ //# sourceMappingURL=incremental.js.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * stitchFrames — video / frame stitching API.
3
+ *
4
+ * Implementation status (Phase 2 of #8):
5
+ * - iOS: Swift native module that vendors upstream OpenCV's iOS
6
+ * framework and calls `cv::Stitcher::SCANS` mode (designed for
7
+ * translational shelf captures). Lives in
8
+ * `retailens-capture-sdk/ios/Sources/RNImageStitcher/`.
9
+ * - Android: deferred to Phase 3 — same OpenCV surface, different
10
+ * build (NDK + Gradle). Until that lands, Android calls hit the
11
+ * `StitchNotImplementedError` path below.
12
+ *
13
+ * Why fail loudly instead of falling back to JS?
14
+ * The cloud-sync pipeline depends on a stitched panorama being
15
+ * present. Silently producing a broken or single-frame "panorama"
16
+ * would corrupt downstream SOS computation. Hard-failing here lets
17
+ * the host app surface the unsupported-platform error to the user
18
+ * immediately rather than discovering it on the server hours later.
19
+ */
20
+ export interface StitchFramesOptions {
21
+ /**
22
+ * Absolute paths to the input image files in capture order.
23
+ * Must share a camera + focal length (we don't blend across sources).
24
+ */
25
+ framePaths: string[];
26
+ /**
27
+ * Output path for the stitched image (JPEG). Host app chooses the
28
+ * location (tmp vs. cache vs. Documents).
29
+ */
30
+ outputPath: string;
31
+ /** JPEG quality [0-100]. Default 85. */
32
+ quality?: number;
33
+ }
34
+ export interface StitchFramesResult {
35
+ /** Absolute path to the stitched image on disk. */
36
+ outputPath: string;
37
+ /** Pixel dimensions of the stitched image. */
38
+ width: number;
39
+ height: number;
40
+ /** Wall-clock ms the stitcher took (host apps log this for perf tracking). */
41
+ durationMs: number;
42
+ }
43
+ /**
44
+ * Stitch ``framePaths`` into a single panoramic image.
45
+ *
46
+ * Throws ``StitchNotImplementedError`` until the native module ships.
47
+ * Callers should catch that specific error and fall back gracefully
48
+ * (e.g. surface a "single-frame mode only" banner in the UI).
49
+ */
50
+ export declare function stitchFrames(options: StitchFramesOptions): Promise<StitchFramesResult>;
51
+ export declare class StitchNotImplementedError extends Error {
52
+ readonly code = "STITCH_NOT_IMPLEMENTED";
53
+ constructor(message: string);
54
+ }
55
+ //# sourceMappingURL=stitchFrames.d.ts.map
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * stitchFrames — video / frame stitching API.
5
+ *
6
+ * Implementation status (Phase 2 of #8):
7
+ * - iOS: Swift native module that vendors upstream OpenCV's iOS
8
+ * framework and calls `cv::Stitcher::SCANS` mode (designed for
9
+ * translational shelf captures). Lives in
10
+ * `retailens-capture-sdk/ios/Sources/RNImageStitcher/`.
11
+ * - Android: deferred to Phase 3 — same OpenCV surface, different
12
+ * build (NDK + Gradle). Until that lands, Android calls hit the
13
+ * `StitchNotImplementedError` path below.
14
+ *
15
+ * Why fail loudly instead of falling back to JS?
16
+ * The cloud-sync pipeline depends on a stitched panorama being
17
+ * present. Silently producing a broken or single-frame "panorama"
18
+ * would corrupt downstream SOS computation. Hard-failing here lets
19
+ * the host app surface the unsupported-platform error to the user
20
+ * immediately rather than discovering it on the server hours later.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.StitchNotImplementedError = void 0;
24
+ exports.stitchFrames = stitchFrames;
25
+ const react_native_1 = require("react-native");
26
+ /**
27
+ * Stitch ``framePaths`` into a single panoramic image.
28
+ *
29
+ * Throws ``StitchNotImplementedError`` until the native module ships.
30
+ * Callers should catch that specific error and fall back gracefully
31
+ * (e.g. surface a "single-frame mode only" banner in the UI).
32
+ */
33
+ async function stitchFrames(options) {
34
+ // Look for the native module by its canonical name so we can flip
35
+ // this function to "implemented" simply by registering the module
36
+ // in AppDelegate / MainApplication.
37
+ const native = react_native_1.NativeModules['BatchStitcher'];
38
+ if (native && typeof native === 'object' && 'stitch' in native) {
39
+ const fn = native.stitch;
40
+ return fn(options);
41
+ }
42
+ throw new StitchNotImplementedError(`stitchFrames is not yet implemented on ${react_native_1.Platform.OS}. `
43
+ + 'The react-native-image-stitcher native stitcher module is expected '
44
+ + 'but not registered — the JS shim is throwing by design so the '
45
+ + 'host app can fall back to single-frame mode rather than ship '
46
+ + 'broken panoramas.');
47
+ }
48
+ class StitchNotImplementedError extends Error {
49
+ constructor(message) {
50
+ super(message);
51
+ this.code = 'STITCH_NOT_IMPLEMENTED';
52
+ this.name = 'StitchNotImplementedError';
53
+ }
54
+ }
55
+ exports.StitchNotImplementedError = StitchNotImplementedError;
56
+ //# sourceMappingURL=stitchFrames.js.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * stitchVideo — end-to-end "video → panorama" API.
3
+ *
4
+ * The host app records video while the user holds the shutter; on
5
+ * release we hand the video file path to this function and it
6
+ * returns a single stitched panoramic JPEG. Internally:
7
+ *
8
+ * 1. Native side extracts N evenly-spaced frames from the video
9
+ * (AVAssetImageGenerator on iOS, MediaMetadataRetriever on
10
+ * Android once Phase 3 lands).
11
+ * 2. Native side feeds the frames into `cv::Stitcher::SCANS`.
12
+ * 3. Frames are deleted; only the final panorama remains.
13
+ *
14
+ * The single bridge call keeps the JS thread out of the
15
+ * extract→stitch dance entirely; nothing has to round-trip frame
16
+ * paths back to JS just to hand them to a sibling native call.
17
+ */
18
+ import type { StitchFramesResult } from './stitchFrames';
19
+ export interface StitchVideoOptions {
20
+ /**
21
+ * Absolute path to the recorded video file. Accepts paths with
22
+ * or without the `file://` prefix — the native module strips it.
23
+ */
24
+ videoPath: string;
25
+ /**
26
+ * Where the resulting panoramic JPEG should be written.
27
+ */
28
+ outputPath: string;
29
+ /**
30
+ * Number of frames to sample from the video. Default 10 — the
31
+ * empirical sweet spot for shelf scans on iPhone hardware: enough
32
+ * overlap that homography stays robust, few enough that stitching
33
+ * stays under 4 seconds. Increase only if the user pans further
34
+ * than ~1 m of shelf in a single hold.
35
+ */
36
+ maxFrames?: number;
37
+ /**
38
+ * JPEG quality [0..100]. Applied to BOTH the intermediate frames
39
+ * (extracted from video) AND the final panorama. Default 85.
40
+ */
41
+ quality?: number;
42
+ /**
43
+ * Projection used during the warp step. See native side for
44
+ * details. Default `'plane'` — straight verticals/horizontals,
45
+ * good for shelf scans + close-up subjects. Hourglass shape on
46
+ * partial arcs is handled by the rectangular-crop step.
47
+ * - `'plane'`: flat projection. Best for ≤30° pans.
48
+ * - `'cylindrical'`: handles rotational mid-arc pans.
49
+ * - `'spherical'`: full 90°+ panoramic captures.
50
+ */
51
+ warperType?: 'plane' | 'cylindrical' | 'spherical';
52
+ /**
53
+ * Blending strategy across stitched seams.
54
+ * - `'multiband'` (default): high-quality seams when alignment
55
+ * is good; can produce visible halos when adjacent frames
56
+ * have inconsistent exposure.
57
+ * - `'feather'`: simple alpha-weighted blend; faster + no
58
+ * halo artifacts; slightly more visible seam edge.
59
+ */
60
+ blenderType?: 'multiband' | 'feather';
61
+ /**
62
+ * Seam finding strategy.
63
+ * - `'graphcut'` (default): cv::detail::GraphCutSeamFinder
64
+ * finds optimal cuts before blending — pairs beautifully
65
+ * with `multiband`, but holds all warped frames in memory
66
+ * simultaneously (higher peak).
67
+ * - `'skip'`: streams warp+feed in a single pass, never
68
+ * holding more than one warped frame. Lower peak memory.
69
+ * Right choice on low-RAM devices or with `feather`.
70
+ */
71
+ seamFinderType?: 'graphcut' | 'skip';
72
+ /**
73
+ * Phase 5: pose-driven stitching. When present and non-empty,
74
+ * the native stitcher skips features → matching → BundleAdjuster
75
+ * and builds cv::detail::CameraParams directly from each pose's
76
+ * intrinsics + quaternion. Each entry has the shape returned
77
+ * by `NativeModules.RNSARSession.snapshotPoseLog()`:
78
+ *
79
+ * { tx, ty, tz, qx, qy, qz, qw,
80
+ * fx, fy, cx, cy,
81
+ * imageWidth, imageHeight,
82
+ * timestampMs, trackingState }
83
+ *
84
+ * Frames whose closest pose is beyond a 100 ms tolerance are
85
+ * dropped before stitching; if fewer than 2 remain the call
86
+ * rejects with `opencv-failed-1032` so the host can fall back
87
+ * to the feature-matched path (re-call `stitchVideo` without
88
+ * `poses`).
89
+ */
90
+ poses?: Array<{
91
+ tx: number;
92
+ ty: number;
93
+ tz: number;
94
+ qx: number;
95
+ qy: number;
96
+ qz: number;
97
+ qw: number;
98
+ fx: number;
99
+ fy: number;
100
+ cx: number;
101
+ cy: number;
102
+ imageWidth: number;
103
+ imageHeight: number;
104
+ timestampMs: number;
105
+ trackingState: number;
106
+ }>;
107
+ }
108
+ /**
109
+ * Stitch a recorded video file into a single panoramic JPEG.
110
+ *
111
+ * Throws `StitchNotImplementedError` on platforms where the native
112
+ * stitcher hasn't shipped yet (Android until Phase 3). Throws an
113
+ * Error with a code-like message on stitcher failures the JS layer
114
+ * may want to recover from (see `StitcherError` cases on the native
115
+ * side: `insufficient-frames`, `read-failed`, `write-failed`,
116
+ * `opencv-failed-<code>`).
117
+ */
118
+ export declare function stitchVideo(options: StitchVideoOptions): Promise<StitchFramesResult>;
119
+ //# sourceMappingURL=stitchVideo.d.ts.map
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * stitchVideo — end-to-end "video → panorama" API.
5
+ *
6
+ * The host app records video while the user holds the shutter; on
7
+ * release we hand the video file path to this function and it
8
+ * returns a single stitched panoramic JPEG. Internally:
9
+ *
10
+ * 1. Native side extracts N evenly-spaced frames from the video
11
+ * (AVAssetImageGenerator on iOS, MediaMetadataRetriever on
12
+ * Android once Phase 3 lands).
13
+ * 2. Native side feeds the frames into `cv::Stitcher::SCANS`.
14
+ * 3. Frames are deleted; only the final panorama remains.
15
+ *
16
+ * The single bridge call keeps the JS thread out of the
17
+ * extract→stitch dance entirely; nothing has to round-trip frame
18
+ * paths back to JS just to hand them to a sibling native call.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.stitchVideo = stitchVideo;
22
+ const react_native_1 = require("react-native");
23
+ const stitchFrames_1 = require("./stitchFrames");
24
+ /**
25
+ * Stitch a recorded video file into a single panoramic JPEG.
26
+ *
27
+ * Throws `StitchNotImplementedError` on platforms where the native
28
+ * stitcher hasn't shipped yet (Android until Phase 3). Throws an
29
+ * Error with a code-like message on stitcher failures the JS layer
30
+ * may want to recover from (see `StitcherError` cases on the native
31
+ * side: `insufficient-frames`, `read-failed`, `write-failed`,
32
+ * `opencv-failed-<code>`).
33
+ */
34
+ async function stitchVideo(options) {
35
+ const native = react_native_1.NativeModules['BatchStitcher'];
36
+ if (native
37
+ && typeof native === 'object'
38
+ && typeof native.stitchVideo === 'function') {
39
+ const fn = native.stitchVideo;
40
+ // 60-second hard deadline. cv::Stitcher::stitch can occasionally
41
+ // grind for minutes inside bundle adjustment on hard inputs (low
42
+ // texture, extreme parallax). A timeout converts those into a
43
+ // clear UI error rather than letting the stitching banner sit
44
+ // forever. Real stitches finish in 2-8 seconds on iPhone with
45
+ // the tuned PANORAMA settings; 60 s is comfortably above that.
46
+ return Promise.race([
47
+ fn(options),
48
+ new Promise((_, reject) => {
49
+ setTimeout(() => reject(new Error('stitch-timeout: stitching took longer than 30 s')), 30000);
50
+ }),
51
+ ]);
52
+ }
53
+ throw new stitchFrames_1.StitchNotImplementedError(`stitchVideo is not yet implemented on ${react_native_1.Platform.OS}. `
54
+ + 'The react-native-image-stitcher native stitcher module is expected '
55
+ + 'but not registered (or the build predates Phase 2.5 of #8).');
56
+ }
57
+ //# sourceMappingURL=stitchVideo.js.map
@@ -0,0 +1,74 @@
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