react-native-image-stitcher 0.16.2 → 0.18.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 (52) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/RNImageStitcher.podspec +26 -1
  3. package/android/build.gradle +20 -0
  4. package/android/src/main/cpp/CMakeLists.txt +46 -3
  5. package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +436 -0
  6. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +6 -0
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +711 -6
  8. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +156 -0
  9. package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +103 -0
  10. package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +338 -0
  11. package/cpp/{stitcher_frame_data.hpp → camera_frame_data.hpp} +96 -13
  12. package/cpp/camera_frame_jsi.cpp +357 -0
  13. package/cpp/camera_frame_jsi.hpp +108 -0
  14. package/cpp/stitcher_proxy_jsi.cpp +140 -0
  15. package/cpp/stitcher_proxy_jsi.hpp +62 -0
  16. package/cpp/stitcher_worklet_dispatch.cpp +103 -0
  17. package/cpp/stitcher_worklet_dispatch.hpp +71 -0
  18. package/cpp/stitcher_worklet_registry.cpp +91 -0
  19. package/cpp/stitcher_worklet_registry.hpp +146 -0
  20. package/dist/camera/ARCameraView.d.ts +77 -0
  21. package/dist/camera/ARCameraView.js +90 -1
  22. package/dist/camera/Camera.d.ts +63 -4
  23. package/dist/camera/Camera.js +2 -2
  24. package/dist/camera/CaptureMemoryPill.d.ts +4 -3
  25. package/dist/camera/CaptureMemoryPill.js +4 -3
  26. package/dist/index.d.ts +2 -1
  27. package/dist/stitching/ARFrameMeta.d.ts +100 -0
  28. package/dist/stitching/{StitcherFrame.js → ARFrameMeta.js} +1 -1
  29. package/dist/stitching/{StitcherFrame.d.ts → CameraFrame.d.ts} +70 -11
  30. package/dist/stitching/CameraFrame.js +4 -0
  31. package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
  32. package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
  33. package/dist/stitching/useStitcherWorklet.d.ts +4 -4
  34. package/dist/stitching/useStitcherWorklet.js +4 -4
  35. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +23 -1
  36. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +137 -2
  37. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +83 -0
  38. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +760 -0
  39. package/ios/Sources/RNImageStitcher/RNSARSession.swift +336 -40
  40. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
  41. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
  42. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
  43. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +160 -0
  44. package/package.json +1 -1
  45. package/src/camera/ARCameraView.tsx +211 -2
  46. package/src/camera/Camera.tsx +81 -4
  47. package/src/camera/CaptureMemoryPill.tsx +4 -3
  48. package/src/index.ts +7 -3
  49. package/src/stitching/ARFrameMeta.ts +107 -0
  50. package/src/stitching/{StitcherFrame.ts → CameraFrame.ts} +79 -11
  51. package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
  52. package/src/stitching/useStitcherWorklet.ts +9 -9
package/CHANGELOG.md CHANGED
@@ -14,6 +14,160 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
14
  > during 0.x are bumped to a new MINOR (e.g., 0.1 → 0.2), and the
15
15
  > upgrade path is documented in this CHANGELOG.
16
16
 
17
+ ## [0.18.0] — 2026-06-18
18
+
19
+ ### ⚠️ Breaking — `StitcherFrame` → `CameraFrame`
20
+
21
+ The frame type a worklet receives is renamed **`StitcherFrame` →
22
+ `CameraFrame`** (and `StitcherFrameProcessor` → `CameraFrameProcessor`).
23
+ The shape is unchanged; only the names changed, to match the
24
+ `arFrameProcessor` prop's role (it's the camera frame, not a "stitcher"
25
+ frame). Update your imports:
26
+
27
+ ```diff
28
+ - import { type StitcherFrame } from 'react-native-image-stitcher';
29
+ + import { type CameraFrame } from 'react-native-image-stitcher';
30
+ ```
31
+
32
+ ### Added — AR depth, anchors, scene mesh, and intrinsics on `CameraFrame`
33
+
34
+ The AR frame a worklet receives can now carry rich per-frame metadata,
35
+ each behind an **opt-in `<Camera>` prop** (all off by default — you pay
36
+ only for what you request):
37
+
38
+ - **`enableDepth`** → `frame.arDepth` — a depth map normalised to **one
39
+ cross-platform shape**: `Float32` **metres** in `depthMap`, optional
40
+ `Uint8` `confidenceMap` (`0`/`1`/`2`). Sourced from ARKit
41
+ `sceneDepth`/`smoothedSceneDepth` (LiDAR) and the ARCore Depth API.
42
+ - **`enableAnchors`** → `frame.arAnchors` — detected planes / images,
43
+ now with plane **`alignment`** (`'horizontal'` | `'vertical'`),
44
+ **`extent`** (`[x, z]` metres), and (iOS only) semantic
45
+ **`classification`** (`'wall'`/`'floor'`/…).
46
+ - **`enableMesh`** → `type: 'mesh'` entries in `arAnchors` carrying
47
+ `meshGeometry` (`vertices`/`faces`/optional `classifications`
48
+ ArrayBuffers). iOS uses ARKit `ARMeshAnchor` scene reconstruction
49
+ (LiDAR); **Android reconstructs a rough mesh from the depth map**
50
+ (camera-local vertices, identity transform, no per-face
51
+ classifications) — so Android mesh requires a Depth-API device and is
52
+ geometry-only.
53
+ - **`planeDetection`** (`'vertical'` (default) | `'horizontal'` |
54
+ `'both'`) — which plane orientations reach `arAnchors`. iOS changes
55
+ ARKit `planeDetection`; Android keeps detecting both (ARCore needs
56
+ horizontal planes to bootstrap tracking) and filters the emitted set,
57
+ so the JS-observable result is identical on both platforms. The
58
+ `'vertical'` default preserves the plane-projected stitch path's
59
+ long-standing behaviour.
60
+ - **`frame.intrinsics`** — per-frame `fx`/`fy`/`cx`/`cy` (px) plus the
61
+ capture resolution, for lifting 2D image coordinates to 3D. Always
62
+ present on AR frames; `undefined` on non-AR (vision-camera) frames,
63
+ which have no intrinsics surface.
64
+
65
+ Depth/anchor/mesh bytes are **eager-copied** out of the native frame at
66
+ extraction time, so they're safe to read anywhere in the worklet (no
67
+ buffer-lifetime footgun). See the new **[Testing the AR frame
68
+ processor](https://bhargavkanda.github.io/react-native-image-stitcher/docs/dev-testing)**
69
+ guide for a copy-paste verification recipe and the expected on-device
70
+ output per platform.
71
+
72
+ ### Added — `onArFrame`: worklet-free AR metadata on the main thread
73
+
74
+ `<Camera onArFrame={(meta) => …}>` is a new callback (also on
75
+ `<ARCameraView>`) that delivers **light per-frame AR metadata on the JS
76
+ main thread** — no worklet involved:
77
+
78
+ ```ts
79
+ onArFrame={(m: ARFrameMeta) => {
80
+ // m.trackingState, m.pose, m.intrinsics,
81
+ // m.depth?.{width,height,hasConfidence},
82
+ // m.anchors[] (id/type/alignment/extent/classification/transform),
83
+ // m.mesh?.{anchorCount,vertexCount,faceCount}
84
+ }}
85
+ ```
86
+
87
+ Native builds the metadata each frame (reusing the same extraction as
88
+ above) and emits it as a throttled event (default ≈10 Hz; tune with
89
+ `arFrameMetaInterval` ms). Costly fields are gated by the same opt-ins
90
+ (`depth` needs `enableDepth`, `mesh` needs `enableMesh`, `anchors` needs
91
+ `enableAnchors`); `pose`/`trackingState`/`intrinsics` are always present.
92
+
93
+ **This is the recommended way to read AR data in JS** for observe/measure
94
+ use cases — it carries *light* data (dims, counts, intrinsics, plane
95
+ geometry), never heavy buffers. For zero-copy access to raw per-frame
96
+ buffers (depth pixels, mesh vertices) you'd use the `arFrameProcessor`
97
+ worklet — see the limitation below.
98
+
99
+ ### Known limitation — `arFrameProcessor` worklets must capture nothing
100
+
101
+ In this release an `arFrameProcessor` worklet must **not capture host
102
+ objects** (a `runOnJS` callback or a shared value) in its closure:
103
+ `react-native-worklets-core` deep-copies the worklet's closure when it's
104
+ installed into the AR worklet runtime, and a captured host object makes
105
+ that copy recurse until the stack overflows (a hard crash, on both Debug
106
+ and Release). A worklet that captures **nothing** installs and runs fine.
107
+ Until this is resolved upstream, **use `onArFrame`** (above) to get AR
108
+ data into JS; reserve the worklet for capture-free per-frame work.
109
+
110
+ ### Known limitation — `enableMesh` is memory-heavy on sustained sessions
111
+
112
+ `enableMesh` turns on ARKit **continuous scene reconstruction**, the most
113
+ memory-intensive AR mode — the mesh model grows as you scan, and a long
114
+ session with depth + mesh both on can be **jetsam-killed by iOS** after a
115
+ few seconds on memory-constrained devices. `onArFrame` reports mesh as
116
+ light *counts* (`anchorCount`/`vertexCount`/`faceCount`) without copying
117
+ geometry, so reading mesh stats is cheap; it's the **underlying ARKit
118
+ meshing** that's heavy. For now, enable `mesh` only for short captures (the
119
+ example demos depth + planes + intrinsics with mesh off). Proper memory
120
+ management for sustained meshing — bounded reconstruction, single depth
121
+ semantic, on-demand geometry — lands with the 0.20 reconstruction work.
122
+
123
+ ### Internal — `StitcherFrameData` → `CameraFrameData`
124
+
125
+ The shared C++ frame struct and its JSI/Obj-C++ host objects were renamed
126
+ (`StitcherFrameData` → `CameraFrameData`, `StitcherFrameJsiHostObject` →
127
+ `CameraFrameJsiHostObject`, `StitcherFrameHostObject` →
128
+ `CameraFrameHostObject`) to match the public `CameraFrame` type. No public
129
+ API change.
130
+
131
+ ### Notes
132
+
133
+ - Compile-verified on both platforms (iOS `xcodebuild` + Android
134
+ `assembleDebug`); all unit tests + typecheck pass. On-device
135
+ observation of depth/planes/mesh/intrinsics against real surfaces is
136
+ the recommended pre-adoption check (see the dev-testing guide).
137
+
138
+ ## [0.17.0] — 2026-06-19
139
+
140
+ ### Added — `arFrameProcessor`: observe AR frames with a host worklet
141
+
142
+ `<Camera>` gains an **`arFrameProcessor`** prop — a `'worklet'` invoked once per
143
+ **ARKit / ARCore frame** while in AR capture, dispatched natively and running
144
+ *alongside* first-party stitching (composition, not replacement). The worklet
145
+ receives a `StitcherFrame` tagged `source: 'ar'` with the world-space `pose` and
146
+ `arTrackingState`. It fires during preview too (continuous observation), at zero
147
+ per-frame cost when no worklet is registered.
148
+
149
+ This restores the previously-archived AR host-worklet capability and re-exposes
150
+ it as an explicit prop (rather than the old auto-registering hook). Under the
151
+ hood it installs `globalThis.__stitcherProxy` (JSI) on first use and fans frames
152
+ out through a shared C++ proxy / registry / dispatch layer on both platforms
153
+ (verified against `react-native-worklets-core` 1.6.3).
154
+
155
+ The non-AR equivalent remains `frameProcessor` (vision-camera); the two modes use
156
+ different runtimes and frame shapes, hence the separate prop. The
157
+ `StitcherFrame` / `StitcherFrameProcessor` type names are unchanged.
158
+
159
+ Verified on device: the worklet fires per frame on **iOS (ARKit, iPhone 16 Pro)**
160
+ and **Android (ARCore, Galaxy A35)**.
161
+
162
+ ### Fixed
163
+
164
+ - **Example app crashed at launch on Android** (`PlatformConstants could not be
165
+ found`). The v0.16.2 OpenCV-reuse demo added an app-level `externalNativeBuild`
166
+ to `example/android/app/build.gradle` that displaced React Native's own
167
+ New-Architecture app native build (so core TurboModules weren't compiled in).
168
+ Removed it; React Native owns the app native build again. **Example-app only —
169
+ the published SDK was never affected.**
170
+
17
171
  ## [0.16.2] — 2026-06-17
18
172
 
19
173
  ### Added — reuse the bundled OpenCV from your host app's native code (Android)
@@ -60,6 +60,18 @@ Pod::Spec.new do |s|
60
60
 
61
61
  s.dependency 'React-Core'
62
62
 
63
+ # react-native-worklets-core — provides the `RNWorklet::WorkletInvoker`
64
+ # + `JsiWorkletContext` primitives the AR-mode JSI fan-out is built on
65
+ # (StitcherJsiInstaller.mm / RNSARWorkletRuntime.mm + the shared
66
+ # cpp/stitcher_worklet_{registry,dispatch}.cpp). In practice this pod
67
+ # is already in every host's graph (vision-camera depends on it), but
68
+ # declaring it here makes the dependency explicit and guarantees its
69
+ # headers are present even for a host that uses AR mode without
70
+ # vision-camera. The bare `WKTJsiWorklet.h` includes in the .mm files
71
+ # resolve via the HEADER_SEARCH_PATHS entry below (the package's own
72
+ # node_modules copy of the worklets-core cpp/ dir).
73
+ s.dependency 'react-native-worklets-core'
74
+
63
75
  # ─────────────────────────────────────────────────────────────────────
64
76
  # OpenCV — pre-built custom xcframework fetched by postinstall
65
77
  # ─────────────────────────────────────────────────────────────────────
@@ -84,6 +96,19 @@ Pod::Spec.new do |s|
84
96
  'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
85
97
  'CLANG_CXX_LIBRARY' => 'libc++',
86
98
  'OTHER_CPLUSPLUSFLAGS' => '$(inherited) -std=c++17',
87
- 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/cpp"',
99
+ # HEADER_SEARCH_PATHS:
100
+ # - "${PODS_TARGET_SRCROOT}/cpp" — the shared C++ port's own
101
+ # headers (keyframe_gate.hpp, camera_frame_jsi.hpp, …).
102
+ # - the worklets-core cpp/ dir — so the bare `#include
103
+ # "WKTJsiWorklet.h"` / "WKTJsiWorkletContext.h" lines in
104
+ # StitcherJsiInstaller.mm + RNSARWorkletRuntime.mm resolve.
105
+ # PODS_ROOT is `<host>/ios/Pods`; the package's worklets-core
106
+ # copy lives at `<host>/node_modules/react-native-worklets-core/
107
+ # cpp`, i.e. `${PODS_ROOT}/../node_modules/...`. (The shared
108
+ # cpp/*.cpp files instead use the namespace-prefixed
109
+ # `<react-native-worklets-core/WKTJsiWorklet.h>` form, which
110
+ # resolves against `${PODS_ROOT}/Headers/Public` — already on
111
+ # the inherited path — and works on Android's prefab too.)
112
+ 'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/cpp" "${PODS_ROOT}/../node_modules/react-native-worklets-core/cpp"',
88
113
  }
89
114
  end
@@ -301,6 +301,26 @@ dependencies {
301
301
  android.sourceSets.main.java.exclude '**/CvFlowGateFrameProcessor.kt'
302
302
  }
303
303
 
304
+ // v0.8.0 Phase 4b.ii/iii — react-native-worklets-core, consumed
305
+ // for its `rnworklets` PREFAB module (RNWorklet::JsiWorkletContext /
306
+ // WorkletInvoker) by the AR frame-processor JSI fan-out
307
+ // (src/main/cpp/stitcher_jsi_install_jni.cpp + the shared cpp/
308
+ // stitcher_*_jsi.cpp). `find_package(react-native-worklets-core ...)`
309
+ // in CMakeLists.txt needs this gradle project on the path so AGP
310
+ // wires the prefab into the native build. Same consumption pattern
311
+ // react-native-vision-camera uses for Frame Processors.
312
+ //
313
+ // `findProject(...)` guard mirrors the vision-camera block above:
314
+ // the SDK still compiles in hosts that don't install worklets-core
315
+ // (those hosts simply don't use the AR frame processor — the JSI
316
+ // sources still compile, but the prefab link only happens when the
317
+ // host provides worklets-core). Autolinking adds the project for
318
+ // any host that depends on vision-camera (which transitively pulls
319
+ // in worklets-core).
320
+ if (findProject(':react-native-worklets-core') != null) {
321
+ implementation project(':react-native-worklets-core')
322
+ }
323
+
304
324
  // v0.10.0 audit #11A — Android JUnit test scaffold. JVM unit
305
325
  // tests for pure-Kotlin data wrappers + algorithm helpers that
306
326
  // don't need an Android device. Run via
@@ -66,6 +66,20 @@ add_library(opencv_stitching STATIC IMPORTED)
66
66
  set_target_properties(opencv_stitching PROPERTIES
67
67
  IMPORTED_LOCATION "${OPENCV_STITCHING_A}")
68
68
 
69
+ # ── React Native + worklets-core prefabs (v0.8.0 Phase 4b.ii/iii) ──
70
+ #
71
+ # The AR frame-processor's JSI fan-out (stitcher_jsi_install_jni.cpp +
72
+ # the shared cpp/stitcher_*_jsi.cpp) needs RN's JSI runtime, fbjni, and
73
+ # react-native-worklets-core's `RNWorklet::JsiWorkletContext` /
74
+ # `WorkletInvoker`. RN 0.71+ ships jsi + the native-modules umbrella as
75
+ # prefab packages; worklets-core ships a `rnworklets` prefab module.
76
+ # `buildFeatures.prefab true` (android/build.gradle) makes these
77
+ # discoverable. We mirror react-native-vision-camera's consumption of
78
+ # the SAME prefabs in this example app (verified working on RN 0.84.1).
79
+ find_package(ReactAndroid REQUIRED CONFIG)
80
+ find_package(fbjni REQUIRED CONFIG)
81
+ find_package(react-native-worklets-core REQUIRED CONFIG)
82
+
69
83
  # ── Shared C++ port (KeyframeGate) ────────────────────────────────
70
84
  #
71
85
  # `cpp/` at the SDK root holds C++ that's compiled into BOTH the iOS
@@ -91,12 +105,26 @@ add_library(image_stitcher SHARED
91
105
  # retry + dimension/memory instrumentation. Used to live in this
92
106
  # file (image_stitcher_jni.cpp). See cpp/stitcher.hpp for design
93
107
  # rationale.
94
- "${SHARED_CPP_DIR}/stitcher.cpp")
108
+ "${SHARED_CPP_DIR}/stitcher.cpp"
109
+ # v0.8.0 Phase 4b.ii/iii — AR frame-processor JSI fan-out. The JNI
110
+ # binding (stitcher_jsi_install_jni.cpp) installs
111
+ # `globalThis.__stitcherProxy` and fans each AR frame out to the
112
+ # registered host worklets. The 4 shared cpp/ JSI files below are
113
+ # the SAME source the iOS pod compiles — one cross-platform JSI
114
+ # surface (proxy host object + native worklet registry + per-frame
115
+ # dispatch helper + CameraFrame JSI host object).
116
+ stitcher_jsi_install_jni.cpp
117
+ "${SHARED_CPP_DIR}/stitcher_proxy_jsi.cpp"
118
+ "${SHARED_CPP_DIR}/stitcher_worklet_registry.cpp"
119
+ "${SHARED_CPP_DIR}/stitcher_worklet_dispatch.cpp"
120
+ "${SHARED_CPP_DIR}/camera_frame_jsi.cpp")
95
121
 
96
122
  target_include_directories(image_stitcher PRIVATE
97
123
  "${OPENCV_INCLUDE_DIR}"
98
124
  # cpp/ holds keyframe_gate.hpp + ar_frame_pose.h that the JNI
99
- # bindings include without relative-path spelunking.
125
+ # bindings include without relative-path spelunking. Also the
126
+ # shared JSI sources (stitcher_proxy_jsi.hpp, camera_frame_data.hpp,
127
+ # stitcher_worklet_*.hpp, camera_frame_jsi.hpp).
100
128
  "${SHARED_CPP_DIR}")
101
129
 
102
130
  # Link order matters:
@@ -118,7 +146,22 @@ target_link_libraries(image_stitcher
118
146
  opencv_stitching
119
147
  -Wl,--no-whole-archive
120
148
  opencv_java
121
- log)
149
+ log
150
+ # v0.8.0 Phase 4b.ii/iii — JSI fan-out deps. Mirrors
151
+ # react-native-vision-camera's link set on RN 0.84.1:
152
+ # ReactAndroid::jsi — facebook::jsi::Runtime / Value / Object
153
+ # ReactAndroid::reactnative — RN's native-modules umbrella prefab
154
+ # (RN >= 0.76; CallInvoker + friends that
155
+ # worklets-core's context depends on)
156
+ # fbjni::fbjni — JNI helpers worklets-core links against
157
+ # react-native-worklets-core::rnworklets — JsiWorkletContext +
158
+ # WorkletInvoker. Carries its own
159
+ # include dir so <react-native-worklets-core/
160
+ # WKTJsiWorkletContext.h> resolves.
161
+ ReactAndroid::jsi
162
+ ReactAndroid::reactnative
163
+ fbjni::fbjni
164
+ react-native-worklets-core::rnworklets)
122
165
 
123
166
  target_compile_options(image_stitcher PRIVATE
124
167
  -fvisibility=hidden