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.
- package/CHANGELOG.md +147 -1
- package/README.md +116 -5
- package/android/src/main/cpp/image_stitcher_jni.cpp +107 -11
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +223 -1
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +87 -30
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +14 -8
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +39 -1
- package/cpp/crop_quad.cpp +162 -0
- package/cpp/crop_quad.hpp +163 -0
- package/cpp/stitcher.cpp +651 -55
- package/cpp/stitcher.hpp +10 -0
- package/cpp/warp_guard.hpp +212 -0
- package/dist/camera/Camera.d.ts +196 -12
- package/dist/camera/Camera.js +629 -35
- package/dist/camera/CameraView.js +62 -5
- package/dist/camera/CaptureCountdownOverlay.d.ts +70 -0
- package/dist/camera/CaptureCountdownOverlay.js +239 -0
- package/dist/camera/CaptureFrameCounterOverlay.d.ts +58 -0
- package/dist/camera/CaptureFrameCounterOverlay.js +142 -0
- package/dist/camera/CaptureMemoryPill.d.ts +9 -1
- package/dist/camera/CaptureMemoryPill.js +3 -3
- package/dist/camera/CapturePreview.js +2 -1
- package/dist/camera/CaptureStatusOverlay.d.ts +11 -4
- package/dist/camera/CaptureStatusOverlay.js +22 -5
- package/dist/camera/CaptureThumbnailStrip.js +2 -1
- package/dist/camera/LateralMotionModal.d.ts +85 -0
- package/dist/camera/LateralMotionModal.js +134 -0
- package/dist/camera/PanHowToOverlay.d.ts +76 -0
- package/dist/camera/PanHowToOverlay.js +222 -0
- package/dist/camera/PanoramaSettings.d.ts +8 -6
- package/dist/camera/PanoramaSettings.js +26 -5
- package/dist/camera/PanoramaSettingsModal.js +4 -4
- package/dist/camera/RectCropPreview.d.ts +161 -0
- package/dist/camera/RectCropPreview.js +480 -0
- package/dist/camera/RotateToLandscapePrompt.d.ts +87 -0
- package/dist/camera/RotateToLandscapePrompt.js +138 -0
- package/dist/camera/buildPanoramaInitialSettings.d.ts +19 -2
- package/dist/camera/buildPanoramaInitialSettings.js +9 -0
- package/dist/camera/cameraErrorMessages.d.ts +30 -1
- package/dist/camera/cameraErrorMessages.js +26 -10
- package/dist/camera/cameraGuidanceCopy.d.ts +87 -0
- package/dist/camera/cameraGuidanceCopy.js +80 -0
- package/dist/camera/captureCountdown.d.ts +52 -0
- package/dist/camera/captureCountdown.js +76 -0
- package/dist/camera/captureWarnings.d.ts +90 -0
- package/dist/camera/captureWarnings.js +108 -0
- package/dist/camera/classifyStitchError.d.ts +30 -0
- package/dist/camera/classifyStitchError.js +42 -0
- package/dist/camera/cropGeometry.d.ts +136 -0
- package/dist/camera/cropGeometry.js +223 -0
- package/dist/camera/displayDecodeImageProps.d.ts +25 -0
- package/dist/camera/displayDecodeImageProps.js +29 -0
- package/dist/camera/guidanceGraphics.d.ts +58 -0
- package/dist/camera/guidanceGraphics.js +280 -0
- package/dist/camera/guidanceTokens.d.ts +54 -0
- package/dist/camera/guidanceTokens.js +58 -0
- package/dist/camera/panModeGate.d.ts +54 -0
- package/dist/camera/panModeGate.js +62 -0
- package/dist/camera/pickCaptureFormat.d.ts +71 -0
- package/dist/camera/pickCaptureFormat.js +85 -0
- package/dist/camera/stitchDebugInfo.d.ts +27 -0
- package/dist/camera/stitchDebugInfo.js +55 -0
- package/dist/camera/usePanMotion.d.ts +250 -0
- package/dist/camera/usePanMotion.js +451 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.js +33 -2
- package/dist/stitching/computeInscribedRect.d.ts +40 -0
- package/dist/stitching/computeInscribedRect.js +55 -0
- package/dist/stitching/cropQuad.d.ts +78 -0
- package/dist/stitching/cropQuad.js +116 -0
- package/dist/stitching/incremental.d.ts +45 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +56 -8
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +48 -5
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +27 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +191 -7
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +25 -1
- package/ios/Sources/RNImageStitcher/Stitcher.swift +34 -1
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +5 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +56 -0
- package/package.json +5 -1
- package/src/camera/Camera.tsx +994 -47
- package/src/camera/CameraView.tsx +75 -5
- package/src/camera/CaptureCountdownOverlay.tsx +272 -0
- package/src/camera/CaptureFrameCounterOverlay.tsx +183 -0
- package/src/camera/CaptureMemoryPill.tsx +17 -3
- package/src/camera/CapturePreview.tsx +5 -0
- package/src/camera/CaptureStatusOverlay.tsx +35 -7
- package/src/camera/CaptureThumbnailStrip.tsx +4 -0
- package/src/camera/LateralMotionModal.tsx +199 -0
- package/src/camera/PanHowToOverlay.tsx +246 -0
- package/src/camera/PanoramaSettings.ts +34 -11
- package/src/camera/PanoramaSettingsModal.tsx +4 -4
- package/src/camera/RectCropPreview.tsx +820 -0
- package/src/camera/RotateToLandscapePrompt.tsx +188 -0
- package/src/camera/buildPanoramaInitialSettings.ts +30 -1
- package/src/camera/cameraErrorMessages.ts +39 -2
- package/src/camera/cameraGuidanceCopy.ts +145 -0
- package/src/camera/captureCountdown.ts +83 -0
- package/src/camera/captureWarnings.ts +190 -0
- package/src/camera/classifyStitchError.ts +68 -0
- package/src/camera/cropGeometry.ts +268 -0
- package/src/camera/displayDecodeImageProps.ts +25 -0
- package/src/camera/guidanceGraphics.tsx +347 -0
- package/src/camera/guidanceTokens.ts +57 -0
- package/src/camera/panModeGate.ts +81 -0
- package/src/camera/pickCaptureFormat.ts +130 -0
- package/src/camera/stitchDebugInfo.ts +71 -0
- package/src/camera/usePanMotion.ts +667 -0
- package/src/index.ts +66 -3
- package/src/stitching/computeInscribedRect.ts +81 -0
- package/src/stitching/cropQuad.ts +167 -0
- package/src/stitching/incremental.ts +45 -0
- package/cpp/tests/CMakeLists.txt +0 -104
- package/cpp/tests/README.md +0 -86
- package/cpp/tests/keyframe_timebudget_test.cpp +0 -65
- package/cpp/tests/pose_test.cpp +0 -74
- package/cpp/tests/stitcher_frame_data_test.cpp +0 -132
- package/cpp/tests/stubs/jsi/jsi.h +0 -33
- package/cpp/tests/stubs/react-native-worklets-core/WKTJsiWorklet.h +0 -34
- package/cpp/tests/warp_guard_test.cpp +0 -48
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +0 -190
- package/src/camera/__tests__/bandThumbRotation.test.ts +0 -120
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +0 -160
- package/src/camera/__tests__/cameraErrorMessages.test.ts +0 -76
- package/src/camera/__tests__/homeIndicatorEdge.test.ts +0 -116
- package/src/camera/__tests__/lowMemDevice.test.ts +0 -52
- package/src/camera/__tests__/selectCaptureDevice.test.ts +0 -210
- package/src/camera/__tests__/useContentRotation.test.ts +0 -89
- package/src/camera/__tests__/useOrientationDrift.test.ts +0 -169
- package/src/stitching/__tests__/subscribeIncrementalState.refine.test.ts +0 -276
- 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
|
-
});
|