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,151 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * useVideoCapture — video recording + frame extraction API.
5
+ *
6
+ * Sibling of ``useCapture`` for the "sweep a shelf with a video" flow
7
+ * that feeds the stitcher. The hook's state machine is:
8
+ *
9
+ * idle → recording → stopping → idle (success)
10
+ * idle → recording → idle (recording errored)
11
+ * idle → recording → stopping → idle (user cancelled)
12
+ *
13
+ * API shape — `startRecording` returns a `Promise<VideoFile>` that:
14
+ * - resolves with the recorded file when the user releases the
15
+ * shutter and `stopRecording()` is called (vision-camera fires
16
+ * `onRecordingFinished`),
17
+ * - rejects if vision-camera fires `onRecordingError` at any point
18
+ * (e.g. `<Camera>` was rendered without `video={true}`, disk
19
+ * full mid-recording, permission revoked).
20
+ *
21
+ * Why a single future instead of separate finished/error callbacks?
22
+ * Lets host code use the natural async/await pattern. Earlier we
23
+ * used a callback + a parked resolver in the host screen; that
24
+ * masked start-time errors (the resolver hung forever) and produced
25
+ * confusing cascade errors when `stopRecording` ran against a
26
+ * non-recording camera.
27
+ *
28
+ * Frame extraction lives behind the `extractFrames` method but is
29
+ * deferred to the higher-level `stitchVideo()` SDK API now — host
30
+ * apps shouldn't have to manage their own tmp dir. This shim is
31
+ * kept for parity / future use; it currently throws.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.useVideoCapture = useVideoCapture;
35
+ const react_1 = require("react");
36
+ function useVideoCapture() {
37
+ const [state, setState] = (0, react_1.useState)('idle');
38
+ /**
39
+ * The active recording's resolve/reject handles. Set when
40
+ * `startRecording` is called; cleared when vision-camera fires
41
+ * `onRecordingFinished` / `onRecordingError`, or when the host
42
+ * calls `cancelRecording`.
43
+ *
44
+ * Stored in a ref (not state) because vision-camera's callbacks
45
+ * fire outside React's render cycle and need synchronous access
46
+ * to the latest handles.
47
+ */
48
+ const recordingFutureRef = (0, react_1.useRef)(null);
49
+ const startRecording = (0, react_1.useCallback)(async (cameraRef) => {
50
+ if (!cameraRef.current) {
51
+ throw new Error('useVideoCapture.startRecording: cameraRef is null');
52
+ }
53
+ if (recordingFutureRef.current !== null) {
54
+ throw new Error('useVideoCapture.startRecording: a recording is already in progress');
55
+ }
56
+ // Brief settle before kicking off a fresh recording.
57
+ // vision-camera's AVCaptureSession needs ~100 ms after a
58
+ // previous stopRecording completes before it can cleanly
59
+ // start a new one — without this, rapid record→stop→record
60
+ // cycles (typical when the user does a panorama, then another
61
+ // panorama right after) can crash the session.
62
+ await new Promise((r) => setTimeout(r, 150));
63
+ return new Promise((resolve, reject) => {
64
+ recordingFutureRef.current = { resolve, reject };
65
+ setState('recording');
66
+ cameraRef.current.startRecording({
67
+ onRecordingFinished: (video) => {
68
+ const future = recordingFutureRef.current;
69
+ recordingFutureRef.current = null;
70
+ setState('idle');
71
+ // If the future was cleared (cancelled), drop the file
72
+ // on the floor. vision-camera still wrote it to disk;
73
+ // that's a known cleanup gap for a future iteration.
74
+ future?.resolve(video);
75
+ },
76
+ onRecordingError: (err) => {
77
+ const future = recordingFutureRef.current;
78
+ recordingFutureRef.current = null;
79
+ setState('idle');
80
+ // eslint-disable-next-line no-console
81
+ console.error('[useVideoCapture] recording error', err);
82
+ future?.reject(err);
83
+ },
84
+ });
85
+ });
86
+ }, []);
87
+ const stopRecording = (0, react_1.useCallback)(async (cameraRef) => {
88
+ // No-op if there's nothing to stop — protects against the
89
+ // cascade where startRecording errored synchronously and the
90
+ // host's release handler still calls stop.
91
+ if (recordingFutureRef.current === null)
92
+ return;
93
+ if (!cameraRef.current)
94
+ return;
95
+ setState('stopping');
96
+ try {
97
+ await cameraRef.current.stopRecording();
98
+ }
99
+ catch (err) {
100
+ // The native call can throw "no-recording-in-progress" if
101
+ // the recording was already finalised by an error path.
102
+ // The future has its own error handling; we just absorb so
103
+ // the host's await doesn't see a confusing secondary error.
104
+ // eslint-disable-next-line no-console
105
+ console.warn('[useVideoCapture] stopRecording threw', err);
106
+ }
107
+ }, []);
108
+ const cancelRecording = (0, react_1.useCallback)(async (cameraRef) => {
109
+ const future = recordingFutureRef.current;
110
+ // Clear FIRST so the eventual onRecordingFinished/Error
111
+ // callback no-ops on a null ref instead of fulfilling a
112
+ // promise the caller has already moved past.
113
+ recordingFutureRef.current = null;
114
+ if (future) {
115
+ future.reject(new Error('useVideoCapture: recording cancelled'));
116
+ }
117
+ if (!cameraRef.current) {
118
+ setState('idle');
119
+ return;
120
+ }
121
+ setState('stopping');
122
+ try {
123
+ await cameraRef.current.stopRecording();
124
+ }
125
+ catch {
126
+ // Expected when no recording is in flight.
127
+ }
128
+ finally {
129
+ setState('idle');
130
+ }
131
+ }, []);
132
+ const extractFrames = (0, react_1.useCallback)(async (_opts) => {
133
+ setState('extracting');
134
+ try {
135
+ throw new Error('[react-native-image-stitcher] useVideoCapture.extractFrames is not '
136
+ + 'available — use `stitchVideo()` from the SDK index, which '
137
+ + 'combines extract + stitch in a single native call.');
138
+ }
139
+ finally {
140
+ setState('idle');
141
+ }
142
+ }, []);
143
+ return {
144
+ state,
145
+ startRecording,
146
+ stopRecording,
147
+ cancelRecording,
148
+ extractFrames,
149
+ };
150
+ }
151
+ //# sourceMappingURL=useVideoCapture.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * react-native-image-stitcher — public API surface.
3
+ *
4
+ * Single component (`<Camera>`) + supporting types + the two public
5
+ * hooks the design doc calls out (`useARSession`, `useIMUTranslationGate`).
6
+ * Everything else (internal sub-components, drivers, bridges) is
7
+ * deliberately NOT re-exported so the v0.1.0 → 1.0 stability window
8
+ * doesn't lock us into an inflated public surface.
9
+ *
10
+ * If you need access to something that used to be exported and isn't
11
+ * now, please open an issue describing the use-case before reaching
12
+ * into the package internals.
13
+ *
14
+ * Public/private split: this lib is the open-source foundation. The
15
+ * `retailens-camera-sdk` package depends on this lib and adds
16
+ * RetaiLens-specific features (measurement, packet detection, etc.)
17
+ * on top. Consumers wanting those features install
18
+ * `retailens-camera-sdk` instead.
19
+ */
20
+ export { Camera, CameraError } from './camera/Camera';
21
+ export type { CameraProps, CameraCaptureResult, CameraErrorCode, CaptureSource, CameraLens, StitchMode, Blender, SeamFinder, Warper, FramesDroppedInfo, } from './camera/Camera';
22
+ export { useARSession, ARTrackingState } from './ar/useARSession';
23
+ export type { UseARSessionReturn, FramePose, } from './ar/useARSession';
24
+ export { useIMUTranslationGate } from './sensors/useIMUTranslationGate';
25
+ export type { UseIMUTranslationGateOptions, UseIMUTranslationGateReturn, } from './sensors/useIMUTranslationGate';
26
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * react-native-image-stitcher — public API surface.
5
+ *
6
+ * Single component (`<Camera>`) + supporting types + the two public
7
+ * hooks the design doc calls out (`useARSession`, `useIMUTranslationGate`).
8
+ * Everything else (internal sub-components, drivers, bridges) is
9
+ * deliberately NOT re-exported so the v0.1.0 → 1.0 stability window
10
+ * doesn't lock us into an inflated public surface.
11
+ *
12
+ * If you need access to something that used to be exported and isn't
13
+ * now, please open an issue describing the use-case before reaching
14
+ * into the package internals.
15
+ *
16
+ * Public/private split: this lib is the open-source foundation. The
17
+ * `retailens-camera-sdk` package depends on this lib and adds
18
+ * RetaiLens-specific features (measurement, packet detection, etc.)
19
+ * on top. Consumers wanting those features install
20
+ * `retailens-camera-sdk` instead.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
24
+ // ── The main component ────────────────────────────────────────────────────
25
+ var Camera_1 = require("./camera/Camera");
26
+ Object.defineProperty(exports, "Camera", { enumerable: true, get: function () { return Camera_1.Camera; } });
27
+ Object.defineProperty(exports, "CameraError", { enumerable: true, get: function () { return Camera_1.CameraError; } });
28
+ // ── AR foundation (public per design doc) ─────────────────────────────────
29
+ // Hosts that want raw AR pose access (e.g., to build their own
30
+ // measurement/detection on top) consume these directly.
31
+ var useARSession_1 = require("./ar/useARSession");
32
+ Object.defineProperty(exports, "useARSession", { enumerable: true, get: function () { return useARSession_1.useARSession; } });
33
+ Object.defineProperty(exports, "ARTrackingState", { enumerable: true, get: function () { return useARSession_1.ARTrackingState; } });
34
+ // ── IMU translation gate (public per design doc R5) ───────────────────────
35
+ // Hosts running their own non-AR capture flow can reuse this hook to
36
+ // get the same gating logic <Camera> uses internally.
37
+ var useIMUTranslationGate_1 = require("./sensors/useIMUTranslationGate");
38
+ Object.defineProperty(exports, "useIMUTranslationGate", { enumerable: true, get: function () { return useIMUTranslationGate_1.useIMUTranslationGate; } });
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * normaliseOrientation — bake EXIF rotation into a JPEG's pixels.
3
+ *
4
+ * vision-camera writes photos with the camera sensor's native
5
+ * landscape pixels and an EXIF Orientation tag describing how to
6
+ * rotate them for display. Most consumers (iOS UIImage, RN's
7
+ * `<Image>`) honour the tag — but enough don't (Sentry breadcrumbs,
8
+ * share sheets, the cv::Stitcher itself, third-party image
9
+ * pipelines) that "what's on disk" diverges from "what the user
10
+ * sees" unless we eagerly normalise.
11
+ *
12
+ * This helper round-trips the file through the SDK's native
13
+ * stitcher module, which decodes the JPEG with EXIF rotation
14
+ * applied and re-encodes a clean JPEG with no orientation
15
+ * metadata. Idempotent on already-normalised files.
16
+ */
17
+ export interface NormaliseOrientationResult {
18
+ /** Image width in pixels AFTER rotation has been applied. */
19
+ width: number;
20
+ /** Image height in pixels AFTER rotation. */
21
+ height: number;
22
+ }
23
+ /**
24
+ * Bake the EXIF rotation of `imagePath` into pixels in-place.
25
+ *
26
+ * Returns the post-rotation dimensions so the caller can update
27
+ * its own width/height fields. No-op on platforms that don't
28
+ * have the native module yet (Android until Phase 3) — falls back
29
+ * to the input shape so callers don't have to special-case
30
+ * platform availability.
31
+ */
32
+ export declare function normaliseOrientation(imagePath: string, fallback?: {
33
+ width: number;
34
+ height: number;
35
+ }): Promise<NormaliseOrientationResult>;
36
+ //# sourceMappingURL=normaliseOrientation.d.ts.map
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * normaliseOrientation — bake EXIF rotation into a JPEG's pixels.
5
+ *
6
+ * vision-camera writes photos with the camera sensor's native
7
+ * landscape pixels and an EXIF Orientation tag describing how to
8
+ * rotate them for display. Most consumers (iOS UIImage, RN's
9
+ * `<Image>`) honour the tag — but enough don't (Sentry breadcrumbs,
10
+ * share sheets, the cv::Stitcher itself, third-party image
11
+ * pipelines) that "what's on disk" diverges from "what the user
12
+ * sees" unless we eagerly normalise.
13
+ *
14
+ * This helper round-trips the file through the SDK's native
15
+ * stitcher module, which decodes the JPEG with EXIF rotation
16
+ * applied and re-encodes a clean JPEG with no orientation
17
+ * metadata. Idempotent on already-normalised files.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.normaliseOrientation = normaliseOrientation;
21
+ const react_native_1 = require("react-native");
22
+ /**
23
+ * Bake the EXIF rotation of `imagePath` into pixels in-place.
24
+ *
25
+ * Returns the post-rotation dimensions so the caller can update
26
+ * its own width/height fields. No-op on platforms that don't
27
+ * have the native module yet (Android until Phase 3) — falls back
28
+ * to the input shape so callers don't have to special-case
29
+ * platform availability.
30
+ */
31
+ async function normaliseOrientation(imagePath, fallback) {
32
+ const native = react_native_1.NativeModules['BatchStitcher'];
33
+ const fn = native
34
+ && typeof native === 'object'
35
+ && typeof native.normaliseOrientation === 'function'
36
+ ? native.normaliseOrientation
37
+ : null;
38
+ if (!fn) {
39
+ // Native module not registered (typically Android in current
40
+ // builds). Skip normalisation and report the caller's
41
+ // fallback dimensions if provided, otherwise zeroes — keeps
42
+ // the API total without forcing every caller to wrap a
43
+ // try/catch.
44
+ if (fallback)
45
+ return fallback;
46
+ // eslint-disable-next-line no-console
47
+ console.warn(`[capture-sdk] normaliseOrientation: native module not available on ${react_native_1.Platform.OS}. `
48
+ + 'Photo orientation may render incorrectly until the native module is registered.');
49
+ return { width: 0, height: 0 };
50
+ }
51
+ try {
52
+ return await fn({ imagePath });
53
+ }
54
+ catch (err) {
55
+ // eslint-disable-next-line no-console
56
+ console.warn('[capture-sdk] normaliseOrientation failed', err);
57
+ if (fallback)
58
+ return fallback;
59
+ throw err;
60
+ }
61
+ }
62
+ //# sourceMappingURL=normaliseOrientation.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * runQualityCheck — public entry point for the SDK's blur + brightness
3
+ * quality gate. Delegates to the native module
4
+ * `RNImageStitcherQualityChecker` when registered (iOS today via
5
+ * `ios/Sources/RNImageStitcher/QualityChecker.swift`, Android in
6
+ * Phase 3); falls back to a conservative pass-through shim when the
7
+ * native module is absent so dev / Jest runs don't crash on a missing
8
+ * NativeModules entry.
9
+ *
10
+ * The shim NEVER fails an image — false negatives in production are
11
+ * worse than missing data. The native path is the only branch that
12
+ * can return `passed=false`.
13
+ */
14
+ import type { QualityReport, QualityThresholds } from '../types';
15
+ /**
16
+ * Numeric scores produced by the native module. The bridge resolves
17
+ * with `{ blurScore, brightnessScore }` — issues are computed in JS
18
+ * so the same threshold logic applies on iOS + Android even if a
19
+ * platform's native impl evolves independently.
20
+ */
21
+ interface NativeQualityScores {
22
+ blurScore: number;
23
+ brightnessScore: number;
24
+ }
25
+ /**
26
+ * Analyse an image file and return a quality report.
27
+ *
28
+ * @param imagePath Filesystem path to the image (with or without
29
+ * `file://` prefix — the native module strips it).
30
+ * @param thresholds Cut-offs the report scores against.
31
+ */
32
+ export declare function runQualityCheck(imagePath: string, thresholds: QualityThresholds): Promise<QualityReport>;
33
+ /**
34
+ * Apply thresholds to native scores → produce the report shape the JS
35
+ * surface promises. Pure function so it's trivially unit-testable.
36
+ *
37
+ * Exported for tests; not part of the SDK's public API.
38
+ */
39
+ export declare function scoreToReport(scores: NativeQualityScores, thresholds: QualityThresholds): QualityReport;
40
+ export {};
41
+ //# sourceMappingURL=runQualityCheck.d.ts.map
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * runQualityCheck — public entry point for the SDK's blur + brightness
5
+ * quality gate. Delegates to the native module
6
+ * `RNImageStitcherQualityChecker` when registered (iOS today via
7
+ * `ios/Sources/RNImageStitcher/QualityChecker.swift`, Android in
8
+ * Phase 3); falls back to a conservative pass-through shim when the
9
+ * native module is absent so dev / Jest runs don't crash on a missing
10
+ * NativeModules entry.
11
+ *
12
+ * The shim NEVER fails an image — false negatives in production are
13
+ * worse than missing data. The native path is the only branch that
14
+ * can return `passed=false`.
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.runQualityCheck = runQualityCheck;
18
+ exports.scoreToReport = scoreToReport;
19
+ const react_native_1 = require("react-native");
20
+ let warnedOnce = false;
21
+ /**
22
+ * Analyse an image file and return a quality report.
23
+ *
24
+ * @param imagePath Filesystem path to the image (with or without
25
+ * `file://` prefix — the native module strips it).
26
+ * @param thresholds Cut-offs the report scores against.
27
+ */
28
+ async function runQualityCheck(imagePath, thresholds) {
29
+ const native = react_native_1.NativeModules['RNImageStitcherQualityChecker'];
30
+ // Native path: registered + has the bridged `measure` method.
31
+ if (native
32
+ && typeof native === 'object'
33
+ && typeof native.measure === 'function') {
34
+ const scores = await native
35
+ .measure(imagePath);
36
+ return scoreToReport(scores, thresholds);
37
+ }
38
+ // Shim fallback — never reached when the SDK's native module is linked
39
+ // into the host app correctly. Surfaces a one-time warning in dev so
40
+ // misconfiguration is loud rather than silent.
41
+ if (!warnedOnce && __DEV__) {
42
+ // eslint-disable-next-line no-console
43
+ console.warn('[react-native-image-stitcher] QualityChecker native module not '
44
+ + `found on ${react_native_1.Platform.OS}; falling back to optimistic shim. Check `
45
+ + 'autolinking + a clean `pod install` (iOS) / `gradle clean` (Android).');
46
+ warnedOnce = true;
47
+ }
48
+ void thresholds;
49
+ return {
50
+ passed: true,
51
+ blurScore: Number.POSITIVE_INFINITY,
52
+ brightnessScore: 128,
53
+ issues: [],
54
+ };
55
+ }
56
+ /**
57
+ * Apply thresholds to native scores → produce the report shape the JS
58
+ * surface promises. Pure function so it's trivially unit-testable.
59
+ *
60
+ * Exported for tests; not part of the SDK's public API.
61
+ */
62
+ function scoreToReport(scores, thresholds) {
63
+ const issues = [];
64
+ if (scores.blurScore < thresholds.minBlurScore) {
65
+ issues.push({
66
+ type: 'blur',
67
+ message: `Image is too blurry (Laplacian variance ${scores.blurScore.toFixed(1)} `
68
+ + `< ${thresholds.minBlurScore}). Hold the camera steady and retry.`,
69
+ severity: 'error',
70
+ });
71
+ }
72
+ if (scores.brightnessScore < thresholds.minBrightness) {
73
+ issues.push({
74
+ type: 'brightness_low',
75
+ message: `Image is too dark (mean luminance ${scores.brightnessScore.toFixed(0)} `
76
+ + `< ${thresholds.minBrightness}). Add light or move closer to a lit area.`,
77
+ severity: 'warning',
78
+ });
79
+ }
80
+ else if (scores.brightnessScore > thresholds.maxBrightness) {
81
+ issues.push({
82
+ type: 'brightness_high',
83
+ message: `Image is overexposed (mean luminance ${scores.brightnessScore.toFixed(0)} `
84
+ + `> ${thresholds.maxBrightness}). Reduce light or move out of direct sunlight.`,
85
+ severity: 'warning',
86
+ });
87
+ }
88
+ return {
89
+ // `passed` is the strict gate — only `error`-severity issues block.
90
+ // Brightness warnings are advisory; SOS is still computable from a
91
+ // dim or bright photo, but a blurry one isn't.
92
+ passed: issues.every((i) => i.severity !== 'error'),
93
+ blurScore: scores.blurScore,
94
+ brightnessScore: scores.brightnessScore,
95
+ issues,
96
+ };
97
+ }
98
+ //# sourceMappingURL=runQualityCheck.js.map
@@ -0,0 +1,70 @@
1
+ export interface UseIMUTranslationGateOptions {
2
+ /**
3
+ * Whether the gate is engaged. Pass `false` to skip the subscription
4
+ * entirely — useful when the host is in AR mode (where the gate
5
+ * gets pose-derived translation natively). Hot-toggleable;
6
+ * subscribing/unsubscribing is cheap.
7
+ */
8
+ enabled: boolean;
9
+ /**
10
+ * Translation budget in METRES along the device-X (pan) axis.
11
+ * When the integrated displacement magnitude exceeds this since
12
+ * the last accept, the hook fires `onBudgetExceeded`. Default
13
+ * 0.40 m / 40 cm (80 % of the 50 cm default
14
+ * `flowMaxTranslationCm`). Caller typically passes
15
+ * `panoramaSettings.flowMaxTranslationCm * 0.8 / 100`.
16
+ */
17
+ budgetMeters?: number;
18
+ /**
19
+ * Update interval in MILLISECONDS for the DeviceMotion sensor.
20
+ * Default 20 ms ≈ 50 Hz. Lower (faster sampling) = more accurate
21
+ * integration; higher = lower CPU + battery. Matches the previous
22
+ * raw-accel cadence so reset/integrate behaviour stays comparable.
23
+ */
24
+ sampleIntervalMs?: number;
25
+ /**
26
+ * Fired exactly once per "budget crossing" — i.e., when the
27
+ * running translation along device-X crosses `budgetMeters` from
28
+ * below. The host is responsible for both (a) calling
29
+ * `IncrementalStitcher.markNextFrameAsLastKeyframe()` and
30
+ * (b) invoking the returned `resetAnchor()` once the next
31
+ * keyframe actually accepts, so the integrator restarts from zero.
32
+ */
33
+ onBudgetExceeded: () => void;
34
+ /**
35
+ * 2026-05-18 (Issue #4 investigation) — when true, log every Nth
36
+ * accelerometer sample (default N=20 ≈ 400 ms at 50 Hz) showing
37
+ * the current `acceleration.x`, accumulated `posX`, and time
38
+ * since anchor reset. Helps diagnose drift behaviour vs real
39
+ * translation magnitude in field testing. Defaults to false —
40
+ * production captures stay quiet.
41
+ */
42
+ debug?: boolean;
43
+ }
44
+ export interface UseIMUTranslationGateReturn {
45
+ /**
46
+ * Reset the running translation to zero. Call this at recording
47
+ * start AND after each confirmed keyframe accept — the typical
48
+ * wiring is to subscribe to `IncrementalStateUpdate` and
49
+ * call `resetAnchor()` from inside the listener AND from the host's
50
+ * `handleHoldStart`.
51
+ */
52
+ resetAnchor: () => void;
53
+ /**
54
+ * Read the current running displacement along device-X in METRES.
55
+ * Returns the absolute value (sign is uninteresting — either left
56
+ * or right counts the same toward the budget).
57
+ * Useful for the on-screen debug HUD ("translation since last
58
+ * accept: 0.07 m"). Not exposed via state — host polls if needed.
59
+ */
60
+ getCurrentTranslationM: () => number;
61
+ }
62
+ /**
63
+ * IMU-based translation tracker — single-axis (device-X / pan axis),
64
+ * fused IMU via `expo-sensors` `DeviceMotion`. See file header for
65
+ * algorithm + rationale. No platform-specific code; the underlying
66
+ * native fusion is platform-aware (CoreMotion on iOS, fused
67
+ * `TYPE_LINEAR_ACCELERATION` on Android).
68
+ */
69
+ export declare function useIMUTranslationGate(options: UseIMUTranslationGateOptions): UseIMUTranslationGateReturn;
70
+ //# sourceMappingURL=useIMUTranslationGate.d.ts.map