react-native-image-stitcher 0.17.0 → 0.19.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 (46) hide show
  1. package/CHANGELOG.md +151 -0
  2. package/RNImageStitcher.podspec +1 -1
  3. package/android/src/main/cpp/CMakeLists.txt +4 -4
  4. package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +216 -7
  5. package/android/src/main/java/io/imagestitcher/rn/ARFrameContext.kt +89 -0
  6. package/android/src/main/java/io/imagestitcher/rn/ARFramePlugin.kt +57 -0
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +831 -6
  8. package/android/src/main/java/io/imagestitcher/rn/RNSARPluginRegistry.kt +109 -0
  9. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +184 -0
  10. package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +1 -1
  11. package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +84 -2
  12. package/cpp/{stitcher_frame_data.hpp → camera_frame_data.hpp} +96 -13
  13. package/cpp/{stitcher_frame_jsi.cpp → camera_frame_jsi.cpp} +154 -11
  14. package/cpp/{stitcher_frame_jsi.hpp → camera_frame_jsi.hpp} +12 -12
  15. package/cpp/stitcher_proxy_jsi.cpp +31 -0
  16. package/cpp/stitcher_proxy_jsi.hpp +16 -0
  17. package/cpp/stitcher_worklet_dispatch.cpp +5 -5
  18. package/cpp/stitcher_worklet_dispatch.hpp +5 -5
  19. package/dist/camera/ARCameraView.d.ts +81 -3
  20. package/dist/camera/ARCameraView.js +103 -1
  21. package/dist/camera/Camera.d.ts +73 -7
  22. package/dist/camera/Camera.js +2 -2
  23. package/dist/index.d.ts +3 -1
  24. package/dist/stitching/ARFrameMeta.d.ts +149 -0
  25. package/dist/stitching/{StitcherFrame.js → ARFrameMeta.js} +1 -1
  26. package/dist/stitching/{StitcherFrame.d.ts → CameraFrame.d.ts} +70 -11
  27. package/dist/stitching/CameraFrame.js +4 -0
  28. package/dist/stitching/useStitcherWorklet.d.ts +4 -4
  29. package/dist/stitching/useStitcherWorklet.js +4 -4
  30. package/ios/Sources/RNImageStitcher/ARSessionBridge.m +23 -1
  31. package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +172 -2
  32. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +108 -0
  33. package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +772 -0
  34. package/ios/Sources/RNImageStitcher/RNISARFramePlugin.swift +247 -0
  35. package/ios/Sources/RNImageStitcher/RNSARSession.swift +418 -34
  36. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +2 -2
  37. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +4 -4
  38. package/package.json +1 -1
  39. package/src/camera/ARCameraView.tsx +230 -5
  40. package/src/camera/Camera.tsx +91 -7
  41. package/src/index.ts +12 -3
  42. package/src/stitching/ARFrameMeta.ts +157 -0
  43. package/src/stitching/{StitcherFrame.ts → CameraFrame.ts} +79 -11
  44. package/src/stitching/useStitcherWorklet.ts +9 -9
  45. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
  46. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
@@ -17,15 +17,144 @@
17
17
  import Foundation
18
18
  import React
19
19
 
20
+ // v0.18.0 — `RNSARSessionBridge` is now an `RCTEventEmitter` (was a
21
+ // plain `NSObject`) so it can deliver the `onArFrame` LIGHT-metadata
22
+ // channel as the JS `RNImageStitcherARFrame` device event. The JS side
23
+ // subscribes via `new NativeEventEmitter(NativeModules.RNSARSession)` —
24
+ // the same module name this bridge is remapped to (see ARSessionBridge.m).
25
+ //
26
+ // Pattern mirrors `IncrementalStitcherBridge`: observe a NotificationCenter
27
+ // post from the framework-free `RNSARSession` engine, then re-emit on the
28
+ // main queue via `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", …)`
29
+ // rather than `RCTEventEmitter.sendEvent(…)`, because under RN bridgeless
30
+ // interop `sendEvent` silently no-ops for some event-body shapes (see the
31
+ // IncrementalStitcherBridge.handleStateUpdate docstring).
20
32
  @objc(RNSARSessionBridge)
21
- public final class RNSARSessionBridge: NSObject {
33
+ public final class RNSARSessionBridge: RCTEventEmitter {
34
+
35
+ /// Whether at least one JS listener is attached to the AR-frame event.
36
+ /// RN's EventEmitter contract: don't emit when no listeners are
37
+ /// registered. Toggled by `startObserving` / `stopObserving`.
38
+ private var hasListeners: Bool = false
39
+
40
+ private static let arFrameEvent = "RNImageStitcherARFrame"
41
+ // v0.19.0 — async AR-plugin result channel (RNISARPluginRegistry.emit).
42
+ private static let arPluginResultEvent = "RNImageStitcherARPluginResult"
22
43
 
23
- @objc public static func requiresMainQueueSetup() -> Bool {
44
+ public override init() {
45
+ super.init()
46
+ // Defensively de-dupe the observer: under RN bridgeless interop a
47
+ // bridge's init() can run twice on the same instance. Remove any
48
+ // prior registration for this notification before adding, so the
49
+ // observer fires at most once per post regardless.
50
+ NotificationCenter.default.removeObserver(
51
+ self,
52
+ name: .retailensARFrameMeta,
53
+ object: nil
54
+ )
55
+ NotificationCenter.default.addObserver(
56
+ self,
57
+ selector: #selector(handleArFrameMeta(_:)),
58
+ name: .retailensARFrameMeta,
59
+ object: nil
60
+ )
61
+ // v0.19.0 — observe the async AR-plugin result channel (posted by
62
+ // `RNISARPluginRegistry.emit`) and re-emit as a JS device event.
63
+ // Same de-dupe rationale as the onArFrame observer above.
64
+ NotificationCenter.default.removeObserver(
65
+ self,
66
+ name: .retailensARPluginResult,
67
+ object: nil
68
+ )
69
+ NotificationCenter.default.addObserver(
70
+ self,
71
+ selector: #selector(handleArPluginResult(_:)),
72
+ name: .retailensARPluginResult,
73
+ object: nil
74
+ )
75
+ }
76
+
77
+ deinit {
78
+ NotificationCenter.default.removeObserver(self)
79
+ }
80
+
81
+ // MARK: - RCTEventEmitter protocol
82
+
83
+ public override static func requiresMainQueueSetup() -> Bool {
24
84
  // ARSession.start() must be called on the main thread —
25
85
  // ARKit needs to attach to the active CVDisplayLink.
26
86
  return true
27
87
  }
28
88
 
89
+ public override func supportedEvents() -> [String]! {
90
+ return [Self.arFrameEvent, Self.arPluginResultEvent]
91
+ }
92
+
93
+ public override func startObserving() {
94
+ hasListeners = true
95
+ }
96
+
97
+ public override func stopObserving() {
98
+ hasListeners = false
99
+ }
100
+
101
+ /// Forward a posted `ARFrameMeta` dictionary to JS as the
102
+ /// `RNImageStitcherARFrame` device event. Dropped when no JS listener
103
+ /// is attached. Emits via `enqueueJSCall` on the main queue (see the
104
+ /// class docstring for why not `sendEvent`).
105
+ @objc private func handleArFrameMeta(_ notification: Notification) {
106
+ guard hasListeners else { return }
107
+ guard let userInfo = notification.userInfo else { return }
108
+ DispatchQueue.main.async { [weak self] in
109
+ guard let self = self, let bridge = self.bridge else { return }
110
+ bridge.enqueueJSCall(
111
+ "RCTDeviceEventEmitter",
112
+ method: "emit",
113
+ args: [Self.arFrameEvent, userInfo],
114
+ completion: nil
115
+ )
116
+ }
117
+ }
118
+
119
+ /// v0.19.0 — forward a posted async AR-plugin result
120
+ /// (`{ plugin, result }`, from `RNISARPluginRegistry.emit`) to JS as
121
+ /// the `RNImageStitcherARPluginResult` device event. Dropped when no
122
+ /// JS listener is attached. Emits via `enqueueJSCall` on the main
123
+ /// queue (same `sendEvent`-avoidance rationale as `handleArFrameMeta`).
124
+ @objc private func handleArPluginResult(_ notification: Notification) {
125
+ guard hasListeners else { return }
126
+ guard let userInfo = notification.userInfo else { return }
127
+ DispatchQueue.main.async { [weak self] in
128
+ guard let self = self, let bridge = self.bridge else { return }
129
+ bridge.enqueueJSCall(
130
+ "RCTDeviceEventEmitter",
131
+ method: "emit",
132
+ args: [Self.arPluginResultEvent, userInfo],
133
+ completion: nil
134
+ )
135
+ }
136
+ }
137
+
138
+ // MARK: - Module methods
139
+
140
+ /// v0.18.0 — toggle the `onArFrame` LIGHT-metadata channel. Called
141
+ /// from JS with `true` + the throttle interval (ms) when a host
142
+ /// supplies `<Camera onArFrame={...}>`, and `false` on
143
+ /// unmount / prop-removal. Resolves with no value.
144
+ @objc(setArFrameMetaEnabled:intervalMs:resolver:rejecter:)
145
+ public func setArFrameMetaEnabled(
146
+ enabled: NSNumber,
147
+ intervalMs: NSNumber,
148
+ resolver: @escaping RCTPromiseResolveBlock,
149
+ rejecter: @escaping RCTPromiseRejectBlock
150
+ ) {
151
+ RNSARSession.shared.setArFrameMetaEnabled(
152
+ enabled.boolValue,
153
+ intervalMs: intervalMs.doubleValue
154
+ )
155
+ resolver(nil)
156
+ }
157
+
29
158
  @objc(isSupported:rejecter:)
30
159
  public func isSupported(
31
160
  resolver: @escaping RCTPromiseResolveBlock,
@@ -68,6 +197,47 @@ public final class RNSARSessionBridge: NSObject {
68
197
  ])
69
198
  }
70
199
 
200
+ /// Toggle ARKit scene reconstruction (LiDAR mesh / `ARMeshAnchor`s).
201
+ /// Driven by the <Camera> `enableMesh` prop; gates the
202
+ /// StitcherFrame `meshGeometry` extraction at the SESSION level
203
+ /// (the per-frame `__stitcherProxy.setExtractionConfig(...mesh)`
204
+ /// gates the marshaling — both must be on for a host to receive
205
+ /// mesh). Resolves with no value.
206
+ ///
207
+ /// Hops to the main queue: `setSceneReconstructionEnabled` may call
208
+ /// `arSession.run(config)` to reconfigure a live session, and
209
+ /// ARKit session lifecycle must run on the main thread (same
210
+ /// constraint as `start`).
211
+ @objc(setSceneReconstructionEnabled:resolver:rejecter:)
212
+ public func setSceneReconstructionEnabled(
213
+ enabled: NSNumber,
214
+ resolver: @escaping RCTPromiseResolveBlock,
215
+ rejecter: @escaping RCTPromiseRejectBlock
216
+ ) {
217
+ let on = enabled.boolValue
218
+ DispatchQueue.main.async {
219
+ RNSARSession.shared.setSceneReconstructionEnabled(on)
220
+ resolver(nil)
221
+ }
222
+ }
223
+
224
+ /// Hops to the main queue: `setPlaneDetection` may call
225
+ /// `arSession.run(config)` to reconfigure a live session, and ARKit
226
+ /// session lifecycle must run on the main thread (same constraint as
227
+ /// `start` / `setSceneReconstructionEnabled`).
228
+ @objc(setPlaneDetection:resolver:rejecter:)
229
+ public func setPlaneDetection(
230
+ mode: NSString,
231
+ resolver: @escaping RCTPromiseResolveBlock,
232
+ rejecter: @escaping RCTPromiseRejectBlock
233
+ ) {
234
+ let m = mode as String
235
+ DispatchQueue.main.async {
236
+ RNSARSession.shared.setPlaneDetection(m)
237
+ resolver(nil)
238
+ }
239
+ }
240
+
71
241
  @objc(snapshotPoseLog:rejecter:)
72
242
  public func snapshotPoseLog(
73
243
  resolver: @escaping RCTPromiseResolveBlock,
@@ -0,0 +1,108 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // CameraFrameHostObject.h — Obj-C facade for the v0.8.0
4
+ // `StitcherFrame` JSI host object. Header is intentionally
5
+ // Obj-C-only (no `<jsi/jsi.h>` import) so this can land in the
6
+ // public CocoaPods umbrella without breaking `use_frameworks!` hosts
7
+ // (same rationale as `KeyframeGateBridge.h`).
8
+ //
9
+ // The C++ JSI host object class lives in the .mm; this facade
10
+ // exposes only what cross-module callers need:
11
+ //
12
+ // - Factory `+ fromARFrame:pose:` that the AR worklet runtime
13
+ // calls per ARFrame to construct a host object backed by the
14
+ // current AR session's frame.
15
+ // - Opaque accessor `- (void *)jsiHostObjectPtr` returning the
16
+ // `std::shared_ptr<facebook::jsi::HostObject> *` (boxed) that
17
+ // the worklet runtime hands to `jsi::Object::createFromHostObject`.
18
+ //
19
+ // Lifetime: the Obj-C wrapper holds the C++ shared_ptr; ARC frees
20
+ // the wrapper when nothing references it. Worklet runtime
21
+ // invalidates the underlying ARFrame retain when the dispatch
22
+ // returns; after invalidation, JSI access throws.
23
+
24
+ #pragma once
25
+
26
+ #import <Foundation/Foundation.h>
27
+ #import <ARKit/ARKit.h>
28
+
29
+ @class RNSARFramePose;
30
+
31
+ NS_ASSUME_NONNULL_BEGIN
32
+
33
+ NS_SWIFT_NAME(CameraFrameHostObject)
34
+ @interface CameraFrameHostObject : NSObject
35
+
36
+ /// Construct a host object backed by the supplied ARFrame + pose.
37
+ /// Retains the ARFrame for the host object's lifetime — caller can
38
+ /// safely release their reference.
39
+ ///
40
+ /// Thread: safe to call from the ARSession delegate queue; the
41
+ /// resulting host object's JSI access must happen on the worklet
42
+ /// runtime's thread (separate queue).
43
+ + (instancetype)fromARFrame:(ARFrame *)arFrame pose:(RNSARFramePose *)pose;
44
+
45
+ /// Mark the host object's underlying ARFrame as no longer accessible.
46
+ /// Subsequent JSI property reads return `undefined` or throw,
47
+ /// depending on the property. Idempotent.
48
+ - (void)invalidate;
49
+
50
+ /// Opaque pointer to a `std::shared_ptr<facebook::jsi::HostObject>`.
51
+ /// The worklet runtime (Obj-C++ context with JSI available) casts
52
+ /// this back via `*reinterpret_cast<std::shared_ptr<facebook::jsi::HostObject>*>(ptr)`
53
+ /// to hand to `jsi::Object::createFromHostObject`.
54
+ ///
55
+ /// Returns `NULL` if the host object has been invalidated.
56
+ - (nullable void *)jsiHostObjectPtr;
57
+
58
+ /// Build the LIGHT per-frame AR metadata dictionary for the `onArFrame`
59
+ /// callback (the `ARFrameMeta` TS shape). Distinct from the full
60
+ /// host-object factory above: this copies NO pixel / vertex / face bytes
61
+ /// — only scalars, dimensions, anchor transforms, and mesh COUNTS — so
62
+ /// it's cheap enough to run at the throttled `onArFrame` cadence.
63
+ ///
64
+ /// Gating mirrors the full extraction path: `depth` only when the JS
65
+ /// `enableDepth` flag is on (read from the shared C++ extraction config),
66
+ /// `anchors` only when `enableAnchors`, `mesh` (counts) only when
67
+ /// `enableMesh`. `intrinsics` / `pose` / `trackingState` / `timestamp`
68
+ /// are always populated. `intrinsics` is `NSNull` only when the frame
69
+ /// reported a degenerate (zero) resolution.
70
+ ///
71
+ /// Returns a JSON-safe `NSDictionary` (NSNumber / NSString / NSArray /
72
+ /// NSDictionary / NSNull leaves) ready to hand to
73
+ /// `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", ...)`.
74
+ ///
75
+ /// Thread: safe to call from the ARSession delegate queue (reads the
76
+ /// frame synchronously; copies nothing that outlives the call).
77
+ + (NSDictionary *)lightArFrameMetaFromARFrame:(ARFrame *)arFrame
78
+ pose:(RNSARFramePose *)pose
79
+ NS_SWIFT_NAME(lightArFrameMeta(from:pose:));
80
+
81
+ /// Build the gated anchor-dictionary array for an ARFrame — the SAME
82
+ /// `[{ id, type, alignment?, extent?, classification?, transform }]`
83
+ /// shape the `onArFrame` LIGHT meta surfaces (mesh anchors excluded;
84
+ /// they're summarised under `mesh`). Returns an EMPTY array when the
85
+ /// JS `enableAnchors` flag is off (read from the shared C++ extraction
86
+ /// config) — matching the TS contract's `Array<...>` (never null).
87
+ ///
88
+ /// Extracted so BOTH the `onArFrame` light-meta path AND the v0.19.0 AR
89
+ /// plugin context (`RNISARFrameContext.anchors`) build anchors from one
90
+ /// source of truth (DRY).
91
+ ///
92
+ /// Thread: safe to call from the ARSession delegate queue (reads the
93
+ /// frame synchronously; copies nothing that outlives the call).
94
+ + (NSArray<NSDictionary *> *)arAnchorDictsFromFrame:(ARFrame *)arFrame
95
+ NS_SWIFT_NAME(arAnchorDicts(from:));
96
+
97
+ /// Whether the shared C++ extraction config currently has `enableDepth`
98
+ /// on (the `<Camera enableDepth>` prop, set via JS
99
+ /// `__stitcherProxy.setExtractionConfig`). The v0.19.0 AR plugin
100
+ /// context uses this to decide whether to expose the raw
101
+ /// `ARFrame.sceneDepth` depthBuffer to plugins — gating the config read
102
+ /// (C++) in the .mm so Swift doesn't need the C++ header.
103
+ + (BOOL)arExtractionDepthEnabled
104
+ NS_SWIFT_NAME(arExtractionDepthEnabled());
105
+
106
+ @end
107
+
108
+ NS_ASSUME_NONNULL_END