react-native-image-stitcher 0.11.1 → 0.12.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 +75 -0
- package/README.md +28 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +3 -2
- package/dist/camera/ARCameraView.d.ts +10 -0
- package/dist/camera/ARCameraView.js +1 -0
- package/dist/camera/Camera.d.ts +20 -0
- package/dist/camera/Camera.js +175 -6
- package/dist/camera/OrientationDriftModal.d.ts +83 -0
- package/dist/camera/OrientationDriftModal.js +159 -0
- package/dist/camera/PanoramaBandOverlay.d.ts +13 -1
- package/dist/camera/PanoramaBandOverlay.js +106 -45
- package/dist/camera/PanoramaSettingsModal.js +15 -1
- package/dist/camera/ViewportCropOverlay.d.ts +35 -31
- package/dist/camera/ViewportCropOverlay.js +39 -30
- package/dist/camera/useDeviceOrientation.d.ts +18 -9
- package/dist/camera/useDeviceOrientation.js +18 -9
- package/dist/camera/useOrientationDrift.d.ts +104 -0
- package/dist/camera/useOrientationDrift.js +120 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -1
- package/dist/stitching/incremental.d.ts +5 -3
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +7 -1
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +4 -3
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +9 -7
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +46 -7
- package/package.json +1 -1
- package/src/camera/ARCameraView.tsx +18 -1
- package/src/camera/Camera.tsx +280 -13
- package/src/camera/OrientationDriftModal.tsx +224 -0
- package/src/camera/PanoramaBandOverlay.tsx +135 -49
- package/src/camera/PanoramaSettingsModal.tsx +14 -0
- package/src/camera/ViewportCropOverlay.tsx +52 -30
- package/src/camera/__tests__/useOrientationDrift.test.ts +169 -0
- package/src/camera/useDeviceOrientation.ts +18 -9
- package/src/camera/useOrientationDrift.ts +172 -0
- package/src/index.ts +13 -0
- package/src/stitching/incremental.ts +5 -3
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* useOrientationDrift — detects mid-capture device rotation.
|
|
4
|
+
*
|
|
5
|
+
* Pairs with `useDeviceOrientation()` to surface the case where the
|
|
6
|
+
* user rotates the device *during* an active capture. The
|
|
7
|
+
* incremental stitching engine supports both portrait (Mode B,
|
|
8
|
+
* horizontal pan) and landscape (Mode A, vertical pan) capture
|
|
9
|
+
* modes as first-class — but mixing them mid-capture produces
|
|
10
|
+
* malformed output ("cross-mode capture is best-effort," per
|
|
11
|
+
* `incremental.ts:373-403`). Hosts that want to protect against
|
|
12
|
+
* this use this hook + `OrientationDriftModal` together: the
|
|
13
|
+
* `<Camera>` flagship component auto-abandons capture the instant
|
|
14
|
+
* `drifted === true` (PR-2 wiring); the modal surfaces an
|
|
15
|
+
* explanatory popup to the user.
|
|
16
|
+
*
|
|
17
|
+
* ## API contract
|
|
18
|
+
*
|
|
19
|
+
* Pass `active` true while a capture is in flight, false otherwise.
|
|
20
|
+
* Returns:
|
|
21
|
+
*
|
|
22
|
+
* - `captureOrientation` — the orientation snapshotted at the
|
|
23
|
+
* moment `active` transitioned false → true. `undefined` when
|
|
24
|
+
* `active` is false.
|
|
25
|
+
* - `currentOrientation` — live orientation from
|
|
26
|
+
* `useDeviceOrientation()`. Always defined (defaults to
|
|
27
|
+
* `'portrait'` until the accelerometer's first sample).
|
|
28
|
+
* - `drifted` — `true` IFF `active` is currently true AND
|
|
29
|
+
* `currentOrientation !== captureOrientation` at some point
|
|
30
|
+
* since the snapshot. **Latching** — once true, stays true
|
|
31
|
+
* until `active` flips back to false. This is intentional:
|
|
32
|
+
* after detection, callers should auto-abandon the capture
|
|
33
|
+
* (engine `stop()`); allowing the flag to clear before then
|
|
34
|
+
* would mask the drift if the user rotated back to the
|
|
35
|
+
* original orientation between the detection tick and the
|
|
36
|
+
* callers' abandonment effect.
|
|
37
|
+
*
|
|
38
|
+
* ## Semantics by transition
|
|
39
|
+
*
|
|
40
|
+
* - `active` false → true: snapshot `currentOrientation`;
|
|
41
|
+
* reset `drifted` to false.
|
|
42
|
+
* - `active` true (steady): if `currentOrientation !==
|
|
43
|
+
* captureOrientation` at any point, latch `drifted = true`.
|
|
44
|
+
* - `active` true → false: clear snapshot; reset `drifted`.
|
|
45
|
+
*
|
|
46
|
+
* ## Why a separate hook (rather than inlining in `<Camera>`)
|
|
47
|
+
*
|
|
48
|
+
* Hosts using the Layer-2 building blocks (`CameraView` directly,
|
|
49
|
+
* custom capture UX) can reuse this hook without mounting the
|
|
50
|
+
* full `<Camera>` flagship. Same composition pattern as
|
|
51
|
+
* `useIMUTranslationGate` and `useKeyframeStream`.
|
|
52
|
+
*
|
|
53
|
+
* ## Testing
|
|
54
|
+
*
|
|
55
|
+
* The pure state-transition function `_computeDriftStateForTests`
|
|
56
|
+
* is exported separately so jest can exercise all 5 transition
|
|
57
|
+
* cases without booting a React render. The hook itself is a
|
|
58
|
+
* thin wrapper around it (verified via on-device manual flow in
|
|
59
|
+
* the v0.12 verification checklist).
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import { useEffect, useState } from 'react';
|
|
63
|
+
|
|
64
|
+
import {
|
|
65
|
+
useDeviceOrientation,
|
|
66
|
+
type DeviceOrientation,
|
|
67
|
+
} from './useDeviceOrientation';
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
export interface UseOrientationDriftReturn {
|
|
71
|
+
/**
|
|
72
|
+
* `true` IFF a capture is active and the device has rotated since
|
|
73
|
+
* the snapshot taken at capture start. Latching: once true, stays
|
|
74
|
+
* true until `active` flips false.
|
|
75
|
+
*/
|
|
76
|
+
drifted: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Snapshot of `currentOrientation` at the moment `active`
|
|
80
|
+
* transitioned false → true. `undefined` when `active` is false.
|
|
81
|
+
*/
|
|
82
|
+
captureOrientation: DeviceOrientation | undefined;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Live device orientation from `useDeviceOrientation()`. Always
|
|
86
|
+
* defined. Exposed so callers (e.g. the drift modal) can show
|
|
87
|
+
* "captured in PORTRAIT, now LANDSCAPE-LEFT" copy without
|
|
88
|
+
* mounting `useDeviceOrientation()` themselves.
|
|
89
|
+
*/
|
|
90
|
+
currentOrientation: DeviceOrientation;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Internal state of the drift detector. Two scalar pieces: the
|
|
96
|
+
* snapshotted capture orientation (undefined when inactive) + the
|
|
97
|
+
* latched drift flag.
|
|
98
|
+
*/
|
|
99
|
+
interface DriftState {
|
|
100
|
+
captureOrientation: DeviceOrientation | undefined;
|
|
101
|
+
drifted: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
const INITIAL_STATE: DriftState = {
|
|
106
|
+
captureOrientation: undefined,
|
|
107
|
+
drifted: false,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Pure state-transition function for the drift detector. Exported
|
|
113
|
+
* with a `_` prefix to signal "internal — not part of the public
|
|
114
|
+
* API." Jest uses this directly so tests don't need a React
|
|
115
|
+
* renderer (the lib's jest config is pure-data / no RN preset).
|
|
116
|
+
*
|
|
117
|
+
* Given the previous state + the current `active` flag + the
|
|
118
|
+
* current device orientation, returns the new state. Idempotent
|
|
119
|
+
* when nothing changed (returns the same object reference) so
|
|
120
|
+
* downstream `useState(setState)` calls become no-ops.
|
|
121
|
+
*/
|
|
122
|
+
export function _computeDriftStateForTests(
|
|
123
|
+
prev: DriftState,
|
|
124
|
+
active: boolean,
|
|
125
|
+
currentOrientation: DeviceOrientation,
|
|
126
|
+
): DriftState {
|
|
127
|
+
if (!active) {
|
|
128
|
+
// active is false (or just transitioned to false). Clear the
|
|
129
|
+
// snapshot + drift flag. Idempotent when already cleared.
|
|
130
|
+
if (prev.captureOrientation === undefined && !prev.drifted) {
|
|
131
|
+
return prev;
|
|
132
|
+
}
|
|
133
|
+
return INITIAL_STATE;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// active is true.
|
|
137
|
+
if (prev.captureOrientation === undefined) {
|
|
138
|
+
// false → true transition. Snapshot the current orientation.
|
|
139
|
+
// drifted starts false because, by definition, the current
|
|
140
|
+
// orientation matches itself.
|
|
141
|
+
return { captureOrientation: currentOrientation, drifted: false };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// active is steady true. Check for drift. Latching: once
|
|
145
|
+
// drifted is true, never set it back to false until active
|
|
146
|
+
// flips (handled above).
|
|
147
|
+
if (!prev.drifted && currentOrientation !== prev.captureOrientation) {
|
|
148
|
+
return { captureOrientation: prev.captureOrientation, drifted: true };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// No transition + no new drift. Return prev to avoid an
|
|
152
|
+
// unnecessary state update + re-render.
|
|
153
|
+
return prev;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
export function useOrientationDrift(
|
|
158
|
+
active: boolean,
|
|
159
|
+
): UseOrientationDriftReturn {
|
|
160
|
+
const currentOrientation = useDeviceOrientation();
|
|
161
|
+
const [state, setState] = useState<DriftState>(INITIAL_STATE);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
setState((prev) => _computeDriftStateForTests(prev, active, currentOrientation));
|
|
165
|
+
}, [active, currentOrientation]);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
drifted: state.drifted,
|
|
169
|
+
captureOrientation: state.captureOrientation,
|
|
170
|
+
currentOrientation,
|
|
171
|
+
};
|
|
172
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -162,6 +162,19 @@ export { useCapture } from './camera/useCapture';
|
|
|
162
162
|
export type { TakePhotoCallOptions } from './camera/useCapture';
|
|
163
163
|
export { useVideoCapture } from './camera/useVideoCapture';
|
|
164
164
|
export { useDeviceOrientation } from './camera/useDeviceOrientation';
|
|
165
|
+
export type { DeviceOrientation } from './camera/useDeviceOrientation';
|
|
166
|
+
|
|
167
|
+
// v0.12.0 — orientation-aware Camera (R2-lite). `useOrientationDrift`
|
|
168
|
+
// snapshots the device orientation at capture start and latches a
|
|
169
|
+
// `drifted` flag if the user rotates mid-capture. Pairs with
|
|
170
|
+
// `OrientationDriftModal` for the auto-abandon UX flow. The
|
|
171
|
+
// flagship `<Camera>` component wires both internally (PR-2);
|
|
172
|
+
// Layer-2 hosts using `CameraView` directly can compose the pair
|
|
173
|
+
// manually (see the modal's docstring for the integration pattern).
|
|
174
|
+
export { useOrientationDrift } from './camera/useOrientationDrift';
|
|
175
|
+
export type { UseOrientationDriftReturn } from './camera/useOrientationDrift';
|
|
176
|
+
export { OrientationDriftModal } from './camera/OrientationDriftModal';
|
|
177
|
+
export type { OrientationDriftModalProps } from './camera/OrientationDriftModal';
|
|
165
178
|
|
|
166
179
|
// ── Incremental stitching engine ──────────────────────────────────────
|
|
167
180
|
// JS bindings around the native `IncrementalStitcher` module. Use
|
|
@@ -139,9 +139,11 @@ export interface IncrementalState {
|
|
|
139
139
|
* at the FIRST-FRAME determination thereafter.
|
|
140
140
|
*
|
|
141
141
|
* **This is the single source of truth for orientation across
|
|
142
|
-
* the SDK + host.**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
142
|
+
* the SDK + host.** Pose-derived detection is preferred over
|
|
143
|
+
* JS-side hooks because it works identically regardless of host
|
|
144
|
+
* configuration — `useWindowDimensions` reports JS-portrait when
|
|
145
|
+
* the host is portrait-locked (even with the device in landscape),
|
|
146
|
+
* while pose data reflects what the camera actually saw. UI
|
|
145
147
|
* components that need to know orientation (band overlay, dim
|
|
146
148
|
* bars, pan guide) MUST consume `state.isLandscape` rather
|
|
147
149
|
* than re-detecting.
|