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.
- package/CHANGELOG.md +241 -0
- package/android/build.gradle +35 -1
- package/android/src/main/cpp/CMakeLists.txt +64 -2
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +227 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +30 -11
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +21 -3
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +78 -3
- package/android/src/main/java/io/imagestitcher/rn/SaveFrameAsJpegPlugin.kt +162 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +103 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +256 -0
- package/android/src/main/java/io/imagestitcher/rn/TransferredNV21.kt +100 -0
- package/cpp/stitcher_frame_data.hpp +141 -0
- package/cpp/stitcher_frame_jsi.cpp +214 -0
- package/cpp/stitcher_frame_jsi.hpp +108 -0
- package/cpp/stitcher_proxy_jsi.cpp +109 -0
- package/cpp/stitcher_proxy_jsi.hpp +46 -0
- package/cpp/stitcher_worklet_dispatch.cpp +103 -0
- package/cpp/stitcher_worklet_dispatch.hpp +71 -0
- package/cpp/stitcher_worklet_registry.cpp +81 -0
- package/cpp/stitcher_worklet_registry.hpp +136 -0
- package/dist/camera/Camera.d.ts +62 -12
- package/dist/camera/Camera.js +30 -15
- package/dist/index.d.ts +6 -0
- package/dist/index.js +30 -1
- package/dist/stitching/StitcherFrame.d.ts +170 -0
- package/dist/stitching/StitcherFrame.js +4 -0
- package/dist/stitching/StitcherWorkletRegistry.d.ts +117 -0
- package/dist/stitching/StitcherWorkletRegistry.js +78 -0
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
- package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
- package/dist/stitching/useFrameProcessor.d.ts +119 -0
- package/dist/stitching/useFrameProcessor.js +196 -0
- package/dist/stitching/useFrameStream.d.ts +34 -0
- package/dist/stitching/useFrameStream.js +219 -0
- package/dist/stitching/useThrottledFrameProcessor.d.ts +33 -0
- package/dist/stitching/useThrottledFrameProcessor.js +132 -0
- package/dist/types.d.ts +87 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +46 -10
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
- package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +185 -0
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +60 -0
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +214 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +103 -0
- package/package.json +1 -1
- package/src/camera/Camera.tsx +93 -28
- package/src/index.ts +35 -0
- package/src/stitching/StitcherFrame.ts +197 -0
- package/src/stitching/StitcherWorkletRegistry.ts +156 -0
- package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +176 -0
- package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +94 -0
- package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +178 -0
- package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
- package/src/stitching/useFrameProcessor.ts +226 -0
- package/src/stitching/useFrameStream.ts +255 -0
- package/src/stitching/useThrottledFrameProcessor.ts +145 -0
- 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
|
package/dist/camera/Camera.d.ts
CHANGED
|
@@ -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.
|
|
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
|
-
*
|
|
201
|
-
* `docs/f8-frame-processor-plan.md`.
|
|
197
|
+
* ## When to set this prop
|
|
202
198
|
*
|
|
203
|
-
*
|
|
204
|
-
* `
|
|
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
|
|
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
|
package/dist/camera/Camera.js
CHANGED
|
@@ -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
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
|
|
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
|
-
&& !
|
|
448
|
-
|
|
458
|
+
&& !hostFrameProcessorAcceptedWarnedRef.current) {
|
|
459
|
+
hostFrameProcessorAcceptedWarnedRef.current = true;
|
|
449
460
|
// eslint-disable-next-line no-console
|
|
450
|
-
console.
|
|
451
|
-
+ '
|
|
452
|
-
+ '
|
|
453
|
-
+ '
|
|
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
|
-
|
|
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
|