react-native-image-stitcher 0.7.1 → 0.9.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 (58) hide show
  1. package/CHANGELOG.md +241 -0
  2. package/android/build.gradle +35 -1
  3. package/android/src/main/cpp/CMakeLists.txt +64 -2
  4. package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +227 -0
  5. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +30 -11
  6. package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +21 -3
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +78 -3
  8. package/android/src/main/java/io/imagestitcher/rn/SaveFrameAsJpegPlugin.kt +162 -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 +256 -0
  11. package/android/src/main/java/io/imagestitcher/rn/TransferredNV21.kt +100 -0
  12. package/cpp/stitcher_frame_data.hpp +141 -0
  13. package/cpp/stitcher_frame_jsi.cpp +214 -0
  14. package/cpp/stitcher_frame_jsi.hpp +108 -0
  15. package/cpp/stitcher_proxy_jsi.cpp +109 -0
  16. package/cpp/stitcher_proxy_jsi.hpp +46 -0
  17. package/cpp/stitcher_worklet_dispatch.cpp +103 -0
  18. package/cpp/stitcher_worklet_dispatch.hpp +71 -0
  19. package/cpp/stitcher_worklet_registry.cpp +81 -0
  20. package/cpp/stitcher_worklet_registry.hpp +136 -0
  21. package/dist/camera/Camera.d.ts +62 -12
  22. package/dist/camera/Camera.js +30 -15
  23. package/dist/index.d.ts +6 -0
  24. package/dist/index.js +30 -1
  25. package/dist/stitching/StitcherFrame.d.ts +170 -0
  26. package/dist/stitching/StitcherFrame.js +4 -0
  27. package/dist/stitching/StitcherWorkletRegistry.d.ts +117 -0
  28. package/dist/stitching/StitcherWorkletRegistry.js +78 -0
  29. package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
  30. package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
  31. package/dist/stitching/useFrameProcessor.d.ts +119 -0
  32. package/dist/stitching/useFrameProcessor.js +196 -0
  33. package/dist/stitching/useFrameStream.d.ts +34 -0
  34. package/dist/stitching/useFrameStream.js +219 -0
  35. package/dist/stitching/useThrottledFrameProcessor.d.ts +33 -0
  36. package/dist/stitching/useThrottledFrameProcessor.js +132 -0
  37. package/dist/types.d.ts +87 -0
  38. package/ios/Sources/RNImageStitcher/RNSARSession.swift +46 -10
  39. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
  40. package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
  41. package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +185 -0
  42. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +60 -0
  43. package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +214 -0
  44. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
  45. package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +103 -0
  46. package/package.json +1 -1
  47. package/src/camera/Camera.tsx +93 -28
  48. package/src/index.ts +35 -0
  49. package/src/stitching/StitcherFrame.ts +197 -0
  50. package/src/stitching/StitcherWorkletRegistry.ts +156 -0
  51. package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +176 -0
  52. package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +94 -0
  53. package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +178 -0
  54. package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
  55. package/src/stitching/useFrameProcessor.ts +226 -0
  56. package/src/stitching/useFrameStream.ts +255 -0
  57. package/src/stitching/useThrottledFrameProcessor.ts +145 -0
  58. package/src/types.ts +95 -0
@@ -0,0 +1,81 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // stitcher_worklet_registry.cpp — implementation of the v0.8.0
4
+ // Phase 4b native worklet registry. See header for the public
5
+ // contract + threading rules.
6
+
7
+ #include "stitcher_worklet_registry.hpp"
8
+
9
+ // Cross-platform worklets-core header include. On iOS the
10
+ // CocoaPods setup publishes worklets-core headers via
11
+ // `HEADER_SEARCH_PATHS` at the root of `Pods/Headers/Public/`,
12
+ // so `<WKTJsiWorklet.h>` works. On Android the prefab puts
13
+ // headers under a `react-native-worklets-core/` subdirectory of
14
+ // the include path (matches the prefab name). The angled
15
+ // namespace-prefixed include works on BOTH — `<x/y.h>` resolves
16
+ // to `Pods/Headers/Public/x/y.h` on iOS (CocoaPods auto-creates
17
+ // symlinked subdirs per pod) and to `build/headers/.../x/y.h` on
18
+ // Android. Pattern lifted from vc's
19
+ // `node_modules/react-native-vision-camera/android/src/main/cpp/`.
20
+ #include <react-native-worklets-core/WKTJsiWorklet.h>
21
+
22
+ #include <algorithm>
23
+ #include <sstream>
24
+
25
+ namespace retailens {
26
+
27
+ StitcherWorkletRegistry& StitcherWorkletRegistry::shared() {
28
+ static StitcherWorkletRegistry s_instance;
29
+ return s_instance;
30
+ }
31
+
32
+ std::string StitcherWorkletRegistry::install(
33
+ facebook::jsi::Runtime& mainRuntime,
34
+ const facebook::jsi::Value& workletValue) {
35
+ // Construct the invoker outside the lock — the WorkletInvoker
36
+ // constructor calls into worklets-core which acquires its own
37
+ // locks; nesting our lock around that would invite a deadlock if
38
+ // worklets-core ever called back into our code synchronously.
39
+ auto invoker = std::make_shared<RNWorklet::WorkletInvoker>(
40
+ mainRuntime, workletValue);
41
+
42
+ std::lock_guard<std::mutex> lock(_mutex);
43
+ std::ostringstream idStream;
44
+ idStream << "host-" << _nextId++;
45
+ std::string id = idStream.str();
46
+ _entries.push_back({id, std::move(invoker)});
47
+ return id;
48
+ }
49
+
50
+ void StitcherWorkletRegistry::uninstall(const std::string& id) {
51
+ std::lock_guard<std::mutex> lock(_mutex);
52
+ // erase-remove with a predicate (single-pass O(n) — n is tiny in
53
+ // practice, typically 0-3).
54
+ _entries.erase(
55
+ std::remove_if(_entries.begin(), _entries.end(),
56
+ [&id](const StitcherWorkletEntry& e) {
57
+ return e.id == id;
58
+ }),
59
+ _entries.end());
60
+ }
61
+
62
+ std::vector<StitcherWorkletEntry> StitcherWorkletRegistry::snapshot() {
63
+ std::lock_guard<std::mutex> lock(_mutex);
64
+ // Copy the vector — entries hold shared_ptrs so this is O(n)
65
+ // refcount bumps, no deep copies. Returning by value lets the
66
+ // caller iterate without holding the lock.
67
+ return _entries;
68
+ }
69
+
70
+ std::size_t StitcherWorkletRegistry::count() {
71
+ std::lock_guard<std::mutex> lock(_mutex);
72
+ return _entries.size();
73
+ }
74
+
75
+ void StitcherWorkletRegistry::_resetForTests() {
76
+ std::lock_guard<std::mutex> lock(_mutex);
77
+ _entries.clear();
78
+ _nextId = 0;
79
+ }
80
+
81
+ } // namespace retailens
@@ -0,0 +1,136 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // stitcher_worklet_registry.hpp — process-scope native registry of
4
+ // host-supplied worklets (v0.8.0 Phase 4b).
5
+ //
6
+ // ## What this is
7
+ //
8
+ // The native-side counterpart to the JS-side `StitcherWorkletRegistry`
9
+ // singleton (`src/stitching/StitcherWorkletRegistry.ts`). When the
10
+ // public `useFrameProcessor` hook is mounted from JS, it calls into a
11
+ // JSI installable (`globalThis.__stitcherProxy.install(workletFn)`)
12
+ // that wraps the worklet's JSI value into a
13
+ // `RNWorklet::WorkletInvoker` and stores it here. Unmount calls
14
+ // `__stitcherProxy.uninstall(id)` which removes the entry.
15
+ //
16
+ // The AR worklet runtime's per-frame dispatch (`RNSARWorkletRuntime::
17
+ // dispatchFrame:pose:` on iOS) reads from this registry to fan out
18
+ // invocations across all registered host worklets. The vc-mode
19
+ // path (non-AR) does NOT touch this registry — vision-camera owns
20
+ // the Frame Processor runtime in that mode and our public hook
21
+ // passes the worklet through to vc unchanged.
22
+ //
23
+ // ## Threading
24
+ //
25
+ // `install` / `uninstall` are called from the main JS thread
26
+ // (`useFrameProcessor`'s `useEffect` body).
27
+ //
28
+ // `snapshot` is called from the AR session callback thread (the
29
+ // caller thread of `RNSARWorkletRuntime::dispatchFrame:pose:`). The
30
+ // snapshot returns shared_ptrs, so even if `uninstall` races on the
31
+ // JS thread between the snapshot and the worklet runtime's
32
+ // invocation, the WorkletInvoker stays alive until the caller drops
33
+ // the shared_ptr. WorkletInvoker itself does NOT need its caller
34
+ // to live on a particular thread — the `call` method takes the
35
+ // target `jsi::Runtime&` as an argument, so callers from any
36
+ // thread can invoke it on any runtime they own.
37
+ //
38
+ // Mutation is serialised through `_mutex` (std::mutex). Reads via
39
+ // `snapshot` lock briefly to copy the entry vector; that's microseconds
40
+ // at most (registry typically has 0-3 entries). No worklet invocation
41
+ // happens under the lock.
42
+ //
43
+ // ## Lifetime
44
+ //
45
+ // The registry is a `static`-local singleton, constructed on first
46
+ // `shared()` call (function-static init = thread-safe per the C++11
47
+ // memory model). It outlives every JS / native runtime in the
48
+ // process. Entries are only added by `install` and only removed by
49
+ // `uninstall` — no GC, no weak refs. Hosts that bypass the
50
+ // `useFrameProcessor` hook and call `install` directly without ever
51
+ // calling `uninstall` leak entries (matches the JS-side singleton's
52
+ // contract).
53
+ //
54
+ // ## Why a singleton (not per-runtime / per-AR-session)
55
+ //
56
+ // The AR worklet runtime (`RNSARWorkletRuntime` / `StitcherWorkletRuntime`)
57
+ // is itself a process-scope singleton. The hook lifecycle is
58
+ // per-component-mount. Registering against a per-AR-session registry
59
+ // would mean re-registering each time AR mode starts — but the host
60
+ // worklet's identity hasn't changed. Process-scope = "registry
61
+ // matches the host's mental model of `useFrameProcessor` semantics".
62
+
63
+ #pragma once
64
+
65
+ #include <jsi/jsi.h>
66
+
67
+ #include <memory>
68
+ #include <mutex>
69
+ #include <string>
70
+ #include <utility>
71
+ #include <vector>
72
+
73
+ // Forward-declare to avoid pulling the whole worklets-core header
74
+ // into every translation unit that just needs to hold an invoker
75
+ // pointer. The .cpp includes the full header.
76
+ namespace RNWorklet {
77
+ class WorkletInvoker;
78
+ }
79
+
80
+ namespace retailens {
81
+
82
+ /// One registered host worklet. Public so callers iterating via
83
+ /// `snapshot` can read both the ID and the invoker.
84
+ struct StitcherWorkletEntry {
85
+ std::string id;
86
+ std::shared_ptr<RNWorklet::WorkletInvoker> invoker;
87
+ };
88
+
89
+ class StitcherWorkletRegistry {
90
+ public:
91
+ /// Process-scope singleton. Thread-safe lazy init via C++11
92
+ /// function-static.
93
+ static StitcherWorkletRegistry& shared();
94
+
95
+ /// Install a host worklet. Wraps the JSI value (the worklet's
96
+ /// `'worklet'`-decorated function from the main JS runtime) into
97
+ /// a `WorkletInvoker` and stores it. Returns a stable string ID
98
+ /// the caller passes to `uninstall`.
99
+ ///
100
+ /// Thread: must be called from a thread that owns `mainRuntime`
101
+ /// (typically the main JS thread). The wrapped `WorkletInvoker`
102
+ /// can then be invoked from any thread on any runtime via
103
+ /// `call(rt, ...)`.
104
+ std::string install(facebook::jsi::Runtime& mainRuntime,
105
+ const facebook::jsi::Value& workletValue);
106
+
107
+ /// Remove a previously-installed entry by ID. No-op for unknown
108
+ /// IDs (matches the JS-side `StitcherWorkletRegistry` semantics).
109
+ void uninstall(const std::string& id);
110
+
111
+ /// Snapshot the current entries. The returned vector holds
112
+ /// shared_ptrs to `WorkletInvoker`; mutations against the
113
+ /// registry after `snapshot` returns do not affect the snapshot.
114
+ /// Callers iterate without holding the registry lock.
115
+ std::vector<StitcherWorkletEntry> snapshot();
116
+
117
+ /// Current entry count. Used by `dispatchFrame:` for the
118
+ /// fast-path early-exit (no fan-out cost when no host worklets
119
+ /// are registered).
120
+ std::size_t count();
121
+
122
+ /// Test-only — clear all entries. Used by C++ unit tests; not
123
+ /// exposed through the JSI surface.
124
+ void _resetForTests();
125
+
126
+ private:
127
+ StitcherWorkletRegistry() = default;
128
+ StitcherWorkletRegistry(const StitcherWorkletRegistry&) = delete;
129
+ StitcherWorkletRegistry& operator=(const StitcherWorkletRegistry&) = delete;
130
+
131
+ std::mutex _mutex;
132
+ std::vector<StitcherWorkletEntry> _entries;
133
+ int _nextId = 0;
134
+ };
135
+
136
+ } // namespace retailens
@@ -192,21 +192,71 @@ export interface CameraProps {
192
192
  onFramesDropped?: (info: FramesDroppedInfo) => void;
193
193
  onError?: (err: CameraError) => void;
194
194
  /**
195
- * Optional vision-camera frame processor. Only attached to the
196
- * non-AR preview (AR mode uses ARCameraView, which doesn't expose
197
- * a worklet seam). Build the worklet on the host side with
198
- * `useFrameProcessor` from `react-native-vision-camera`.
195
+ * Optional host-supplied vision-camera frame processor.
199
196
  *
200
- * Introduced for F8 (FrameProcessor port) — see
201
- * `docs/f8-frame-processor-plan.md`.
197
+ * ## When to set this prop
202
198
  *
203
- * The SDK installs its own frame processor via
204
- * `useFrameProcessorDriver`. Setting this prop is ignored with
205
- * a one-time `console.warn` — supplying a host worklet would
206
- * race with the SDK's pixel-buffer feed. Either remove the prop
207
- * or fork the SDK if you genuinely need a custom worklet.
199
+ * v0.8.0+ canonical answer: use the lib's own `useFrameProcessor`
200
+ * hook, NOT `react-native-vision-camera`'s. The lib's hook:
208
201
  *
209
- * AR mode is irrelevant: vision-camera's Camera isn't mounted.
202
+ * - **AR mode**: auto-registers the worklet in the native
203
+ * `__stitcherProxy` registry; the AR session's per-frame
204
+ * dispatch fans out to it alongside the lib's first-party
205
+ * stitching. No prop wiring needed — just mount the hook
206
+ * anywhere in the tree.
207
+ * - **Non-AR mode**: returns a vc processor object that this
208
+ * prop accepts. Wiring it through enables the host's
209
+ * worklet to fire on vc's Frame Processor runtime.
210
+ *
211
+ * ```tsx
212
+ * import { Camera, useFrameProcessor, type StitcherFrame }
213
+ * from 'react-native-image-stitcher';
214
+ *
215
+ * function MyScreen() {
216
+ * const fp = useFrameProcessor((frame: StitcherFrame) => {
217
+ * 'worklet';
218
+ * // ...
219
+ * }, []);
220
+ * return <Camera frameProcessor={fp} ... />;
221
+ * }
222
+ * ```
223
+ *
224
+ * ## Non-AR mode tradeoff (HONEST)
225
+ *
226
+ * vision-camera's `<Camera>` accepts ONLY ONE frame processor.
227
+ * The lib's internal `useFrameProcessorDriver` produces the
228
+ * processor that drives first-party panorama stitching in non-AR
229
+ * mode. If you supply your own via this prop, **the lib's
230
+ * first-party stitching is replaced** — panorama capture in
231
+ * non-AR mode will not produce stitched output until you remove
232
+ * the prop or fork the SDK to compose both worklets manually.
233
+ *
234
+ * For the common case (host wants worklet + lib wants stitching
235
+ * concurrently), prefer AR mode: the AR-mode path natively fans
236
+ * out to both the lib's first-party stitching AND every
237
+ * registered host worklet on every frame, with per-worklet
238
+ * failure isolation.
239
+ *
240
+ * Composition for non-AR mode (lib stitching + host worklet on
241
+ * the same vc processor) is tracked as a v0.9+ follow-up;
242
+ * needs the lib's first-party logic exposed as a vc Frame
243
+ * Processor plugin the host's worklet can call.
244
+ *
245
+ * ## AR mode behaviour
246
+ *
247
+ * In AR mode (`defaultCaptureSource="ar"` or runtime-toggled),
248
+ * vc's `<Camera>` isn't mounted; this prop has no effect.
249
+ * Host worklets registered via the lib's `useFrameProcessor`
250
+ * fire automatically through the AR-session dispatch path
251
+ * (iOS Phase 4b.i / Android Phase 4b.iii).
252
+ *
253
+ * ## Backwards compatibility
254
+ *
255
+ * The pre-v0.8.0 behaviour (warn + ignore) is preserved when the
256
+ * supplied processor is recognisably from
257
+ * `react-native-vision-camera`'s `useFrameProcessor` directly
258
+ * (no `__stitcherFrame` marker). Hosts should migrate to the
259
+ * lib's `useFrameProcessor` to benefit from AR-mode dispatch.
210
260
  *
211
261
  * (v0.5 had a `legacyDriver` escape hatch that routed back to
212
262
  * `useIncrementalJSDriver`. That hook + prop were removed in
@@ -435,25 +435,40 @@ function Camera(props) {
435
435
  // Safety: stop the driver if the component unmounts mid-recording.
436
436
  // eslint-disable-next-line react-hooks/exhaustive-deps
437
437
  (0, react_1.useEffect)(() => () => { fpDriver.stop(); }, []);
438
- // One-shot deprecation warning when the host supplies their own
439
- // `frameProcessor` prop. Two worklets racing on the same
440
- // producer thread would corrupt the engine's workQueue ordering,
441
- // so the SDK's own worklet wins and the host's is silently
442
- // ignored. (v0.5 had a `legacyDriver` opt-out for hosts that
443
- // wanted to route around the SDK driver; that was removed in
444
- // v0.6 along with `useIncrementalJSDriver`.)
445
- const hostFrameProcessorIgnoredWarnedRef = (0, react_1.useRef)(false);
438
+ // v0.8.0 Phase 5 frameProcessor prop semantics:
439
+ //
440
+ // - Host supplied? use host's processor; lib's first-party
441
+ // stitching is DISABLED in non-AR mode (vc accepts only one
442
+ // processor). One-shot console.info documents the tradeoff
443
+ // so the host isn't surprised by "panorama capture stopped
444
+ // producing output" in non-AR mode. AR-mode capture is
445
+ // unaffected the AR-session dispatch path fans out to BOTH
446
+ // first-party and host worklets independently.
447
+ //
448
+ // - No host processor? → use `fpDriver.frameProcessor` which is
449
+ // the lib's internal worklet driving first-party stitching
450
+ // via `useFrameProcessorDriver`. Default behaviour for the
451
+ // common "I just want panorama capture" case.
452
+ //
453
+ // The pre-v0.8.0 behaviour (host's prop silently ignored with
454
+ // a warning) is gone — Phase 5 plumbs the prop through. The
455
+ // tradeoff is honestly documented in the CameraProps docstring.
456
+ const hostFrameProcessorAcceptedWarnedRef = (0, react_1.useRef)(false);
446
457
  if (hostFrameProcessor != null
447
- && !hostFrameProcessorIgnoredWarnedRef.current) {
448
- hostFrameProcessorIgnoredWarnedRef.current = true;
458
+ && !hostFrameProcessorAcceptedWarnedRef.current) {
459
+ hostFrameProcessorAcceptedWarnedRef.current = true;
449
460
  // eslint-disable-next-line no-console
450
- console.warn('[react-native-image-stitcher] The `frameProcessor` prop on '
451
- + '<Camera> is ignored the SDK installs its own worklet '
452
- + 'via useFrameProcessorDriver. Remove the prop, or fork '
453
- + 'the SDK if you genuinely need a custom worklet.');
461
+ console.info('[react-native-image-stitcher] Host frameProcessor supplied '
462
+ + 'non-AR mode will run YOUR worklet instead of the lib\'s '
463
+ + 'first-party stitching plugin (vc accepts only one frame '
464
+ + 'processor). Non-AR panorama capture will not produce '
465
+ + 'stitched output until this prop is removed. AR-mode '
466
+ + 'capture is unaffected (AR-session dispatch fans out to '
467
+ + 'both first-party and host worklets independently).');
454
468
  }
455
469
  // The Frame Processor worklet bound to vision-camera's Camera.
456
- const effectiveFrameProcessor = fpDriver.frameProcessor;
470
+ // Host's wins if supplied; lib's internal driver otherwise.
471
+ const effectiveFrameProcessor = hostFrameProcessor ?? fpDriver.frameProcessor;
457
472
  // ── Subscribe to engine state for live keyframe thumbs ──────────
458
473
  (0, react_1.useEffect)(() => {
459
474
  const sub = (0, incremental_1.subscribeIncrementalState)((state) => {
package/dist/index.d.ts CHANGED
@@ -65,6 +65,12 @@ export { IncrementalOutcome, incrementalStitcherIsAvailable, subscribeIncrementa
65
65
  export type { IncrementalState, AcceptedKeyframe } from './stitching/incremental';
66
66
  export { useIncrementalStitcher } from './stitching/useIncrementalStitcher';
67
67
  export { useKeyframeStream } from './stitching/useKeyframeStream';
68
+ export type { StitcherFrame, StitcherFrameProcessor, ARAnchor, } from './stitching/StitcherFrame';
69
+ export { useFrameProcessor } from './stitching/useFrameProcessor';
70
+ export { useThrottledFrameProcessor } from './stitching/useThrottledFrameProcessor';
71
+ export type { ThrottledFrameProcessorOptions } from './types';
72
+ export { useFrameStream } from './stitching/useFrameStream';
73
+ export type { FrameStreamOptions, SampledFrame } from './types';
68
74
  export { useFrameProcessorDriver } from './stitching/useFrameProcessorDriver';
69
75
  export type { UseFrameProcessorDriverOptions, FrameProcessorDriverHandle, } from './stitching/useFrameProcessorDriver';
70
76
  export { stitchVideo } from './stitching/stitchVideo';
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@
22
22
  * adds RetaiLens-specific features on top.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.stitchVideo = exports.useFrameProcessorDriver = exports.useKeyframeStream = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.hybridSettingsToNativeConfig = exports.slitscanSettingsToNativeConfig = exports.panoramaSettingsToNativeConfig = exports.DEFAULT_HYBRID_SETTINGS = exports.DEFAULT_SLITSCAN_SETTINGS = exports.DEFAULT_FLOW_GATE_SETTINGS = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaGuidance = exports.PanoramaBandOverlay = exports.IncrementalPanGuide = exports.CaptureThumbnailStrip = exports.useStitchStatsToast = exports.CaptureStitchStatsToast = exports.CaptureOrientationPill = exports.CaptureKeyframePill = exports.CaptureMemoryPill = exports.CaptureDebugOverlay = exports.CaptureStatusOverlay = exports.CapturePreview = exports.CaptureControlsBar = exports.CaptureHeader = exports.CameraView = exports.ARCameraView = exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
25
+ exports.stitchVideo = exports.useFrameProcessorDriver = exports.useFrameStream = exports.useThrottledFrameProcessor = exports.useFrameProcessor = exports.useKeyframeStream = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.hybridSettingsToNativeConfig = exports.slitscanSettingsToNativeConfig = exports.panoramaSettingsToNativeConfig = exports.DEFAULT_HYBRID_SETTINGS = exports.DEFAULT_SLITSCAN_SETTINGS = exports.DEFAULT_FLOW_GATE_SETTINGS = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaGuidance = exports.PanoramaBandOverlay = exports.IncrementalPanGuide = exports.CaptureThumbnailStrip = exports.useStitchStatsToast = exports.CaptureStitchStatsToast = exports.CaptureOrientationPill = exports.CaptureKeyframePill = exports.CaptureMemoryPill = exports.CaptureDebugOverlay = exports.CaptureStatusOverlay = exports.CapturePreview = exports.CaptureControlsBar = exports.CaptureHeader = exports.CameraView = exports.ARCameraView = exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
26
26
  // ─────────────────────────────────────────────────────────────────────
27
27
  // Layer 1 — the high-level <Camera> component
28
28
  // ─────────────────────────────────────────────────────────────────────
@@ -145,6 +145,35 @@ Object.defineProperty(exports, "useIncrementalStitcher", { enumerable: true, get
145
145
  // keyframe, packet detection, server-side analysis, etc.).
146
146
  var useKeyframeStream_1 = require("./stitching/useKeyframeStream");
147
147
  Object.defineProperty(exports, "useKeyframeStream", { enumerable: true, get: function () { return useKeyframeStream_1.useKeyframeStream; } });
148
+ // v0.8.0 Phase 4a — public host-worklet hook. Hosts that want a
149
+ // per-frame callback (OCR overlay, packet detection, ML inference)
150
+ // use this to attach a `'worklet'`-prefixed function that fires
151
+ // on the camera producer thread. Non-AR mode is fully wired
152
+ // today via vision-camera passthrough; AR-mode dispatch is
153
+ // API-stable but registration-only until Phase 4b lands the
154
+ // cross-runtime handoff (the AR runtime iterating the registry).
155
+ // See the hook's docstring + StitcherFrame.ts for the contract.
156
+ var useFrameProcessor_1 = require("./stitching/useFrameProcessor");
157
+ Object.defineProperty(exports, "useFrameProcessor", { enumerable: true, get: function () { return useFrameProcessor_1.useFrameProcessor; } });
158
+ // v0.9.0 Layer 2 — `useThrottledFrameProcessor`. Throttle gate over
159
+ // `useFrameProcessor` for sub-frame-rate worklet-native processing
160
+ // (native OCR via Vision.framework / ML Kit, TFLite ML detection,
161
+ // LiDAR depth). The worklet runtime has direct access to
162
+ // `frame.toArrayBuffer()` / `frame.arDepth`; bridge small payloads
163
+ // (bboxes, depth-derived metrics) to JS via `runOnJS`. For JS-thread
164
+ // JPEG consumers (file-path OCR libs, cloud upload, thumbnail UI),
165
+ // prefer `useFrameStream` (Layer 3, ships in the same release).
166
+ var useThrottledFrameProcessor_1 = require("./stitching/useThrottledFrameProcessor");
167
+ Object.defineProperty(exports, "useThrottledFrameProcessor", { enumerable: true, get: function () { return useThrottledFrameProcessor_1.useThrottledFrameProcessor; } });
168
+ // v0.9.0 Layer 3 — `useFrameStream`. JS-thread sampled-frame
169
+ // stream over Layer 1 (`save_frame_as_jpeg` vc plugin) + Layer 2
170
+ // (`useThrottledFrameProcessor`). Use for JS-thread consumers:
171
+ // file-path OCR libs (RN modules), cloud upload, thumbnail UI.
172
+ // For worklet-native processing (Vision/ML Kit as vc plugins,
173
+ // TFLite ML, LiDAR depth), prefer `useThrottledFrameProcessor`
174
+ // (Layer 2) — lower latency, no JPEG roundtrip.
175
+ var useFrameStream_1 = require("./stitching/useFrameStream");
176
+ Object.defineProperty(exports, "useFrameStream", { enumerable: true, get: function () { return useFrameStream_1.useFrameStream; } });
148
177
  // vision-camera Frame Processor driver for non-AR captures. As
149
178
  // of v0.6 the only non-AR driver exported (the legacy
150
179
  // `useIncrementalJSDriver` was removed; was deprecated in v0.5).
@@ -0,0 +1,170 @@
1
+ /**
2
+ * v0.8.0 — unified frame contract for the lib's worklet processor.
3
+ *
4
+ * Worklets registered via the v0.8.0 `useFrameProcessor` hook (also in
5
+ * this directory) receive a `StitcherFrame` regardless of capture mode.
6
+ * The lib-owned worklet runtime guarantees the same JS-visible shape
7
+ * whether the underlying source is a vision-camera `Frame` (non-AR
8
+ * mode, sourced from the FP plugin) or an ARKit `ARFrame` / ARCore
9
+ * `Frame` (AR mode, sourced from a lib-managed delegate that the AR
10
+ * worklet runtime drives).
11
+ *
12
+ * ## Why structural (NOT `extends Frame`)
13
+ *
14
+ * vision-camera's iOS `Frame` is `CMSampleBufferRef`-shaped; ARFrame's
15
+ * `capturedImage` (a `CVPixelBufferRef`) can be wrapped into one
16
+ * (Phase-0 audit confirmed the iOS path). But vision-camera's
17
+ * **Android** `Frame` is `androidx.camera.core.ImageProxy`-coupled —
18
+ * ARCore does NOT produce `ImageProxy` instances. Forcing
19
+ * `StitcherFrame extends Frame` would either (a) require reverse-
20
+ * engineering ImageProxy on Android (intractable + fragile), or
21
+ * (b) make the type asymmetric per platform. Both are worse than
22
+ * making `StitcherFrame` a structural sibling type that vc Frames
23
+ * happen to satisfy (because vc Frames carry the same width / height /
24
+ * orientation / pixelFormat / timestamp / toArrayBuffer surface).
25
+ *
26
+ * The `__source: 'vc' | 'ar'` discriminator lets worklets gate on
27
+ * mode without a typeof / try-catch dance — e.g., skip work that
28
+ * needs AR tracking state when the source is `'vc'`.
29
+ *
30
+ * ## Buffer lifetime
31
+ *
32
+ * The underlying camera buffer (CMSampleBufferRef / ImageProxy /
33
+ * ARFrame.capturedImage) is valid only for the duration of the worklet
34
+ * call. Worklets that need to retain frame data MUST copy
35
+ * synchronously inside the worklet body (via `toArrayBuffer()` or via
36
+ * a JPEG-encode frame-processor plugin). Returning a reference and
37
+ * reading it later will read into freed memory.
38
+ */
39
+ export interface StitcherFrame {
40
+ /** Pixel width of the camera image. */
41
+ width: number;
42
+ /** Pixel height of the camera image. */
43
+ height: number;
44
+ /**
45
+ * Pixel format identifier. Both modes today emit `'yuv'` (NV12 on
46
+ * iOS, NV21 on Android). Other vision-camera formats may appear
47
+ * in future releases.
48
+ *
49
+ * **`'unknown'` semantics:** the lib reached a code path that
50
+ * doesn't recognise the underlying camera buffer's pixel format
51
+ * (e.g., a future ARKit version emits BGRA when historically it
52
+ * only emitted NV12). Worklets that depend on a known layout
53
+ * should treat `'unknown'` as "skip this frame". `toArrayBuffer()`
54
+ * still returns bytes when the format is `'unknown'`, but the
55
+ * layout is undefined — the bytes are the underlying buffer's
56
+ * first plane and may not be interpretable. When this happens
57
+ * the native side also emits an `os_log` / logcat warning.
58
+ */
59
+ pixelFormat: 'yuv' | 'rgb' | 'unknown';
60
+ /**
61
+ * Display orientation tag, matching vision-camera's
62
+ * `Frame.orientation`.
63
+ *
64
+ * **AR-mode limitation (v0.8.0):** AR-source frames return only
65
+ * the coarse two-value set `'landscape-right' | 'portrait'` (the
66
+ * lib reads `pose.imageWidth >= pose.imageHeight` as the
67
+ * discriminator since ARKit's `capturedImage` is always in the
68
+ * camera's native landscape-right orientation regardless of
69
+ * device pose). Worklets that need to distinguish
70
+ * `landscape-left` (upside-down landscape) or
71
+ * `portrait-upside-down` should consult device-orientation sensors
72
+ * separately while running in AR mode. Non-AR frames (vc source)
73
+ * return the full four-value set. Fixing the AR side requires
74
+ * threading `UIDevice.current.orientation` through; deferred to
75
+ * v0.8.1+ unless a consumer hits it.
76
+ */
77
+ orientation: 'portrait' | 'portrait-upside-down' | 'landscape-left' | 'landscape-right';
78
+ /**
79
+ * Monotonic timestamp in **nanoseconds** (matches vision-camera's
80
+ * `Frame.timestamp` convention). Use timestamp deltas for
81
+ * inter-frame timing; the absolute value is implementation-defined
82
+ * and not comparable to `Date.now()`.
83
+ */
84
+ timestamp: number;
85
+ /**
86
+ * Copies the underlying pixel buffer into a JSI `ArrayBuffer`.
87
+ * Worklet-callable. Allocates O(width × height × bytesPerPixel)
88
+ * each call — avoid in tight inner loops; prefer plugin-side
89
+ * processing where possible.
90
+ */
91
+ toArrayBuffer(): ArrayBuffer;
92
+ /**
93
+ * Camera pose at frame-capture time. Always present.
94
+ *
95
+ * Rotation quaternion order is `(x, y, z, w)`; the lib uses
96
+ * `q = q_yaw * q_pitch * q_roll` throughout the engine + sensor
97
+ * fusion. Same convention surfaced by the v0.7.0
98
+ * `AcceptedKeyframe.pose` field.
99
+ *
100
+ * Translation is metres in world coordinates. Populated by AR
101
+ * mode (real ARKit / ARCore camera transform); undefined in
102
+ * non-AR mode (gyro provides only rotation — no spatial anchor).
103
+ */
104
+ pose: {
105
+ rotation: [number, number, number, number];
106
+ translation?: [number, number, number];
107
+ };
108
+ /**
109
+ * Discriminator for the frame source. Worklets branch on this to
110
+ * gate AR-only field access without try/catch. Standard TS
111
+ * discriminated-union pattern.
112
+ *
113
+ * - `'vc'` — vision-camera Frame Processor (non-AR mode)
114
+ * - `'ar'` — AR-session frame (AR mode); `arDepth` / `arAnchors` /
115
+ * `arTrackingState` fields may be populated
116
+ */
117
+ source: 'vc' | 'ar';
118
+ /**
119
+ * Depth data when available — AR mode + a device that supports
120
+ * the AR framework's depth API (iPhone Pro LiDAR; ARCore Depth
121
+ * API on supported Android devices).
122
+ *
123
+ * Resolution is typically lower than the camera image (e.g.,
124
+ * 256×192 on iPhone Pro LiDAR). `confidenceMap` is per-pixel:
125
+ * `0` = low, `1` = medium, `2` = high confidence. `Float32`
126
+ * depth in metres; `Uint8` confidence.
127
+ */
128
+ arDepth?: {
129
+ width: number;
130
+ height: number;
131
+ depthMap: ArrayBuffer;
132
+ confidenceMap?: ArrayBuffer;
133
+ };
134
+ /**
135
+ * Tracked AR anchors visible in this frame. Empty array if AR
136
+ * is active but no anchors are tracked. Undefined in non-AR mode.
137
+ */
138
+ arAnchors?: ARAnchor[];
139
+ /**
140
+ * AR tracking quality. Worklets that should skip work when
141
+ * tracking is degraded check this. Undefined in non-AR mode.
142
+ */
143
+ arTrackingState?: 'notAvailable' | 'limited' | 'normal';
144
+ }
145
+ /**
146
+ * v0.8.0 — public AR anchor type. Subset of ARKit/ARCore anchor info
147
+ * exposed to JS worklets. Extend with plane-extent / image-name
148
+ * fields as the JSI binding learns them.
149
+ */
150
+ export interface ARAnchor {
151
+ /** Stable per-session anchor identifier. */
152
+ id: string;
153
+ /** Anchor kind. `'point'` is Android (ARCore) only. */
154
+ type: 'plane' | 'image' | 'point';
155
+ /**
156
+ * 4×4 row-major transform from anchor space to world space.
157
+ * 16 numbers.
158
+ */
159
+ transform: number[];
160
+ }
161
+ /**
162
+ * v0.8.0 — worklet function signature for the unified frame processor.
163
+ *
164
+ * Must be a `'worklet'`-prefixed function (so it can run on the
165
+ * worklet runtime). Receives a `StitcherFrame` per camera frame; the
166
+ * return value is ignored (use `runOnJS` / shared values to surface
167
+ * results back to the JS thread).
168
+ */
169
+ export type StitcherFrameProcessor = (frame: StitcherFrame) => void;
170
+ //# sourceMappingURL=StitcherFrame.d.ts.map
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ //# sourceMappingURL=StitcherFrame.js.map