react-native-image-stitcher 0.15.1 → 0.16.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 (133) hide show
  1. package/CHANGELOG.md +147 -1
  2. package/README.md +116 -5
  3. package/android/src/main/cpp/image_stitcher_jni.cpp +107 -11
  4. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +223 -1
  5. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +87 -30
  6. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +14 -8
  8. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +39 -1
  9. package/cpp/crop_quad.cpp +162 -0
  10. package/cpp/crop_quad.hpp +163 -0
  11. package/cpp/stitcher.cpp +651 -55
  12. package/cpp/stitcher.hpp +10 -0
  13. package/cpp/warp_guard.hpp +212 -0
  14. package/dist/camera/Camera.d.ts +196 -12
  15. package/dist/camera/Camera.js +629 -35
  16. package/dist/camera/CameraView.js +62 -5
  17. package/dist/camera/CaptureCountdownOverlay.d.ts +70 -0
  18. package/dist/camera/CaptureCountdownOverlay.js +239 -0
  19. package/dist/camera/CaptureFrameCounterOverlay.d.ts +58 -0
  20. package/dist/camera/CaptureFrameCounterOverlay.js +142 -0
  21. package/dist/camera/CaptureMemoryPill.d.ts +9 -1
  22. package/dist/camera/CaptureMemoryPill.js +3 -3
  23. package/dist/camera/CapturePreview.js +2 -1
  24. package/dist/camera/CaptureStatusOverlay.d.ts +11 -4
  25. package/dist/camera/CaptureStatusOverlay.js +22 -5
  26. package/dist/camera/CaptureThumbnailStrip.js +2 -1
  27. package/dist/camera/LateralMotionModal.d.ts +85 -0
  28. package/dist/camera/LateralMotionModal.js +134 -0
  29. package/dist/camera/PanHowToOverlay.d.ts +76 -0
  30. package/dist/camera/PanHowToOverlay.js +222 -0
  31. package/dist/camera/PanoramaSettings.d.ts +8 -6
  32. package/dist/camera/PanoramaSettings.js +26 -5
  33. package/dist/camera/PanoramaSettingsModal.js +4 -4
  34. package/dist/camera/RectCropPreview.d.ts +161 -0
  35. package/dist/camera/RectCropPreview.js +480 -0
  36. package/dist/camera/RotateToLandscapePrompt.d.ts +87 -0
  37. package/dist/camera/RotateToLandscapePrompt.js +138 -0
  38. package/dist/camera/buildPanoramaInitialSettings.d.ts +19 -2
  39. package/dist/camera/buildPanoramaInitialSettings.js +9 -0
  40. package/dist/camera/cameraErrorMessages.d.ts +30 -1
  41. package/dist/camera/cameraErrorMessages.js +26 -10
  42. package/dist/camera/cameraGuidanceCopy.d.ts +87 -0
  43. package/dist/camera/cameraGuidanceCopy.js +80 -0
  44. package/dist/camera/captureCountdown.d.ts +52 -0
  45. package/dist/camera/captureCountdown.js +76 -0
  46. package/dist/camera/captureWarnings.d.ts +90 -0
  47. package/dist/camera/captureWarnings.js +108 -0
  48. package/dist/camera/classifyStitchError.d.ts +30 -0
  49. package/dist/camera/classifyStitchError.js +42 -0
  50. package/dist/camera/cropGeometry.d.ts +136 -0
  51. package/dist/camera/cropGeometry.js +223 -0
  52. package/dist/camera/displayDecodeImageProps.d.ts +25 -0
  53. package/dist/camera/displayDecodeImageProps.js +29 -0
  54. package/dist/camera/guidanceGraphics.d.ts +58 -0
  55. package/dist/camera/guidanceGraphics.js +280 -0
  56. package/dist/camera/guidanceTokens.d.ts +54 -0
  57. package/dist/camera/guidanceTokens.js +58 -0
  58. package/dist/camera/panModeGate.d.ts +54 -0
  59. package/dist/camera/panModeGate.js +62 -0
  60. package/dist/camera/pickCaptureFormat.d.ts +71 -0
  61. package/dist/camera/pickCaptureFormat.js +85 -0
  62. package/dist/camera/stitchDebugInfo.d.ts +27 -0
  63. package/dist/camera/stitchDebugInfo.js +55 -0
  64. package/dist/camera/usePanMotion.d.ts +250 -0
  65. package/dist/camera/usePanMotion.js +451 -0
  66. package/dist/index.d.ts +24 -3
  67. package/dist/index.js +33 -2
  68. package/dist/stitching/computeInscribedRect.d.ts +40 -0
  69. package/dist/stitching/computeInscribedRect.js +55 -0
  70. package/dist/stitching/cropQuad.d.ts +78 -0
  71. package/dist/stitching/cropQuad.js +116 -0
  72. package/dist/stitching/incremental.d.ts +45 -0
  73. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +56 -8
  74. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +2 -2
  75. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +48 -5
  76. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +27 -0
  77. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +191 -7
  78. package/ios/Sources/RNImageStitcher/RNSARSession.swift +25 -1
  79. package/ios/Sources/RNImageStitcher/Stitcher.swift +34 -1
  80. package/ios/Sources/RNImageStitcher/StitcherBridge.m +5 -0
  81. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +56 -0
  82. package/package.json +5 -1
  83. package/src/camera/Camera.tsx +994 -47
  84. package/src/camera/CameraView.tsx +75 -5
  85. package/src/camera/CaptureCountdownOverlay.tsx +272 -0
  86. package/src/camera/CaptureFrameCounterOverlay.tsx +183 -0
  87. package/src/camera/CaptureMemoryPill.tsx +17 -3
  88. package/src/camera/CapturePreview.tsx +5 -0
  89. package/src/camera/CaptureStatusOverlay.tsx +35 -7
  90. package/src/camera/CaptureThumbnailStrip.tsx +4 -0
  91. package/src/camera/LateralMotionModal.tsx +199 -0
  92. package/src/camera/PanHowToOverlay.tsx +246 -0
  93. package/src/camera/PanoramaSettings.ts +34 -11
  94. package/src/camera/PanoramaSettingsModal.tsx +4 -4
  95. package/src/camera/RectCropPreview.tsx +820 -0
  96. package/src/camera/RotateToLandscapePrompt.tsx +188 -0
  97. package/src/camera/buildPanoramaInitialSettings.ts +30 -1
  98. package/src/camera/cameraErrorMessages.ts +39 -2
  99. package/src/camera/cameraGuidanceCopy.ts +145 -0
  100. package/src/camera/captureCountdown.ts +83 -0
  101. package/src/camera/captureWarnings.ts +190 -0
  102. package/src/camera/classifyStitchError.ts +68 -0
  103. package/src/camera/cropGeometry.ts +268 -0
  104. package/src/camera/displayDecodeImageProps.ts +25 -0
  105. package/src/camera/guidanceGraphics.tsx +347 -0
  106. package/src/camera/guidanceTokens.ts +57 -0
  107. package/src/camera/panModeGate.ts +81 -0
  108. package/src/camera/pickCaptureFormat.ts +130 -0
  109. package/src/camera/stitchDebugInfo.ts +71 -0
  110. package/src/camera/usePanMotion.ts +667 -0
  111. package/src/index.ts +66 -3
  112. package/src/stitching/computeInscribedRect.ts +81 -0
  113. package/src/stitching/cropQuad.ts +167 -0
  114. package/src/stitching/incremental.ts +45 -0
  115. package/cpp/tests/CMakeLists.txt +0 -104
  116. package/cpp/tests/README.md +0 -86
  117. package/cpp/tests/keyframe_timebudget_test.cpp +0 -65
  118. package/cpp/tests/pose_test.cpp +0 -74
  119. package/cpp/tests/stitcher_frame_data_test.cpp +0 -132
  120. package/cpp/tests/stubs/jsi/jsi.h +0 -33
  121. package/cpp/tests/stubs/react-native-worklets-core/WKTJsiWorklet.h +0 -34
  122. package/cpp/tests/warp_guard_test.cpp +0 -48
  123. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +0 -190
  124. package/src/camera/__tests__/bandThumbRotation.test.ts +0 -120
  125. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +0 -160
  126. package/src/camera/__tests__/cameraErrorMessages.test.ts +0 -76
  127. package/src/camera/__tests__/homeIndicatorEdge.test.ts +0 -116
  128. package/src/camera/__tests__/lowMemDevice.test.ts +0 -52
  129. package/src/camera/__tests__/selectCaptureDevice.test.ts +0 -210
  130. package/src/camera/__tests__/useContentRotation.test.ts +0 -89
  131. package/src/camera/__tests__/useOrientationDrift.test.ts +0 -169
  132. package/src/stitching/__tests__/subscribeIncrementalState.refine.test.ts +0 -276
  133. package/src/stitching/__tests__/useStitcherWorklet.test.ts +0 -202
@@ -1,202 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- /**
3
- * Unit tests for `useStitcherWorklet`.
4
- *
5
- * Coverage focus (v0.11.1):
6
- *
7
- * - **AR-source short-circuit.** The hook's docstring promises
8
- * that AR-mode hosts can call `stitcher.call(frame)` from a
9
- * single composed worklet body without per-mode branching; AR
10
- * stitching runs natively via the AR-side dispatcher. Pre-
11
- * v0.11.1 the code didn't enforce that — `stitcher.call` would
12
- * invoke the vc Frame Processor plugin even on AR-source
13
- * frames, which throws `getPropertyAsObject: property '__frame'
14
- * is undefined` because AR frames are `StitcherFrameHostObject`
15
- * instances and don't carry vc's JSI `Frame` proxy marker. The
16
- * throw was caught silently by the per-worklet error handler in
17
- * `RNSARWorkletRuntime.mm`, surfacing only as an `os_log` entry
18
- * — invisible to JS, which is why composed hosts saw their
19
- * post-`stitcher.call` lines (`fireFrameProcessorLog`,
20
- * `runOnJS` callbacks) silently never execute in AR mode. Test
21
- * 2 of `docs/v0.11.0-manual-verification-checklist.md`
22
- * reproduced this on Ram's iPhone. This test pins the fix.
23
- *
24
- * - **vc-source happy path.** vc-source frames (and frames whose
25
- * `source` is `undefined` — which is what vc's raw `Frame`
26
- * looks like; the lib doesn't wrap vc frames in Phase 4a) MUST
27
- * still invoke the plugin.
28
- *
29
- * ## Why mock React's hooks directly
30
- *
31
- * The hook owns state via `useState` (the JSI plugin handle) and
32
- * side effects via `useEffect` (plugin acquisition retry loop + gyro
33
- * subscription). The existing test pattern in this directory (see
34
- * `useThrottledFrameProcessor.test.ts`) doesn't use a React renderer
35
- * — instead it mocks the hooks the SUT calls so the SUT can be
36
- * executed as a plain function. Same approach here: we mock
37
- * `useState` to return a pre-resolved plugin, `useCallback` to
38
- * return the function as-is, `useEffect` as a no-op (we don't need
39
- * the plugin-acquisition retry or gyro for the call-routing test).
40
- */
41
-
42
- import type { StitcherFrame } from '../StitcherFrame';
43
-
44
- // ─── Mock vision-camera ──────────────────────────────────────────
45
- const pluginCallSpy = jest.fn();
46
- const fakePlugin = { call: pluginCallSpy } as unknown as object;
47
-
48
- jest.mock('react-native-vision-camera', () => ({
49
- VisionCameraProxy: {
50
- initFrameProcessorPlugin: jest.fn(() => fakePlugin),
51
- },
52
- }));
53
-
54
- // ─── Mock react-native-worklets-core ─────────────────────────────
55
- jest.mock('react-native-worklets-core', () => ({
56
- useSharedValue: (initial: number) => ({ value: initial }),
57
- }));
58
-
59
- // ─── Mock react-native-sensors ───────────────────────────────────
60
- jest.mock('react-native-sensors', () => ({
61
- gyroscope: { subscribe: jest.fn(() => ({ unsubscribe: jest.fn() })) },
62
- setUpdateIntervalForType: jest.fn(),
63
- SensorTypes: { gyroscope: 'gyroscope' },
64
- }));
65
-
66
- // ─── Mock React's hooks so the SUT runs as a plain function ──────
67
- //
68
- // `useState` returns the plugin pre-resolved. `useCallback` returns
69
- // the function identity (deps array ignored — we're not testing
70
- // re-render semantics). `useEffect` is a no-op (no plugin retry,
71
- // no gyro subscription). This lets us call the hook synchronously
72
- // and exercise the worklet body via the returned `call` function.
73
- jest.mock('react', () => {
74
- const actual = jest.requireActual('react');
75
- return {
76
- ...actual,
77
- useState: <T,>(initial: T): [T, (next: T) => void] => {
78
- // For the [plugin, setPlugin] tuple: return the fake plugin
79
- // immediately rather than starting at `null`. This skips the
80
- // plugin-acquisition retry path and lets `call` actually
81
- // invoke `plugin.call(...)`.
82
- const resolved = (initial === null ? fakePlugin : initial) as T;
83
- return [resolved, () => {}];
84
- },
85
- useEffect: () => {},
86
- useCallback: <T,>(fn: T): T => fn,
87
- };
88
- });
89
-
90
- // SUT — imported AFTER mocks so the hook sees them.
91
- // eslint-disable-next-line import/first
92
- import { useStitcherWorklet } from '../useStitcherWorklet';
93
-
94
- describe('useStitcherWorklet', () => {
95
- beforeEach(() => {
96
- pluginCallSpy.mockReset();
97
- });
98
-
99
- describe('AR-source short-circuit (v0.11.1 fix)', () => {
100
- it('does NOT invoke the vc plugin for AR-source frames', () => {
101
- const { call } = useStitcherWorklet();
102
- const arFrame: StitcherFrame = {
103
- width: 1920,
104
- height: 1080,
105
- pixelFormat: 'yuv',
106
- orientation: 'landscape-right',
107
- timestamp: 0,
108
- toArrayBuffer: () => new ArrayBuffer(0),
109
- source: 'ar',
110
- pose: { rotation: [0, 0, 0, 1] },
111
- };
112
- call(arFrame);
113
- expect(pluginCallSpy).not.toHaveBeenCalled();
114
- });
115
-
116
- it('does NOT invoke the vc plugin for AR-source frames even when called repeatedly', () => {
117
- const { call } = useStitcherWorklet();
118
- const arFrame: StitcherFrame = {
119
- width: 1920,
120
- height: 1080,
121
- pixelFormat: 'yuv',
122
- orientation: 'landscape-right',
123
- timestamp: 0,
124
- toArrayBuffer: () => new ArrayBuffer(0),
125
- source: 'ar',
126
- pose: { rotation: [0, 0, 0, 1] },
127
- };
128
- for (let i = 0; i < 30; i++) call(arFrame);
129
- expect(pluginCallSpy).not.toHaveBeenCalled();
130
- });
131
- });
132
-
133
- describe('vc-source happy path', () => {
134
- it('invokes the vc plugin for vc-source frames', () => {
135
- const { call } = useStitcherWorklet();
136
- const vcFrame: StitcherFrame = {
137
- width: 1920,
138
- height: 1080,
139
- pixelFormat: 'yuv',
140
- orientation: 'landscape-right',
141
- timestamp: 0,
142
- toArrayBuffer: () => new ArrayBuffer(0),
143
- source: 'vc',
144
- pose: { rotation: [0, 0, 0, 1] },
145
- };
146
- call(vcFrame);
147
- expect(pluginCallSpy).toHaveBeenCalledTimes(1);
148
- });
149
-
150
- it('invokes the vc plugin for frames with undefined source (raw vc Frame)', () => {
151
- // vc's raw `Frame` doesn't carry the `source` field — the lib's
152
- // Phase 4a deferral means we don't wrap vc frames into
153
- // `StitcherFrame`. The AR-source check must treat undefined
154
- // as "not AR" to preserve the non-AR worklet path.
155
- const { call } = useStitcherWorklet();
156
- const rawVcFrame = {
157
- width: 1920,
158
- height: 1080,
159
- pixelFormat: 'yuv',
160
- orientation: 'landscape-right',
161
- timestamp: 0,
162
- toArrayBuffer: () => new ArrayBuffer(0),
163
- // `source` intentionally absent
164
- } as unknown as StitcherFrame;
165
- call(rawVcFrame);
166
- expect(pluginCallSpy).toHaveBeenCalledTimes(1);
167
- });
168
- });
169
-
170
- describe('plugin.call payload shape', () => {
171
- it('passes the frame + a numeric-intrinsics params object', () => {
172
- const { call } = useStitcherWorklet();
173
- const vcFrame: StitcherFrame = {
174
- width: 1920,
175
- height: 1080,
176
- pixelFormat: 'yuv',
177
- orientation: 'landscape-right',
178
- timestamp: 0,
179
- toArrayBuffer: () => new ArrayBuffer(0),
180
- source: 'vc',
181
- pose: { rotation: [0, 0, 0, 1] },
182
- };
183
- call(vcFrame);
184
- expect(pluginCallSpy).toHaveBeenCalledWith(
185
- vcFrame,
186
- expect.objectContaining({
187
- tx: 0, ty: 0, tz: 0,
188
- qx: expect.any(Number),
189
- qy: expect.any(Number),
190
- qz: expect.any(Number),
191
- qw: expect.any(Number),
192
- fx: expect.any(Number),
193
- fy: expect.any(Number),
194
- cx: 960,
195
- cy: 540,
196
- imageWidth: 1920,
197
- imageHeight: 1080,
198
- }),
199
- );
200
- });
201
- });
202
- });