react-native-image-stitcher 0.7.0 → 0.8.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 +180 -1
- 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 +4 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +78 -3
- 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 +2 -0
- package/dist/index.js +11 -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/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/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 +16 -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/ensureStitcherProxyInstalled.ts +141 -0
- package/src/stitching/useFrameProcessor.ts +226 -0
|
@@ -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,117 @@
|
|
|
1
|
+
import type { StitcherFrameProcessor } from './StitcherFrame';
|
|
2
|
+
/**
|
|
3
|
+
* v0.8.0 Phase 4a — process-scope registry of host-supplied worklets
|
|
4
|
+
* that the v0.8.0 `useFrameProcessor` hook registers into.
|
|
5
|
+
*
|
|
6
|
+
* ## What this is (Phase 4a)
|
|
7
|
+
*
|
|
8
|
+
* A plain JS singleton holding an ordered list of registered
|
|
9
|
+
* worklets. Hosts mount the `useFrameProcessor` hook (in this
|
|
10
|
+
* directory); the hook registers its worklet into this singleton
|
|
11
|
+
* on mount and unregisters on unmount. Each entry carries:
|
|
12
|
+
*
|
|
13
|
+
* - `id`: stable identifier issued by `register`; passed to
|
|
14
|
+
* `unregister`.
|
|
15
|
+
* - `worklet`: the host's `StitcherFrameProcessor` function.
|
|
16
|
+
* MUST be `'worklet'`-prefixed at the call site (TS can't
|
|
17
|
+
* enforce that — convention).
|
|
18
|
+
* - `isFirstParty`: `false` for host-supplied worklets;
|
|
19
|
+
* reserved for the lib's own first-party stitching path which
|
|
20
|
+
* today is wired natively (not through this registry).
|
|
21
|
+
*
|
|
22
|
+
* Order is stable: first-party entries (none in Phase 4a) come
|
|
23
|
+
* first, then host entries by registration order. Re-registration
|
|
24
|
+
* of the same worklet by identity yields a new entry — hosts that
|
|
25
|
+
* re-render and call `register` again ARE responsible for calling
|
|
26
|
+
* `unregister` first. The `useFrameProcessor` hook handles this
|
|
27
|
+
* via its `deps` dependency array.
|
|
28
|
+
*
|
|
29
|
+
* ## What this is NOT (Phase 4b)
|
|
30
|
+
*
|
|
31
|
+
* **The native AR worklet runtime does NOT yet read this registry.**
|
|
32
|
+
* Worklets registered here for AR-mode captures will not fire
|
|
33
|
+
* until Phase 4b lands the cross-runtime handoff (a
|
|
34
|
+
* worklets-core `SharedValue` mirror that `RNSARWorkletRuntime`
|
|
35
|
+
* reads on each `dispatchFrame:pose:` call; the runtime then
|
|
36
|
+
* constructs a `StitcherFrameHostObject` + invokes each
|
|
37
|
+
* registered worklet via `RNWorklet::WorkletInvoker::call`).
|
|
38
|
+
*
|
|
39
|
+
* In non-AR mode the host-supplied worklet IS invoked, but via
|
|
40
|
+
* vision-camera's Frame Processor runtime directly (the
|
|
41
|
+
* `useFrameProcessor` hook returns vc's processor object which
|
|
42
|
+
* `<Camera>` passes to vision-camera). So Phase 4a's public API
|
|
43
|
+
* is fully functional for non-AR; AR is API-stable but
|
|
44
|
+
* runtime-deferred.
|
|
45
|
+
*
|
|
46
|
+
* ## Singleton lifetime
|
|
47
|
+
*
|
|
48
|
+
* The registry is a module-level instance. It lives for the
|
|
49
|
+
* lifetime of the JS runtime (= until app reload). Entries
|
|
50
|
+
* accumulate only via `register` and shed only via `unregister`
|
|
51
|
+
* — no GC / weak-ref logic. Hosts that mount `useFrameProcessor`
|
|
52
|
+
* inside React components MUST rely on the hook's effect cleanup
|
|
53
|
+
* to unregister on unmount, or they'll leak entries until
|
|
54
|
+
* reload. The hook handles this correctly today.
|
|
55
|
+
*
|
|
56
|
+
* ## Why a singleton (vs context provider)
|
|
57
|
+
*
|
|
58
|
+
* The native AR worklet runtime is itself a process-scope
|
|
59
|
+
* singleton (`RNSARWorkletRuntime`, `StitcherWorkletRuntime`).
|
|
60
|
+
* The Phase 4b handoff between TS and native is necessarily
|
|
61
|
+
* process-scope. Wrapping the registry in a React context
|
|
62
|
+
* would force every consumer to be in the same provider tree
|
|
63
|
+
* which is friction for layer-2 hosts that compose
|
|
64
|
+
* `<ARCameraView>` / `useIncrementalStitcher` themselves. The
|
|
65
|
+
* singleton is the right shape; the React-level ergonomics are
|
|
66
|
+
* provided by the `useFrameProcessor` hook.
|
|
67
|
+
*/
|
|
68
|
+
export interface StitcherWorkletEntry {
|
|
69
|
+
readonly id: string;
|
|
70
|
+
readonly worklet: StitcherFrameProcessor;
|
|
71
|
+
readonly isFirstParty: boolean;
|
|
72
|
+
}
|
|
73
|
+
declare class Registry {
|
|
74
|
+
private entries;
|
|
75
|
+
private nextHostCounter;
|
|
76
|
+
/**
|
|
77
|
+
* Register a worklet. Returns a stable ID for `unregister`.
|
|
78
|
+
*
|
|
79
|
+
* Entries are appended in registration order; first-party
|
|
80
|
+
* entries (if any are added in future) sort to the front.
|
|
81
|
+
*/
|
|
82
|
+
register(opts: {
|
|
83
|
+
worklet: StitcherFrameProcessor;
|
|
84
|
+
isFirstParty?: boolean;
|
|
85
|
+
}): string;
|
|
86
|
+
/**
|
|
87
|
+
* Remove a previously-registered worklet by ID. No-op if the ID
|
|
88
|
+
* isn't found. Hosts call this in their effect's cleanup.
|
|
89
|
+
*/
|
|
90
|
+
unregister(id: string): void;
|
|
91
|
+
/**
|
|
92
|
+
* Snapshot the current entries. Returned array is a copy —
|
|
93
|
+
* mutations don't affect the registry. Phase 4b's native
|
|
94
|
+
* handoff will read a `SharedValue` mirror of this list so the
|
|
95
|
+
* AR runtime doesn't need a JS-thread hop on the hot per-frame
|
|
96
|
+
* path; for Phase 4a this method is the JS-side accessor.
|
|
97
|
+
*/
|
|
98
|
+
getEntries(): readonly StitcherWorkletEntry[];
|
|
99
|
+
/**
|
|
100
|
+
* Total number of registered worklets (first-party + host).
|
|
101
|
+
* Useful for diagnostics + tests.
|
|
102
|
+
*/
|
|
103
|
+
get count(): number;
|
|
104
|
+
/**
|
|
105
|
+
* Test-only — clear all entries. NOT exported from
|
|
106
|
+
* `src/index.ts`. Used in unit tests to reset state between
|
|
107
|
+
* cases.
|
|
108
|
+
*/
|
|
109
|
+
_resetForTests(): void;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Process-scope singleton. Imported by `useFrameProcessor` (in
|
|
113
|
+
* this directory) + by the Phase 4b native-handoff code (TBD).
|
|
114
|
+
*/
|
|
115
|
+
export declare const StitcherWorkletRegistry: Registry;
|
|
116
|
+
export {};
|
|
117
|
+
//# sourceMappingURL=StitcherWorkletRegistry.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.StitcherWorkletRegistry = void 0;
|
|
5
|
+
class Registry {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.entries = [];
|
|
8
|
+
this.nextHostCounter = 0;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Register a worklet. Returns a stable ID for `unregister`.
|
|
12
|
+
*
|
|
13
|
+
* Entries are appended in registration order; first-party
|
|
14
|
+
* entries (if any are added in future) sort to the front.
|
|
15
|
+
*/
|
|
16
|
+
register(opts) {
|
|
17
|
+
const isFirstParty = opts.isFirstParty ?? false;
|
|
18
|
+
const id = isFirstParty
|
|
19
|
+
? `fp-${this.nextHostCounter++}`
|
|
20
|
+
: `host-${this.nextHostCounter++}`;
|
|
21
|
+
const entry = {
|
|
22
|
+
id,
|
|
23
|
+
worklet: opts.worklet,
|
|
24
|
+
isFirstParty,
|
|
25
|
+
};
|
|
26
|
+
this.entries.push(entry);
|
|
27
|
+
// Re-sort so first-party always runs before host entries.
|
|
28
|
+
// Stable sort: registration order is preserved within each
|
|
29
|
+
// partition. Single-pass O(n log n) is fine — registration
|
|
30
|
+
// is rare (per-`<Camera>`-mount, not per-frame).
|
|
31
|
+
this.entries.sort((a, b) => {
|
|
32
|
+
if (a.isFirstParty !== b.isFirstParty) {
|
|
33
|
+
return a.isFirstParty ? -1 : 1;
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
});
|
|
37
|
+
return id;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Remove a previously-registered worklet by ID. No-op if the ID
|
|
41
|
+
* isn't found. Hosts call this in their effect's cleanup.
|
|
42
|
+
*/
|
|
43
|
+
unregister(id) {
|
|
44
|
+
this.entries = this.entries.filter((e) => e.id !== id);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Snapshot the current entries. Returned array is a copy —
|
|
48
|
+
* mutations don't affect the registry. Phase 4b's native
|
|
49
|
+
* handoff will read a `SharedValue` mirror of this list so the
|
|
50
|
+
* AR runtime doesn't need a JS-thread hop on the hot per-frame
|
|
51
|
+
* path; for Phase 4a this method is the JS-side accessor.
|
|
52
|
+
*/
|
|
53
|
+
getEntries() {
|
|
54
|
+
return [...this.entries];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Total number of registered worklets (first-party + host).
|
|
58
|
+
* Useful for diagnostics + tests.
|
|
59
|
+
*/
|
|
60
|
+
get count() {
|
|
61
|
+
return this.entries.length;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Test-only — clear all entries. NOT exported from
|
|
65
|
+
* `src/index.ts`. Used in unit tests to reset state between
|
|
66
|
+
* cases.
|
|
67
|
+
*/
|
|
68
|
+
_resetForTests() {
|
|
69
|
+
this.entries = [];
|
|
70
|
+
this.nextHostCounter = 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Process-scope singleton. Imported by `useFrameProcessor` (in
|
|
75
|
+
* this directory) + by the Phase 4b native-handoff code (TBD).
|
|
76
|
+
*/
|
|
77
|
+
exports.StitcherWorkletRegistry = new Registry();
|
|
78
|
+
//# sourceMappingURL=StitcherWorkletRegistry.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function ensureStitcherProxyInstalled(): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Test-only — reset module-internal state. Used by jest to allow
|
|
4
|
+
* multiple test cases to re-trigger the install path independently.
|
|
5
|
+
* NOT exported from `src/index.ts`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function _resetStitcherProxyInstallStateForTests(): void;
|
|
8
|
+
//# sourceMappingURL=ensureStitcherProxyInstalled.d.ts.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ensureStitcherProxyInstalled = ensureStitcherProxyInstalled;
|
|
5
|
+
exports._resetStitcherProxyInstallStateForTests = _resetStitcherProxyInstallStateForTests;
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
/**
|
|
8
|
+
* `__DEV__` is RN's global dev-flag. Guard the read with `typeof`
|
|
9
|
+
* so the helper works in any environment that imports it without
|
|
10
|
+
* defining __DEV__ (jest, SSR, custom tooling). Same pattern RN's
|
|
11
|
+
* own debug code uses.
|
|
12
|
+
*/
|
|
13
|
+
function isDev() {
|
|
14
|
+
return typeof __DEV__ !== 'undefined' && __DEV__;
|
|
15
|
+
}
|
|
16
|
+
let installed = false;
|
|
17
|
+
function ensureStitcherProxyInstalled() {
|
|
18
|
+
if (installed)
|
|
19
|
+
return true;
|
|
20
|
+
// Already installed by an earlier hook mount. Cheap fast-path.
|
|
21
|
+
if (typeof globalThis.__stitcherProxy !== 'undefined') {
|
|
22
|
+
installed = true;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const mod = react_native_1.NativeModules
|
|
26
|
+
.StitcherJsiInstaller;
|
|
27
|
+
if (mod == null || typeof mod.install !== 'function') {
|
|
28
|
+
// Module not present — Android until Phase 4b.ii lands, or
|
|
29
|
+
// an old iOS build. Surface this once at debug-info level so
|
|
30
|
+
// the host can see "your worklets are JS-registered only" in
|
|
31
|
+
// logcat / Console.app without a noisy per-frame warning.
|
|
32
|
+
if (isDev() && !warnedAboutMissingModule) {
|
|
33
|
+
warnedAboutMissingModule = true;
|
|
34
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller native ' +
|
|
35
|
+
'module not found; host worklets registered in JS-side ' +
|
|
36
|
+
'registry only. AR-mode dispatch requires the native install ' +
|
|
37
|
+
'(iOS Phase 4b.i — included in v0.8.0; Android Phase 4b.ii ' +
|
|
38
|
+
'— follow-up release).');
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const ok = mod.install();
|
|
44
|
+
if (!ok) {
|
|
45
|
+
// Native module ran but couldn't install (JSI runtime
|
|
46
|
+
// unreachable). Same fallback as the missing-module case.
|
|
47
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
48
|
+
warnedAboutFailedInstall = true;
|
|
49
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
50
|
+
'returned false (JSI runtime unreachable — remote debug ' +
|
|
51
|
+
'mode?). Falling back to JS-side host worklet registry.');
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
installed = true;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
60
|
+
warnedAboutFailedInstall = true;
|
|
61
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
62
|
+
'threw: ' +
|
|
63
|
+
String(err) +
|
|
64
|
+
'. Falling back to JS-side host worklet registry.');
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let warnedAboutMissingModule = false;
|
|
70
|
+
let warnedAboutFailedInstall = false;
|
|
71
|
+
/**
|
|
72
|
+
* Test-only — reset module-internal state. Used by jest to allow
|
|
73
|
+
* multiple test cases to re-trigger the install path independently.
|
|
74
|
+
* NOT exported from `src/index.ts`.
|
|
75
|
+
*/
|
|
76
|
+
function _resetStitcherProxyInstallStateForTests() {
|
|
77
|
+
installed = false;
|
|
78
|
+
warnedAboutMissingModule = false;
|
|
79
|
+
warnedAboutFailedInstall = false;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ensureStitcherProxyInstalled.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type DependencyList } from 'react';
|
|
2
|
+
import { type DrawableFrameProcessor, type ReadonlyFrameProcessor } from 'react-native-vision-camera';
|
|
3
|
+
import type { StitcherFrameProcessor } from './StitcherFrame';
|
|
4
|
+
/**
|
|
5
|
+
* v0.8.0 Phase 4a — public hook for hosts to attach a per-frame
|
|
6
|
+
* worklet that runs in BOTH AR and non-AR capture modes.
|
|
7
|
+
*
|
|
8
|
+
* ## Quick start
|
|
9
|
+
*
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { useFrameProcessor, type StitcherFrame } from 'react-native-image-stitcher';
|
|
12
|
+
*
|
|
13
|
+
* function MyOcrOverlay() {
|
|
14
|
+
* const processor = useFrameProcessor((frame: StitcherFrame) => {
|
|
15
|
+
* 'worklet';
|
|
16
|
+
* // Pixel data is in `frame.toArrayBuffer()`.
|
|
17
|
+
* // AR-only fields: `frame.arDepth`, `frame.arAnchors`, `frame.arTrackingState`.
|
|
18
|
+
* // Discriminate via `frame.source === 'ar'` / `'vc'`.
|
|
19
|
+
* }, []);
|
|
20
|
+
* return <Camera frameProcessor={processor} ... />;
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Two behaviours, depending on mode
|
|
25
|
+
*
|
|
26
|
+
* **Non-AR mode (today, fully working):** the worklet runs on
|
|
27
|
+
* vision-camera's Frame Processor runtime. Same thread + same
|
|
28
|
+
* cost envelope as a plain `useFrameProcessor` from
|
|
29
|
+
* `react-native-vision-camera`. The lib's own first-party
|
|
30
|
+
* stitching plugin runs alongside on the same producer-thread
|
|
31
|
+
* runtime (composition is handled by vision-camera's own dispatch
|
|
32
|
+
* order).
|
|
33
|
+
*
|
|
34
|
+
* Your worklet receives whatever vision-camera delivers — vc's raw
|
|
35
|
+
* `Frame`. This is a structural subset of `StitcherFrame`: the
|
|
36
|
+
* vc-shaped fields (`width`, `height`, `pixelFormat`, `orientation`,
|
|
37
|
+
* `timestamp`, `toArrayBuffer`) are guaranteed; the
|
|
38
|
+
* `StitcherFrame`-only fields (`source`, `pose`, `arDepth`,
|
|
39
|
+
* `arAnchors`, `arTrackingState`) are **undefined** at runtime
|
|
40
|
+
* because the lib does NOT wrap or augment vc's `Frame` in Phase 4a
|
|
41
|
+
* (cross-worklet-boundary field injection is Phase 4b work).
|
|
42
|
+
* Worklets that need to read `source` / `pose` MUST guard for
|
|
43
|
+
* `undefined`:
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* if (frame.source === 'ar') { ... } // false in non-AR mode
|
|
47
|
+
* if (frame.pose) { ... } // skipped in non-AR mode
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* **AR mode — iOS Phase 4b.i (this release):** the worklet is
|
|
51
|
+
* installed into the native registry via
|
|
52
|
+
* `globalThis.__stitcherProxy.install(workletFn)`, where
|
|
53
|
+
* `__stitcherProxy` is a JSI host object installed at lib
|
|
54
|
+
* bootstrap by the native `StitcherJsiInstaller` module. The
|
|
55
|
+
* AR worklet runtime (`RNSARWorkletRuntime`) reads from the
|
|
56
|
+
* native registry on each `dispatchFrame:pose:` call and fans
|
|
57
|
+
* out invocations — your worklet fires alongside the lib's
|
|
58
|
+
* first-party stitching path.
|
|
59
|
+
*
|
|
60
|
+
* **AR mode — Android Phase 4b.ii (deferred):** the native
|
|
61
|
+
* installer + JNI bridge from `StitcherWorkletRuntime.kt`'s
|
|
62
|
+
* `runFirstParty {...}` path to a parallel C++ registry land in
|
|
63
|
+
* a follow-up release. Until then, on Android the hook falls
|
|
64
|
+
* back to the JS-side `StitcherWorkletRegistry`; AR-mode host
|
|
65
|
+
* worklets register but do not invoke. No regression vs.
|
|
66
|
+
* Phase 4a; iOS gets the API first.
|
|
67
|
+
*
|
|
68
|
+
* ### When Phase 4b.ii lands (Android)
|
|
69
|
+
*
|
|
70
|
+
* The hook's call signature does NOT change. Android hosts that
|
|
71
|
+
* write code today against this API will see their worklets
|
|
72
|
+
* start firing in AR mode automatically when Phase 4b.ii is
|
|
73
|
+
* merged. No migration required.
|
|
74
|
+
*
|
|
75
|
+
* ## Frame contract
|
|
76
|
+
*
|
|
77
|
+
* The worklet receives a {@link StitcherFrame} (see
|
|
78
|
+
* `src/stitching/StitcherFrame.ts` for the full contract +
|
|
79
|
+
* lifecycle). Highlights:
|
|
80
|
+
*
|
|
81
|
+
* - **`source`** discriminator: `'vc'` or `'ar'`. Branch on this
|
|
82
|
+
* before reading `arDepth` / `arAnchors` / `arTrackingState`
|
|
83
|
+
* so non-AR captures don't break.
|
|
84
|
+
* - **`pose`** always present. `pose.translation` is `undefined`
|
|
85
|
+
* in non-AR mode (gyro provides only rotation; no spatial
|
|
86
|
+
* anchor).
|
|
87
|
+
* - **Buffer lifetime**: pixel data is valid only for the
|
|
88
|
+
* duration of the worklet call. Worklets that need to retain
|
|
89
|
+
* data must `toArrayBuffer()` synchronously inside the
|
|
90
|
+
* worklet body — returning a reference and reading it later
|
|
91
|
+
* reads freed memory.
|
|
92
|
+
*
|
|
93
|
+
* ## Threading
|
|
94
|
+
*
|
|
95
|
+
* The worklet runs on the producer thread (vision-camera's
|
|
96
|
+
* runtime in non-AR mode; the AR-session callback thread under
|
|
97
|
+
* Phase 4b). Worklets MUST NOT block the producer thread for
|
|
98
|
+
* more than a few ms — the next frame's processing is gated on
|
|
99
|
+
* the previous frame returning. Long work belongs on a queue
|
|
100
|
+
* crossed via Reanimated / worklets-core's `runOnJS`.
|
|
101
|
+
*
|
|
102
|
+
* @param worklet The host's frame processor function. Must be
|
|
103
|
+
* `'worklet'`-prefixed at the call site. TS
|
|
104
|
+
* cannot enforce the prefix; the runtime will
|
|
105
|
+
* throw at attempt to invoke a non-worklet
|
|
106
|
+
* function.
|
|
107
|
+
* @param deps Standard React deps array. When `deps` change,
|
|
108
|
+
* the previous registration is removed and the
|
|
109
|
+
* new worklet is registered. Same semantics as
|
|
110
|
+
* vision-camera's `useFrameProcessor`.
|
|
111
|
+
*
|
|
112
|
+
* @returns A vision-camera frame-processor object that
|
|
113
|
+
* `<Camera frameProcessor={...}>` accepts. In non-AR
|
|
114
|
+
* mode this is what drives the per-frame worklet
|
|
115
|
+
* invocation; in AR mode it's currently a no-op (vc
|
|
116
|
+
* isn't mounted in AR mode anyway).
|
|
117
|
+
*/
|
|
118
|
+
export declare function useFrameProcessor(worklet: StitcherFrameProcessor, deps: DependencyList): ReadonlyFrameProcessor | DrawableFrameProcessor;
|
|
119
|
+
//# sourceMappingURL=useFrameProcessor.d.ts.map
|