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
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* PanoramaSettingsBridge — JS-side adapters that convert the v0.4
|
|
4
|
+
* typed `PanoramaSettings` / `SlitscanSettings` / `HybridSettings`
|
|
5
|
+
* shape into the flat `configOverrides` dictionary the native
|
|
6
|
+
* bridges read.
|
|
7
|
+
*
|
|
8
|
+
* Why this file exists
|
|
9
|
+
* ────────────────────
|
|
10
|
+
*
|
|
11
|
+
* The v0.4 types use hierarchical sub-trees (`stitcher`,
|
|
12
|
+
* `frameSelection.flow`, `painting`, `registration.ncc1d`,
|
|
13
|
+
* `registration.ncc2d.emaSmoothing`, `plane`, …) to give consumers
|
|
14
|
+
* a clean, ergonomic settings surface that mirrors the native
|
|
15
|
+
* engine's domain. But the native bridges (iOS Swift's
|
|
16
|
+
* `applyConfigOverrides`, Android Kotlin's `IncrementalStitcher.start`)
|
|
17
|
+
* read a FLAT dictionary of native-named keys (e.g. `nccSearchRadius1d`,
|
|
18
|
+
* `enable1dNcc`, `ncc2dEmaAlpha`, `flowMaxTranslationCm`).
|
|
19
|
+
*
|
|
20
|
+
* Two semantic gaps to bridge:
|
|
21
|
+
*
|
|
22
|
+
* 1. **Naming.** JS `registration.ncc1d.searchRadius` →
|
|
23
|
+
* native `nccSearchRadius1d`. JS `painting.paintMode` →
|
|
24
|
+
* native `paintMode` (same). Etc.
|
|
25
|
+
*
|
|
26
|
+
* 2. **Presence-as-enable.** The native side reads explicit
|
|
27
|
+
* `enable1dNcc`, `enable2dNcc`, `enableNcc2dEmaSmoothing`,
|
|
28
|
+
* `enableNcc2dPanAxisLock` booleans. JS models these as
|
|
29
|
+
* optional sub-objects (sub-object present ⇒ enabled). This
|
|
30
|
+
* adapter flattens the booleans for the wire.
|
|
31
|
+
*
|
|
32
|
+
* 3. **Skipped engine defaults.** Hybrid engine presets internally
|
|
33
|
+
* clobber most fields (see HybridSettings JSDoc), so we don't
|
|
34
|
+
* send overrides that would be ignored — just the small useful
|
|
35
|
+
* surface.
|
|
36
|
+
*
|
|
37
|
+
* The Camera component calls `panoramaSettingsToNativeConfig` once
|
|
38
|
+
* per capture start to produce the value passed as
|
|
39
|
+
* `incremental.start({ config: … })`. Layer 2 callers building
|
|
40
|
+
* SlitscanSettings or HybridSettings call the matching adapter
|
|
41
|
+
* before reaching `incremental.start()`.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
DEFAULT_FLOW_GATE_SETTINGS,
|
|
46
|
+
type PanoramaSettings,
|
|
47
|
+
type SlitscanSettings,
|
|
48
|
+
type HybridSettings,
|
|
49
|
+
} from './PanoramaSettings';
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Flat config dictionary type — what the native bridges expect.
|
|
54
|
+
* Indexed by the native-side key name; values are platform-
|
|
55
|
+
* marshallable (booleans / numbers / strings). Keep this type
|
|
56
|
+
* loose: native validates each key individually, and silently
|
|
57
|
+
* ignores keys it doesn't recognise.
|
|
58
|
+
*/
|
|
59
|
+
export type NativeConfigDict = Record<string, boolean | number | string>;
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Convert a v0.4 PanoramaSettings tree into the flat dict the
|
|
64
|
+
* batch-keyframe native side reads. Maps every consumed field
|
|
65
|
+
* exactly once and skips fields the engine doesn't reach.
|
|
66
|
+
*
|
|
67
|
+
* Verified against:
|
|
68
|
+
* - iOS `IncrementalStitcher.swift:810-960` (batch path)
|
|
69
|
+
* - Android `IncrementalStitcher.kt:280-430` (batch path)
|
|
70
|
+
*/
|
|
71
|
+
export function panoramaSettingsToNativeConfig(
|
|
72
|
+
s: PanoramaSettings,
|
|
73
|
+
): NativeConfigDict {
|
|
74
|
+
const cfg: NativeConfigDict = {
|
|
75
|
+
// ── Cross-cutting ────────────────────────────────────────────
|
|
76
|
+
captureSource: s.captureSource,
|
|
77
|
+
|
|
78
|
+
// ── BatchStitcherSettings → cv::Stitcher knobs ───────────────
|
|
79
|
+
stitchMode: s.stitcher.stitchMode,
|
|
80
|
+
warperType: s.stitcher.warperType,
|
|
81
|
+
blenderType: s.stitcher.blenderType,
|
|
82
|
+
seamFinderType: s.stitcher.seamFinderType,
|
|
83
|
+
enableMaxInscribedRectCrop: s.stitcher.enableMaxInscribedRectCrop,
|
|
84
|
+
|
|
85
|
+
// ── FrameSelectionSettings → KeyframeGate knobs ──────────────
|
|
86
|
+
frameSelectionMode: s.frameSelection.mode,
|
|
87
|
+
keyframeMaxCount: s.frameSelection.maxKeyframes,
|
|
88
|
+
keyframeOverlapThreshold: s.frameSelection.overlapThreshold,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Flow strategy knobs — always serialised, regardless of
|
|
92
|
+
// `frameSelection.mode`. Two reasons:
|
|
93
|
+
//
|
|
94
|
+
// 1. Mode-flip-mid-session: hosts can change `mode` without
|
|
95
|
+
// restarting capture; consistent flow serialisation means
|
|
96
|
+
// `'time-based' → 'flow-based'` mid-session doesn't slip
|
|
97
|
+
// back to stale native-side defaults. Native ignores these
|
|
98
|
+
// keys when the active mode doesn't use them.
|
|
99
|
+
//
|
|
100
|
+
// 2. **Native compiled-in defaults disagree with the JS
|
|
101
|
+
// defaults.** Specifically: native sets `flowMaxTranslationCm
|
|
102
|
+
// = 0` and `flowEvalEveryNFrames = 1` when the keys are
|
|
103
|
+
// missing (iOS `IncrementalStitcher.swift:1003-1029`,
|
|
104
|
+
// Android `IncrementalStitcher.kt:419-445`), whereas the JS
|
|
105
|
+
// `DEFAULT_PANORAMA_SETTINGS.frameSelection.flow` values are
|
|
106
|
+
// `50` and `5`. Hosts who write sparse settings literals
|
|
107
|
+
// (omitted `flow` sub-tree, legal per the optional `?`)
|
|
108
|
+
// would silently get IMU translation gate disabled and
|
|
109
|
+
// ~5× CPU on flow evaluation — a v0.3-style behaviour
|
|
110
|
+
// regression on the wire that the type system can't catch.
|
|
111
|
+
// Filling from `DEFAULT_FLOW_GATE_SETTINGS` here closes the
|
|
112
|
+
// gap; the JS defaults become the canonical defaults across
|
|
113
|
+
// both layers.
|
|
114
|
+
//
|
|
115
|
+
// See the F10 Phase 2 review (B1 + N3 + N6) for the full
|
|
116
|
+
// discussion of why this matters.
|
|
117
|
+
const f = s.frameSelection.flow ?? DEFAULT_FLOW_GATE_SETTINGS;
|
|
118
|
+
cfg.flowNoveltyPercentile = f.noveltyPercentile;
|
|
119
|
+
cfg.flowEvalEveryNFrames = f.evalEveryNFrames;
|
|
120
|
+
cfg.flowMaxTranslationCm = f.maxTranslationCm;
|
|
121
|
+
cfg.flowMaxCorners = f.maxCorners;
|
|
122
|
+
cfg.flowQualityLevel = f.qualityLevel;
|
|
123
|
+
cfg.flowMinDistance = f.minDistance;
|
|
124
|
+
|
|
125
|
+
return cfg;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Convert a v0.4 SlitscanSettings tree into the flat dict the
|
|
131
|
+
* slit-scan / firstwins native engines read. Handles the
|
|
132
|
+
* "presence-as-enable" boolean expansion: a non-undefined
|
|
133
|
+
* `registration.ncc1d` means `enable1dNcc: true` on the wire,
|
|
134
|
+
* with the sub-object's `searchRadius` carried alongside.
|
|
135
|
+
*
|
|
136
|
+
* Verified against:
|
|
137
|
+
* - iOS `IncrementalStitcher.swift:1006-1100` (applyConfigOverrides)
|
|
138
|
+
* - iOS `OpenCVSlitScanStitcher.mm` (all numbered references in
|
|
139
|
+
* the audit ground-truth matrix)
|
|
140
|
+
*/
|
|
141
|
+
export function slitscanSettingsToNativeConfig(
|
|
142
|
+
s: SlitscanSettings,
|
|
143
|
+
): NativeConfigDict {
|
|
144
|
+
const cfg: NativeConfigDict = {
|
|
145
|
+
captureSource: s.captureSource,
|
|
146
|
+
// The native side reads `engine: 'slitscan-…'` at start time
|
|
147
|
+
// from a separate top-level field, NOT from configOverrides.
|
|
148
|
+
// We still serialise the variant here for hosts that want to
|
|
149
|
+
// round-trip a single settings object through both surfaces.
|
|
150
|
+
engineVariant: s.variant,
|
|
151
|
+
|
|
152
|
+
// ── Painting ─────────────────────────────────────────────────
|
|
153
|
+
paintMode: s.painting.paintMode,
|
|
154
|
+
sliverPosition: s.painting.sliverPosition,
|
|
155
|
+
firstFrameFullFrame: s.painting.firstFrameFullFrame,
|
|
156
|
+
|
|
157
|
+
// ── Registration (explicit booleans) ─────────────────────────
|
|
158
|
+
enableTriangulation: s.registration.enableTriangulation,
|
|
159
|
+
enableTriAccumulator: s.registration.enableTriAccumulator,
|
|
160
|
+
enableRansacHomography: s.registration.enableRansacHomography,
|
|
161
|
+
|
|
162
|
+
// ── Plane projection ─────────────────────────────────────────
|
|
163
|
+
planeSource: s.plane.source,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// ── 1D NCC: presence-as-enable ─────────────────────────────────
|
|
167
|
+
if (s.registration.ncc1d) {
|
|
168
|
+
cfg.enable1dNcc = true;
|
|
169
|
+
cfg.nccSearchRadius1d = s.registration.ncc1d.searchRadius;
|
|
170
|
+
} else {
|
|
171
|
+
cfg.enable1dNcc = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── 2D NCC: presence-as-enable + nested optionals ──────────────
|
|
175
|
+
if (s.registration.ncc2d) {
|
|
176
|
+
const n2 = s.registration.ncc2d;
|
|
177
|
+
cfg.enable2dNcc = true;
|
|
178
|
+
cfg.nccSearchMargin2d = n2.searchMargin;
|
|
179
|
+
cfg.nccConfidenceThreshold2d = n2.confidenceThreshold;
|
|
180
|
+
if (n2.emaSmoothing) {
|
|
181
|
+
cfg.enableNcc2dEmaSmoothing = true;
|
|
182
|
+
cfg.ncc2dEmaAlpha = n2.emaSmoothing.alpha;
|
|
183
|
+
} else {
|
|
184
|
+
cfg.enableNcc2dEmaSmoothing = false;
|
|
185
|
+
}
|
|
186
|
+
if (n2.panAxisLock) {
|
|
187
|
+
cfg.enableNcc2dPanAxisLock = true;
|
|
188
|
+
cfg.ncc2dCrossAxisLockPx = n2.panAxisLock.crossAxisLockPx;
|
|
189
|
+
} else {
|
|
190
|
+
cfg.enableNcc2dPanAxisLock = false;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
cfg.enable2dNcc = false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Plane optionals ────────────────────────────────────────────
|
|
197
|
+
// Only emit when `source` actually consumes the field. Native
|
|
198
|
+
// tolerates unsolicited keys but the modal also walks the dict
|
|
199
|
+
// to decide which sliders to render — extra keys would mislead.
|
|
200
|
+
if (s.plane.source !== 'Disabled' && s.plane.projectionStyle !== undefined) {
|
|
201
|
+
cfg.planeProjectionStyle = s.plane.projectionStyle;
|
|
202
|
+
}
|
|
203
|
+
if (s.plane.source === 'Virtual' && s.plane.virtualDepthMeters !== undefined) {
|
|
204
|
+
cfg.virtualPlaneDepthMeters = s.plane.virtualDepthMeters;
|
|
205
|
+
}
|
|
206
|
+
if (s.plane.source === 'ARKitDetected' && s.plane.alignmentThreshold !== undefined) {
|
|
207
|
+
cfg.arkitPlaneAlignmentThreshold = s.plane.alignmentThreshold;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Advanced motion knobs (only emit if explicitly set) ────────
|
|
211
|
+
if (s.advanced?.panAxisFractionRect !== undefined) {
|
|
212
|
+
cfg.kPanAxisFractionRect = s.advanced.panAxisFractionRect;
|
|
213
|
+
}
|
|
214
|
+
if (s.advanced?.minAcceptDeltaPx !== undefined) {
|
|
215
|
+
cfg.kMinAcceptDeltaPx = s.advanced.minAcceptDeltaPx;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return cfg;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Convert a v0.4 HybridSettings tree into the flat dict the hybrid
|
|
224
|
+
* engine reads. Minimal surface — hybrid presets internally clobber
|
|
225
|
+
* almost everything; see HybridSettings JSDoc for context.
|
|
226
|
+
*
|
|
227
|
+
* Verified against:
|
|
228
|
+
* - iOS `OpenCVIncrementalStitcher.mm:139-180` (preset paths)
|
|
229
|
+
* - iOS `IncrementalStitcher.swift:1034-1040` (hybridProjection override)
|
|
230
|
+
*/
|
|
231
|
+
export function hybridSettingsToNativeConfig(
|
|
232
|
+
s: HybridSettings,
|
|
233
|
+
): NativeConfigDict {
|
|
234
|
+
return {
|
|
235
|
+
captureSource: s.captureSource,
|
|
236
|
+
hybridProjection: s.projection,
|
|
237
|
+
};
|
|
238
|
+
}
|