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
@@ -0,0 +1,247 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // RNISARFramePlugin — v0.19.0 native AR plugin framework (iOS).
4
+ //
5
+ // The SDK owns the ARSession and drives one per-frame callback path
6
+ // (`RNSARSession.session(_:didUpdate:)`). Host apps that need to run
7
+ // heavier native per-frame analysis (OCR, barcode reading, ML
8
+ // inference, …) register a *native* plugin against this framework — the
9
+ // SDK ships ONLY the generic plumbing; no OCR or other concrete plugin.
10
+ //
11
+ // The ergonomics mirror vision-camera's FrameProcessorPlugin
12
+ // registration: the host conforms a class to `RNISARFramePlugin`,
13
+ // registers it once at startup via `RNISARPluginRegistry.shared`, and
14
+ // the SDK calls `process(_:)` on the AR thread for every ARFrame while
15
+ // the registry is non-empty.
16
+ //
17
+ // Two result channels:
18
+ // 1. SYNC — `process(_:)` returns a light `[String: Any]?`. Non-nil
19
+ // results are folded into the throttled `onArFrame` `ARFrameMeta`
20
+ // under `plugins: { [name]: result }`, riding the existing
21
+ // `RNImageStitcherARFrame` device event. Use for cheap, per-frame
22
+ // scalars (brightness, a quick blur score, …).
23
+ // 2. ASYNC — the plugin offloads heavy work to its own queue and later
24
+ // calls `RNISARPluginRegistry.shared.emit(name, result)`, which the
25
+ // SDK re-emits as the `RNImageStitcherARPluginResult` device event
26
+ // `{ plugin, result }`. Use for OCR / ML whose latency exceeds a
27
+ // frame interval.
28
+ //
29
+ // PERFORMANCE CONTRACT: the SDK only builds the `RNISARFrameContext` and
30
+ // calls plugins when the registry is NON-EMPTY, so a zero-plugin app
31
+ // pays nothing on the AR hot path. Plugins MUST self-throttle (the SDK
32
+ // calls `process(_:)` on every ARFrame) and MUST offload anything
33
+ // heavier than a few hundred microseconds.
34
+
35
+ import Foundation
36
+ import ARKit
37
+ import CoreVideo
38
+
39
+ // MARK: - Async result event channel
40
+
41
+ /// v0.19.0 — async AR-plugin result channel. `RNISARPluginRegistry.emit`
42
+ /// posts this notification (carrying `{ plugin, result }`); the
43
+ /// `RNSARSessionBridge` (an RCTEventEmitter) observes it and re-emits as
44
+ /// the JS `RNImageStitcherARPluginResult` device event. We route via
45
+ /// NotificationCenter — rather than the registry holding a bridge
46
+ /// reference — mirroring the `.retailensARFrameMeta` (`onArFrame`)
47
+ /// channel, so the framework-free engine pattern is preserved.
48
+ public extension Notification.Name {
49
+ static let retailensARPluginResult =
50
+ Notification.Name("RNImageStitcherARPluginResult")
51
+ }
52
+
53
+
54
+ // MARK: - Plugin protocol
55
+
56
+ /// A native AR frame plugin. Host apps conform a class to this and
57
+ /// register it once via `RNISARPluginRegistry.shared.register(_:)`.
58
+ ///
59
+ /// The SDK calls `process(_:)` once per ARFrame on the AR (ARSession
60
+ /// delegate) thread while the registry is non-empty. Return a light
61
+ /// `[String: Any]?` for the SYNC channel, or `nil`; for heavy work,
62
+ /// offload to your own queue and later call
63
+ /// `RNISARPluginRegistry.shared.emit(name(), result)` for the ASYNC
64
+ /// channel.
65
+ @objc public protocol RNISARFramePlugin: AnyObject {
66
+ /// Stable identifier for this plugin. Used as the key in the
67
+ /// `onArFrame` meta's `plugins` map AND as the `plugin` field of the
68
+ /// async `RNImageStitcherARPluginResult` event. Keep it constant for
69
+ /// the plugin's lifetime; the registry stores plugins keyed by name
70
+ /// (a second `register` with the same name replaces the first).
71
+ func name() -> String
72
+
73
+ /// Called on the AR thread once per ARFrame while the registry is
74
+ /// non-empty. Return a light JSON-safe result for the SYNC channel
75
+ /// (NSNumber / NSString / NSArray / NSDictionary leaves) or `nil`.
76
+ ///
77
+ /// LIFETIME: `context.pixelBuffer` (and `context.depthBuffer`) are the
78
+ /// live ARFrame buffers — VALID ONLY for the duration of this call.
79
+ /// ARKit recycles them once `process(_:)` returns. If you offload
80
+ /// work to another thread/queue, you MUST copy the bytes you need
81
+ /// BEFORE returning. Do NOT retain the CVPixelBuffer expecting the
82
+ /// pixels to survive — a CF retain does not protect against ARKit's
83
+ /// pool reuse.
84
+ func process(_ context: RNISARFrameContext) -> [String: Any]?
85
+ }
86
+
87
+
88
+ // MARK: - Per-frame context
89
+
90
+ /// Zero-copy native view of one ARFrame, handed to each plugin's
91
+ /// `process(_:)`. Exposes the live capture buffer + pose + intrinsics +
92
+ /// (opt-in) depth + (opt-in) anchors. Nothing here is copied for the
93
+ /// plugin's benefit; the buffers belong to ARKit and are valid only
94
+ /// during the synchronous `process(_:)` call (see the lifetime note on
95
+ /// `RNISARFramePlugin.process(_:)`).
96
+ @objc(RNISARFrameContext)
97
+ public final class RNISARFrameContext: NSObject {
98
+
99
+ /// The ARFrame's `capturedImage` (BGRA/YUV `CVPixelBuffer`). VALID
100
+ /// ONLY during `process(_:)` — copy before offloading (see the
101
+ /// lifetime note on `RNISARFramePlugin.process(_:)`).
102
+ @objc public let pixelBuffer: CVPixelBuffer
103
+
104
+ /// Frame timestamp in NANOSECONDS (AR-framework monotonic clock) —
105
+ /// matches `CameraFrame.timestampNs` and the `ARFrameMeta.timestamp`
106
+ /// contract.
107
+ @objc public let timestampNs: Double
108
+
109
+ /// Camera intrinsics (pixels). `imageWidth`/`imageHeight` are the
110
+ /// capture resolution the intrinsics are expressed against.
111
+ @objc public let fx: Double
112
+ @objc public let fy: Double
113
+ @objc public let cx: Double
114
+ @objc public let cy: Double
115
+ @objc public let imageWidth: Int
116
+ @objc public let imageHeight: Int
117
+
118
+ /// World-space camera pose: rotation as a unit quaternion
119
+ /// `[x, y, z, w]` and translation `[x, y, z]` in metres (ARKit's
120
+ /// right-handed, Y-up, -Z-forward world frame).
121
+ @objc public let poseRotation: [Double]
122
+ @objc public let poseTranslation: [Double]
123
+
124
+ /// Tracking quality: `"notAvailable"` / `"limited"` / `"normal"`.
125
+ @objc public let trackingState: String
126
+
127
+ /// The ARFrame's `sceneDepth` (or `smoothedSceneDepth`) depth map
128
+ /// `CVPixelBuffer` — `nil` unless the `<Camera enableDepth>` prop is
129
+ /// on AND the device produced depth this frame. VALID ONLY during
130
+ /// `process(_:)`; copy before offloading.
131
+ @objc public let depthBuffer: CVPixelBuffer?
132
+
133
+ /// Tracking anchors as the same light dicts the `onArFrame` meta
134
+ /// surfaces (`{ id, type, alignment?, extent?, classification?,
135
+ /// transform }`; mesh anchors excluded). EMPTY unless the
136
+ /// `<Camera enableAnchors>` prop is on.
137
+ @objc public let anchors: [[String: Any]]
138
+
139
+ @objc public init(
140
+ pixelBuffer: CVPixelBuffer,
141
+ timestampNs: Double,
142
+ fx: Double, fy: Double, cx: Double, cy: Double,
143
+ imageWidth: Int, imageHeight: Int,
144
+ poseRotation: [Double],
145
+ poseTranslation: [Double],
146
+ trackingState: String,
147
+ depthBuffer: CVPixelBuffer?,
148
+ anchors: [[String: Any]]
149
+ ) {
150
+ self.pixelBuffer = pixelBuffer
151
+ self.timestampNs = timestampNs
152
+ self.fx = fx; self.fy = fy; self.cx = cx; self.cy = cy
153
+ self.imageWidth = imageWidth
154
+ self.imageHeight = imageHeight
155
+ self.poseRotation = poseRotation
156
+ self.poseTranslation = poseTranslation
157
+ self.trackingState = trackingState
158
+ self.depthBuffer = depthBuffer
159
+ self.anchors = anchors
160
+ }
161
+ }
162
+
163
+
164
+ // MARK: - Registry
165
+
166
+ /// Process-wide registry of native AR plugins + the async result router.
167
+ ///
168
+ /// The host registers plugins at startup (e.g. in the AppDelegate); the
169
+ /// SDK reads `plugins()` on the AR thread each frame. Also the entry
170
+ /// point for the ASYNC channel: a plugin calls `emit(name, result)` from
171
+ /// its own queue and the SDK re-emits a `RNImageStitcherARPluginResult`
172
+ /// JS event.
173
+ ///
174
+ /// THREAD SAFETY: registration (host/startup thread) and reads (AR
175
+ /// thread) are serialised by an internal lock. `plugins()` returns a
176
+ /// snapshot array so the AR thread can iterate without holding the lock.
177
+ @objc(RNISARPluginRegistry)
178
+ public final class RNISARPluginRegistry: NSObject {
179
+
180
+ /// Shared instance — the only way hosts register plugins.
181
+ @objc public static let shared = RNISARPluginRegistry()
182
+
183
+ /// Registered plugins, keyed by `name()` for O(1) replace/unregister.
184
+ /// Insertion order is preserved for deterministic `process(_:)`
185
+ /// ordering via a parallel ordered key list.
186
+ private var pluginsByName: [String: RNISARFramePlugin] = [:]
187
+ private var order: [String] = []
188
+ private let lock = NSLock()
189
+
190
+ private override init() { super.init() }
191
+
192
+ /// Register (or replace) a plugin. Keyed by `plugin.name()`: a
193
+ /// second register with the same name replaces the first (and keeps
194
+ /// its position in the ordering). Idempotent for the same instance.
195
+ @objc public func register(_ plugin: RNISARFramePlugin) {
196
+ let key = plugin.name()
197
+ lock.lock()
198
+ defer { lock.unlock() }
199
+ if pluginsByName[key] == nil {
200
+ order.append(key)
201
+ }
202
+ pluginsByName[key] = plugin
203
+ }
204
+
205
+ /// Remove the plugin registered under `name`. No-op if absent.
206
+ @objc public func unregister(_ name: String) {
207
+ lock.lock()
208
+ defer { lock.unlock() }
209
+ pluginsByName.removeValue(forKey: name)
210
+ order.removeAll { $0 == name }
211
+ }
212
+
213
+ /// Snapshot of registered plugins in registration order. Returns a
214
+ /// copy so the AR thread can iterate without holding the lock.
215
+ @objc public func plugins() -> [RNISARFramePlugin] {
216
+ lock.lock()
217
+ defer { lock.unlock() }
218
+ return order.compactMap { pluginsByName[$0] }
219
+ }
220
+
221
+ /// Whether any plugin is registered. Cheap gate the SDK checks per
222
+ /// ARFrame before building the (per-frame) `RNISARFrameContext`.
223
+ @objc public var isEmpty: Bool {
224
+ lock.lock()
225
+ defer { lock.unlock() }
226
+ return order.isEmpty
227
+ }
228
+
229
+ /// ASYNC channel — route a plugin's later-computed result to JS as the
230
+ /// `RNImageStitcherARPluginResult` device event `{ plugin, result }`.
231
+ /// Safe to call from any thread (the plugin's own queue); posts on
232
+ /// NotificationCenter, which the bridge observes + re-emits on the
233
+ /// main queue.
234
+ ///
235
+ /// `pluginName` should match the plugin's `name()` so JS can correlate
236
+ /// the result with its source.
237
+ @objc public func emit(_ pluginName: String, _ result: [String: Any]) {
238
+ NotificationCenter.default.post(
239
+ name: .retailensARPluginResult,
240
+ object: nil,
241
+ userInfo: [
242
+ "plugin": pluginName,
243
+ "result": result,
244
+ ]
245
+ )
246
+ }
247
+ }