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.
Files changed (151) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +21 -0
  4. package/README.md +189 -0
  5. package/RNImageStitcher.podspec +76 -0
  6. package/android/build.gradle +224 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/cpp/CMakeLists.txt +124 -0
  9. package/android/src/main/cpp/image_stitcher_jni.cpp +145 -0
  10. package/android/src/main/cpp/keyframe_gate_jni.cpp +204 -0
  11. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +426 -0
  12. package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +960 -0
  13. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +2371 -0
  14. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +256 -0
  15. package/android/src/main/java/io/imagestitcher/rn/QualityChecker.kt +167 -0
  16. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +39 -0
  17. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +558 -0
  18. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +35 -0
  19. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +784 -0
  20. package/android/src/main/java/io/imagestitcher/rn/ar/BackgroundRenderer.kt +176 -0
  21. package/android/src/main/java/io/imagestitcher/rn/ar/ShaderUtil.kt +67 -0
  22. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +201 -0
  23. package/cpp/ar_frame_pose.h +63 -0
  24. package/cpp/keyframe_gate.cpp +927 -0
  25. package/cpp/keyframe_gate.hpp +240 -0
  26. package/cpp/stitcher.cpp +2207 -0
  27. package/cpp/stitcher.hpp +275 -0
  28. package/dist/ar/useARSession.d.ts +102 -0
  29. package/dist/ar/useARSession.js +133 -0
  30. package/dist/camera/ARCameraView.d.ts +93 -0
  31. package/dist/camera/ARCameraView.js +170 -0
  32. package/dist/camera/Camera.d.ts +134 -0
  33. package/dist/camera/Camera.js +688 -0
  34. package/dist/camera/CameraShutter.d.ts +80 -0
  35. package/dist/camera/CameraShutter.js +237 -0
  36. package/dist/camera/CameraView.d.ts +65 -0
  37. package/dist/camera/CameraView.js +117 -0
  38. package/dist/camera/CaptureControlsBar.d.ts +87 -0
  39. package/dist/camera/CaptureControlsBar.js +82 -0
  40. package/dist/camera/CaptureHeader.d.ts +62 -0
  41. package/dist/camera/CaptureHeader.js +81 -0
  42. package/dist/camera/CapturePreview.d.ts +70 -0
  43. package/dist/camera/CapturePreview.js +188 -0
  44. package/dist/camera/CaptureStatusOverlay.d.ts +75 -0
  45. package/dist/camera/CaptureStatusOverlay.js +326 -0
  46. package/dist/camera/CaptureThumbnailStrip.d.ts +87 -0
  47. package/dist/camera/CaptureThumbnailStrip.js +177 -0
  48. package/dist/camera/IncrementalPanGuide.d.ts +83 -0
  49. package/dist/camera/IncrementalPanGuide.js +267 -0
  50. package/dist/camera/PanoramaBandOverlay.d.ts +107 -0
  51. package/dist/camera/PanoramaBandOverlay.js +399 -0
  52. package/dist/camera/PanoramaConfirmModal.d.ts +57 -0
  53. package/dist/camera/PanoramaConfirmModal.js +128 -0
  54. package/dist/camera/PanoramaGuidance.d.ts +79 -0
  55. package/dist/camera/PanoramaGuidance.js +246 -0
  56. package/dist/camera/PanoramaSettingsModal.d.ts +311 -0
  57. package/dist/camera/PanoramaSettingsModal.js +611 -0
  58. package/dist/camera/ViewportCropOverlay.d.ts +46 -0
  59. package/dist/camera/ViewportCropOverlay.js +67 -0
  60. package/dist/camera/useCapture.d.ts +111 -0
  61. package/dist/camera/useCapture.js +160 -0
  62. package/dist/camera/useDeviceOrientation.d.ts +48 -0
  63. package/dist/camera/useDeviceOrientation.js +131 -0
  64. package/dist/camera/useVideoCapture.d.ts +79 -0
  65. package/dist/camera/useVideoCapture.js +151 -0
  66. package/dist/index.d.ts +26 -0
  67. package/dist/index.js +39 -0
  68. package/dist/quality/normaliseOrientation.d.ts +36 -0
  69. package/dist/quality/normaliseOrientation.js +62 -0
  70. package/dist/quality/runQualityCheck.d.ts +41 -0
  71. package/dist/quality/runQualityCheck.js +98 -0
  72. package/dist/sensors/useIMUTranslationGate.d.ts +70 -0
  73. package/dist/sensors/useIMUTranslationGate.js +235 -0
  74. package/dist/stitching/IncrementalStitcherView.d.ts +41 -0
  75. package/dist/stitching/IncrementalStitcherView.js +157 -0
  76. package/dist/stitching/incremental.d.ts +930 -0
  77. package/dist/stitching/incremental.js +133 -0
  78. package/dist/stitching/stitchFrames.d.ts +55 -0
  79. package/dist/stitching/stitchFrames.js +56 -0
  80. package/dist/stitching/stitchVideo.d.ts +119 -0
  81. package/dist/stitching/stitchVideo.js +57 -0
  82. package/dist/stitching/useIncrementalJSDriver.d.ts +74 -0
  83. package/dist/stitching/useIncrementalJSDriver.js +199 -0
  84. package/dist/stitching/useIncrementalStitcher.d.ts +58 -0
  85. package/dist/stitching/useIncrementalStitcher.js +172 -0
  86. package/dist/types.d.ts +58 -0
  87. package/dist/types.js +15 -0
  88. package/ios/Package.swift +72 -0
  89. package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +33 -0
  90. package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +40 -0
  91. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +55 -0
  92. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +149 -0
  93. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +2727 -0
  94. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +85 -0
  95. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +625 -0
  96. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +328 -0
  97. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +141 -0
  98. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +278 -0
  99. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +473 -0
  100. package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +1326 -0
  101. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +97 -0
  102. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +296 -0
  103. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +103 -0
  104. package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +3285 -0
  105. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +238 -0
  106. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +1880 -0
  107. package/ios/Sources/RNImageStitcher/QualityChecker.swift +252 -0
  108. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.m +26 -0
  109. package/ios/Sources/RNImageStitcher/QualityCheckerBridge.swift +72 -0
  110. package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +114 -0
  111. package/ios/Sources/RNImageStitcher/RNSARSession.swift +1111 -0
  112. package/ios/Sources/RNImageStitcher/Stitcher.swift +243 -0
  113. package/ios/Sources/RNImageStitcher/StitcherBridge.m +28 -0
  114. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +246 -0
  115. package/package.json +73 -0
  116. package/react-native.config.js +34 -0
  117. package/scripts/opencv-version.txt +1 -0
  118. package/scripts/postinstall-fetch-binaries.js +286 -0
  119. package/src/ar/useARSession.ts +210 -0
  120. package/src/camera/.gitkeep +0 -0
  121. package/src/camera/ARCameraView.tsx +256 -0
  122. package/src/camera/Camera.tsx +1053 -0
  123. package/src/camera/CameraShutter.tsx +292 -0
  124. package/src/camera/CameraView.tsx +157 -0
  125. package/src/camera/CaptureControlsBar.tsx +204 -0
  126. package/src/camera/CaptureHeader.tsx +184 -0
  127. package/src/camera/CapturePreview.tsx +318 -0
  128. package/src/camera/CaptureStatusOverlay.tsx +391 -0
  129. package/src/camera/CaptureThumbnailStrip.tsx +277 -0
  130. package/src/camera/IncrementalPanGuide.tsx +328 -0
  131. package/src/camera/PanoramaBandOverlay.tsx +498 -0
  132. package/src/camera/PanoramaConfirmModal.tsx +206 -0
  133. package/src/camera/PanoramaGuidance.tsx +327 -0
  134. package/src/camera/PanoramaSettingsModal.tsx +1357 -0
  135. package/src/camera/ViewportCropOverlay.tsx +81 -0
  136. package/src/camera/useCapture.ts +279 -0
  137. package/src/camera/useDeviceOrientation.ts +140 -0
  138. package/src/camera/useVideoCapture.ts +236 -0
  139. package/src/index.ts +53 -0
  140. package/src/quality/.gitkeep +0 -0
  141. package/src/quality/normaliseOrientation.ts +79 -0
  142. package/src/quality/runQualityCheck.ts +131 -0
  143. package/src/sensors/useIMUTranslationGate.ts +347 -0
  144. package/src/stitching/.gitkeep +0 -0
  145. package/src/stitching/IncrementalStitcherView.tsx +198 -0
  146. package/src/stitching/incremental.ts +1021 -0
  147. package/src/stitching/stitchFrames.ts +88 -0
  148. package/src/stitching/stitchVideo.ts +153 -0
  149. package/src/stitching/useIncrementalJSDriver.ts +273 -0
  150. package/src/stitching/useIncrementalStitcher.ts +252 -0
  151. 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,3 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="io.imagestitcher.rn" />
@@ -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
+ }