react-native-image-stitcher 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +511 -1
  2. package/README.md +1 -1
  3. package/android/src/main/cpp/keyframe_gate_jni.cpp +138 -0
  4. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +412 -40
  5. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +128 -0
  6. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +87 -45
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +46 -4
  8. package/cpp/stitcher.cpp +101 -1
  9. package/cpp/stitcher.hpp +8 -0
  10. package/dist/camera/Camera.d.ts +9 -0
  11. package/dist/camera/Camera.js +165 -43
  12. package/dist/camera/CaptureDebugOverlay.d.ts +45 -0
  13. package/dist/camera/CaptureDebugOverlay.js +146 -0
  14. package/dist/camera/CaptureKeyframePill.d.ts +28 -0
  15. package/dist/camera/CaptureKeyframePill.js +60 -0
  16. package/dist/camera/CaptureMemoryPill.d.ts +28 -0
  17. package/dist/camera/CaptureMemoryPill.js +109 -0
  18. package/dist/camera/CaptureOrientationPill.d.ts +22 -0
  19. package/dist/camera/CaptureOrientationPill.js +44 -0
  20. package/dist/camera/CaptureStitchStatsToast.d.ts +45 -0
  21. package/dist/camera/CaptureStitchStatsToast.js +133 -0
  22. package/dist/camera/PanoramaSettings.d.ts +478 -0
  23. package/dist/camera/PanoramaSettings.js +120 -0
  24. package/dist/camera/PanoramaSettingsBridge.d.ts +84 -0
  25. package/dist/camera/PanoramaSettingsBridge.js +208 -0
  26. package/dist/camera/PanoramaSettingsModal.d.ts +50 -298
  27. package/dist/camera/PanoramaSettingsModal.js +189 -354
  28. package/dist/camera/buildPanoramaInitialSettings.d.ts +70 -0
  29. package/dist/camera/buildPanoramaInitialSettings.js +97 -0
  30. package/dist/camera/lowMemDevice.d.ts +24 -0
  31. package/dist/camera/lowMemDevice.js +69 -0
  32. package/dist/index.d.ts +16 -2
  33. package/dist/index.js +37 -2
  34. package/dist/sensors/useIMUTranslationGate.d.ts +26 -0
  35. package/dist/sensors/useIMUTranslationGate.js +83 -1
  36. package/dist/stitching/incremental.d.ts +25 -0
  37. package/dist/stitching/useIncrementalStitcher.d.ts +12 -1
  38. package/dist/stitching/useIncrementalStitcher.js +7 -1
  39. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +321 -7
  40. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +8 -0
  41. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +12 -0
  42. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +13 -0
  43. package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +15 -0
  44. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +1 -0
  45. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +17 -4
  46. package/ios/Sources/RNImageStitcher/Stitcher.swift +6 -1
  47. package/package.json +6 -2
  48. package/src/camera/Camera.tsx +220 -54
  49. package/src/camera/CaptureDebugOverlay.tsx +180 -0
  50. package/src/camera/CaptureKeyframePill.tsx +77 -0
  51. package/src/camera/CaptureMemoryPill.tsx +96 -0
  52. package/src/camera/CaptureOrientationPill.tsx +57 -0
  53. package/src/camera/CaptureStitchStatsToast.tsx +155 -0
  54. package/src/camera/PanoramaSettings.ts +605 -0
  55. package/src/camera/PanoramaSettingsBridge.ts +238 -0
  56. package/src/camera/PanoramaSettingsModal.tsx +296 -988
  57. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +375 -0
  58. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +119 -0
  59. package/src/camera/__tests__/lowMemDevice.test.ts +52 -0
  60. package/src/camera/buildPanoramaInitialSettings.ts +139 -0
  61. package/src/camera/lowMemDevice.ts +71 -0
  62. package/src/index.ts +61 -3
  63. package/src/sensors/useIMUTranslationGate.ts +112 -1
  64. package/src/stitching/incremental.ts +25 -0
  65. package/src/stitching/useIncrementalStitcher.ts +18 -0
@@ -120,6 +120,46 @@ Java_io_imagestitcher_rn_KeyframeGate_nativeSetFlowNoveltyPercentile(
120
120
  gate(handle)->setFlowNoveltyPercentile(static_cast<double>(percentile));
121
121
  }
122
122
 
123
+ // 2026-05-22 (audit F5) — Android JNI parity for the Shi-Tomasi
124
+ // corner tunables. Pre-audit, iOS bridges these via KeyframeGateBridge
125
+ // but Android had no equivalent — JS Settings sliders for
126
+ // flowMaxCorners / flowQualityLevel / flowMinDistance were no-ops on
127
+ // Android. See setFlowMaxCorners / setFlowQualityLevel /
128
+ // setFlowMinDistance docs in keyframe_gate.hpp.
129
+ JNIEXPORT void JNICALL
130
+ Java_io_imagestitcher_rn_KeyframeGate_nativeSetFlowMaxCorners(
131
+ JNIEnv*, jclass, jlong handle, jint maxCorners)
132
+ {
133
+ gate(handle)->setFlowMaxCorners(static_cast<int32_t>(maxCorners));
134
+ }
135
+
136
+ JNIEXPORT void JNICALL
137
+ Java_io_imagestitcher_rn_KeyframeGate_nativeSetFlowQualityLevel(
138
+ JNIEnv*, jclass, jlong handle, jdouble quality)
139
+ {
140
+ gate(handle)->setFlowQualityLevel(static_cast<double>(quality));
141
+ }
142
+
143
+ JNIEXPORT void JNICALL
144
+ Java_io_imagestitcher_rn_KeyframeGate_nativeSetFlowMinDistance(
145
+ JNIEnv*, jclass, jlong handle, jdouble minDistance)
146
+ {
147
+ gate(handle)->setFlowMinDistance(static_cast<double>(minDistance));
148
+ }
149
+
150
+ // 2026-05-22 (audit F6) — gate strategy selector. Maps the Kotlin
151
+ // enum's int value back to the C++ GateStrategy enum. Pre-audit
152
+ // Android had no way to flip strategy → was stuck on the C++ default
153
+ // (Pose), making `frameSelectionMode = 'flow-based'` a silent no-op
154
+ // on Android.
155
+ JNIEXPORT void JNICALL
156
+ Java_io_imagestitcher_rn_KeyframeGate_nativeSetStrategy(
157
+ JNIEnv*, jclass, jlong handle, jint strategyInt)
158
+ {
159
+ auto strategy = static_cast<retailens::GateStrategy>(strategyInt);
160
+ gate(handle)->setStrategy(strategy);
161
+ }
162
+
123
163
  JNIEXPORT void JNICALL
124
164
  Java_io_imagestitcher_rn_KeyframeGate_nativeReset(
125
165
  JNIEnv*, jclass, jlong handle)
@@ -201,4 +241,102 @@ Java_io_imagestitcher_rn_KeyframeGate_nativeEvaluate(
201
241
  return out;
202
242
  }
203
243
 
244
+ // ── Per-frame evaluate WITH PIXEL DATA ──────────────────────────
245
+ //
246
+ // 2026-05-21 (v0.3) — pixel-aware Flow-strategy entry point. The
247
+ // `nativeEvaluate` above hands the gate pose + plane only, which
248
+ // forces the C++ side to silently fall back from Flow strategy to
249
+ // Pose strategy in cpp/keyframe_gate.cpp's evaluateWithFrame()
250
+ // (defensive fallback at the grayData==nullptr branch). This thunk
251
+ // is the proper Flow-strategy entry point: the caller supplies the
252
+ // frame's grayscale plane (Y plane for YUV camera images, or a
253
+ // JPEG-decode result for the JS-driver path), and the C++ Flow
254
+ // path actually runs feature tracking on it.
255
+ //
256
+ // grayBytes: Java byte[] holding the grayscale plane. Accessed via
257
+ // GetPrimitiveArrayCritical (no copy, pins GC briefly for
258
+ // the duration of the gate.evaluateWithFrame call —
259
+ // evaluation is ~1-5 ms so the pin window is tight).
260
+ // width: grayscale image width in pixels.
261
+ // height: grayscale image height in pixels.
262
+ // stride: bytes per row. May exceed width when the plane has
263
+ // padding (ARCore's Image.Plane.getRowStride() can pad).
264
+ //
265
+ // plane16OrNull: same as nativeEvaluate — column-major 4×4 plane
266
+ // transform, or null for angular-delta fallback.
267
+ //
268
+ // Returns DoubleArray[5] identical to nativeEvaluate.
269
+ JNIEXPORT jdoubleArray JNICALL
270
+ Java_io_imagestitcher_rn_KeyframeGate_nativeEvaluateWithFrame(
271
+ JNIEnv* env, jclass, jlong handle,
272
+ jfloat tx, jfloat ty, jfloat tz,
273
+ jfloat qx, jfloat qy, jfloat qz, jfloat qw,
274
+ jfloat fx, jfloat fy, jfloat cx, jfloat cy,
275
+ jint imageWidth, jint imageHeight,
276
+ jfloatArray plane16OrNull,
277
+ jbyteArray grayBytes,
278
+ jint grayWidth, jint grayHeight, jint grayStride)
279
+ {
280
+ retailens::Pose pose;
281
+ pose.tx = tx; pose.ty = ty; pose.tz = tz;
282
+ pose.qx = qx; pose.qy = qy; pose.qz = qz; pose.qw = qw;
283
+ pose.fx = fx; pose.fy = fy; pose.cx = cx; pose.cy = cy;
284
+ pose.imageWidth = static_cast<int32_t>(imageWidth);
285
+ pose.imageHeight = static_cast<int32_t>(imageHeight);
286
+
287
+ retailens::PlaneTransform planeStorage;
288
+ const retailens::PlaneTransform* planePtr = nullptr;
289
+ if (plane16OrNull) {
290
+ jsize len = env->GetArrayLength(plane16OrNull);
291
+ if (len == 16) {
292
+ jfloat* src = env->GetFloatArrayElements(plane16OrNull, nullptr);
293
+ if (src) {
294
+ std::memcpy(planeStorage.m, src, sizeof(float) * 16);
295
+ env->ReleaseFloatArrayElements(plane16OrNull, src, JNI_ABORT);
296
+ planePtr = &planeStorage;
297
+ }
298
+ }
299
+ }
300
+
301
+ // Pin the byte[] for the duration of the gate evaluate. Use
302
+ // GetPrimitiveArrayCritical (zero-copy, JVM pins the GC) over
303
+ // GetByteArrayElements (may copy on some VMs) because at 30-60
304
+ // Hz of 2 MB Y-planes, the copy cost adds up. Evaluate is
305
+ // ~1-5 ms so the pin window is short. Always paired with
306
+ // ReleasePrimitiveArrayCritical even on the error paths below.
307
+ retailens::KeyframeGateDecision d;
308
+ if (grayBytes && grayWidth > 0 && grayHeight > 0 && grayStride >= grayWidth) {
309
+ void* raw = env->GetPrimitiveArrayCritical(grayBytes, nullptr);
310
+ if (raw) {
311
+ d = gate(handle)->evaluateWithFrame(
312
+ pose, planePtr,
313
+ static_cast<const uint8_t*>(raw),
314
+ static_cast<int32_t>(grayWidth),
315
+ static_cast<int32_t>(grayHeight),
316
+ static_cast<int32_t>(grayStride));
317
+ env->ReleasePrimitiveArrayCritical(grayBytes, raw, JNI_ABORT);
318
+ } else {
319
+ // GetPrimitiveArrayCritical failed (rare, but defensive).
320
+ // Fall back to pose-only path so we degrade gracefully
321
+ // rather than crashing the whole capture pipeline.
322
+ d = gate(handle)->evaluate(pose, planePtr);
323
+ }
324
+ } else {
325
+ // Caller passed null / invalid dims — defensive fall-through
326
+ // to pose-only path (matches the C++ side's own defensive
327
+ // fallback in evaluateWithFrame when grayData == nullptr).
328
+ d = gate(handle)->evaluate(pose, planePtr);
329
+ }
330
+
331
+ jdoubleArray out = env->NewDoubleArray(5);
332
+ jdouble values[5];
333
+ values[0] = d.accept ? 1.0 : 0.0;
334
+ values[1] = static_cast<jdouble>(static_cast<int32_t>(d.reason));
335
+ values[2] = d.newContentFraction;
336
+ values[3] = static_cast<jdouble>(d.acceptedCount);
337
+ values[4] = static_cast<jdouble>(d.maxCount);
338
+ env->SetDoubleArrayRegion(out, 0, 5, values);
339
+ return out;
340
+ }
341
+
204
342
  } // extern "C"