react-native-image-stitcher 0.14.2 → 0.15.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 +131 -0
- package/README.md +35 -0
- package/RNImageStitcher.podspec +8 -7
- package/android/build.gradle +0 -16
- package/android/src/main/cpp/CMakeLists.txt +2 -63
- package/android/src/main/cpp/image_stitcher_jni.cpp +14 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +13 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +285 -3
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +180 -1162
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +29 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +0 -4
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +13 -64
- package/cpp/keyframe_gate.cpp +82 -23
- package/cpp/keyframe_gate.hpp +31 -2
- package/cpp/stitcher.cpp +208 -28
- package/cpp/tests/CMakeLists.txt +18 -12
- package/cpp/tests/keyframe_timebudget_test.cpp +65 -0
- package/cpp/tests/warp_guard_test.cpp +48 -0
- package/cpp/warp_guard.hpp +41 -0
- package/dist/camera/Camera.d.ts +31 -16
- package/dist/camera/Camera.js +10 -2
- package/dist/camera/CaptureStitchStatsToast.d.ts +15 -2
- package/dist/camera/CaptureStitchStatsToast.js +27 -7
- package/dist/camera/PanoramaSettings.d.ts +10 -223
- package/dist/camera/PanoramaSettings.js +6 -28
- package/dist/camera/PanoramaSettingsBridge.d.ts +1 -24
- package/dist/camera/PanoramaSettingsBridge.js +3 -102
- package/dist/camera/PanoramaSettingsModal.js +7 -1
- package/dist/camera/buildPanoramaInitialSettings.d.ts +11 -0
- package/dist/camera/buildPanoramaInitialSettings.js +4 -0
- package/dist/camera/cameraErrorMessages.d.ts +32 -0
- package/dist/camera/cameraErrorMessages.js +53 -0
- package/dist/camera/selectCaptureDevice.d.ts +5 -1
- package/dist/camera/selectCaptureDevice.js +22 -2
- package/dist/camera/useCapture.js +38 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +11 -34
- package/dist/stitching/incremental.d.ts +1 -117
- package/dist/stitching/stitchVideo.d.ts +0 -35
- package/dist/types.d.ts +0 -87
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +96 -674
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +9 -12
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +14 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +7 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +6 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +3 -3
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +28 -60
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +180 -921
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +10 -35
- package/ios/Sources/RNImageStitcher/Stitcher.swift +84 -35
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +13 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +132 -5
- package/package.json +3 -2
- package/src/camera/Camera.tsx +43 -22
- package/src/camera/CaptureStitchStatsToast.tsx +58 -14
- package/src/camera/PanoramaSettings.ts +16 -289
- package/src/camera/PanoramaSettingsBridge.ts +3 -114
- package/src/camera/PanoramaSettingsModal.tsx +14 -1
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +3 -188
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +41 -0
- package/src/camera/__tests__/cameraErrorMessages.test.ts +76 -0
- package/src/camera/__tests__/selectCaptureDevice.test.ts +33 -0
- package/src/camera/buildPanoramaInitialSettings.ts +17 -0
- package/src/camera/cameraErrorMessages.ts +84 -0
- package/src/camera/selectCaptureDevice.ts +28 -3
- package/src/camera/useCapture.ts +44 -1
- package/src/index.ts +11 -40
- package/src/stitching/incremental.ts +3 -140
- package/src/stitching/stitchVideo.ts +0 -26
- package/src/types.ts +0 -95
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +0 -227
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +0 -1081
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +0 -103
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +0 -256
- package/cpp/stitcher_frame_jsi.cpp +0 -214
- package/cpp/stitcher_frame_jsi.hpp +0 -108
- package/cpp/stitcher_proxy_jsi.cpp +0 -109
- package/cpp/stitcher_proxy_jsi.hpp +0 -46
- package/cpp/stitcher_worklet_dispatch.cpp +0 -103
- package/cpp/stitcher_worklet_dispatch.hpp +0 -71
- package/cpp/stitcher_worklet_registry.cpp +0 -91
- package/cpp/stitcher_worklet_registry.hpp +0 -146
- package/cpp/tests/stitcher_worklet_registry_test.cpp +0 -195
- package/dist/stitching/IncrementalStitcherView.d.ts +0 -41
- package/dist/stitching/IncrementalStitcherView.js +0 -157
- package/dist/stitching/StitcherWorkletRegistry.d.ts +0 -117
- package/dist/stitching/StitcherWorkletRegistry.js +0 -78
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +0 -8
- package/dist/stitching/ensureStitcherProxyInstalled.js +0 -81
- package/dist/stitching/useFrameProcessor.d.ts +0 -119
- package/dist/stitching/useFrameProcessor.js +0 -196
- package/dist/stitching/useFrameStream.d.ts +0 -34
- package/dist/stitching/useFrameStream.js +0 -234
- package/dist/stitching/useThrottledFrameProcessor.d.ts +0 -33
- package/dist/stitching/useThrottledFrameProcessor.js +0 -132
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +0 -474
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +0 -1328
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +0 -103
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +0 -3285
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +0 -128
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +0 -313
- package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +0 -185
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +0 -42
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +0 -160
- package/src/stitching/IncrementalStitcherView.tsx +0 -198
- package/src/stitching/StitcherWorkletRegistry.ts +0 -156
- package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +0 -176
- package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +0 -94
- package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +0 -178
- package/src/stitching/ensureStitcherProxyInstalled.ts +0 -141
- package/src/stitching/useFrameProcessor.ts +0 -226
- package/src/stitching/useFrameStream.ts +0 -271
- package/src/stitching/useThrottledFrameProcessor.ts +0 -145
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,137 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [0.15.0] — 2026-06-07
|
|
20
|
+
|
|
21
|
+
### Breaking — only `batch-keyframe` remains; host-worklet / frame-stream hooks removed
|
|
22
|
+
|
|
23
|
+
The live/incremental stitching engines (hybrid, slit-scan, firstwins) and the
|
|
24
|
+
third-party host-worklet / frame-stream observer API were archived (kept under
|
|
25
|
+
`archive/`, excluded from every build surface) so the SDK now ships only the
|
|
26
|
+
`batch-keyframe` capture path. Removed from the public API:
|
|
27
|
+
|
|
28
|
+
- **Hooks** `useFrameProcessor`, `useThrottledFrameProcessor`, `useFrameStream`
|
|
29
|
+
and their option types (`ThrottledFrameProcessorOptions`, `FrameStreamOptions`,
|
|
30
|
+
`SampledFrame`). To compose first-party stitching, use vision-camera's own
|
|
31
|
+
`useFrameProcessor` with `useStitcherWorklet().call(frame)` (see the example app).
|
|
32
|
+
- The **slit-scan / hybrid** panorama-engine settings types and their
|
|
33
|
+
native-config adapters (`slitscanSettingsToNativeConfig`,
|
|
34
|
+
`hybridSettingsToNativeConfig`).
|
|
35
|
+
|
|
36
|
+
A type-only break for the default batch-keyframe path; per the 0.x stability
|
|
37
|
+
policy this bumps a new MINOR.
|
|
38
|
+
|
|
39
|
+
### Changed — iOS + Android unified on the manual `cv::detail` stitch pipeline
|
|
40
|
+
|
|
41
|
+
Both platforms now run the **same** manual `cv::detail` stitch pipeline
|
|
42
|
+
(`useManualPipeline=true` on both), so a given capture produces consistent,
|
|
43
|
+
more robust output regardless of platform. Previously iOS used the manual
|
|
44
|
+
pipeline while Android used the high-level `cv::Stitcher` — the two diverged on
|
|
45
|
+
resolution, exposure handling, and wide-capture robustness. The unified manual
|
|
46
|
+
path carries:
|
|
47
|
+
|
|
48
|
+
- **Exposure compensation** (`cv::detail::GainCompensator`, GAIN_BLOCKS) — evens
|
|
49
|
+
brightness/colour across frames before blending, removing the visible seam
|
|
50
|
+
steps the manual path previously had.
|
|
51
|
+
- **Matched registration / compositing resolution** (registration 0.6 MP,
|
|
52
|
+
composite 1.0 MP) on both platforms.
|
|
53
|
+
- The **cylindrical warp fallback** (below), so wide / 0.5× captures survive on
|
|
54
|
+
both platforms.
|
|
55
|
+
|
|
56
|
+
The decision was made after an on-device A/B (manual vs high-level at matched
|
|
57
|
+
resolution): with parity the manual path matched the high-level on quality and
|
|
58
|
+
was strictly more robust on wide captures. Background + the verification trail
|
|
59
|
+
are recorded in [`docs/stitch-pipeline-architecture.md`](docs/stitch-pipeline-architecture.md).
|
|
60
|
+
|
|
61
|
+
### Added — cylindrical warp fallback for wide / 0.5× captures
|
|
62
|
+
|
|
63
|
+
When the configured (plane) warper would diverge on a wide or 0.5× ultra-wide
|
|
64
|
+
capture — a single frame's warp canvas exceeding the 100 MP guard — the stitcher
|
|
65
|
+
now auto-retries with the bounded cylindrical projection instead of failing with
|
|
66
|
+
`STITCH_CAMERA_PARAMS_FAIL`. Wide and ultra-wide (0.5×) panoramas that
|
|
67
|
+
previously errored out now complete. Because the pipeline is now unified
|
|
68
|
+
(above), this fallback applies on both iOS and Android.
|
|
69
|
+
|
|
70
|
+
### Added — `userFacingStitchError()` for friendly recoverable-stitch copy
|
|
71
|
+
|
|
72
|
+
New public SDK export that maps a recoverable stitch `CameraErrorCode`
|
|
73
|
+
(`STITCH_NEED_MORE_IMGS`, `STITCH_CAMERA_PARAMS_FAIL`, `STITCH_HOMOGRAPHY_FAIL`,
|
|
74
|
+
`STITCH_OOM`) to friendly, action-guiding `{ title, message }` copy for a host
|
|
75
|
+
`Alert` / toast — so the user sees "pan more slowly" / "pivot in place" instead
|
|
76
|
+
of the raw `cv::Stitcher` diagnostic. Returns `null` for every non-recoverable
|
|
77
|
+
code (permission denied, device unavailable, generic finalize failure, unknown,
|
|
78
|
+
…), so the host falls back to its generic error UI. Call it from `onError`:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { userFacingStitchError } from 'react-native-image-stitcher';
|
|
82
|
+
|
|
83
|
+
onError={(err) => {
|
|
84
|
+
const friendly = userFacingStitchError(err.code);
|
|
85
|
+
if (friendly) Alert.alert(friendly.title, friendly.message);
|
|
86
|
+
else reportGenericError(err);
|
|
87
|
+
}}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Also exports the `UserFacingStitchError` type (`{ title, message }`). Lives in
|
|
91
|
+
the SDK (not per-host) so every consumer shows the same vetted guidance for the
|
|
92
|
+
same failure, and so the mapping is unit-testable in isolation.
|
|
93
|
+
|
|
94
|
+
### Fixed — friendlier stitch-failure classification + example UX
|
|
95
|
+
|
|
96
|
+
- `STITCH_NEED_MORE_IMGS` now also classifies the manual pipeline's "0 valid
|
|
97
|
+
pairwise matches / frames may not overlap enough" failure, which previously
|
|
98
|
+
surfaced as a generic `PANORAMA_FINALIZE_FAILED`. Both insufficient-overlap
|
|
99
|
+
signals now map to the same recoverable "pan more slowly" outcome (and so pick
|
|
100
|
+
up the `userFacingStitchError` copy above).
|
|
101
|
+
- The example app now shows friendly, action-guiding guidance — via
|
|
102
|
+
`userFacingStitchError` (an Alert) on a stitch failure (`onError`), and a
|
|
103
|
+
transient **toast** when frames are dropped for insufficient overlap
|
|
104
|
+
(`onFramesDropped`) — shown only when **>30%** of the requested frames are
|
|
105
|
+
missing from the final stitch (e.g. ≥2 of 6), so minor drops stay silent. The toast (`CaptureStitchStatsToast`) also
|
|
106
|
+
gained optional `title` (bold, above the message) and `placement`
|
|
107
|
+
(`'top'` | `'center'`) props; the example shows a centered title+body toast.
|
|
108
|
+
Failure alerts now lead with the corrective ask as the title (e.g. "Please
|
|
109
|
+
pan more slowly" / "Try a shorter sweep") and explain the cause in the body.
|
|
110
|
+
|
|
111
|
+
### Fixed — reach the ultra-wide by device-swap when a logical multi-cam can't (Samsung / Camera2)
|
|
112
|
+
|
|
113
|
+
`selectCaptureDevice` now device-swaps to a standalone ultra-wide camera when a
|
|
114
|
+
logical multi-cam device merely *lists* the ultra-wide but can't reach it by
|
|
115
|
+
`zoom` (its zoom range starts at 1.0 — common on Android / Camera2 / Samsung,
|
|
116
|
+
where the ultra-wide is a separate physical camera rather than a zoom target).
|
|
117
|
+
Previously such devices stayed on the multi-cam device and 0.5× showed the
|
|
118
|
+
wide-angle FOV. A logical device whose zoom range genuinely extends to the
|
|
119
|
+
ultra-wide (e.g. iOS virtual devices, `minZoom ≈ 0.5`) is still preferred and
|
|
120
|
+
lens-switches via zoom as before.
|
|
121
|
+
|
|
122
|
+
### Added — time-budget keyframe force-accept (`maxKeyframeIntervalMs`)
|
|
123
|
+
|
|
124
|
+
The keyframe gate now force-accepts a keyframe when a configurable wall-clock
|
|
125
|
+
interval has elapsed since the last accepted keyframe — even if the novelty /
|
|
126
|
+
overlap threshold wasn't met — so a slow or static pan never leaves a temporal
|
|
127
|
+
gap. Default **2000 ms (2 s)**; `0` disables it. Configurable via the
|
|
128
|
+
`<Camera defaultMaxKeyframeIntervalMs>` prop, the `FrameSelectionSettings.maxKeyframeIntervalMs`
|
|
129
|
+
field, or the in-app settings panel. Applies to BOTH AR (plane-overlap) and
|
|
130
|
+
non-AR (flow) capture paths; force-accepted keyframes count toward
|
|
131
|
+
`maxKeyframes` (the cap still finalises the capture).
|
|
132
|
+
|
|
133
|
+
### Added — inscribed-rect panorama crop (opt-in)
|
|
134
|
+
|
|
135
|
+
`<Camera maxInscribedRectCrop={true}>` (and the `enableMaxInscribedRectCrop`
|
|
136
|
+
panorama setting) crops the finished panorama to the largest axis-aligned
|
|
137
|
+
rectangle inscribed in the coverage mask — clean edges with no black corners
|
|
138
|
+
from unfilled projection regions. **It is opt-in; the default is off.** The
|
|
139
|
+
default crop stays the bounding box of non-black pixels, which preserves all
|
|
140
|
+
stitched content but can leave black corners. Inscribed-rect can shrink the
|
|
141
|
+
output substantially on lopsided or ultra-wide masks, so it isn't the default.
|
|
142
|
+
|
|
143
|
+
### Fixed — Android keyframe-gate flow reason labels
|
|
144
|
+
|
|
145
|
+
`KeyframeGate.reasonFromCode` (Android) didn't map the v0.3.0 flow-strategy
|
|
146
|
+
reason codes 12–15, so accepted keyframes logged as `unknown(12)`. They now
|
|
147
|
+
read `ok-flow` / `first-flow` / `overlap-too-high (flow)` / `ok-flow-translation`,
|
|
148
|
+
matching the iOS labels. Logging only — keyframe selection is unchanged.
|
|
149
|
+
|
|
19
150
|
## [0.14.2] — 2026-06-03
|
|
20
151
|
|
|
21
152
|
### Fixed — AR preview blank on first entry (intermittent camera-handoff race)
|
package/README.md
CHANGED
|
@@ -212,7 +212,9 @@ These mirror the in-app settings panel; most apps never set them.
|
|
|
212
212
|
| `defaultFlowMaxTranslationCm` | `number` | `50` | Max IMU translation between keyframes; 0 = disabled. |
|
|
213
213
|
| `defaultKeyframeMaxCount` | `number` | `6` | Keyframe cap per capture (3–10). |
|
|
214
214
|
| `defaultKeyframeOverlapThreshold` | `number` | `0.20` | Min overlap to accept a keyframe (0.20–0.60). |
|
|
215
|
+
| `defaultMaxKeyframeIntervalMs` | `number` | `2000` | Time-budget force-accept: take a keyframe at least every N ms during a pan even if the overlap/novelty threshold isn't met, so a slow or static pan never leaves a temporal gap. Force-accepted keyframes still count toward the keyframe cap. `0` = disabled. AR + non-AR. Also exposed as the `FrameSelectionSettings.maxKeyframeIntervalMs` settings field and in the in-app settings panel. |
|
|
215
216
|
| `defaultCompositingResolMP` / `defaultRegistrationResolMP` / `defaultSeamEstimationResolMP` | `number` | — | Forward-looking cv::Stitcher resolution knobs (currently no-ops). |
|
|
217
|
+
| `maxInscribedRectCrop` | `boolean` | `false` | Opt in with `true` to crop the panorama to the largest inscribed rectangle (clean edges, no black corners) instead of the bounding box. Default keeps the bounding-box crop (all stitched content; may show black corners). Inscribed-rect can shrink the output on lopsided / ultra-wide masks. |
|
|
216
218
|
|
|
217
219
|
### UI toggles
|
|
218
220
|
|
|
@@ -287,6 +289,39 @@ type CameraCaptureResult =
|
|
|
287
289
|
`STITCH_HOMOGRAPHY_FAIL`, `STITCH_CAMERA_PARAMS_FAIL`, `STITCH_OOM`,
|
|
288
290
|
`OUTPUT_WRITE_FAILED`, plus `VISION_CAMERA_RUNTIME`.
|
|
289
291
|
|
|
292
|
+
#### Friendly copy for recoverable stitch failures — `userFacingStitchError`
|
|
293
|
+
|
|
294
|
+
The four `STITCH_*` codes are *recoverable*: the user can usually fix them by
|
|
295
|
+
re-capturing (pan more slowly, pivot in place, shorten the sweep). For those,
|
|
296
|
+
the SDK exports `userFacingStitchError(code)` — it returns
|
|
297
|
+
`{ title, message }` of vetted, action-guiding copy you can drop straight into a
|
|
298
|
+
host `Alert`/toast (instead of surfacing the raw `cv::Stitcher` diagnostic), and
|
|
299
|
+
returns `null` for every non-recoverable code so you fall back to your generic
|
|
300
|
+
error UI:
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import {
|
|
304
|
+
Camera,
|
|
305
|
+
userFacingStitchError,
|
|
306
|
+
type UserFacingStitchError,
|
|
307
|
+
} from 'react-native-image-stitcher';
|
|
308
|
+
import { Alert } from 'react-native';
|
|
309
|
+
|
|
310
|
+
<Camera
|
|
311
|
+
onError={(err) => {
|
|
312
|
+
const friendly: UserFacingStitchError | null = userFacingStitchError(err.code);
|
|
313
|
+
if (friendly) {
|
|
314
|
+
Alert.alert(friendly.title, friendly.message); // "pan more slowly", "pivot in place", …
|
|
315
|
+
} else {
|
|
316
|
+
reportGenericError(err); // permission denied, device unavailable, etc.
|
|
317
|
+
}
|
|
318
|
+
}}
|
|
319
|
+
/>;
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
It lives in the SDK (not per-host) so every consumer shows the same guidance for
|
|
323
|
+
the same failure. The `example/` app uses it end-to-end.
|
|
324
|
+
|
|
290
325
|
### Migration from 0.13.x
|
|
291
326
|
|
|
292
327
|
- **Removed:** the `panGuide` and `panoramaGuidance` props (the
|
package/RNImageStitcher.podspec
CHANGED
|
@@ -35,14 +35,15 @@ Pod::Spec.new do |s|
|
|
|
35
35
|
|
|
36
36
|
# Sources: iOS-specific Swift/Obj-C/Obj-C++ AND the shared C++ port
|
|
37
37
|
# (cpp/) that both iOS and Android compile from a single source.
|
|
38
|
+
# cpp/ glob is NON-RECURSIVE on purpose: it picks up the shared C++
|
|
39
|
+
# port (all top-level cpp/*.cpp) but skips the maintainer-only
|
|
40
|
+
# GoogleTest harnesses under cpp/tests/ (which would otherwise fail
|
|
41
|
+
# the pod with `'gtest/gtest.h' file not found`). NOTE: using
|
|
42
|
+
# `cpp/**` + `s.exclude_files = ['cpp/tests/**/*']` instead broke the
|
|
43
|
+
# vendored opencv2.xcframework header integration for the remaining
|
|
44
|
+
# cpp/ files — keep this as a single non-recursive glob.
|
|
38
45
|
s.source_files = ['ios/Sources/**/*.{swift,h,m,mm}',
|
|
39
|
-
'cpp
|
|
40
|
-
# Exclude the lib's own C++ unit tests — they #include <gtest/gtest.h>,
|
|
41
|
-
# which consumer apps don't vendor. The `cpp/**/*.cpp` glob above
|
|
42
|
-
# otherwise slurps cpp/tests/*.cpp into every host pod build, failing
|
|
43
|
-
# with `'gtest/gtest.h' file not found`. Tests build only in the lib's
|
|
44
|
-
# CI / example app, never in a consumer.
|
|
45
|
-
s.exclude_files = ['cpp/tests/**/*']
|
|
46
|
+
'cpp/*.{h,hpp,cpp}']
|
|
46
47
|
# Restrict the umbrella header to ONLY the iOS-side Obj-C `.h`
|
|
47
48
|
# files. Without this, CocoaPods defaults every header in
|
|
48
49
|
# `source_files` (including the C++ `.hpp` files under cpp/) to
|
package/android/build.gradle
CHANGED
|
@@ -267,22 +267,6 @@ dependencies {
|
|
|
267
267
|
android.sourceSets.main.java.exclude '**/CvFlowGateFrameProcessor.kt'
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
// v0.8.0 Phase 4b.ii — react-native-worklets-core's Android
|
|
271
|
-
// prefab (`rnworklets`) is consumed by the native shim
|
|
272
|
-
// (`stitcher_worklet_registry.cpp` constructs
|
|
273
|
-
// `RNWorklet::WorkletInvoker`s). `implementation` not
|
|
274
|
-
// `compileOnly` because we need the prefab's `.so` available at
|
|
275
|
-
// both link time AND runtime — without the runtime presence,
|
|
276
|
-
// `dlopen` would fail when our `libimage_stitcher.so` is loaded.
|
|
277
|
-
//
|
|
278
|
-
// Host apps that use this lib already declare worklets-core as
|
|
279
|
-
// a peer dep (see package.json's peerDependencies); RN
|
|
280
|
-
// autolinking + Gradle deduplicates, so the host doesn't get
|
|
281
|
-
// a second copy.
|
|
282
|
-
if (findProject(':react-native-worklets-core') != null) {
|
|
283
|
-
implementation project(':react-native-worklets-core')
|
|
284
|
-
}
|
|
285
|
-
|
|
286
270
|
// v0.10.0 audit #11A — Android JUnit test scaffold. JVM unit
|
|
287
271
|
// tests for pure-Kotlin data wrappers + algorithm helpers that
|
|
288
272
|
// don't need an Android device. Run via
|
|
@@ -80,34 +80,6 @@ if(NOT EXISTS "${SHARED_CPP_DIR}/keyframe_gate.hpp")
|
|
|
80
80
|
"Expected react-native-image-stitcher/cpp/ — was the package layout broken?")
|
|
81
81
|
endif()
|
|
82
82
|
|
|
83
|
-
# ── React Native prefab packages for JSI ──────────────────────────
|
|
84
|
-
#
|
|
85
|
-
# v0.8.0 Phase 3 — activating the previously-deferred JSI integration.
|
|
86
|
-
# The shared C++ host object (cpp/stitcher_frame_jsi.cpp) depends on
|
|
87
|
-
# `facebook::jsi`. ReactAndroid ships JSI as a prefab starting
|
|
88
|
-
# RN 0.71+; the lib targets RN 0.84 so this is always available.
|
|
89
|
-
#
|
|
90
|
-
# `buildFeatures { prefab true }` in android/build.gradle enables
|
|
91
|
-
# consumption + `ANDROID_STL=c++_shared` aligns the STL with what
|
|
92
|
-
# the prefabs require. The Phase-2 STL probe (`llvm-nm
|
|
93
|
-
# libopencv_stitching.a | grep '__ndk1'`) confirmed OpenCV's
|
|
94
|
-
# stitching archive was already built with c++_shared (768
|
|
95
|
-
# __ndk1 symbols + 0 __cxx11 / NSt3) — switching the lib's flag
|
|
96
|
-
# from c++_static to c++_shared just aligns + matches. The
|
|
97
|
-
# previous c++_static was working only because the JNI shim's
|
|
98
|
-
# `.so` boundary used POD/C types; the new c++_shared is properly
|
|
99
|
-
# matched throughout.
|
|
100
|
-
find_package(ReactAndroid REQUIRED CONFIG)
|
|
101
|
-
find_package(fbjni REQUIRED CONFIG)
|
|
102
|
-
|
|
103
|
-
# v0.8.0 Phase 4b.ii — react-native-worklets-core prefab. The
|
|
104
|
-
# Gradle module name is `react-native-worklets-core`; inside it
|
|
105
|
-
# publishes a library named `rnworklets` (matches vc's consumption
|
|
106
|
-
# pattern in node_modules/react-native-vision-camera/android/CMakeLists.txt).
|
|
107
|
-
# We consume both the headers (for `WKTJsiWorklet.h` etc.) AND
|
|
108
|
-
# the .so (for `RNWorklet::WorkletInvoker` + `JsiWrapper::unwrap`
|
|
109
|
-
# symbols, which are defined in worklets-core's WKTJsiWrapper.cpp).
|
|
110
|
-
find_package(react-native-worklets-core REQUIRED CONFIG)
|
|
111
83
|
|
|
112
84
|
# ── Our shim ───────────────────────────────────────────────────────
|
|
113
85
|
add_library(image_stitcher SHARED
|
|
@@ -119,30 +91,7 @@ add_library(image_stitcher SHARED
|
|
|
119
91
|
# retry + dimension/memory instrumentation. Used to live in this
|
|
120
92
|
# file (image_stitcher_jni.cpp). See cpp/stitcher.hpp for design
|
|
121
93
|
# rationale.
|
|
122
|
-
"${SHARED_CPP_DIR}/stitcher.cpp"
|
|
123
|
-
# v0.8.0 Phase 3 — shared JSI host object for `StitcherFrame`.
|
|
124
|
-
# Compiles to identical dispatch on both platforms; iOS consumes
|
|
125
|
-
# it via the .mm shim at
|
|
126
|
-
# `ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm`.
|
|
127
|
-
# See cpp/stitcher_frame_jsi.hpp for the class API.
|
|
128
|
-
"${SHARED_CPP_DIR}/stitcher_frame_jsi.cpp"
|
|
129
|
-
# v0.8.0 Phase 4b.ii — shared C++ registry of host-supplied
|
|
130
|
-
# worklets + the `globalThis.__stitcherProxy` host object that
|
|
131
|
-
# JS calls into. iOS picked these up via the podspec glob in
|
|
132
|
-
# Phase 4b.i; Android adds them here.
|
|
133
|
-
"${SHARED_CPP_DIR}/stitcher_worklet_registry.cpp"
|
|
134
|
-
"${SHARED_CPP_DIR}/stitcher_proxy_jsi.cpp"
|
|
135
|
-
# v0.8.0 Phase 4b.iii — shared per-frame fan-out helper. Posts
|
|
136
|
-
# a `StitcherFrameData` onto worklets-core's default context's
|
|
137
|
-
# worklet thread; iterates the host worklet registry; invalidates
|
|
138
|
-
# the JSI host object after dispatch completes.
|
|
139
|
-
"${SHARED_CPP_DIR}/stitcher_worklet_dispatch.cpp"
|
|
140
|
-
# v0.8.0 Phase 4b.ii — Android JNI bindings for the JSI install
|
|
141
|
-
# (`StitcherJsiInstallerModule.nativeInstall`). Reaches into the
|
|
142
|
-
# main JS runtime via the `long` JSI handle Kotlin pulls from
|
|
143
|
-
# `ReactApplicationContext.getJavaScriptContextHolder()`. See
|
|
144
|
-
# worklets-core's `WorkletsModule.java` for the canonical pattern.
|
|
145
|
-
stitcher_jsi_install_jni.cpp)
|
|
94
|
+
"${SHARED_CPP_DIR}/stitcher.cpp")
|
|
146
95
|
|
|
147
96
|
target_include_directories(image_stitcher PRIVATE
|
|
148
97
|
"${OPENCV_INCLUDE_DIR}"
|
|
@@ -169,17 +118,7 @@ target_link_libraries(image_stitcher
|
|
|
169
118
|
opencv_stitching
|
|
170
119
|
-Wl,--no-whole-archive
|
|
171
120
|
opencv_java
|
|
172
|
-
log
|
|
173
|
-
# v0.8.0 Phase 3 — JSI for the shared C++ host object
|
|
174
|
-
# (cpp/stitcher_frame_jsi.cpp's `facebook::jsi::HostObject`
|
|
175
|
-
# subclass). fbjni for the Phase 3c JNI bridge between Kotlin
|
|
176
|
-
# worklet runtime + C++ host object construction.
|
|
177
|
-
ReactAndroid::jsi
|
|
178
|
-
fbjni::fbjni
|
|
179
|
-
# v0.8.0 Phase 4b.ii — worklets-core's `RNWorklet::WorkletInvoker`
|
|
180
|
-
# is constructed in the C++ registry's `install` method and
|
|
181
|
-
# invoked from the Android per-frame dispatch path.
|
|
182
|
-
react-native-worklets-core::rnworklets)
|
|
121
|
+
log)
|
|
183
122
|
|
|
184
123
|
target_compile_options(image_stitcher PRIVATE
|
|
185
124
|
-fvisibility=hidden
|
|
@@ -116,6 +116,20 @@ Java_io_imagestitcher_rn_BatchStitcher_nativeStitchFramePaths(
|
|
|
116
116
|
? retailens::StitchMode::Panorama
|
|
117
117
|
: retailens::StitchMode::Scans;
|
|
118
118
|
|
|
119
|
+
// 2026-06-07 — unify on the manual cv::detail pipeline. It won the
|
|
120
|
+
// on-device A/B: equals the high-level cv::Stitcher on quality after
|
|
121
|
+
// parity AND is strictly more robust — the cylindrical fallback, warp
|
|
122
|
+
// guard, and exposure comp all live only in the manual path, so the
|
|
123
|
+
// high-level path garbages wide/0.5x captures. Mirrors iOS'
|
|
124
|
+
// OpenCVStitcher.mm. See docs/stitch-pipeline-architecture.md §7.
|
|
125
|
+
cfg.useManualPipeline = true;
|
|
126
|
+
// Match iOS' parity resolution: the manual entry's default registration
|
|
127
|
+
// is 0.3 MP (vs the high-level's 0.6); bump to 0.6 unless the caller set
|
|
128
|
+
// an explicit value. (compositingResolMP already arrives as 1.0.)
|
|
129
|
+
if (cfg.registrationResolMP <= 0.0) {
|
|
130
|
+
cfg.registrationResolMP = 0.6;
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
const std::string outPath = jstring_to_string(env, outputPath);
|
|
120
134
|
|
|
121
135
|
retailens::StitchResult result = retailens::stitchFramePaths(
|
|
@@ -110,6 +110,19 @@ Java_io_imagestitcher_rn_KeyframeGate_nativeSetFlowMaxTranslationM(
|
|
|
110
110
|
gate(handle)->setFlowMaxTranslationM(static_cast<double>(metres));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Wall-clock keyframe-interval budget (milliseconds). Force-accepts a
|
|
114
|
+
// frame once the elapsed time since the last accepted keyframe exceeds
|
|
115
|
+
// this value (applies to BOTH Pose and Flow strategies); 0 disables.
|
|
116
|
+
// Passed straight through — no unit conversion. See
|
|
117
|
+
// setMaxKeyframeIntervalMs doc in keyframe_gate.hpp. Android JNI
|
|
118
|
+
// counterpart of the iOS bridge method in KeyframeGateBridge.
|
|
119
|
+
JNIEXPORT void JNICALL
|
|
120
|
+
Java_io_imagestitcher_rn_KeyframeGate_nativeSetMaxKeyframeIntervalMs(
|
|
121
|
+
JNIEnv*, jclass, jlong handle, jdouble ms)
|
|
122
|
+
{
|
|
123
|
+
gate(handle)->setMaxKeyframeIntervalMs(static_cast<double>(ms));
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
// 2026-05-14 — Android JNI for the percentile setter so JS Settings
|
|
114
127
|
// can tune novelty aggregation on Android (was iOS-only until now).
|
|
115
128
|
// See setFlowNoveltyPercentile doc in keyframe_gate.hpp.
|