react-native-image-stitcher 0.1.2 → 0.2.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 +110 -1
- package/README.md +0 -9
- package/android/src/main/cpp/keyframe_gate_jni.cpp +1 -1
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +1 -1
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2 -2
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
- package/dist/camera/Camera.js +5 -1
- package/dist/camera/useDeviceOrientation.d.ts +26 -23
- package/dist/camera/useDeviceOrientation.js +64 -77
- package/dist/index.js +3 -0
- package/dist/sensors/useIMUTranslationGate.d.ts +18 -46
- package/dist/sensors/useIMUTranslationGate.js +115 -211
- package/dist/stitching/stitchFrames.d.ts +1 -1
- package/dist/stitching/stitchFrames.js +1 -1
- package/ios/Package.swift +1 -1
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +4 -4
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +1 -1
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +1 -1
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +1 -1
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +1 -1
- package/package.json +1 -3
- package/src/camera/Camera.tsx +5 -1
- package/src/camera/useDeviceOrientation.ts +73 -77
- package/src/index.ts +3 -0
- package/src/sensors/useIMUTranslationGate.ts +145 -284
- package/src/stitching/stitchFrames.ts +1 -1
|
@@ -12,42 +12,49 @@
|
|
|
12
12
|
* the app is orientation-locked: window dimensions don't change
|
|
13
13
|
* when only the device rotates.
|
|
14
14
|
*
|
|
15
|
-
* 2026-05-
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* rotation, which cascaded into wrong panorama bake-rotation and
|
|
25
|
-
* a broken landscape band layout.
|
|
15
|
+
* 2026-05-21 (v0.2 — Expo modules removal) — rewritten back onto
|
|
16
|
+
* `react-native-sensors` accelerometer. `expo-sensors`'
|
|
17
|
+
* `DeviceMotion` was used previously (Issue #3 / 2026-05-18) because
|
|
18
|
+
* it normalised Android signs to iOS convention for us, but that
|
|
19
|
+
* pulled the entire Expo modules runtime into every consuming
|
|
20
|
+
* host app — a heavy tax for one orientation hook (see
|
|
21
|
+
* `docs/host-app-integration.md`). We now do the same sign
|
|
22
|
+
* normalisation explicitly in JS and stay on `react-native-sensors`
|
|
23
|
+
* (already a peer dep for the pan-guide gyroscope).
|
|
26
24
|
*
|
|
27
25
|
* Sign conventions used here (per platform docs):
|
|
28
26
|
*
|
|
29
|
-
* iOS (
|
|
30
|
-
* m/s
|
|
31
|
-
* portrait → y ≈ -
|
|
32
|
-
* portrait-upside-down → y ≈ +
|
|
33
|
-
* landscape-left (home indicator on user's RIGHT) → x ≈ +
|
|
34
|
-
* landscape-right (home indicator on user's LEFT) → x ≈ -
|
|
27
|
+
* iOS (CMAccelerometerData, reported in G's; react-native-sensors
|
|
28
|
+
* passes through, in m/s²-ish G-multiples):
|
|
29
|
+
* portrait → y ≈ -1 (gravity along device -Y)
|
|
30
|
+
* portrait-upside-down → y ≈ +1
|
|
31
|
+
* landscape-left (home indicator on user's RIGHT) → x ≈ +1
|
|
32
|
+
* landscape-right (home indicator on user's LEFT) → x ≈ -1
|
|
35
33
|
*
|
|
36
|
-
* Android (Sensor.TYPE_ACCELEROMETER, reaction-force convention
|
|
37
|
-
*
|
|
34
|
+
* Android (Sensor.TYPE_ACCELEROMETER, reaction-force convention,
|
|
35
|
+
* m/s²):
|
|
36
|
+
* portrait → y ≈ +9.8 ← OPPOSITE SIGN vs iOS
|
|
38
37
|
* portrait-upside-down → y ≈ -9.8
|
|
39
38
|
* landscape-left → x ≈ -9.8
|
|
40
39
|
* landscape-right → x ≈ +9.8
|
|
41
40
|
*
|
|
42
|
-
* We flip the Android x/y to match the iOS convention
|
|
43
|
-
* classification so the
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* We flip the Android x/y signs to match the iOS convention
|
|
42
|
+
* before classification, so the classifier stays platform-
|
|
43
|
+
* agnostic and operates entirely in iOS-convention values.
|
|
44
|
+
* (Previous react-native-sensors implementation, pre-Issue-#3,
|
|
45
|
+
* forgot this — Apple's CoreMotion convention is `y < 0` ⇒
|
|
46
|
+
* portrait, but the old code used `y > 0` ⇒ portrait, so iOS
|
|
47
|
+
* was stuck at the initial value regardless of rotation. Don't
|
|
48
|
+
* regress.)
|
|
46
49
|
*/
|
|
47
50
|
|
|
48
51
|
import { useEffect, useState } from 'react';
|
|
49
|
-
import {
|
|
50
|
-
import
|
|
52
|
+
import { Platform } from 'react-native';
|
|
53
|
+
import {
|
|
54
|
+
accelerometer,
|
|
55
|
+
setUpdateIntervalForType,
|
|
56
|
+
SensorTypes,
|
|
57
|
+
} from 'react-native-sensors';
|
|
51
58
|
|
|
52
59
|
|
|
53
60
|
export type DeviceOrientation =
|
|
@@ -57,59 +64,43 @@ export type DeviceOrientation =
|
|
|
57
64
|
| 'landscape-right';
|
|
58
65
|
|
|
59
66
|
|
|
60
|
-
/// Threshold
|
|
61
|
-
///
|
|
62
|
-
///
|
|
63
|
-
///
|
|
64
|
-
|
|
67
|
+
/// Threshold above which a single axis is considered to dominate.
|
|
68
|
+
/// Phone-at-rest under gravity reads ~1 G on whichever axis is
|
|
69
|
+
/// aligned with vertical; the off-axis reading is ~0. Anything
|
|
70
|
+
/// more than half a G (~5 m/s² on Android, ~0.5 on iOS in G's) is
|
|
71
|
+
/// safely in "dominant" territory without flipping on small wobbles.
|
|
72
|
+
/// We compare against the magnitude after sign-normalisation, so
|
|
73
|
+
/// the threshold is platform-dependent: iOS reports in G's,
|
|
74
|
+
/// Android in m/s².
|
|
75
|
+
const DOMINANT_AXIS_THRESHOLD_IOS = 0.5; // G's
|
|
76
|
+
const DOMINANT_AXIS_THRESHOLD_ANDROID = 5.0; // m/s²
|
|
65
77
|
|
|
66
78
|
/// Sample at ~10 Hz — plenty for orientation detection (phones
|
|
67
79
|
/// don't physically flip faster than this).
|
|
68
80
|
const SAMPLE_INTERVAL_MS = 100;
|
|
69
81
|
|
|
70
82
|
|
|
71
|
-
function classify(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
// to phone-top; +Z out of the screen toward the viewer.
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// portrait-upside-down → y ≈ +9.8
|
|
86
|
-
// Phone-Y points down in world; gravity is along device +Y.
|
|
87
|
-
//
|
|
88
|
-
// landscape-left (Apple: home indicator on user's RIGHT;
|
|
89
|
-
// phone rotated 90° CCW from portrait):
|
|
90
|
-
// phone-X axis points from user-bottom to user-top in this
|
|
91
|
-
// orientation, so gravity (world-down) is along device -X.
|
|
92
|
-
// → x ≈ -9.8
|
|
93
|
-
//
|
|
94
|
-
// landscape-right (Apple: home indicator on user's LEFT;
|
|
95
|
-
// phone rotated 90° CW from portrait):
|
|
96
|
-
// phone-X axis points from user-top to user-bottom, so
|
|
97
|
-
// gravity is along device +X.
|
|
98
|
-
// → x ≈ +9.8
|
|
99
|
-
//
|
|
100
|
-
// The earlier implementation had an Android-specific axis flip
|
|
101
|
-
// baked in. Removed — expo-sensors normalizes Android signs to
|
|
102
|
-
// match iOS, and the platform branch was producing wrong values
|
|
103
|
-
// (Android portrait → reported as portrait-upside-down; iOS
|
|
104
|
-
// landscape-left → reported as landscape-right).
|
|
83
|
+
function classify(
|
|
84
|
+
x: number,
|
|
85
|
+
y: number,
|
|
86
|
+
threshold: number,
|
|
87
|
+
): DeviceOrientation | null {
|
|
88
|
+
// Inputs are in iOS-convention gravity-vector signs:
|
|
89
|
+
// +X points from phone-left to phone-right; +Y from phone-
|
|
90
|
+
// bottom to phone-top; +Z out of the screen toward the viewer.
|
|
91
|
+
// At rest under gravity:
|
|
92
|
+
// portrait (upright) → y ≈ -g (phone-Y points up; gravity is -Y)
|
|
93
|
+
// portrait-upside-down → y ≈ +g
|
|
94
|
+
// landscape-left → x ≈ -g (phone-X points up; gravity is -X)
|
|
95
|
+
// landscape-right → x ≈ +g
|
|
105
96
|
if (Math.abs(y) > Math.abs(x)) {
|
|
106
|
-
if (y < -
|
|
107
|
-
if (y >
|
|
97
|
+
if (y < -threshold) return 'portrait';
|
|
98
|
+
if (y > threshold) return 'portrait-upside-down';
|
|
108
99
|
} else {
|
|
109
|
-
if (x < -
|
|
110
|
-
if (x >
|
|
100
|
+
if (x < -threshold) return 'landscape-left';
|
|
101
|
+
if (x > threshold) return 'landscape-right';
|
|
111
102
|
}
|
|
112
|
-
// Phone face-up or face-down (z dominates)
|
|
103
|
+
// Phone face-up or face-down (z dominates) — keep the previous
|
|
113
104
|
// orientation rather than flicker.
|
|
114
105
|
return null;
|
|
115
106
|
}
|
|
@@ -119,21 +110,26 @@ export function useDeviceOrientation(): DeviceOrientation {
|
|
|
119
110
|
const [orientation, setOrientation] = useState<DeviceOrientation>('portrait');
|
|
120
111
|
|
|
121
112
|
useEffect(() => {
|
|
122
|
-
|
|
113
|
+
setUpdateIntervalForType(SensorTypes.accelerometer, SAMPLE_INTERVAL_MS);
|
|
114
|
+
|
|
115
|
+
const isAndroid = Platform.OS === 'android';
|
|
116
|
+
const threshold = isAndroid
|
|
117
|
+
? DOMINANT_AXIS_THRESHOLD_ANDROID
|
|
118
|
+
: DOMINANT_AXIS_THRESHOLD_IOS;
|
|
123
119
|
|
|
124
120
|
let last: DeviceOrientation = 'portrait';
|
|
125
|
-
const sub =
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const next = classify(
|
|
121
|
+
const sub = accelerometer.subscribe(({ x, y }) => {
|
|
122
|
+
// Normalise Android reaction-force convention to iOS gravity
|
|
123
|
+
// convention by flipping signs. No-op on iOS.
|
|
124
|
+
const gx = isAndroid ? -x : x;
|
|
125
|
+
const gy = isAndroid ? -y : y;
|
|
126
|
+
const next = classify(gx, gy, threshold);
|
|
131
127
|
if (next && next !== last) {
|
|
132
128
|
last = next;
|
|
133
129
|
setOrientation(next);
|
|
134
130
|
}
|
|
135
131
|
});
|
|
136
|
-
return () => sub.
|
|
132
|
+
return () => sub.unsubscribe();
|
|
137
133
|
}, []);
|
|
138
134
|
|
|
139
135
|
return orientation;
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,9 @@ export type {
|
|
|
54
54
|
// ─────────────────────────────────────────────────────────────────────
|
|
55
55
|
// Hosts running their own non-AR capture flow can reuse this hook to
|
|
56
56
|
// get the same translation-budget gating logic <Camera> uses internally.
|
|
57
|
+
// As of v0.2 this hook is implemented on `react-native-sensors` raw
|
|
58
|
+
// accelerometer + JS IIR gravity subtraction (was `expo-sensors`'
|
|
59
|
+
// fused DeviceMotion through 0.1.x — see the hook's file header).
|
|
57
60
|
export { useIMUTranslationGate } from './sensors/useIMUTranslationGate';
|
|
58
61
|
export type {
|
|
59
62
|
UseIMUTranslationGateOptions,
|