react-native-image-stitcher 0.1.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 +96 -0
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +189 -0
- package/RNImageStitcher.podspec +76 -0
- package/android/build.gradle +224 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +124 -0
- package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
- package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
- package/cpp/ar_frame_pose.h +63 -0
- package/cpp/keyframe_gate.cpp +927 -0
- package/cpp/keyframe_gate.hpp +240 -0
- package/cpp/stitcher.cpp +2207 -0
- package/cpp/stitcher.hpp +275 -0
- package/dist/ar/useARSession.d.ts +102 -0
- package/dist/ar/useARSession.js +133 -0
- package/dist/camera/ARCameraView.d.ts +93 -0
- package/dist/camera/ARCameraView.js +170 -0
- package/dist/camera/Camera.d.ts +134 -0
- package/dist/camera/Camera.js +688 -0
- package/dist/camera/CameraShutter.d.ts +80 -0
- package/dist/camera/CameraShutter.js +237 -0
- package/dist/camera/CameraView.d.ts +65 -0
- package/dist/camera/CameraView.js +117 -0
- package/dist/camera/CaptureControlsBar.d.ts +87 -0
- package/dist/camera/CaptureControlsBar.js +82 -0
- package/dist/camera/CaptureHeader.d.ts +62 -0
- package/dist/camera/CaptureHeader.js +81 -0
- package/dist/camera/CapturePreview.d.ts +70 -0
- package/dist/camera/CapturePreview.js +188 -0
- package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
- package/dist/camera/CaptureStatusOverlay.js +326 -0
- package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
- package/dist/camera/CaptureThumbnailStrip.js +177 -0
- package/dist/camera/IncrementalPanGuide.d.ts +83 -0
- package/dist/camera/IncrementalPanGuide.js +267 -0
- package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
- package/dist/camera/PanoramaBandOverlay.js +399 -0
- package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
- package/dist/camera/PanoramaConfirmModal.js +128 -0
- package/dist/camera/PanoramaGuidance.d.ts +79 -0
- package/dist/camera/PanoramaGuidance.js +246 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
- package/dist/camera/PanoramaSettingsModal.js +611 -0
- package/dist/camera/ViewportCropOverlay.d.ts +46 -0
- package/dist/camera/ViewportCropOverlay.js +67 -0
- package/dist/camera/useCapture.d.ts +111 -0
- package/dist/camera/useCapture.js +160 -0
- package/dist/camera/useDeviceOrientation.d.ts +48 -0
- package/dist/camera/useDeviceOrientation.js +131 -0
- package/dist/camera/useVideoCapture.d.ts +79 -0
- package/dist/camera/useVideoCapture.js +151 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +39 -0
- package/dist/quality/normaliseOrientation.d.ts +36 -0
- package/dist/quality/normaliseOrientation.js +62 -0
- package/dist/quality/runQualityCheck.d.ts +41 -0
- package/dist/quality/runQualityCheck.js +98 -0
- package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
- package/dist/sensors/useIMUTranslationGate.js +235 -0
- package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
- package/dist/stitching/IncrementalStitcherView.js +157 -0
- package/dist/stitching/incremental.d.ts +930 -0
- package/dist/stitching/incremental.js +133 -0
- package/dist/stitching/stitchFrames.d.ts +55 -0
- package/dist/stitching/stitchFrames.js +56 -0
- package/dist/stitching/stitchVideo.d.ts +119 -0
- package/dist/stitching/stitchVideo.js +57 -0
- package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
- package/dist/stitching/useIncrementalJSDriver.js +199 -0
- package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
- package/dist/stitching/useIncrementalStitcher.js +172 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +15 -0
- package/ios/Package.swift +72 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
- package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
- package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
- package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
- package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
- package/package.json +73 -0
- package/react-native.config.js +34 -0
- package/scripts/opencv-version.txt +1 -0
- package/scripts/postinstall-fetch-binaries.js +286 -0
- package/src/ar/useARSession.ts +210 -0
- package/src/camera/.gitkeep +0 -0
- package/src/camera/ARCameraView.tsx +256 -0
- package/src/camera/Camera.tsx +1053 -0
- package/src/camera/CameraShutter.tsx +292 -0
- package/src/camera/CameraView.tsx +157 -0
- package/src/camera/CaptureControlsBar.tsx +204 -0
- package/src/camera/CaptureHeader.tsx +184 -0
- package/src/camera/CapturePreview.tsx +318 -0
- package/src/camera/CaptureStatusOverlay.tsx +391 -0
- package/src/camera/CaptureThumbnailStrip.tsx +277 -0
- package/src/camera/IncrementalPanGuide.tsx +328 -0
- package/src/camera/PanoramaBandOverlay.tsx +498 -0
- package/src/camera/PanoramaConfirmModal.tsx +206 -0
- package/src/camera/PanoramaGuidance.tsx +327 -0
- package/src/camera/PanoramaSettingsModal.tsx +1357 -0
- package/src/camera/ViewportCropOverlay.tsx +81 -0
- package/src/camera/useCapture.ts +279 -0
- package/src/camera/useDeviceOrientation.ts +140 -0
- package/src/camera/useVideoCapture.ts +236 -0
- package/src/index.ts +53 -0
- package/src/quality/.gitkeep +0 -0
- package/src/quality/normaliseOrientation.ts +79 -0
- package/src/quality/runQualityCheck.ts +131 -0
- package/src/sensors/useIMUTranslationGate.ts +347 -0
- package/src/stitching/.gitkeep +0 -0
- package/src/stitching/IncrementalStitcherView.tsx +198 -0
- package/src/stitching/incremental.ts +1021 -0
- package/src/stitching/stitchFrames.ts +88 -0
- package/src/stitching/stitchVideo.ts +153 -0
- package/src/stitching/useIncrementalJSDriver.ts +273 -0
- package/src/stitching/useIncrementalStitcher.ts +252 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* IncrementalPanGuide — V12.11 Step 2 (item 2 of the four-step
|
|
4
|
+
* preview-UX overhaul).
|
|
5
|
+
*
|
|
6
|
+
* Apple-pano-style "keep the arrow on the line" pan guide for the
|
|
7
|
+
* incremental capture flow.
|
|
8
|
+
*
|
|
9
|
+
* ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
10
|
+
* ▲
|
|
11
|
+
* ● ← marker drifts perpendicular
|
|
12
|
+
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← guide line
|
|
13
|
+
* (along pan axis)
|
|
14
|
+
* ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
15
|
+
*
|
|
16
|
+
* The user is told (via the band overlay's caption) to pan along
|
|
17
|
+
* one axis. This guide gives them live feedback on how WELL they
|
|
18
|
+
* are obeying that instruction:
|
|
19
|
+
*
|
|
20
|
+
* • A solid GUIDE LINE runs along the intended pan axis (the
|
|
21
|
+
* "ideal" pan path) across the centre of the screen.
|
|
22
|
+
* • A circular MARKER sits on the line. As the user tilts the
|
|
23
|
+
* device perpendicular to the pan axis, the marker drifts
|
|
24
|
+
* OFF the line by an amount proportional to the integrated
|
|
25
|
+
* perpendicular rotation since capture started.
|
|
26
|
+
* • The marker's COLOUR signals how far off they've drifted —
|
|
27
|
+
* green (on track), amber (slight drift), red (significant
|
|
28
|
+
* drift). Same colour scale as PanoramaGuidance for
|
|
29
|
+
* consistency.
|
|
30
|
+
*
|
|
31
|
+
* Why integrate perpendicular rotation rather than absolute device
|
|
32
|
+
* angle? We don't care about the user's starting orientation (they
|
|
33
|
+
* may begin a horizontal pan with the phone tilted slightly down
|
|
34
|
+
* — that's fine). We care about CHANGE during the pan. So we
|
|
35
|
+
* reset the integral to 0 at `active=true` and accumulate the
|
|
36
|
+
* perpendicular gyro rate from there. Drift over a typical 5–10 s
|
|
37
|
+
* capture is well within tolerance (a few degrees max).
|
|
38
|
+
*
|
|
39
|
+
* Why not consume ARKit pose? Two reasons:
|
|
40
|
+
* 1. The vision-camera (non-AR) capture path doesn't have ARKit
|
|
41
|
+
* pose at all — gyro is the only common signal.
|
|
42
|
+
* 2. We want this guide to work the SAME way regardless of
|
|
43
|
+
* whether AR mode is on or off — gyro keeps the UX
|
|
44
|
+
* consistent.
|
|
45
|
+
*
|
|
46
|
+
* Performance: gyro at 30 Hz, integration is two multiplies per
|
|
47
|
+
* sample, marker position update via setState. Negligible.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
51
|
+
import {
|
|
52
|
+
StyleSheet,
|
|
53
|
+
View,
|
|
54
|
+
type StyleProp,
|
|
55
|
+
type ViewStyle,
|
|
56
|
+
} from 'react-native';
|
|
57
|
+
import {
|
|
58
|
+
gyroscope,
|
|
59
|
+
setUpdateIntervalForType,
|
|
60
|
+
SensorTypes,
|
|
61
|
+
} from 'react-native-sensors';
|
|
62
|
+
import type { Subscription } from 'rxjs';
|
|
63
|
+
|
|
64
|
+
import { useDeviceOrientation, type DeviceOrientation } from './useDeviceOrientation';
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
export interface IncrementalPanGuideProps {
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe to the gyroscope only while this is true. Typically
|
|
70
|
+
* driven by the host's `statusPhase === 'recording' &&
|
|
71
|
+
* useIncrementalPath`. When this flips false the integral
|
|
72
|
+
* resets so the next capture starts from a known zero.
|
|
73
|
+
*/
|
|
74
|
+
active: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Pixels per radian of perpendicular drift. Higher = more
|
|
77
|
+
* sensitive (small drifts move the marker more visibly).
|
|
78
|
+
* Default 600 px/rad gives ~10 px of marker travel for ~1° of
|
|
79
|
+
* tilt — visible without feeling twitchy.
|
|
80
|
+
*/
|
|
81
|
+
pixelsPerRad?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Maximum marker travel from the line, in pixels. Beyond this
|
|
84
|
+
* the marker pins to the edge of its track. Default 80 px.
|
|
85
|
+
*/
|
|
86
|
+
maxTravelPx?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Drift thresholds for the colour scale, in radians. Tuned so
|
|
89
|
+
* "green" covers ±1° (no human-perceptible misalignment),
|
|
90
|
+
* "amber" up to ±3°, "red" beyond. Same defaults as
|
|
91
|
+
* PanoramaGuidance's pan-speed pill for visual consistency.
|
|
92
|
+
*/
|
|
93
|
+
warnRad?: number;
|
|
94
|
+
badRad?: number;
|
|
95
|
+
style?: StyleProp<ViewStyle>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
type DriftBucket = 'good' | 'warn' | 'bad';
|
|
100
|
+
|
|
101
|
+
const COLOR_GOOD = '#34C759';
|
|
102
|
+
const COLOR_WARN = '#FFCC00';
|
|
103
|
+
const COLOR_BAD = '#FF3B30';
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
function bucketFor(absRad: number, warn: number, bad: number): DriftBucket {
|
|
107
|
+
if (absRad <= warn) return 'good';
|
|
108
|
+
if (absRad <= bad) return 'warn';
|
|
109
|
+
return 'bad';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
function colorFor(bucket: DriftBucket): string {
|
|
114
|
+
switch (bucket) {
|
|
115
|
+
case 'good': return COLOR_GOOD;
|
|
116
|
+
case 'warn': return COLOR_WARN;
|
|
117
|
+
case 'bad': return COLOR_BAD;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Pan axis vs orientation — same convention as
|
|
124
|
+
* PanoramaBandOverlay (and PanoramaGuidance):
|
|
125
|
+
* • portrait → pan horizontal → guide line is HORIZONTAL across
|
|
126
|
+
* the screen, marker drifts vertically.
|
|
127
|
+
* • landscape → pan vertical → guide line is VERTICAL down the
|
|
128
|
+
* screen, marker drifts horizontally.
|
|
129
|
+
*/
|
|
130
|
+
function panAxis(orientation: DeviceOrientation): 'horizontal' | 'vertical' {
|
|
131
|
+
return orientation === 'landscape-left' || orientation === 'landscape-right'
|
|
132
|
+
? 'vertical'
|
|
133
|
+
: 'horizontal';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
export function IncrementalPanGuide({
|
|
138
|
+
active,
|
|
139
|
+
pixelsPerRad = 600,
|
|
140
|
+
maxTravelPx = 80,
|
|
141
|
+
warnRad = 0.05, // ≈ 2.9°
|
|
142
|
+
badRad = 0.10, // ≈ 5.7°
|
|
143
|
+
style,
|
|
144
|
+
}: IncrementalPanGuideProps): React.JSX.Element | null {
|
|
145
|
+
const orientation = useDeviceOrientation();
|
|
146
|
+
const axis = panAxis(orientation);
|
|
147
|
+
|
|
148
|
+
// Integrated PERPENDICULAR rotation since `active` flipped true.
|
|
149
|
+
// Held in a ref so per-sample updates don't re-render. We re-
|
|
150
|
+
// render only when the displayed marker offset (rounded to int
|
|
151
|
+
// px) or colour bucket changes.
|
|
152
|
+
const perpIntegralRef = useRef(0);
|
|
153
|
+
const lastSampleTsRef = useRef<number | null>(null);
|
|
154
|
+
|
|
155
|
+
// Displayed marker offset in px (signed; sign tells us which
|
|
156
|
+
// side of the line the user has drifted to). Capped to
|
|
157
|
+
// ±maxTravelPx. Rounded to int to keep React reconciliation
|
|
158
|
+
// cheap (one re-render per integer pixel of travel).
|
|
159
|
+
const [markerOffsetPx, setMarkerOffsetPx] = useState(0);
|
|
160
|
+
const [bucket, setBucket] = useState<DriftBucket>('good');
|
|
161
|
+
const lastBucketRef = useRef<DriftBucket>('good');
|
|
162
|
+
const lastOffsetRef = useRef(0);
|
|
163
|
+
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
// Inactive: reset integral and on-screen state so the next
|
|
166
|
+
// capture starts at a clean zero.
|
|
167
|
+
if (!active) {
|
|
168
|
+
perpIntegralRef.current = 0;
|
|
169
|
+
lastSampleTsRef.current = null;
|
|
170
|
+
lastBucketRef.current = 'good';
|
|
171
|
+
lastOffsetRef.current = 0;
|
|
172
|
+
setMarkerOffsetPx(0);
|
|
173
|
+
setBucket('good');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setUpdateIntervalForType(SensorTypes.gyroscope, 33);
|
|
178
|
+
|
|
179
|
+
let subscription: Subscription | null = gyroscope.subscribe({
|
|
180
|
+
next: ({ x, y }) => {
|
|
181
|
+
// The PERPENDICULAR axis is the orthogonal one to the pan:
|
|
182
|
+
// portrait pans horizontally → pan axis = gyro Y →
|
|
183
|
+
// perp axis = gyro X (pitch — head up/down).
|
|
184
|
+
// landscape pans vertically → pan axis = gyro X →
|
|
185
|
+
// perp axis = gyro Y (yaw — head left/right).
|
|
186
|
+
const perpRate = axis === 'horizontal' ? x : y;
|
|
187
|
+
|
|
188
|
+
// Δt in seconds since the previous sample. Use Date.now()
|
|
189
|
+
// rather than the upstream's `timestamp` field because
|
|
190
|
+
// react-native-sensors emits seconds on iOS and ms on
|
|
191
|
+
// Android — Date.now() is unambiguous and reliable enough
|
|
192
|
+
// at 30 Hz integration.
|
|
193
|
+
const nowMs = Date.now();
|
|
194
|
+
const last = lastSampleTsRef.current ?? nowMs;
|
|
195
|
+
const dt = Math.min(0.1, Math.max(0, (nowMs - last) / 1000));
|
|
196
|
+
lastSampleTsRef.current = nowMs;
|
|
197
|
+
|
|
198
|
+
perpIntegralRef.current += perpRate * dt;
|
|
199
|
+
|
|
200
|
+
// Convert integral (radians) → px offset, clamp.
|
|
201
|
+
const rawPx = perpIntegralRef.current * pixelsPerRad;
|
|
202
|
+
const clampedPx = Math.max(
|
|
203
|
+
-maxTravelPx,
|
|
204
|
+
Math.min(maxTravelPx, rawPx),
|
|
205
|
+
);
|
|
206
|
+
const roundedPx = Math.round(clampedPx);
|
|
207
|
+
|
|
208
|
+
// setState only when the rounded px changed — keeps the
|
|
209
|
+
// re-render rate to ~max(60, gyro rate) fps and usually
|
|
210
|
+
// far less since drift is gradual.
|
|
211
|
+
if (roundedPx !== lastOffsetRef.current) {
|
|
212
|
+
lastOffsetRef.current = roundedPx;
|
|
213
|
+
setMarkerOffsetPx(roundedPx);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const nextBucket = bucketFor(
|
|
217
|
+
Math.abs(perpIntegralRef.current),
|
|
218
|
+
warnRad,
|
|
219
|
+
badRad,
|
|
220
|
+
);
|
|
221
|
+
if (nextBucket !== lastBucketRef.current) {
|
|
222
|
+
lastBucketRef.current = nextBucket;
|
|
223
|
+
setBucket(nextBucket);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
error: (err) => {
|
|
227
|
+
// eslint-disable-next-line no-console
|
|
228
|
+
console.warn('[IncrementalPanGuide] gyroscope error', err);
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
subscription?.unsubscribe();
|
|
234
|
+
subscription = null;
|
|
235
|
+
};
|
|
236
|
+
}, [active, axis, pixelsPerRad, maxTravelPx, warnRad, badRad]);
|
|
237
|
+
|
|
238
|
+
if (!active) return null;
|
|
239
|
+
|
|
240
|
+
const markerColor = colorFor(bucket);
|
|
241
|
+
|
|
242
|
+
if (axis === 'horizontal') {
|
|
243
|
+
// Portrait: guide line spans horizontally across the centre,
|
|
244
|
+
// marker drifts vertically. Marker is a small circle outlined
|
|
245
|
+
// in the bucket colour.
|
|
246
|
+
return (
|
|
247
|
+
<View
|
|
248
|
+
pointerEvents="none"
|
|
249
|
+
style={[styles.rootCentered, style]}
|
|
250
|
+
>
|
|
251
|
+
<View style={styles.lineHorizontal} />
|
|
252
|
+
<View
|
|
253
|
+
style={[
|
|
254
|
+
styles.marker,
|
|
255
|
+
{ borderColor: markerColor },
|
|
256
|
+
// Vertical translation from the line. Negative px =
|
|
257
|
+
// device tilted UP from start → marker UP.
|
|
258
|
+
{ transform: [{ translateY: markerOffsetPx }] },
|
|
259
|
+
]}
|
|
260
|
+
/>
|
|
261
|
+
</View>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Landscape: guide line is VERTICAL down the centre, marker
|
|
266
|
+
// drifts horizontally.
|
|
267
|
+
return (
|
|
268
|
+
<View
|
|
269
|
+
pointerEvents="none"
|
|
270
|
+
style={[styles.rootCentered, style]}
|
|
271
|
+
>
|
|
272
|
+
<View style={styles.lineVertical} />
|
|
273
|
+
<View
|
|
274
|
+
style={[
|
|
275
|
+
styles.marker,
|
|
276
|
+
{ borderColor: markerColor },
|
|
277
|
+
{ transform: [{ translateX: markerOffsetPx }] },
|
|
278
|
+
]}
|
|
279
|
+
/>
|
|
280
|
+
</View>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
// Layout: position the entire guide as an absolute, full-screen
|
|
286
|
+
// overlay so the line spans the camera viewport edge-to-edge.
|
|
287
|
+
// The marker is rendered absolutely at the centre and translated
|
|
288
|
+
// by markerOffsetPx (signed) along the perpendicular axis.
|
|
289
|
+
const styles = StyleSheet.create({
|
|
290
|
+
rootCentered: {
|
|
291
|
+
position: 'absolute',
|
|
292
|
+
top: 0,
|
|
293
|
+
left: 0,
|
|
294
|
+
right: 0,
|
|
295
|
+
bottom: 0,
|
|
296
|
+
alignItems: 'center',
|
|
297
|
+
justifyContent: 'center',
|
|
298
|
+
},
|
|
299
|
+
// Horizontal line across the screen, centred vertically. Thin
|
|
300
|
+
// (1.5 px), white-translucent, dashed via repeating segments
|
|
301
|
+
// would be nicer but a flat translucent rectangle is sufficient
|
|
302
|
+
// for V12.11 Step 2 — leaves room for a polish pass later.
|
|
303
|
+
lineHorizontal: {
|
|
304
|
+
position: 'absolute',
|
|
305
|
+
left: 0,
|
|
306
|
+
right: 0,
|
|
307
|
+
height: 1.5,
|
|
308
|
+
backgroundColor: 'rgba(255, 255, 255, 0.55)',
|
|
309
|
+
},
|
|
310
|
+
// Vertical line down the screen, centred horizontally.
|
|
311
|
+
lineVertical: {
|
|
312
|
+
position: 'absolute',
|
|
313
|
+
top: 0,
|
|
314
|
+
bottom: 0,
|
|
315
|
+
width: 1.5,
|
|
316
|
+
backgroundColor: 'rgba(255, 255, 255, 0.55)',
|
|
317
|
+
},
|
|
318
|
+
// Marker — a 22 px ringed circle. Filled with a translucent
|
|
319
|
+
// dark so the bucket-coloured ring reads cleanly against any
|
|
320
|
+
// camera-feed background.
|
|
321
|
+
marker: {
|
|
322
|
+
width: 22,
|
|
323
|
+
height: 22,
|
|
324
|
+
borderRadius: 11,
|
|
325
|
+
borderWidth: 3,
|
|
326
|
+
backgroundColor: 'rgba(0, 0, 0, 0.35)',
|
|
327
|
+
},
|
|
328
|
+
});
|