react-native-image-stitcher 0.2.1 → 0.4.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 (65) hide show
  1. package/CHANGELOG.md +511 -1
  2. package/README.md +1 -1
  3. package/android/src/main/cpp/keyframe_gate_jni.cpp +138 -0
  4. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +412 -40
  5. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +128 -0
  6. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +87 -45
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +46 -4
  8. package/cpp/stitcher.cpp +101 -1
  9. package/cpp/stitcher.hpp +8 -0
  10. package/dist/camera/Camera.d.ts +9 -0
  11. package/dist/camera/Camera.js +165 -43
  12. package/dist/camera/CaptureDebugOverlay.d.ts +45 -0
  13. package/dist/camera/CaptureDebugOverlay.js +146 -0
  14. package/dist/camera/CaptureKeyframePill.d.ts +28 -0
  15. package/dist/camera/CaptureKeyframePill.js +60 -0
  16. package/dist/camera/CaptureMemoryPill.d.ts +28 -0
  17. package/dist/camera/CaptureMemoryPill.js +109 -0
  18. package/dist/camera/CaptureOrientationPill.d.ts +22 -0
  19. package/dist/camera/CaptureOrientationPill.js +44 -0
  20. package/dist/camera/CaptureStitchStatsToast.d.ts +45 -0
  21. package/dist/camera/CaptureStitchStatsToast.js +133 -0
  22. package/dist/camera/PanoramaSettings.d.ts +478 -0
  23. package/dist/camera/PanoramaSettings.js +120 -0
  24. package/dist/camera/PanoramaSettingsBridge.d.ts +84 -0
  25. package/dist/camera/PanoramaSettingsBridge.js +208 -0
  26. package/dist/camera/PanoramaSettingsModal.d.ts +50 -298
  27. package/dist/camera/PanoramaSettingsModal.js +189 -354
  28. package/dist/camera/buildPanoramaInitialSettings.d.ts +70 -0
  29. package/dist/camera/buildPanoramaInitialSettings.js +97 -0
  30. package/dist/camera/lowMemDevice.d.ts +24 -0
  31. package/dist/camera/lowMemDevice.js +69 -0
  32. package/dist/index.d.ts +16 -2
  33. package/dist/index.js +37 -2
  34. package/dist/sensors/useIMUTranslationGate.d.ts +26 -0
  35. package/dist/sensors/useIMUTranslationGate.js +83 -1
  36. package/dist/stitching/incremental.d.ts +25 -0
  37. package/dist/stitching/useIncrementalStitcher.d.ts +12 -1
  38. package/dist/stitching/useIncrementalStitcher.js +7 -1
  39. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +321 -7
  40. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +8 -0
  41. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +12 -0
  42. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +13 -0
  43. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +15 -0
  44. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +1 -0
  45. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +17 -4
  46. package/ios/Sources/RNImageStitcher/Stitcher.swift +6 -1
  47. package/package.json +6 -2
  48. package/src/camera/Camera.tsx +220 -54
  49. package/src/camera/CaptureDebugOverlay.tsx +180 -0
  50. package/src/camera/CaptureKeyframePill.tsx +77 -0
  51. package/src/camera/CaptureMemoryPill.tsx +96 -0
  52. package/src/camera/CaptureOrientationPill.tsx +57 -0
  53. package/src/camera/CaptureStitchStatsToast.tsx +155 -0
  54. package/src/camera/PanoramaSettings.ts +605 -0
  55. package/src/camera/PanoramaSettingsBridge.ts +238 -0
  56. package/src/camera/PanoramaSettingsModal.tsx +296 -988
  57. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +375 -0
  58. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +119 -0
  59. package/src/camera/__tests__/lowMemDevice.test.ts +52 -0
  60. package/src/camera/buildPanoramaInitialSettings.ts +139 -0
  61. package/src/camera/lowMemDevice.ts +71 -0
  62. package/src/index.ts +61 -3
  63. package/src/sensors/useIMUTranslationGate.ts +112 -1
  64. package/src/stitching/incremental.ts +25 -0
  65. package/src/stitching/useIncrementalStitcher.ts +18 -0
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CaptureMemoryPill — top-right diagnostic pill showing native
5
+ * process memory footprint in MB, polled at 500 ms.
6
+ *
7
+ * Color-coded against the iPhone 16 Pro per-process jetsam limit:
8
+ *
9
+ * - green <1500 MB (comfortable)
10
+ * - amber 1500–2200 (approaching pressure)
11
+ * - red >2200 (close to limit — capture may be killed)
12
+ *
13
+ * Backed by the existing `getMemoryFootprintMB()` native module
14
+ * (iOS: `task_info phys_footprint`, Android: `Debug.MemoryInfo
15
+ * getTotalPss * 1024`). Returns -1 if the native call fails.
16
+ *
17
+ * Mount this pill inside a `settings.debug`-gated branch — it
18
+ * polls native every 500 ms and is unwanted in production builds.
19
+ */
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.CaptureMemoryPill = CaptureMemoryPill;
55
+ const react_1 = __importStar(require("react"));
56
+ const react_native_1 = require("react-native");
57
+ const incremental_1 = require("../stitching/incremental");
58
+ function CaptureMemoryPill({ topInset = 0, pollIntervalMs = 500, }) {
59
+ const [memMB, setMemMB] = (0, react_1.useState)(null);
60
+ (0, react_1.useEffect)(() => {
61
+ const native = (0, incremental_1.getIncrementalNativeModule)();
62
+ if (!native?.getMemoryFootprintMB)
63
+ return undefined;
64
+ let cancelled = false;
65
+ const tick = async () => {
66
+ try {
67
+ const mb = await native.getMemoryFootprintMB();
68
+ if (!cancelled)
69
+ setMemMB(mb);
70
+ }
71
+ catch {
72
+ // Bridge error — leave the previous reading visible.
73
+ }
74
+ };
75
+ tick();
76
+ const id = setInterval(tick, pollIntervalMs);
77
+ return () => {
78
+ cancelled = true;
79
+ clearInterval(id);
80
+ };
81
+ }, [pollIntervalMs]);
82
+ if (memMB === null || memMB < 0)
83
+ return null;
84
+ const bg = memMB > 2200 ? 'rgba(239, 68, 68, 0.92)' // red
85
+ : memMB > 1500 ? 'rgba(245, 158, 11, 0.92)' // amber
86
+ : 'rgba(34, 197, 94, 0.92)'; // green
87
+ return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [
88
+ styles.container,
89
+ { top: topInset + 56, backgroundColor: bg },
90
+ ], accessibilityRole: "alert" },
91
+ react_1.default.createElement(react_native_1.Text, { style: styles.text }, `${Math.round(memMB)} MB`)));
92
+ }
93
+ const styles = react_native_1.StyleSheet.create({
94
+ container: {
95
+ position: 'absolute',
96
+ right: 12,
97
+ paddingHorizontal: 10,
98
+ paddingVertical: 5,
99
+ borderRadius: 999,
100
+ zIndex: 100,
101
+ },
102
+ text: {
103
+ color: '#fff',
104
+ fontSize: 12,
105
+ fontWeight: '700',
106
+ fontFamily: 'Menlo',
107
+ },
108
+ });
109
+ //# sourceMappingURL=CaptureMemoryPill.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CaptureOrientationPill — diagnostic pill showing the operator's
3
+ * current hold orientation as detected by the pose-derived hook.
4
+ *
5
+ * Useful for diagnosing rotation issues — if the pill says
6
+ * `landscape-left` but the band overlay is rendering as if it's
7
+ * `portrait`, there's a mismatch between the JS orientation hook
8
+ * and the engine's pose-derived isLandscape signal.
9
+ *
10
+ * Pinned top-left below the status bar. Layer-2 hosts can mount
11
+ * this directly; Layer-1 `<Camera>` mounts it automatically when
12
+ * `settings.debug = true`.
13
+ */
14
+ import React from 'react';
15
+ export interface CaptureOrientationPillProps {
16
+ /** Current device orientation (typically from useDeviceOrientation). */
17
+ orientation: string;
18
+ /** Top inset for safe-area placement. Pill pinned `topInset + 56`. */
19
+ topInset?: number;
20
+ }
21
+ export declare function CaptureOrientationPill({ orientation, topInset, }: CaptureOrientationPillProps): React.JSX.Element;
22
+ //# sourceMappingURL=CaptureOrientationPill.d.ts.map
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CaptureOrientationPill — diagnostic pill showing the operator's
5
+ * current hold orientation as detected by the pose-derived hook.
6
+ *
7
+ * Useful for diagnosing rotation issues — if the pill says
8
+ * `landscape-left` but the band overlay is rendering as if it's
9
+ * `portrait`, there's a mismatch between the JS orientation hook
10
+ * and the engine's pose-derived isLandscape signal.
11
+ *
12
+ * Pinned top-left below the status bar. Layer-2 hosts can mount
13
+ * this directly; Layer-1 `<Camera>` mounts it automatically when
14
+ * `settings.debug = true`.
15
+ */
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.CaptureOrientationPill = CaptureOrientationPill;
21
+ const react_1 = __importDefault(require("react"));
22
+ const react_native_1 = require("react-native");
23
+ function CaptureOrientationPill({ orientation, topInset = 0, }) {
24
+ return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [styles.container, { top: topInset + 56 }], accessibilityRole: "alert" },
25
+ react_1.default.createElement(react_native_1.Text, { style: styles.text }, `orient: ${orientation}`)));
26
+ }
27
+ const styles = react_native_1.StyleSheet.create({
28
+ container: {
29
+ position: 'absolute',
30
+ left: 12,
31
+ paddingHorizontal: 10,
32
+ paddingVertical: 5,
33
+ borderRadius: 999,
34
+ backgroundColor: 'rgba(99, 102, 241, 0.92)',
35
+ zIndex: 100,
36
+ },
37
+ text: {
38
+ color: '#fff',
39
+ fontSize: 11,
40
+ fontWeight: '700',
41
+ fontFamily: 'Menlo',
42
+ },
43
+ });
44
+ //# sourceMappingURL=CaptureOrientationPill.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CaptureStitchStatsToast — auto-dismissing toast that shows the
3
+ * batch-stitcher's leaveBiggestComponent telemetry + the resolved
4
+ * cv::Stitcher mode after every successful finalize.
5
+ *
6
+ * Pattern: top-center capsule, dark translucent background, dismisses
7
+ * itself after `dismissAfterMs` (default 4500). Replaces the
8
+ * Alert.alert blocking modal that used to interrupt the next
9
+ * capture. See `useStitchStatsToast` hook for the matching
10
+ * imperative API.
11
+ *
12
+ * Layer-2 hosts can mount this directly + pass their own message;
13
+ * Layer-1 `<Camera>` mounts it under `settings.debug` and feeds
14
+ * the formatted message from the finalize result automatically.
15
+ */
16
+ import React from 'react';
17
+ import type { IncrementalFinalizeResult } from '../stitching/incremental';
18
+ export interface CaptureStitchStatsToastProps {
19
+ /** Toast message to show. Pass null to hide. */
20
+ message: string | null;
21
+ /** Top inset for safe-area placement. Toast pinned `topInset + 12`. */
22
+ topInset?: number;
23
+ }
24
+ export declare function CaptureStitchStatsToast({ message, topInset, }: CaptureStitchStatsToastProps): React.JSX.Element | null;
25
+ /**
26
+ * Imperative API for showing transient stitch-stats toasts.
27
+ *
28
+ * Returns `{ message, showFor, showResult }`:
29
+ * - `message` — current toast text (pass to CaptureStitchStatsToast)
30
+ * - `showFor` — show an arbitrary string, auto-dismiss
31
+ * - `showResult` — format an `IncrementalFinalizeResult` into the
32
+ * standard "Stitch: N/M frames • thresh X.XX •
33
+ * N attempt(s) • mode" line and show it. Convenience
34
+ * for hosts that just want the canonical format.
35
+ *
36
+ * Auto-clears its setTimeout on unmount so callers don't have to
37
+ * worry about setState-on-unmounted warnings.
38
+ */
39
+ export interface UseStitchStatsToastReturn {
40
+ message: string | null;
41
+ showFor: (msg: string, ms?: number) => void;
42
+ showResult: (result: IncrementalFinalizeResult, ms?: number) => void;
43
+ }
44
+ export declare function useStitchStatsToast(): UseStitchStatsToastReturn;
45
+ //# sourceMappingURL=CaptureStitchStatsToast.d.ts.map
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * CaptureStitchStatsToast — auto-dismissing toast that shows the
5
+ * batch-stitcher's leaveBiggestComponent telemetry + the resolved
6
+ * cv::Stitcher mode after every successful finalize.
7
+ *
8
+ * Pattern: top-center capsule, dark translucent background, dismisses
9
+ * itself after `dismissAfterMs` (default 4500). Replaces the
10
+ * Alert.alert blocking modal that used to interrupt the next
11
+ * capture. See `useStitchStatsToast` hook for the matching
12
+ * imperative API.
13
+ *
14
+ * Layer-2 hosts can mount this directly + pass their own message;
15
+ * Layer-1 `<Camera>` mounts it under `settings.debug` and feeds
16
+ * the formatted message from the finalize result automatically.
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.CaptureStitchStatsToast = CaptureStitchStatsToast;
53
+ exports.useStitchStatsToast = useStitchStatsToast;
54
+ const react_1 = __importStar(require("react"));
55
+ const react_native_1 = require("react-native");
56
+ function CaptureStitchStatsToast({ message, topInset = 0, }) {
57
+ if (message === null)
58
+ return null;
59
+ return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: [
60
+ styles.wrap,
61
+ { top: topInset + 12 },
62
+ ] },
63
+ react_1.default.createElement(react_native_1.View, { style: styles.capsule, accessibilityRole: "alert", accessibilityLiveRegion: "polite" },
64
+ react_1.default.createElement(react_native_1.Text, { style: styles.text, numberOfLines: 3 }, message))));
65
+ }
66
+ const styles = react_native_1.StyleSheet.create({
67
+ wrap: {
68
+ position: 'absolute',
69
+ left: 24,
70
+ right: 24,
71
+ alignItems: 'center',
72
+ zIndex: 110,
73
+ },
74
+ capsule: {
75
+ paddingHorizontal: 16,
76
+ paddingVertical: 10,
77
+ borderRadius: 16,
78
+ backgroundColor: 'rgba(15, 23, 42, 0.92)',
79
+ maxWidth: '100%',
80
+ },
81
+ text: {
82
+ color: '#ffffff',
83
+ fontSize: 13,
84
+ fontWeight: '600',
85
+ textAlign: 'center',
86
+ },
87
+ });
88
+ const DEFAULT_DISMISS_MS = 4500;
89
+ function useStitchStatsToast() {
90
+ const [message, setMessage] = (0, react_1.useState)(null);
91
+ const timerRef = (0, react_1.useRef)(null);
92
+ const showFor = (0, react_1.useCallback)((msg, ms = DEFAULT_DISMISS_MS) => {
93
+ if (timerRef.current)
94
+ clearTimeout(timerRef.current);
95
+ setMessage(msg);
96
+ timerRef.current = setTimeout(() => {
97
+ setMessage(null);
98
+ timerRef.current = null;
99
+ }, ms);
100
+ }, []);
101
+ const showResult = (0, react_1.useCallback)((result, ms = DEFAULT_DISMISS_MS) => {
102
+ // Format mirrors the RetaiLens debug toast that operators
103
+ // already recognise. Includes the new (audit F2g) resolved
104
+ // stitchMode as a fourth segment when present.
105
+ const requested = result.framesRequested;
106
+ const included = result.framesIncluded;
107
+ const thresh = result.finalConfidenceThresh;
108
+ const mode = result.stitchModeResolved;
109
+ // The retry-attempt count is derived deterministically from
110
+ // the threshold used on the successful attempt (1.0→1, 0.5→2,
111
+ // 0.3→3) per cpp/stitcher.cpp's retry loop.
112
+ const attempts = typeof thresh === 'number'
113
+ ? thresh >= 0.99 ? 1
114
+ : thresh >= 0.49 ? 2
115
+ : thresh >= 0.29 ? 3
116
+ : null
117
+ : null;
118
+ const reqStr = typeof requested === 'number' ? requested : '?';
119
+ const incStr = typeof included === 'number' ? included : '?';
120
+ const threshStr = typeof thresh === 'number' && thresh >= 0
121
+ ? thresh.toFixed(2)
122
+ : 'n/a';
123
+ const attStr = attempts !== null ? `${attempts} attempt${attempts > 1 ? 's' : ''}` : '? attempts';
124
+ const modeStr = mode ? ` • ${mode}` : '';
125
+ showFor(`Stitch: ${incStr}/${reqStr} frames • thresh ${threshStr} • ${attStr}${modeStr}`, ms);
126
+ }, [showFor]);
127
+ (0, react_1.useEffect)(() => () => {
128
+ if (timerRef.current)
129
+ clearTimeout(timerRef.current);
130
+ }, []);
131
+ return { message, showFor, showResult };
132
+ }
133
+ //# sourceMappingURL=CaptureStitchStatsToast.js.map