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,224 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 Tiger Analytics
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// Gradle module for the Android side of react-native-image-stitcher.
|
|
5
|
+
//
|
|
6
|
+
// What lives here:
|
|
7
|
+
// - QualityChecker.kt — Laplacian-variance blur scoring
|
|
8
|
+
// and mean-luminance brightness scoring via OpenCV. Mirror of
|
|
9
|
+
// the iOS Phase 1 implementation (which used vImage + vDSP).
|
|
10
|
+
// - Stitcher.kt — extractFrames + stitchFrames +
|
|
11
|
+
// stitchVideo + normaliseImage, mirroring the iOS Phase 2 / 2.5
|
|
12
|
+
// ObjC++ surface.
|
|
13
|
+
// - RNImageStitcherPackage.kt — ReactPackage that surfaces the
|
|
14
|
+
// two RN modules to the host app via Android autolinking.
|
|
15
|
+
//
|
|
16
|
+
// OpenCV vendor:
|
|
17
|
+
// We use the `quickbirdstudios/opencv-android` Maven artifact
|
|
18
|
+
// because it ships a clean AAR with the standard
|
|
19
|
+
// `org.opencv.*` Java packages, including the stitching module
|
|
20
|
+
// (which `opencv-mobile` strips on iOS — same trade-off applies
|
|
21
|
+
// here). Version pinned to 4.x to match the iOS framework.
|
|
22
|
+
|
|
23
|
+
apply plugin: 'com.android.library'
|
|
24
|
+
apply plugin: 'kotlin-android'
|
|
25
|
+
|
|
26
|
+
def reactNativeRoot = rootProject.findProperty("reactNativeRoot") ?: project.file("../../../node_modules/react-native")
|
|
27
|
+
|
|
28
|
+
// ── OpenCV Android SDK download ──────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// The OpenCV custom Android SDK is delivered by the package's
|
|
31
|
+
// postinstall script (`scripts/postinstall-fetch-binaries.js`). It
|
|
32
|
+
// downloads the version-matched release asset from GitHub Releases
|
|
33
|
+
// and extracts it into `android/vendor/OpenCV-android-sdk/` — exactly
|
|
34
|
+
// where this build.gradle (and `cpp/CMakeLists.txt`) read it from.
|
|
35
|
+
//
|
|
36
|
+
// We deliberately do NOT have a fallback that fetches stock OpenCV
|
|
37
|
+
// from upstream: stock 4.x ships WITHOUT `BUILD_opencv_stitching=ON`,
|
|
38
|
+
// so the static lib `libopencv_stitching.a` that CMake links isn't
|
|
39
|
+
// in the upstream archive. If we silently downloaded the upstream
|
|
40
|
+
// release, the build would explode with a confusing CMake "static
|
|
41
|
+
// archive not found" error. Better to fail FAST here with a clear
|
|
42
|
+
// message pointing at the actual cause.
|
|
43
|
+
def opencvVendorDir = file("$projectDir/vendor")
|
|
44
|
+
def opencvSdkDir = file("$opencvVendorDir/OpenCV-android-sdk/sdk")
|
|
45
|
+
if (!opencvSdkDir.exists()) {
|
|
46
|
+
throw new GradleException(
|
|
47
|
+
"OpenCV custom SDK missing at ${opencvSdkDir.absolutePath}.\n" +
|
|
48
|
+
"Did `npm install` succeed? The package's postinstall script\n" +
|
|
49
|
+
"(scripts/postinstall-fetch-binaries.js) downloads the binaries\n" +
|
|
50
|
+
"from GitHub Releases on install. Recovery:\n" +
|
|
51
|
+
" - `npm install --force` to re-run postinstall\n" +
|
|
52
|
+
" - Or set OPENCV_BINARY_BASE_URL env var to an internal mirror\n" +
|
|
53
|
+
" - Or run `SKIP_OPENCV_FETCH=1 npm install` and stage the SDK manually"
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
android {
|
|
58
|
+
namespace 'io.imagestitcher.rn'
|
|
59
|
+
compileSdkVersion safeExtGet('compileSdkVersion', 34)
|
|
60
|
+
// Inherit the host's NDK pin (Expo apps expose `ndkVersion` on
|
|
61
|
+
// rootProject.ext) so AGP doesn't fall back to its built-in default
|
|
62
|
+
// (which on AGP 8.6+ is 27.0.12077973, a version that's commonly
|
|
63
|
+
// half-installed on developer machines and trips [CXX1101]
|
|
64
|
+
// "source.properties missing"). 27.1.12297006 is the
|
|
65
|
+
// last-known-good fallback for raw RN hosts without an Expo pin.
|
|
66
|
+
ndkVersion safeExtGet('ndkVersion', '27.1.12297006')
|
|
67
|
+
|
|
68
|
+
defaultConfig {
|
|
69
|
+
minSdkVersion safeExtGet('minSdkVersion', 24)
|
|
70
|
+
targetSdkVersion safeExtGet('targetSdkVersion', 34)
|
|
71
|
+
versionCode 1
|
|
72
|
+
versionName "0.1.0"
|
|
73
|
+
|
|
74
|
+
// ── ABI filter ───────────────────────────────────────────
|
|
75
|
+
// Only build arm64-v8a. Our custom OpenCV is built for
|
|
76
|
+
// arm64-v8a only (matches the production Samsung physical
|
|
77
|
+
// test device and modern Android phones). Skipping
|
|
78
|
+
// armeabi-v7a / x86_64 / x86 saves ~120 MB on the final APK
|
|
79
|
+
// and avoids "libopencv_java4.so missing for ABI X" link
|
|
80
|
+
// errors during the native build.
|
|
81
|
+
ndk {
|
|
82
|
+
abiFilters 'arm64-v8a'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── JNI shim (image_stitcher) build args ─────────────
|
|
86
|
+
// Hands the cpp/CMakeLists.txt the location of the vendored
|
|
87
|
+
// OpenCV (with stitching symbols) so the shim can link.
|
|
88
|
+
externalNativeBuild {
|
|
89
|
+
cmake {
|
|
90
|
+
arguments "-DOPENCV_ANDROID_SDK=${file("$projectDir/vendor/OpenCV-android-sdk").absolutePath}",
|
|
91
|
+
"-DANDROID_STL=c++_static"
|
|
92
|
+
cppFlags "-std=c++17"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── JNI shim build path ─────────────────────────────────────────
|
|
98
|
+
// Gradle compiles cpp/image_stitcher_jni.cpp into
|
|
99
|
+
// libimage_stitcher.so for the ABIs filtered above. The shim
|
|
100
|
+
// is what bridges Kotlin's `external fun nativeStitchFramePaths`
|
|
101
|
+
// to the cv::Stitcher C++ API inside our custom libopencv_java4.so.
|
|
102
|
+
externalNativeBuild {
|
|
103
|
+
cmake {
|
|
104
|
+
path file("src/main/cpp/CMakeLists.txt")
|
|
105
|
+
version "3.22.1"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
compileOptions {
|
|
110
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
111
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
kotlinOptions {
|
|
115
|
+
jvmTarget = "17"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The hand-rolled JNI wrapper for cv::Stitcher (planned for
|
|
119
|
+
// Android stitching parity with iOS) was removed because
|
|
120
|
+
// OpenCV's prebuilt Android `libopencv_java4.so` does NOT
|
|
121
|
+
// contain `cv::Stitcher::create` symbols — the stitching
|
|
122
|
+
// module is dropped from the binary, not just from the Java
|
|
123
|
+
// bindings. Re-enabling Android-side stitching requires
|
|
124
|
+
// building OpenCV Android from source with `BUILD_opencv_stitching=ON`,
|
|
125
|
+
// which is a hours-long build job out of scope for the SDK
|
|
126
|
+
// package itself. See the Kotlin BatchStitcher for the
|
|
127
|
+
// current "iOS-only" stitch behaviour.
|
|
128
|
+
|
|
129
|
+
sourceSets {
|
|
130
|
+
main {
|
|
131
|
+
// OpenCV ships its Java side as plain source files (not
|
|
132
|
+
// a pre-built JAR) under sdk/java/src/, so we compile it
|
|
133
|
+
// into our own AAR alongside the Kotlin code. The
|
|
134
|
+
// pre-built .so libraries live under sdk/native/libs/
|
|
135
|
+
// and ride along as jniLibs.
|
|
136
|
+
java.srcDirs += [
|
|
137
|
+
"$opencvSdkDir/java/src",
|
|
138
|
+
]
|
|
139
|
+
jniLibs.srcDirs = [
|
|
140
|
+
"$opencvSdkDir/native/libs",
|
|
141
|
+
]
|
|
142
|
+
// OpenCV references some resources (logos, drawables)
|
|
143
|
+
// from its old wrapper; pointing res at the SDK keeps
|
|
144
|
+
// them available without copying.
|
|
145
|
+
res.srcDirs += [
|
|
146
|
+
"$opencvSdkDir/java/res",
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// OpenCV's MatAt.kt uses Kotlin inline-class types (UByte etc.)
|
|
152
|
+
// that the Kotlin 1.9.x compiler bundled with our toolchain
|
|
153
|
+
// can't lower. We don't use the typed-Mat-accessor API anyway.
|
|
154
|
+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
|
155
|
+
exclude '**/MatAt.kt'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// OpenCV's `org.opencv.android.*` package is the legacy
|
|
159
|
+
// OpenCV-Manager-service support layer + the camera UI views.
|
|
160
|
+
// We don't use ANY of it (we load the .so directly via
|
|
161
|
+
// System.loadLibrary and use vision-camera for camera UI).
|
|
162
|
+
// Excluding the whole package side-steps a wave of compile
|
|
163
|
+
// errors caused by missing AIDL-generated symbols and the
|
|
164
|
+
// missing `org.opencv.R` resource class.
|
|
165
|
+
tasks.withType(JavaCompile).configureEach {
|
|
166
|
+
exclude 'org/opencv/android/**'
|
|
167
|
+
exclude 'org/opencv/engine/**'
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
buildTypes {
|
|
171
|
+
release {
|
|
172
|
+
minifyEnabled false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
repositories {
|
|
178
|
+
google()
|
|
179
|
+
mavenCentral()
|
|
180
|
+
// OpenCV Android — the QuickBird build is the most up-to-date
|
|
181
|
+
// Maven artifact with full module set (including stitching).
|
|
182
|
+
maven { url 'https://jitpack.io' }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
dependencies {
|
|
186
|
+
// React Native — provided by the host app via gradle's
|
|
187
|
+
// "compileOnly" so we don't pull a duplicate copy when the
|
|
188
|
+
// SDK is consumed from a hosting RN app.
|
|
189
|
+
compileOnly "com.facebook.react:react-android"
|
|
190
|
+
|
|
191
|
+
// OpenCV is wired in via sourceSets above (java/src compiled
|
|
192
|
+
// alongside Kotlin, native/libs surfaced as jniLibs). No
|
|
193
|
+
// Maven coordinate needed.
|
|
194
|
+
|
|
195
|
+
// Kotlin stdlib + coroutines for the background-queue dispatch
|
|
196
|
+
// pattern that matches the iOS DispatchQueue.global(.userInitiated).
|
|
197
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.22"
|
|
198
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
199
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
200
|
+
|
|
201
|
+
// ExifInterface for reading rotation metadata from JPEGs taken
|
|
202
|
+
// by Android cameras (which save in sensor-native landscape
|
|
203
|
+
// with an EXIF Orientation tag, same pattern as iOS). The
|
|
204
|
+
// androidx version supports more file types and stays
|
|
205
|
+
// up-to-date with platform changes.
|
|
206
|
+
implementation "androidx.exifinterface:exifinterface:1.3.7"
|
|
207
|
+
|
|
208
|
+
// ARCore for Phase 4 of the AR-measurement plan: 6DoF pose
|
|
209
|
+
// tracking from camera + IMU on supported Android devices.
|
|
210
|
+
// Falls back to "AR not available" when:
|
|
211
|
+
// - Device isn't on Google's ARCore-supported list
|
|
212
|
+
// - Google Play Services for AR isn't installed / up-to-date
|
|
213
|
+
// The sample app doesn't require AR, so the dependency adds
|
|
214
|
+
// ~2 MB to the AAR; the Play Services for AR app is downloaded
|
|
215
|
+
// on demand (~30 MB) the first time the user opens an AR
|
|
216
|
+
// capture screen on a supported device.
|
|
217
|
+
implementation "com.google.ar:core:1.45.0"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Helper from the React Native gradle convention to read host-app
|
|
221
|
+
// SDK versions when present, with a sane default otherwise.
|
|
222
|
+
def safeExtGet(prop, fallback) {
|
|
223
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
224
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# CMakeLists.txt — builds `libimage_stitcher.so`, the JNI shim that
|
|
2
|
+
# exposes our custom-built OpenCV's `cv::Stitcher` to the Kotlin SDK.
|
|
3
|
+
#
|
|
4
|
+
# Architecture
|
|
5
|
+
# ────────────
|
|
6
|
+
# OpenCV's official prebuilt Android `libopencv_java4.so` strips the
|
|
7
|
+
# stitching module entirely. When we rebuild OpenCV with
|
|
8
|
+
# BUILD_opencv_stitching=ON the stitching module is COMPILED (yields a
|
|
9
|
+
# static archive `libopencv_stitching.a`) but it's NOT included in the
|
|
10
|
+
# fat `libopencv_java4.so` either — the fat lib only bundles modules
|
|
11
|
+
# that have Java auto-bindings, and the stitching module declares only
|
|
12
|
+
# `WRAP python` upstream.
|
|
13
|
+
#
|
|
14
|
+
# Our shim closes that gap:
|
|
15
|
+
# - libopencv_stitching.a is STATICALLY linked into our shim, so
|
|
16
|
+
# `cv::Stitcher::create()` and related code lives inside
|
|
17
|
+
# `libimage_stitcher.so`.
|
|
18
|
+
# - libopencv_java4.so is DYNAMICALLY linked, providing cv::Mat,
|
|
19
|
+
# cv::imread/imwrite, cv::features2d, cv::calib3d, cv::flann, etc.
|
|
20
|
+
# at runtime — the fat lib already exports all the C++ symbols
|
|
21
|
+
# stitching depends on.
|
|
22
|
+
#
|
|
23
|
+
# Net result: a ~5-10 MB self-contained JNI shim that uses the
|
|
24
|
+
# already-bundled fat .so for core OpenCV.
|
|
25
|
+
|
|
26
|
+
cmake_minimum_required(VERSION 3.18)
|
|
27
|
+
project(image_stitcher CXX)
|
|
28
|
+
|
|
29
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
30
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
31
|
+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
|
32
|
+
|
|
33
|
+
# Gradle passes OPENCV_ANDROID_SDK as a CMake -D arg. Points to the
|
|
34
|
+
# directory holding `sdk/native/jni/include/`,
|
|
35
|
+
# `sdk/native/libs/<ABI>/libopencv_java4.so` and
|
|
36
|
+
# `sdk/native/staticlibs/<ABI>/libopencv_stitching.a`.
|
|
37
|
+
if(NOT DEFINED OPENCV_ANDROID_SDK)
|
|
38
|
+
message(FATAL_ERROR
|
|
39
|
+
"OPENCV_ANDROID_SDK must be passed in by the Gradle "
|
|
40
|
+
"externalNativeBuild block. Check the SDK's android/build.gradle.")
|
|
41
|
+
endif()
|
|
42
|
+
|
|
43
|
+
set(OPENCV_INCLUDE_DIR "${OPENCV_ANDROID_SDK}/sdk/native/jni/include")
|
|
44
|
+
set(OPENCV_FAT_SO "${OPENCV_ANDROID_SDK}/sdk/native/libs/${ANDROID_ABI}/libopencv_java4.so")
|
|
45
|
+
set(OPENCV_STITCHING_A "${OPENCV_ANDROID_SDK}/sdk/native/staticlibs/${ANDROID_ABI}/libopencv_stitching.a")
|
|
46
|
+
|
|
47
|
+
if(NOT EXISTS "${OPENCV_FAT_SO}")
|
|
48
|
+
message(FATAL_ERROR
|
|
49
|
+
"OpenCV fat shared lib not found at:\n ${OPENCV_FAT_SO}\n"
|
|
50
|
+
"Run the OpenCV custom build (see scripts/build-opencv-android.sh) "
|
|
51
|
+
"and copy artifacts into vendor/OpenCV-android-sdk/.")
|
|
52
|
+
endif()
|
|
53
|
+
if(NOT EXISTS "${OPENCV_STITCHING_A}")
|
|
54
|
+
message(FATAL_ERROR
|
|
55
|
+
"OpenCV stitching static archive not found at:\n ${OPENCV_STITCHING_A}\n"
|
|
56
|
+
"Was the OpenCV build compiled with BUILD_opencv_stitching=ON?")
|
|
57
|
+
endif()
|
|
58
|
+
|
|
59
|
+
# Import the prebuilt OpenCV fat .so (dynamic link target).
|
|
60
|
+
add_library(opencv_java SHARED IMPORTED)
|
|
61
|
+
set_target_properties(opencv_java PROPERTIES
|
|
62
|
+
IMPORTED_LOCATION "${OPENCV_FAT_SO}")
|
|
63
|
+
|
|
64
|
+
# Import the static archive for the stitching module (static link target).
|
|
65
|
+
add_library(opencv_stitching STATIC IMPORTED)
|
|
66
|
+
set_target_properties(opencv_stitching PROPERTIES
|
|
67
|
+
IMPORTED_LOCATION "${OPENCV_STITCHING_A}")
|
|
68
|
+
|
|
69
|
+
# ── Shared C++ port (KeyframeGate) ────────────────────────────────
|
|
70
|
+
#
|
|
71
|
+
# `cpp/` at the SDK root holds C++ that's compiled into BOTH the iOS
|
|
72
|
+
# pod (via the podspec source globs + HEADER_SEARCH_PATHS) and the
|
|
73
|
+
# Android JNI shim (here). Same source, same algorithm, same
|
|
74
|
+
# panorama output — closes the iOS↔Android parity gap that the V16
|
|
75
|
+
# Phase 1 frame-counter MVP placeholder left open.
|
|
76
|
+
set(SHARED_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cpp")
|
|
77
|
+
if(NOT EXISTS "${SHARED_CPP_DIR}/keyframe_gate.hpp")
|
|
78
|
+
message(FATAL_ERROR
|
|
79
|
+
"Shared C++ port missing at:\n ${SHARED_CPP_DIR}\n"
|
|
80
|
+
"Expected react-native-image-stitcher/cpp/ — was the package layout broken?")
|
|
81
|
+
endif()
|
|
82
|
+
|
|
83
|
+
# ── Our shim ───────────────────────────────────────────────────────
|
|
84
|
+
add_library(image_stitcher SHARED
|
|
85
|
+
image_stitcher_jni.cpp
|
|
86
|
+
keyframe_gate_jni.cpp
|
|
87
|
+
"${SHARED_CPP_DIR}/keyframe_gate.cpp"
|
|
88
|
+
# 2026-05-15 — shared stitcher.cpp, used by BOTH platforms.
|
|
89
|
+
# Owns the cv::Stitcher orchestration + C+D progressive-confidence
|
|
90
|
+
# retry + dimension/memory instrumentation. Used to live in this
|
|
91
|
+
# file (image_stitcher_jni.cpp). See cpp/stitcher.hpp for design
|
|
92
|
+
# rationale.
|
|
93
|
+
"${SHARED_CPP_DIR}/stitcher.cpp")
|
|
94
|
+
|
|
95
|
+
target_include_directories(image_stitcher PRIVATE
|
|
96
|
+
"${OPENCV_INCLUDE_DIR}"
|
|
97
|
+
# cpp/ holds keyframe_gate.hpp + ar_frame_pose.h that the JNI
|
|
98
|
+
# bindings include without relative-path spelunking.
|
|
99
|
+
"${SHARED_CPP_DIR}")
|
|
100
|
+
|
|
101
|
+
# Link order matters:
|
|
102
|
+
# 1. opencv_stitching (static .a) — pulls in cv::Stitcher code
|
|
103
|
+
# 2. opencv_java (dynamic .so) — resolves cv::Mat, cv::imread,
|
|
104
|
+
# cv::features2d, cv::calib3d,
|
|
105
|
+
# cv::flann, cv::imgproc, etc. at
|
|
106
|
+
# runtime via the host app's
|
|
107
|
+
# already-loaded libopencv_java4.so
|
|
108
|
+
# 3. log — for __android_log_print
|
|
109
|
+
#
|
|
110
|
+
# `-Wl,--whole-archive` around the static lib forces the linker to
|
|
111
|
+
# pull in every .o file even if it can't statically prove they're
|
|
112
|
+
# needed. Without it, cv::Stitcher::create() may be eliminated as
|
|
113
|
+
# dead code (it's called via the JNI dispatch which the linker can't
|
|
114
|
+
# trace at static-link time).
|
|
115
|
+
target_link_libraries(image_stitcher
|
|
116
|
+
-Wl,--whole-archive
|
|
117
|
+
opencv_stitching
|
|
118
|
+
-Wl,--no-whole-archive
|
|
119
|
+
opencv_java
|
|
120
|
+
log)
|
|
121
|
+
|
|
122
|
+
target_compile_options(image_stitcher PRIVATE
|
|
123
|
+
-fvisibility=hidden
|
|
124
|
+
-Wno-deprecated-declarations)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// image_stitcher_jni.cpp — JNI shim that marshals Java/Kotlin args
|
|
4
|
+
// into the shared C++ `retailens::stitchFramePaths` function in
|
|
5
|
+
// cpp/stitcher.{hpp,cpp}.
|
|
6
|
+
//
|
|
7
|
+
// As of 2026-05-15, the algorithm itself lives in shared C++ (used by
|
|
8
|
+
// both iOS via Obj-C++ bridge + Android via this JNI shim). This
|
|
9
|
+
// file's job is now ONLY:
|
|
10
|
+
// 1. Unmarshal jobjectArray → std::vector<std::string>
|
|
11
|
+
// 2. Unmarshal jstring args → StitchConfig
|
|
12
|
+
// 3. Plug Android's __android_log_print into the shared LogFn
|
|
13
|
+
// 4. Call retailens::stitchFramePaths(...)
|
|
14
|
+
// 5. Marshal StitchResult → jintArray for Kotlin
|
|
15
|
+
//
|
|
16
|
+
// History
|
|
17
|
+
// ───────
|
|
18
|
+
//
|
|
19
|
+
// Before 2026-05-15 commit feature/shared-stitcher-port, this file
|
|
20
|
+
// owned the algorithm directly (~600 lines, used cv::Stitcher
|
|
21
|
+
// high-level API). The iOS side owned its own ~3000-line manual
|
|
22
|
+
// pipeline. Moving both behind a shared C++ stitcher eliminates
|
|
23
|
+
// the platform divergence.
|
|
24
|
+
|
|
25
|
+
#include <jni.h>
|
|
26
|
+
#include <android/log.h>
|
|
27
|
+
#include "stitcher.hpp"
|
|
28
|
+
|
|
29
|
+
#include <string>
|
|
30
|
+
#include <vector>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#define LOG_TAG "BatchStitcher.JNI"
|
|
34
|
+
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
|
35
|
+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
namespace {
|
|
39
|
+
|
|
40
|
+
std::string jstring_to_string(JNIEnv* env, jstring jstr) {
|
|
41
|
+
if (jstr == nullptr) return std::string();
|
|
42
|
+
const char* cstr = env->GetStringUTFChars(jstr, nullptr);
|
|
43
|
+
std::string result(cstr);
|
|
44
|
+
env->ReleaseStringUTFChars(jstr, cstr);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void throw_runtime(JNIEnv* env, const std::string& msg) {
|
|
49
|
+
LOGE("%s", msg.c_str());
|
|
50
|
+
jclass cls = env->FindClass("java/lang/RuntimeException");
|
|
51
|
+
if (cls != nullptr) {
|
|
52
|
+
env->ThrowNew(cls, msg.c_str());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Bridge the shared LogFn to __android_log_print so the shared C++
|
|
57
|
+
// stitcher's [stitch]/[dimstat]/[memstat] log lines flow into logcat
|
|
58
|
+
// under the same LOG_TAG the previous owner used.
|
|
59
|
+
void androidLogBridge(int level, const char* tag, const char* msg) {
|
|
60
|
+
int prio = (level == 2) ? ANDROID_LOG_ERROR
|
|
61
|
+
: (level == 1) ? ANDROID_LOG_WARN
|
|
62
|
+
: ANDROID_LOG_INFO;
|
|
63
|
+
__android_log_print(prio, LOG_TAG, "%s %s", tag ? tag : "", msg ? msg : "");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
} // namespace
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
extern "C" JNIEXPORT jintArray JNICALL
|
|
70
|
+
Java_io_imagestitcher_rn_BatchStitcher_nativeStitchFramePaths(
|
|
71
|
+
JNIEnv* env,
|
|
72
|
+
jobject /*thiz*/,
|
|
73
|
+
jobjectArray framePaths,
|
|
74
|
+
jstring outputPath,
|
|
75
|
+
jint jpegQuality,
|
|
76
|
+
jstring warperType,
|
|
77
|
+
jstring blenderType,
|
|
78
|
+
jstring seamFinderType,
|
|
79
|
+
jstring captureOrientation,
|
|
80
|
+
jboolean useInscribedRectCrop,
|
|
81
|
+
jdouble registrationResolMP,
|
|
82
|
+
jdouble seamEstimationResolMP,
|
|
83
|
+
jdouble compositingResolMP,
|
|
84
|
+
jstring stitchModeStr) {
|
|
85
|
+
|
|
86
|
+
if (framePaths == nullptr) {
|
|
87
|
+
throw_runtime(env, "framePaths is null");
|
|
88
|
+
return nullptr;
|
|
89
|
+
}
|
|
90
|
+
const jsize frameCount = env->GetArrayLength(framePaths);
|
|
91
|
+
std::vector<std::string> paths;
|
|
92
|
+
paths.reserve(frameCount);
|
|
93
|
+
for (jsize i = 0; i < frameCount; ++i) {
|
|
94
|
+
jstring jPath = (jstring) env->GetObjectArrayElement(framePaths, i);
|
|
95
|
+
if (jPath == nullptr) {
|
|
96
|
+
throw_runtime(env, "framePaths[" + std::to_string(i) + "] is null");
|
|
97
|
+
return nullptr;
|
|
98
|
+
}
|
|
99
|
+
paths.push_back(jstring_to_string(env, jPath));
|
|
100
|
+
env->DeleteLocalRef(jPath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build the shared StitchConfig.
|
|
104
|
+
retailens::StitchConfig cfg;
|
|
105
|
+
cfg.warperType = jstring_to_string(env, warperType);
|
|
106
|
+
cfg.blenderType = jstring_to_string(env, blenderType);
|
|
107
|
+
cfg.seamFinderType = jstring_to_string(env, seamFinderType);
|
|
108
|
+
cfg.captureOrientation = jstring_to_string(env, captureOrientation);
|
|
109
|
+
cfg.useInscribedRectCrop = useInscribedRectCrop;
|
|
110
|
+
cfg.registrationResolMP = registrationResolMP;
|
|
111
|
+
cfg.seamEstimationResolMP = seamEstimationResolMP;
|
|
112
|
+
cfg.compositingResolMP = compositingResolMP;
|
|
113
|
+
cfg.jpegQuality = jpegQuality;
|
|
114
|
+
const std::string modeStr = jstring_to_string(env, stitchModeStr);
|
|
115
|
+
cfg.stitchMode = (modeStr == "panorama")
|
|
116
|
+
? retailens::StitchMode::Panorama
|
|
117
|
+
: retailens::StitchMode::Scans;
|
|
118
|
+
|
|
119
|
+
const std::string outPath = jstring_to_string(env, outputPath);
|
|
120
|
+
|
|
121
|
+
retailens::StitchResult result = retailens::stitchFramePaths(
|
|
122
|
+
paths, outPath, cfg, &androidLogBridge);
|
|
123
|
+
|
|
124
|
+
if (!result.success) {
|
|
125
|
+
const std::string msg = "Stitch failed: " + result.errorMessage +
|
|
126
|
+
" (code=" + std::to_string(static_cast<int>(result.errorCode)) + ")";
|
|
127
|
+
throw_runtime(env, msg);
|
|
128
|
+
return nullptr;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Return [width, height, framesRequested, framesIncluded, finalThresholdMilli]
|
|
132
|
+
// — same JNI return layout as the previous file (Kotlin already
|
|
133
|
+
// parses indices 0-4). The threshold is multiplied by 1000 +
|
|
134
|
+
// rounded to int since IntArray can't hold doubles.
|
|
135
|
+
jintArray dims = env->NewIntArray(5);
|
|
136
|
+
jint values[5] = {
|
|
137
|
+
result.width,
|
|
138
|
+
result.height,
|
|
139
|
+
result.framesRequested,
|
|
140
|
+
result.framesIncluded,
|
|
141
|
+
static_cast<jint>(result.finalConfidenceThresh * 1000.0),
|
|
142
|
+
};
|
|
143
|
+
env->SetIntArrayRegion(dims, 0, 5, values);
|
|
144
|
+
return dims;
|
|
145
|
+
}
|