react-native-image-stitcher 0.3.0 → 0.4.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 +195 -0
- package/dist/camera/Camera.js +70 -58
- package/dist/camera/PanoramaSettings.d.ts +478 -0
- package/dist/camera/PanoramaSettings.js +120 -0
- package/dist/camera/PanoramaSettingsBridge.d.ts +84 -0
- package/dist/camera/PanoramaSettingsBridge.js +208 -0
- package/dist/camera/PanoramaSettingsModal.d.ts +50 -299
- package/dist/camera/PanoramaSettingsModal.js +189 -354
- package/dist/camera/buildPanoramaInitialSettings.d.ts +70 -0
- package/dist/camera/buildPanoramaInitialSettings.js +97 -0
- package/dist/camera/lowMemDevice.d.ts +24 -0
- package/dist/camera/lowMemDevice.js +69 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +23 -2
- package/package.json +6 -2
- package/src/camera/Camera.tsx +79 -71
- package/src/camera/PanoramaSettings.ts +605 -0
- package/src/camera/PanoramaSettingsBridge.ts +238 -0
- package/src/camera/PanoramaSettingsModal.tsx +296 -989
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +375 -0
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +119 -0
- package/src/camera/__tests__/lowMemDevice.test.ts +52 -0
- package/src/camera/buildPanoramaInitialSettings.ts +139 -0
- package/src/camera/lowMemDevice.ts +71 -0
- package/src/index.ts +42 -3
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,201 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.4.0] — 2026-05-23
|
|
20
|
+
|
|
21
|
+
### v0.4 settings revamp (F10)
|
|
22
|
+
|
|
23
|
+
> [!WARNING]
|
|
24
|
+
> **Breaking type change.** The flat 45-field `PanoramaSettings`
|
|
25
|
+
> interface from v0.3 has been replaced with three engine-discriminated
|
|
26
|
+
> hierarchical types (`PanoramaSettings`, `SlitscanSettings`,
|
|
27
|
+
> `HybridSettings`). Consumers passing custom settings literals to
|
|
28
|
+
> `<Camera>` or to a Layer 2 modal must migrate to the new shape; the
|
|
29
|
+
> v0.3 type is deleted, not aliased. The C++ engine wire format is
|
|
30
|
+
> unchanged — only the JS-side type surface moved.
|
|
31
|
+
>
|
|
32
|
+
> **Migration guide:** [`docs/migrations/v0.3-to-v0.4-panorama-settings.md`](docs/migrations/v0.3-to-v0.4-panorama-settings.md)
|
|
33
|
+
> walks through every recipe (default-only hosts, custom-literal
|
|
34
|
+
> hosts, slit-scan / hybrid hosts, storage migration for persisted
|
|
35
|
+
> settings).
|
|
36
|
+
|
|
37
|
+
#### Why
|
|
38
|
+
|
|
39
|
+
The 2026-05-22 audit (entry below in v0.3.0) traced every
|
|
40
|
+
`PanoramaSettings` field's native consumer and proved the flat type
|
|
41
|
+
mixed three engines' (batch-keyframe, slit-scan, hybrid) settings into
|
|
42
|
+
one bag of disjoint subsets. Hosts had no way to know at the type
|
|
43
|
+
level which settings their chosen engine would even read; the modal
|
|
44
|
+
exposed knobs that were silently ignored on the active engine. The
|
|
45
|
+
revamp splits the type along engine boundaries so the types match what
|
|
46
|
+
each engine actually consumes.
|
|
47
|
+
|
|
48
|
+
#### What changed
|
|
49
|
+
|
|
50
|
+
- **New file:** `src/camera/PanoramaSettings.ts` — `CaptureBaseSettings`
|
|
51
|
+
+ three top-level types (`PanoramaSettings`, `SlitscanSettings`,
|
|
52
|
+
`HybridSettings`), each with co-located `DEFAULT_*_SETTINGS`. Sub-trees
|
|
53
|
+
group related knobs: `stitcher` / `frameSelection.flow` (panorama);
|
|
54
|
+
`painting` / `registration.ncc1d` / `registration.ncc2d.emaSmoothing` /
|
|
55
|
+
`registration.ncc2d.panAxisLock` / `plane` / `advanced` (slitscan).
|
|
56
|
+
- **New file:** `src/camera/PanoramaSettingsBridge.ts` — three pure
|
|
57
|
+
adapter functions (`panoramaSettingsToNativeConfig`,
|
|
58
|
+
`slitscanSettingsToNativeConfig`, `hybridSettingsToNativeConfig`)
|
|
59
|
+
that translate the typed JS tree → the flat
|
|
60
|
+
`Record<string, primitive>` the native bridges consume. Handles
|
|
61
|
+
presence-as-enable (`ncc1d` defined ⇒ `enable1dNcc: true` on the
|
|
62
|
+
wire) and source-conditional plane optionals.
|
|
63
|
+
- **New file:** `src/camera/buildPanoramaInitialSettings.ts` — pure
|
|
64
|
+
helper that translates `<Camera>`'s `default*` props into the
|
|
65
|
+
initial `PanoramaSettings` snapshot. Takes the device's low-mem
|
|
66
|
+
classification as an argument so the function stays pure and
|
|
67
|
+
testable.
|
|
68
|
+
- **Rewritten:** `src/camera/PanoramaSettingsModal.tsx` — now consumes
|
|
69
|
+
the new `PanoramaSettings` shape. UI sections mirror the type tree
|
|
70
|
+
(Capture source, Debug, Stitcher accordion, Frame Selection
|
|
71
|
+
accordion with nested Flow tunables). ~600 LOC smaller than v0.3
|
|
72
|
+
because dead slit-scan / hybrid / video-recording fields are gone.
|
|
73
|
+
- **Rewired:** `src/camera/Camera.tsx` — settings state uses the new
|
|
74
|
+
type; `incremental.start({ config })` now passes
|
|
75
|
+
`panoramaSettingsToNativeConfig(settings)` instead of an inline flat
|
|
76
|
+
dict. IMU translation gate reads
|
|
77
|
+
`settings.frameSelection.flow?.maxTranslationCm`. Debug overlay
|
|
78
|
+
reads `settings.frameSelection.mode` + `settings.stitcher.stitchMode`.
|
|
79
|
+
- **Updated:** `src/index.ts` — exports the new types + adapters; drops
|
|
80
|
+
the deleted v0.3 type.
|
|
81
|
+
- **Test infra:** added `jest` + `ts-jest` + `@types/jest` devDeps; new
|
|
82
|
+
`jest.config.js`, `tsconfig.test.json`, `tsconfig.build.json` (the
|
|
83
|
+
latter excludes `__tests__/` from the shipped `dist/`). 19 tests
|
|
84
|
+
across two suites cover the bridge round-trips, presence-as-enable
|
|
85
|
+
cases, plane-source variants, and prop→settings-tree translation.
|
|
86
|
+
|
|
87
|
+
#### Migration table — v0.3 flat → v0.4 hierarchical
|
|
88
|
+
|
|
89
|
+
For `<Camera>`-consuming hosts (the only public path that took
|
|
90
|
+
`PanoramaSettings` in v0.3):
|
|
91
|
+
|
|
92
|
+
| v0.3 field | v0.4 path |
|
|
93
|
+
|----------------------------------|-------------------------------------------------|
|
|
94
|
+
| `captureSource` | `captureSource` (unchanged) |
|
|
95
|
+
| `debug` | `debug` (unchanged) |
|
|
96
|
+
| `stitchMode` | `stitcher.stitchMode` |
|
|
97
|
+
| `warperType` | `stitcher.warperType` |
|
|
98
|
+
| `blenderType` | `stitcher.blenderType` |
|
|
99
|
+
| `seamFinderType` | `stitcher.seamFinderType` |
|
|
100
|
+
| `enableMaxInscribedRectCrop` | `stitcher.enableMaxInscribedRectCrop` |
|
|
101
|
+
| `frameSelectionMode` | `frameSelection.mode` |
|
|
102
|
+
| `keyframeMaxCount` | `frameSelection.maxKeyframes` |
|
|
103
|
+
| `keyframeOverlapThreshold` | `frameSelection.overlapThreshold` |
|
|
104
|
+
| `flowNoveltyPercentile` | `frameSelection.flow.noveltyPercentile` |
|
|
105
|
+
| `flowEvalEveryNFrames` | `frameSelection.flow.evalEveryNFrames` |
|
|
106
|
+
| `flowMaxTranslationCm` | `frameSelection.flow.maxTranslationCm` |
|
|
107
|
+
| `flowMaxCorners` | `frameSelection.flow.maxCorners` |
|
|
108
|
+
| `flowQualityLevel` | `frameSelection.flow.qualityLevel` |
|
|
109
|
+
| `flowMinDistance` | `frameSelection.flow.minDistance` |
|
|
110
|
+
|
|
111
|
+
#### Deleted from the public type surface
|
|
112
|
+
|
|
113
|
+
These fields were consumed only by slit-scan or hybrid engines (or
|
|
114
|
+
not consumed at all per the audit) and were dead surface on
|
|
115
|
+
`<Camera>`'s batch-keyframe path:
|
|
116
|
+
|
|
117
|
+
- `incrementalEngine` — `<Camera>` always uses `batch-keyframe`; the
|
|
118
|
+
knob never reached this component. Hosts that want slit-scan or
|
|
119
|
+
hybrid build their own capture flow on `incremental.start()` and
|
|
120
|
+
pass `SlitscanSettings` / `HybridSettings` instead.
|
|
121
|
+
- `useARPreview` — superseded by `captureSource` ('ar' / 'non-ar').
|
|
122
|
+
- `useDetectedPlane` — superseded by `SlitscanSettings.plane.source`.
|
|
123
|
+
- `planeSource`, `virtualPlaneDepthMeters`, `arkitPlaneAlignmentThreshold`,
|
|
124
|
+
`planeProjectionStyle` — slit-scan only; on `SlitscanSettings.plane.*`.
|
|
125
|
+
- `slitWidthFraction`, `sliverPosition`, `firstFrameFullFrame`,
|
|
126
|
+
`paintMode` — slit-scan only; on `SlitscanSettings.painting.*`.
|
|
127
|
+
- `acceptGate`, `enableTriangulation`, `enableTriAccumulator`,
|
|
128
|
+
`enable2dNcc`, `enableRansacHomography`, `nccSearchRadius1d`,
|
|
129
|
+
`nccSearchMargin2d`, `nccConfidenceThreshold2d`,
|
|
130
|
+
`enableNcc2dEmaSmoothing`, `ncc2dEmaAlpha`,
|
|
131
|
+
`enableNcc2dPanAxisLock`, `ncc2dCrossAxisLockPx` — slit-scan only;
|
|
132
|
+
on `SlitscanSettings.registration.*`.
|
|
133
|
+
- `hybridProjection` — hybrid only; on `HybridSettings.projection`.
|
|
134
|
+
- `maxRecordingMs`, `framesPerSecond`, `minFrames`, `maxFrames`,
|
|
135
|
+
`quality` — historical video-recording fallback fields with no
|
|
136
|
+
consumer on `<Camera>`'s batch-keyframe path.
|
|
137
|
+
|
|
138
|
+
#### Latent v0.3 bug fixed in passing
|
|
139
|
+
|
|
140
|
+
The v0.3 `<Camera>` accepted a `defaultCaptureSource` prop but the
|
|
141
|
+
internal `buildInitialSettings` function never copied it into
|
|
142
|
+
`settings.captureSource` — only into `arPreference` state. The
|
|
143
|
+
discrepancy meant the wire dict sent to native always reported
|
|
144
|
+
`captureSource: 'ar'` even when the operator's effective source was
|
|
145
|
+
`'non-ar'`, which silently disabled Android's `disableAngularFallback`
|
|
146
|
+
opt-out (audit fix F1). v0.4's `extractPanoramaOverrides` +
|
|
147
|
+
`buildPanoramaInitialSettings` route the prop through correctly.
|
|
148
|
+
Hosts using `defaultCaptureSource="non-ar"` will see native receive
|
|
149
|
+
the matching value for the first time.
|
|
150
|
+
|
|
151
|
+
#### Known limitation — modal Capture-source field vs. AR toggle
|
|
152
|
+
|
|
153
|
+
The on-screen AR toggle button at the bottom of `<Camera>` updates
|
|
154
|
+
`arPreference` state (and through it `effectiveCaptureSource`),
|
|
155
|
+
which decides which preview component mounts. The Capture-source
|
|
156
|
+
segmented control inside the settings modal updates
|
|
157
|
+
`settings.captureSource`, which only affects what's reported to the
|
|
158
|
+
native engine via `panoramaSettingsToNativeConfig` (gates Android's
|
|
159
|
+
angular-fallback opt-out per audit fix F1). These two values can
|
|
160
|
+
drift if the operator toggles the AR button without re-opening
|
|
161
|
+
settings, OR flips the modal field without touching the AR button.
|
|
162
|
+
The on-screen toggle is the canonical UI affordance for the live
|
|
163
|
+
preview path; the modal field is best thought of as a tester escape
|
|
164
|
+
hatch for the wire-format consequence. A future cleanup is to make
|
|
165
|
+
both update the same source of truth — out of scope for v0.4.
|
|
166
|
+
|
|
167
|
+
#### Migration example
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
// Before (v0.3)
|
|
171
|
+
const settings: PanoramaSettings = {
|
|
172
|
+
captureSource: 'ar',
|
|
173
|
+
stitchMode: 'auto',
|
|
174
|
+
blenderType: 'multiband',
|
|
175
|
+
flowMaxTranslationCm: 50,
|
|
176
|
+
flowNoveltyPercentile: 0.85,
|
|
177
|
+
keyframeMaxCount: 6,
|
|
178
|
+
frameSelectionMode: 'flow-based',
|
|
179
|
+
// … 40+ more fields
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// After (v0.4)
|
|
183
|
+
const settings: PanoramaSettings = {
|
|
184
|
+
captureSource: 'ar',
|
|
185
|
+
debug: false,
|
|
186
|
+
stitcher: {
|
|
187
|
+
stitchMode: 'auto',
|
|
188
|
+
warperType: 'plane',
|
|
189
|
+
blenderType: 'multiband',
|
|
190
|
+
seamFinderType: 'graphcut',
|
|
191
|
+
enableMaxInscribedRectCrop: false,
|
|
192
|
+
},
|
|
193
|
+
frameSelection: {
|
|
194
|
+
mode: 'flow-based',
|
|
195
|
+
maxKeyframes: 6,
|
|
196
|
+
overlapThreshold: 0.20,
|
|
197
|
+
flow: {
|
|
198
|
+
noveltyPercentile: 0.85,
|
|
199
|
+
evalEveryNFrames: 5,
|
|
200
|
+
maxTranslationCm: 50,
|
|
201
|
+
maxCorners: 150,
|
|
202
|
+
qualityLevel: 0.01,
|
|
203
|
+
minDistance: 10,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Or just use the default:
|
|
209
|
+
import { DEFAULT_PANORAMA_SETTINGS } from 'react-native-image-stitcher';
|
|
210
|
+
const settings = { ...DEFAULT_PANORAMA_SETTINGS, captureSource: 'non-ar' };
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
|
|
19
214
|
## [0.3.0] — 2026-05-23
|
|
20
215
|
|
|
21
216
|
> [!IMPORTANT]
|
package/dist/camera/Camera.js
CHANGED
|
@@ -90,7 +90,10 @@ const CaptureKeyframePill_1 = require("./CaptureKeyframePill");
|
|
|
90
90
|
const CaptureOrientationPill_1 = require("./CaptureOrientationPill");
|
|
91
91
|
const CaptureStitchStatsToast_1 = require("./CaptureStitchStatsToast");
|
|
92
92
|
const PanoramaBandOverlay_1 = require("./PanoramaBandOverlay");
|
|
93
|
+
const PanoramaSettingsBridge_1 = require("./PanoramaSettingsBridge");
|
|
93
94
|
const PanoramaSettingsModal_1 = require("./PanoramaSettingsModal");
|
|
95
|
+
const buildPanoramaInitialSettings_1 = require("./buildPanoramaInitialSettings");
|
|
96
|
+
const lowMemDevice_1 = require("./lowMemDevice");
|
|
94
97
|
const useCapture_1 = require("./useCapture");
|
|
95
98
|
const useDeviceOrientation_1 = require("./useDeviceOrientation");
|
|
96
99
|
const incremental_1 = require("../stitching/incremental");
|
|
@@ -228,32 +231,31 @@ function deriveEffectiveCaptureSource(arPreference, lens, isARSupportedOnDevice)
|
|
|
228
231
|
return arPreference ? 'ar' : 'non-ar';
|
|
229
232
|
}
|
|
230
233
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
+
* Pluck the props that influence the initial PanoramaSettings tree.
|
|
235
|
+
* Kept inline (vs. a wide structural type) so future Camera prop
|
|
236
|
+
* additions don't accidentally widen the settings-translation
|
|
237
|
+
* surface — the pure builder in `./buildPanoramaInitialSettings.ts`
|
|
238
|
+
* has the canonical interface; this just forwards the relevant
|
|
239
|
+
* fields.
|
|
234
240
|
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
241
|
+
* The `default*ResolMP` props on `CameraProps` are documented as
|
|
242
|
+
* forward-looking no-ops; the new PanoramaSettings tree has no home
|
|
243
|
+
* for them yet (the v0.3 audit found cv::Stitcher's resol knobs
|
|
244
|
+
* aren't reached by either platform's bridge). They're accepted on
|
|
245
|
+
* the prop interface for API stability and ignored here.
|
|
239
246
|
*/
|
|
240
|
-
function
|
|
247
|
+
function extractPanoramaOverrides(props) {
|
|
241
248
|
return {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.flowMaxTranslationCm,
|
|
253
|
-
keyframeMaxCount: props.defaultKeyframeMaxCount ??
|
|
254
|
-
PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.keyframeMaxCount,
|
|
255
|
-
keyframeOverlapThreshold: props.defaultKeyframeOverlapThreshold ??
|
|
256
|
-
PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS.keyframeOverlapThreshold,
|
|
249
|
+
defaultCaptureSource: props.defaultCaptureSource,
|
|
250
|
+
defaultStitchMode: props.defaultStitchMode,
|
|
251
|
+
defaultBlender: props.defaultBlender,
|
|
252
|
+
defaultSeamFinder: props.defaultSeamFinder,
|
|
253
|
+
defaultWarper: props.defaultWarper,
|
|
254
|
+
defaultFlowNoveltyPercentile: props.defaultFlowNoveltyPercentile,
|
|
255
|
+
defaultFlowEvalEveryNFrames: props.defaultFlowEvalEveryNFrames,
|
|
256
|
+
defaultFlowMaxTranslationCm: props.defaultFlowMaxTranslationCm,
|
|
257
|
+
defaultKeyframeMaxCount: props.defaultKeyframeMaxCount,
|
|
258
|
+
defaultKeyframeOverlapThreshold: props.defaultKeyframeOverlapThreshold,
|
|
257
259
|
};
|
|
258
260
|
}
|
|
259
261
|
// `toFileUri` (used to be an inline `toFileUri` here) lives in
|
|
@@ -273,7 +275,7 @@ function Camera(props) {
|
|
|
273
275
|
// ── State ───────────────────────────────────────────────────────
|
|
274
276
|
const [arPreference, setArPreference] = (0, react_1.useState)(defaultCaptureSource === 'ar');
|
|
275
277
|
const [lens, setLens] = (0, react_1.useState)(defaultLens);
|
|
276
|
-
const [settings, setSettings] = (0, react_1.useState)(() =>
|
|
278
|
+
const [settings, setSettings] = (0, react_1.useState)(() => (0, buildPanoramaInitialSettings_1.buildPanoramaInitialSettings)(extractPanoramaOverrides(props), (0, lowMemDevice_1.isLowMemDevice)()));
|
|
277
279
|
const [settingsModalVisible, setSettingsModalVisible] = (0, react_1.useState)(false);
|
|
278
280
|
const [statusPhase, setStatusPhase] = (0, react_1.useState)('idle');
|
|
279
281
|
const [recordingStartedAt, setRecordingStartedAt] = (0, react_1.useState)(null);
|
|
@@ -399,11 +401,16 @@ function Camera(props) {
|
|
|
399
401
|
// |residual|` — which undercounted any time a non-IMU accept
|
|
400
402
|
// (flow novelty, force-last) reset the integrator before the
|
|
401
403
|
// budget threshold was reached.
|
|
404
|
+
// The translation budget lives at `frameSelection.flow.maxTranslationCm`
|
|
405
|
+
// in the new hierarchical settings shape. When `flow` is undefined
|
|
406
|
+
// (the consumer opted out of the flow strategy entirely), the gate
|
|
407
|
+
// stays disabled — same observable behaviour as v0.3's `0` default.
|
|
408
|
+
const flowMaxTranslationCm = settings.frameSelection.flow?.maxTranslationCm ?? 0;
|
|
402
409
|
const imuGate = (0, useIMUTranslationGate_1.useIMUTranslationGate)({
|
|
403
410
|
enabled: isNonAR
|
|
404
411
|
&& statusPhase === 'recording'
|
|
405
|
-
&&
|
|
406
|
-
budgetMeters: Math.max(0.001,
|
|
412
|
+
&& flowMaxTranslationCm > 0,
|
|
413
|
+
budgetMeters: Math.max(0.001, flowMaxTranslationCm / 100.0),
|
|
407
414
|
onBudgetExceeded: () => {
|
|
408
415
|
const mod = (0, incremental_1.getIncrementalNativeModule)();
|
|
409
416
|
mod?.markNextFrameAsLastKeyframe?.().catch(() => undefined);
|
|
@@ -578,6 +585,25 @@ function Camera(props) {
|
|
|
578
585
|
const orientationRotation = deviceOrientation === 'portrait' ? 90
|
|
579
586
|
: deviceOrientation === 'portrait-upside-down' ? 270
|
|
580
587
|
: 0;
|
|
588
|
+
// v0.4 — the inline-flat config dict that v0.3 maintained here
|
|
589
|
+
// moved into `panoramaSettingsToNativeConfig` (see
|
|
590
|
+
// PanoramaSettingsBridge.ts). That adapter is the single source
|
|
591
|
+
// of truth for the JS→native wire format; both this call site
|
|
592
|
+
// AND the modal's reset-to-defaults preview agree on the same
|
|
593
|
+
// mapping. Audit fixes F1 / F4 / F6 from v0.3 are now properties
|
|
594
|
+
// of the bridge (verified by the unit tests in
|
|
595
|
+
// src/camera/__tests__/PanoramaSettingsBridge.test.ts).
|
|
596
|
+
//
|
|
597
|
+
// 2026-05-23 — override `captureSource` with the runtime-derived
|
|
598
|
+
// `effectiveCaptureSource` (from `arPreference + lens +
|
|
599
|
+
// AR-device-support`). Pre-this change the camera-screen AR
|
|
600
|
+
// toggle wrote ONLY to local `arPreference` state while the
|
|
601
|
+
// bridge read `settings.captureSource` — so native could think
|
|
602
|
+
// the capture was AR while the operator had toggled it off (or
|
|
603
|
+
// vice-versa). Single source of truth now: whatever camera the
|
|
604
|
+
// operator can see is what native is told it is. The settings
|
|
605
|
+
// modal's `captureSource` control has been removed for the same
|
|
606
|
+
// reason — see PanoramaSettingsModal.tsx for the rationale.
|
|
581
607
|
await incremental.start({
|
|
582
608
|
snapshotJpegQuality: 75,
|
|
583
609
|
snapshotEveryNAccepts: 1,
|
|
@@ -589,37 +615,10 @@ function Camera(props) {
|
|
|
589
615
|
canvasWidth: 5000,
|
|
590
616
|
canvasHeight: 5000,
|
|
591
617
|
engine: 'batch-keyframe',
|
|
592
|
-
config: {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
blenderType: settings.blenderType,
|
|
597
|
-
seamFinderType: settings.seamFinderType,
|
|
598
|
-
enableMaxInscribedRectCrop: settings.enableMaxInscribedRectCrop,
|
|
599
|
-
// ── KeyframeGate (per-frame selection) ────────────────────
|
|
600
|
-
// F6 audit fix: pass settings.frameSelectionMode through
|
|
601
|
-
// instead of hardcoding 'flow-based' (which silently made the
|
|
602
|
-
// time-based / pose-based modal options no-ops).
|
|
603
|
-
frameSelectionMode: settings.frameSelectionMode,
|
|
604
|
-
keyframeMaxCount: settings.keyframeMaxCount,
|
|
605
|
-
keyframeOverlapThreshold: settings.keyframeOverlapThreshold,
|
|
606
|
-
// ── Flow-strategy tunables ────────────────────────────────
|
|
607
|
-
// F4 audit fix: previously omitted, which made the modal
|
|
608
|
-
// sliders for these three a complete no-op (only iOS native
|
|
609
|
-
// even read them, and only when JS sent them).
|
|
610
|
-
flowNoveltyPercentile: settings.flowNoveltyPercentile,
|
|
611
|
-
flowEvalEveryNFrames: settings.flowEvalEveryNFrames,
|
|
612
|
-
flowMaxTranslationCm: settings.flowMaxTranslationCm,
|
|
613
|
-
flowMaxCorners: settings.flowMaxCorners,
|
|
614
|
-
flowQualityLevel: settings.flowQualityLevel,
|
|
615
|
-
flowMinDistance: settings.flowMinDistance,
|
|
616
|
-
// ── Engine-routing flags consumed by native ───────────────
|
|
617
|
-
// F1 audit fix: Android keyframe gate's disableAngularFallback
|
|
618
|
-
// opt-out reads this to decide whether to skip the angular
|
|
619
|
-
// fallback (gyro pose is too noisy for the FoV-overlap calc
|
|
620
|
-
// in non-AR mode, causing degenerate cv::Stitcher params).
|
|
621
|
-
captureSource: settings.captureSource,
|
|
622
|
-
},
|
|
618
|
+
config: (0, PanoramaSettingsBridge_1.panoramaSettingsToNativeConfig)({
|
|
619
|
+
...settings,
|
|
620
|
+
captureSource: effectiveCaptureSource,
|
|
621
|
+
}),
|
|
623
622
|
});
|
|
624
623
|
imuGate.resetAnchor();
|
|
625
624
|
// Start pumping vision-camera snapshots into the engine for
|
|
@@ -642,6 +641,7 @@ function Camera(props) {
|
|
|
642
641
|
isNonAR,
|
|
643
642
|
deviceOrientation,
|
|
644
643
|
settings,
|
|
644
|
+
effectiveCaptureSource,
|
|
645
645
|
imuGate,
|
|
646
646
|
jsDriver,
|
|
647
647
|
onError,
|
|
@@ -723,6 +723,18 @@ function Camera(props) {
|
|
|
723
723
|
onError,
|
|
724
724
|
recordingStartedAt,
|
|
725
725
|
jsDriver,
|
|
726
|
+
// F10 Phase 2 review N1 — these four were missing pre-fix. The
|
|
727
|
+
// callback reads `settings.debug` (to gate the stitchToast),
|
|
728
|
+
// `isNonAR` (to decide whether to read IMU totalAbs translation),
|
|
729
|
+
// `imuGate` (the read itself), and `stitchToast` (the toast hook
|
|
730
|
+
// object). If any of those identities change between the user
|
|
731
|
+
// pressing-and-holding the shutter and the release, the stale-
|
|
732
|
+
// closure read could disagree with the actual current state.
|
|
733
|
+
// Pre-existing v0.3 bug; v0.4 was the natural time to address it.
|
|
734
|
+
settings,
|
|
735
|
+
isNonAR,
|
|
736
|
+
imuGate,
|
|
737
|
+
stitchToast,
|
|
726
738
|
]);
|
|
727
739
|
// ── Lens / AR-toggle handlers ───────────────────────────────────
|
|
728
740
|
const handleLensChange = (0, react_1.useCallback)((next) => {
|
|
@@ -749,7 +761,7 @@ function Camera(props) {
|
|
|
749
761
|
react_1.default.createElement(CaptureOrientationPill_1.CaptureOrientationPill, { orientation: deviceOrientation, topInset: insets.top }),
|
|
750
762
|
react_1.default.createElement(CaptureKeyframePill_1.CaptureKeyframePill, { state: incrementalState, topInset: insets.top }),
|
|
751
763
|
react_1.default.createElement(CaptureMemoryPill_1.CaptureMemoryPill, { topInset: insets.top }),
|
|
752
|
-
react_1.default.createElement(CaptureDebugOverlay_1.CaptureDebugOverlay, { incrementalState: incrementalState, imuTranslationMetres: isNonAR ? imuGate.getTranslationMetres() : null, captureSource: effectiveCaptureSource, frameSelectionMode: settings.
|
|
764
|
+
react_1.default.createElement(CaptureDebugOverlay_1.CaptureDebugOverlay, { incrementalState: incrementalState, imuTranslationMetres: isNonAR ? imuGate.getTranslationMetres() : null, captureSource: effectiveCaptureSource, frameSelectionMode: settings.frameSelection.mode, stitchMode: settings.stitcher.stitchMode }))),
|
|
753
765
|
react_1.default.createElement(CaptureStitchStatsToast_1.CaptureStitchStatsToast, { message: stitchToast.message, topInset: insets.top }),
|
|
754
766
|
showSettingsButton && (react_1.default.createElement(SettingsButton, { topInset: insets.top, onPress: () => setSettingsModalVisible(true) })),
|
|
755
767
|
react_1.default.createElement(react_native_1.View, { pointerEvents: "box-none", style: [styles.bottomArea, { paddingBottom: insets.bottom + 12 }] },
|