react-native-image-stitcher 0.1.0 → 0.1.2
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 +115 -1
- package/RNImageStitcher.podspec +10 -4
- package/android/src/main/java/io/imagestitcher/rn/FileBridge.kt +79 -0
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +9 -1
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +1 -0
- package/dist/camera/Camera.d.ts +29 -1
- package/dist/camera/Camera.js +47 -37
- package/dist/camera/useCapture.d.ts +31 -1
- package/dist/camera/useCapture.js +23 -2
- package/dist/index.d.ts +41 -12
- package/dist/index.js +90 -17
- package/dist/utils/files.d.ts +44 -0
- package/dist/utils/files.js +84 -0
- package/dist/utils/paths.d.ts +30 -0
- package/dist/utils/paths.js +48 -0
- package/ios/Sources/RNImageStitcher/FileBridge.m +20 -0
- package/ios/Sources/RNImageStitcher/FileBridge.swift +110 -0
- package/package.json +1 -1
- package/src/camera/Camera.tsx +85 -35
- package/src/camera/useCapture.ts +62 -3
- package/src/index.ts +88 -16
- package/src/utils/files.ts +97 -0
- package/src/utils/paths.ts +42 -0
package/dist/index.js
CHANGED
|
@@ -3,37 +3,110 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* react-native-image-stitcher — public API surface.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Two layers:
|
|
7
|
+
* 1. The high-level `<Camera>` component for hosts that want a
|
|
8
|
+
* drop-in capture experience. Tap = photo, hold + pan + release
|
|
9
|
+
* = panorama. Single mount, all UI included.
|
|
10
|
+
* 2. The lower-level building blocks (views, hooks, stitching
|
|
11
|
+
* engine bindings, settings modal, status overlays) for hosts
|
|
12
|
+
* that want to compose their own capture UX while reusing the
|
|
13
|
+
* battle-tested camera and stitching internals.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* Layer 1 (`<Camera>`) is the recommended starting point. Reach for
|
|
16
|
+
* layer 2 when the high-level component doesn't give you enough
|
|
17
|
+
* control — e.g., the private `retailens-camera-sdk` adds
|
|
18
|
+
* measurement + packet detection on top of these building blocks.
|
|
15
19
|
*
|
|
16
20
|
* Public/private split: this lib is the open-source foundation. The
|
|
17
|
-
* `retailens-camera-sdk` package depends on this lib and
|
|
18
|
-
* RetaiLens-specific features
|
|
19
|
-
* on top. Consumers wanting those features install
|
|
20
|
-
* `retailens-camera-sdk` instead.
|
|
21
|
+
* `retailens-camera-sdk` package depends on this lib (peer dep) and
|
|
22
|
+
* adds RetaiLens-specific features on top.
|
|
21
23
|
*/
|
|
22
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.useIMUTranslationGate = exports.ARTrackingState = exports.useARSession = exports.CameraError = exports.Camera = void 0;
|
|
24
|
-
//
|
|
25
|
+
exports.stitchVideo = exports.useIncrementalJSDriver = exports.useIncrementalStitcher = exports.cleanupOldKeyframes = exports.getIncrementalNativeModule = exports.subscribeIncrementalState = exports.incrementalStitcherIsAvailable = exports.IncrementalOutcome = exports.useDeviceOrientation = exports.useVideoCapture = exports.useCapture = exports.ViewportCropOverlay = exports.DEFAULT_PANORAMA_SETTINGS = exports.PanoramaSettingsModal = exports.PanoramaGuidance = exports.PanoramaBandOverlay = exports.IncrementalPanGuide = exports.CaptureThumbnailStrip = 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
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
27
|
+
// Layer 1 — the high-level <Camera> component
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
25
29
|
var Camera_1 = require("./camera/Camera");
|
|
26
30
|
Object.defineProperty(exports, "Camera", { enumerable: true, get: function () { return Camera_1.Camera; } });
|
|
27
31
|
Object.defineProperty(exports, "CameraError", { enumerable: true, get: function () { return Camera_1.CameraError; } });
|
|
28
|
-
//
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
33
|
+
// AR foundation (public since 0.1.0)
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
29
35
|
// Hosts that want raw AR pose access (e.g., to build their own
|
|
30
36
|
// measurement/detection on top) consume these directly.
|
|
31
37
|
var useARSession_1 = require("./ar/useARSession");
|
|
32
38
|
Object.defineProperty(exports, "useARSession", { enumerable: true, get: function () { return useARSession_1.useARSession; } });
|
|
33
39
|
Object.defineProperty(exports, "ARTrackingState", { enumerable: true, get: function () { return useARSession_1.ARTrackingState; } });
|
|
34
|
-
//
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
41
|
+
// IMU translation gate (public since 0.1.0)
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
35
43
|
// Hosts running their own non-AR capture flow can reuse this hook to
|
|
36
|
-
// get the same gating logic <Camera> uses internally.
|
|
44
|
+
// get the same translation-budget gating logic <Camera> uses internally.
|
|
37
45
|
var useIMUTranslationGate_1 = require("./sensors/useIMUTranslationGate");
|
|
38
46
|
Object.defineProperty(exports, "useIMUTranslationGate", { enumerable: true, get: function () { return useIMUTranslationGate_1.useIMUTranslationGate; } });
|
|
47
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
48
|
+
// Layer 2 — composable building blocks (added in 0.1.1)
|
|
49
|
+
// ═════════════════════════════════════════════════════════════════════
|
|
50
|
+
// ── Camera view components ────────────────────────────────────────────
|
|
51
|
+
// Drop-in replacements for vision-camera's raw <Camera> (non-AR) and a
|
|
52
|
+
// parallel ARKit/ARCore-backed view (AR). Use these when you need to
|
|
53
|
+
// hand-compose your capture UI instead of mounting <Camera>.
|
|
54
|
+
var ARCameraView_1 = require("./camera/ARCameraView");
|
|
55
|
+
Object.defineProperty(exports, "ARCameraView", { enumerable: true, get: function () { return ARCameraView_1.ARCameraView; } });
|
|
56
|
+
var CameraView_1 = require("./camera/CameraView");
|
|
57
|
+
Object.defineProperty(exports, "CameraView", { enumerable: true, get: function () { return CameraView_1.CameraView; } });
|
|
58
|
+
// ── UI components ─────────────────────────────────────────────────────
|
|
59
|
+
// Presentational pieces of the standard capture screen. Each is a
|
|
60
|
+
// pure component; the host wires the props.
|
|
61
|
+
var CaptureHeader_1 = require("./camera/CaptureHeader");
|
|
62
|
+
Object.defineProperty(exports, "CaptureHeader", { enumerable: true, get: function () { return CaptureHeader_1.CaptureHeader; } });
|
|
63
|
+
var CaptureControlsBar_1 = require("./camera/CaptureControlsBar");
|
|
64
|
+
Object.defineProperty(exports, "CaptureControlsBar", { enumerable: true, get: function () { return CaptureControlsBar_1.CaptureControlsBar; } });
|
|
65
|
+
var CapturePreview_1 = require("./camera/CapturePreview");
|
|
66
|
+
Object.defineProperty(exports, "CapturePreview", { enumerable: true, get: function () { return CapturePreview_1.CapturePreview; } });
|
|
67
|
+
var CaptureStatusOverlay_1 = require("./camera/CaptureStatusOverlay");
|
|
68
|
+
Object.defineProperty(exports, "CaptureStatusOverlay", { enumerable: true, get: function () { return CaptureStatusOverlay_1.CaptureStatusOverlay; } });
|
|
69
|
+
var CaptureThumbnailStrip_1 = require("./camera/CaptureThumbnailStrip");
|
|
70
|
+
Object.defineProperty(exports, "CaptureThumbnailStrip", { enumerable: true, get: function () { return CaptureThumbnailStrip_1.CaptureThumbnailStrip; } });
|
|
71
|
+
var IncrementalPanGuide_1 = require("./camera/IncrementalPanGuide");
|
|
72
|
+
Object.defineProperty(exports, "IncrementalPanGuide", { enumerable: true, get: function () { return IncrementalPanGuide_1.IncrementalPanGuide; } });
|
|
73
|
+
var PanoramaBandOverlay_1 = require("./camera/PanoramaBandOverlay");
|
|
74
|
+
Object.defineProperty(exports, "PanoramaBandOverlay", { enumerable: true, get: function () { return PanoramaBandOverlay_1.PanoramaBandOverlay; } });
|
|
75
|
+
var PanoramaGuidance_1 = require("./camera/PanoramaGuidance");
|
|
76
|
+
Object.defineProperty(exports, "PanoramaGuidance", { enumerable: true, get: function () { return PanoramaGuidance_1.PanoramaGuidance; } });
|
|
77
|
+
var PanoramaSettingsModal_1 = require("./camera/PanoramaSettingsModal");
|
|
78
|
+
Object.defineProperty(exports, "PanoramaSettingsModal", { enumerable: true, get: function () { return PanoramaSettingsModal_1.PanoramaSettingsModal; } });
|
|
79
|
+
Object.defineProperty(exports, "DEFAULT_PANORAMA_SETTINGS", { enumerable: true, get: function () { return PanoramaSettingsModal_1.DEFAULT_PANORAMA_SETTINGS; } });
|
|
80
|
+
var ViewportCropOverlay_1 = require("./camera/ViewportCropOverlay");
|
|
81
|
+
Object.defineProperty(exports, "ViewportCropOverlay", { enumerable: true, get: function () { return ViewportCropOverlay_1.ViewportCropOverlay; } });
|
|
82
|
+
// ── Capture hooks ─────────────────────────────────────────────────────
|
|
83
|
+
// vision-camera wrappers (useCapture / useVideoCapture) + a
|
|
84
|
+
// device-orientation reader that works under iOS portrait-lock.
|
|
85
|
+
var useCapture_1 = require("./camera/useCapture");
|
|
86
|
+
Object.defineProperty(exports, "useCapture", { enumerable: true, get: function () { return useCapture_1.useCapture; } });
|
|
87
|
+
var useVideoCapture_1 = require("./camera/useVideoCapture");
|
|
88
|
+
Object.defineProperty(exports, "useVideoCapture", { enumerable: true, get: function () { return useVideoCapture_1.useVideoCapture; } });
|
|
89
|
+
var useDeviceOrientation_1 = require("./camera/useDeviceOrientation");
|
|
90
|
+
Object.defineProperty(exports, "useDeviceOrientation", { enumerable: true, get: function () { return useDeviceOrientation_1.useDeviceOrientation; } });
|
|
91
|
+
// ── Incremental stitching engine ──────────────────────────────────────
|
|
92
|
+
// JS bindings around the native `IncrementalStitcher` module. Use
|
|
93
|
+
// these when you need finer control than <Camera>'s built-in
|
|
94
|
+
// hold-to-pan flow (e.g., feeding frames from a custom source, or
|
|
95
|
+
// reading the engine's running state to drive a custom UI).
|
|
96
|
+
var incremental_1 = require("./stitching/incremental");
|
|
97
|
+
Object.defineProperty(exports, "IncrementalOutcome", { enumerable: true, get: function () { return incremental_1.IncrementalOutcome; } });
|
|
98
|
+
Object.defineProperty(exports, "incrementalStitcherIsAvailable", { enumerable: true, get: function () { return incremental_1.incrementalStitcherIsAvailable; } });
|
|
99
|
+
Object.defineProperty(exports, "subscribeIncrementalState", { enumerable: true, get: function () { return incremental_1.subscribeIncrementalState; } });
|
|
100
|
+
Object.defineProperty(exports, "getIncrementalNativeModule", { enumerable: true, get: function () { return incremental_1.getIncrementalNativeModule; } });
|
|
101
|
+
Object.defineProperty(exports, "cleanupOldKeyframes", { enumerable: true, get: function () { return incremental_1.cleanupOldKeyframes; } });
|
|
102
|
+
var useIncrementalStitcher_1 = require("./stitching/useIncrementalStitcher");
|
|
103
|
+
Object.defineProperty(exports, "useIncrementalStitcher", { enumerable: true, get: function () { return useIncrementalStitcher_1.useIncrementalStitcher; } });
|
|
104
|
+
var useIncrementalJSDriver_1 = require("./stitching/useIncrementalJSDriver");
|
|
105
|
+
Object.defineProperty(exports, "useIncrementalJSDriver", { enumerable: true, get: function () { return useIncrementalJSDriver_1.useIncrementalJSDriver; } });
|
|
106
|
+
// ── Batch stitching ───────────────────────────────────────────────────
|
|
107
|
+
// Feed a video file straight to OpenCV's cv::Stitcher, bypassing the
|
|
108
|
+
// incremental pipeline. Useful when you have content captured
|
|
109
|
+
// outside the SDK and just want a panorama out.
|
|
110
|
+
var stitchVideo_1 = require("./stitching/stitchVideo");
|
|
111
|
+
Object.defineProperty(exports, "stitchVideo", { enumerable: true, get: function () { return stitchVideo_1.stitchVideo; } });
|
|
39
112
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin JS wrapper around the `RNImageStitcherFileUtils` native
|
|
3
|
+
* module (defined in `ios/Sources/RNImageStitcher/FileBridge.{swift,m}`
|
|
4
|
+
* and `android/src/main/.../FileBridge.kt`). Internal — not
|
|
5
|
+
* re-exported from `src/index.ts`.
|
|
6
|
+
*
|
|
7
|
+
* Two operations:
|
|
8
|
+
* - `moveFile(from, to)` — move a file, creating the destination's
|
|
9
|
+
* parent directory tree on demand. Used to relocate
|
|
10
|
+
* vision-camera's auto-named tmp output into the lib's canonical
|
|
11
|
+
* capture dir.
|
|
12
|
+
* - `getDefaultCaptureDir()` — resolve (and create on first call)
|
|
13
|
+
* the canonical default capture directory:
|
|
14
|
+
*
|
|
15
|
+
* iOS: `<NSCachesDirectory>/react-native-image-stitcher/`
|
|
16
|
+
* Android: `<context.cacheDir>/react-native-image-stitcher/`
|
|
17
|
+
*
|
|
18
|
+
* Predictable, evictable-by-OS, NOT backed up. Captures live
|
|
19
|
+
* here until the host moves them somewhere durable (which is
|
|
20
|
+
* intentional — the lib doesn't promise persistence beyond the
|
|
21
|
+
* immediate capture flow).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Move a file via the native bridge. Both paths accepted in bare
|
|
25
|
+
* or `file://`-prefixed form. Resolves to the bare destination
|
|
26
|
+
* path on success. Throws on disk failure.
|
|
27
|
+
*/
|
|
28
|
+
export declare function moveFile(from: string, to: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the canonical default capture directory. Lazy +
|
|
31
|
+
* memoised — the native side creates the dir on first call, JS
|
|
32
|
+
* caches the result for the rest of the app session.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDefaultCaptureDir(): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Compose a default filename for a tap-photo capture, using a
|
|
37
|
+
* millisecond Unix timestamp for ordering. Pure helper; no I/O.
|
|
38
|
+
*/
|
|
39
|
+
export declare function defaultPhotoFilename(): string;
|
|
40
|
+
/**
|
|
41
|
+
* Same as `defaultPhotoFilename` but for panoramas.
|
|
42
|
+
*/
|
|
43
|
+
export declare function defaultPanoramaFilename(): string;
|
|
44
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Thin JS wrapper around the `RNImageStitcherFileUtils` native
|
|
5
|
+
* module (defined in `ios/Sources/RNImageStitcher/FileBridge.{swift,m}`
|
|
6
|
+
* and `android/src/main/.../FileBridge.kt`). Internal — not
|
|
7
|
+
* re-exported from `src/index.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Two operations:
|
|
10
|
+
* - `moveFile(from, to)` — move a file, creating the destination's
|
|
11
|
+
* parent directory tree on demand. Used to relocate
|
|
12
|
+
* vision-camera's auto-named tmp output into the lib's canonical
|
|
13
|
+
* capture dir.
|
|
14
|
+
* - `getDefaultCaptureDir()` — resolve (and create on first call)
|
|
15
|
+
* the canonical default capture directory:
|
|
16
|
+
*
|
|
17
|
+
* iOS: `<NSCachesDirectory>/react-native-image-stitcher/`
|
|
18
|
+
* Android: `<context.cacheDir>/react-native-image-stitcher/`
|
|
19
|
+
*
|
|
20
|
+
* Predictable, evictable-by-OS, NOT backed up. Captures live
|
|
21
|
+
* here until the host moves them somewhere durable (which is
|
|
22
|
+
* intentional — the lib doesn't promise persistence beyond the
|
|
23
|
+
* immediate capture flow).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.moveFile = moveFile;
|
|
27
|
+
exports.getDefaultCaptureDir = getDefaultCaptureDir;
|
|
28
|
+
exports.defaultPhotoFilename = defaultPhotoFilename;
|
|
29
|
+
exports.defaultPanoramaFilename = defaultPanoramaFilename;
|
|
30
|
+
const react_native_1 = require("react-native");
|
|
31
|
+
function bridge() {
|
|
32
|
+
const m = react_native_1.NativeModules.RNImageStitcherFileUtils;
|
|
33
|
+
if (!m || typeof m !== 'object')
|
|
34
|
+
return null;
|
|
35
|
+
return m;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Move a file via the native bridge. Both paths accepted in bare
|
|
39
|
+
* or `file://`-prefixed form. Resolves to the bare destination
|
|
40
|
+
* path on success. Throws on disk failure.
|
|
41
|
+
*/
|
|
42
|
+
async function moveFile(from, to) {
|
|
43
|
+
const b = bridge();
|
|
44
|
+
if (!b) {
|
|
45
|
+
throw new Error('react-native-image-stitcher: RNImageStitcherFileUtils native '
|
|
46
|
+
+ 'module is not registered. Check that the host app has '
|
|
47
|
+
+ 'rebuilt against the latest pod/Gradle install.');
|
|
48
|
+
}
|
|
49
|
+
return b.moveFile(from, to);
|
|
50
|
+
}
|
|
51
|
+
// Cached after the first resolve — the dir doesn't move during the
|
|
52
|
+
// lifetime of the app, and the on-first-call mkdir is idempotent.
|
|
53
|
+
let cachedDefaultDir = null;
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the canonical default capture directory. Lazy +
|
|
56
|
+
* memoised — the native side creates the dir on first call, JS
|
|
57
|
+
* caches the result for the rest of the app session.
|
|
58
|
+
*/
|
|
59
|
+
async function getDefaultCaptureDir() {
|
|
60
|
+
if (cachedDefaultDir !== null)
|
|
61
|
+
return cachedDefaultDir;
|
|
62
|
+
const b = bridge();
|
|
63
|
+
if (!b) {
|
|
64
|
+
throw new Error('react-native-image-stitcher: RNImageStitcherFileUtils native '
|
|
65
|
+
+ 'module is not registered. Check that the host app has '
|
|
66
|
+
+ 'rebuilt against the latest pod/Gradle install.');
|
|
67
|
+
}
|
|
68
|
+
cachedDefaultDir = await b.defaultCaptureDir();
|
|
69
|
+
return cachedDefaultDir;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Compose a default filename for a tap-photo capture, using a
|
|
73
|
+
* millisecond Unix timestamp for ordering. Pure helper; no I/O.
|
|
74
|
+
*/
|
|
75
|
+
function defaultPhotoFilename() {
|
|
76
|
+
return `photo-${Date.now()}.jpg`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Same as `defaultPhotoFilename` but for panoramas.
|
|
80
|
+
*/
|
|
81
|
+
function defaultPanoramaFilename() {
|
|
82
|
+
return `panorama-${Date.now()}.jpg`;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path normalisation helpers. Internal — NOT re-exported from
|
|
3
|
+
* `src/index.ts`; the public surface intentionally doesn't promise
|
|
4
|
+
* these utilities to consumers (every host app has its own copy).
|
|
5
|
+
*
|
|
6
|
+
* Two shapes a file path can take when crossing the JS / native /
|
|
7
|
+
* React layers in this library:
|
|
8
|
+
*
|
|
9
|
+
* - **`file://`-prefixed URI** — what RN's `<Image source={{ uri }}>`
|
|
10
|
+
* (Android strict, iOS lenient) and `expo-file-system` APIs
|
|
11
|
+
* accept. Whenever this library emits a path to JS (via
|
|
12
|
+
* `onCapture`, the `IncrementalStateUpdate` event, etc.) it
|
|
13
|
+
* should be in this form so consumers can render it directly.
|
|
14
|
+
*
|
|
15
|
+
* - **Bare path** — what `fs`-style native APIs (`cv::imwrite`,
|
|
16
|
+
* `NSFileManager`, `BitmapFactory.decodeFile`) accept. These
|
|
17
|
+
* treat a `file://` prefix as part of the literal filename and
|
|
18
|
+
* fail to open it. Native bridges expect bare paths in.
|
|
19
|
+
*
|
|
20
|
+
* Both helpers are pure and idempotent. No-op on the empty string.
|
|
21
|
+
*
|
|
22
|
+
* (The Swift and Kotlin sides have their own `stripFileScheme` —
|
|
23
|
+
* cross-language sharing isn't worth a small helper. Keeping the
|
|
24
|
+
* JS copy here just centralises the rule for the TS surface.)
|
|
25
|
+
*/
|
|
26
|
+
/** Add the `file://` scheme to a bare path, idempotently. */
|
|
27
|
+
export declare function toFileUri(path: string | null | undefined): string;
|
|
28
|
+
/** Strip the `file://` scheme from a URI, idempotently. */
|
|
29
|
+
export declare function toBareFilePath(path: string | null | undefined): string;
|
|
30
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Path normalisation helpers. Internal — NOT re-exported from
|
|
5
|
+
* `src/index.ts`; the public surface intentionally doesn't promise
|
|
6
|
+
* these utilities to consumers (every host app has its own copy).
|
|
7
|
+
*
|
|
8
|
+
* Two shapes a file path can take when crossing the JS / native /
|
|
9
|
+
* React layers in this library:
|
|
10
|
+
*
|
|
11
|
+
* - **`file://`-prefixed URI** — what RN's `<Image source={{ uri }}>`
|
|
12
|
+
* (Android strict, iOS lenient) and `expo-file-system` APIs
|
|
13
|
+
* accept. Whenever this library emits a path to JS (via
|
|
14
|
+
* `onCapture`, the `IncrementalStateUpdate` event, etc.) it
|
|
15
|
+
* should be in this form so consumers can render it directly.
|
|
16
|
+
*
|
|
17
|
+
* - **Bare path** — what `fs`-style native APIs (`cv::imwrite`,
|
|
18
|
+
* `NSFileManager`, `BitmapFactory.decodeFile`) accept. These
|
|
19
|
+
* treat a `file://` prefix as part of the literal filename and
|
|
20
|
+
* fail to open it. Native bridges expect bare paths in.
|
|
21
|
+
*
|
|
22
|
+
* Both helpers are pure and idempotent. No-op on the empty string.
|
|
23
|
+
*
|
|
24
|
+
* (The Swift and Kotlin sides have their own `stripFileScheme` —
|
|
25
|
+
* cross-language sharing isn't worth a small helper. Keeping the
|
|
26
|
+
* JS copy here just centralises the rule for the TS surface.)
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.toFileUri = toFileUri;
|
|
30
|
+
exports.toBareFilePath = toBareFilePath;
|
|
31
|
+
/** Add the `file://` scheme to a bare path, idempotently. */
|
|
32
|
+
function toFileUri(path) {
|
|
33
|
+
if (!path)
|
|
34
|
+
return '';
|
|
35
|
+
if (path.startsWith('file://') || path.startsWith('content://') || path.startsWith('http')) {
|
|
36
|
+
return path;
|
|
37
|
+
}
|
|
38
|
+
return `file://${path}`;
|
|
39
|
+
}
|
|
40
|
+
/** Strip the `file://` scheme from a URI, idempotently. */
|
|
41
|
+
function toBareFilePath(path) {
|
|
42
|
+
if (!path)
|
|
43
|
+
return '';
|
|
44
|
+
if (path.startsWith('file://'))
|
|
45
|
+
return path.slice('file://'.length);
|
|
46
|
+
return path;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// FileBridge.m — Obj-C side of the React Native module registration
|
|
4
|
+
// for `FileBridge.swift`. Required because RN's bridge discovery
|
|
5
|
+
// scans for `@interface RCT_EXTERN_MODULE(...)` blocks via Obj-C
|
|
6
|
+
// runtime introspection; Swift-only modules aren't visible.
|
|
7
|
+
|
|
8
|
+
#import <React/RCTBridgeModule.h>
|
|
9
|
+
|
|
10
|
+
@interface RCT_EXTERN_MODULE(RNImageStitcherFileUtils, NSObject)
|
|
11
|
+
|
|
12
|
+
RCT_EXTERN_METHOD(moveFile:(NSString *)from
|
|
13
|
+
to:(NSString *)to
|
|
14
|
+
resolver:(RCTPromiseResolveBlock)resolver
|
|
15
|
+
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
16
|
+
|
|
17
|
+
RCT_EXTERN_METHOD(defaultCaptureDir:(RCTPromiseResolveBlock)resolver
|
|
18
|
+
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
19
|
+
|
|
20
|
+
@end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// FileBridge.swift
|
|
4
|
+
//
|
|
5
|
+
// Small native module exposing two file operations the JS layer
|
|
6
|
+
// needs in order to:
|
|
7
|
+
//
|
|
8
|
+
// 1. Move vision-camera's auto-named tmp photo into our canonical
|
|
9
|
+
// default capture dir, so JS-level paths returned to the host
|
|
10
|
+
// are predictable (vs. opaque `<uuid>.jpg` paths in
|
|
11
|
+
// `NSTemporaryDirectory()`).
|
|
12
|
+
// 2. Resolve the canonical default capture dir itself, so the JS
|
|
13
|
+
// layer can compose `<defaultDir>/photo-<ms>.jpg` filenames
|
|
14
|
+
// consistently across both platforms.
|
|
15
|
+
//
|
|
16
|
+
// Kept narrow on purpose — this isn't a general-purpose fs API. If
|
|
17
|
+
// host apps want to read/write arbitrary files they can pull in
|
|
18
|
+
// `expo-file-system` themselves; the lib only exposes what it needs
|
|
19
|
+
// for its own capture flow.
|
|
20
|
+
//
|
|
21
|
+
// Canonical capture dir lives under `NSCachesDirectory` (`Library/
|
|
22
|
+
// Caches/`) because:
|
|
23
|
+
// * It persists across app restarts (unlike `NSTemporaryDirectory()`).
|
|
24
|
+
// * iOS may evict cache files under memory pressure, which matches
|
|
25
|
+
// the lib's contract: "capture file lives until host moves it
|
|
26
|
+
// somewhere durable."
|
|
27
|
+
// * Not backed up to iCloud, so the user doesn't pay for the
|
|
28
|
+
// ephemeral capture files.
|
|
29
|
+
|
|
30
|
+
import Foundation
|
|
31
|
+
import React
|
|
32
|
+
|
|
33
|
+
@objc(RNImageStitcherFileUtils)
|
|
34
|
+
public class FileBridge: NSObject {
|
|
35
|
+
|
|
36
|
+
@objc public static func requiresMainQueueSetup() -> Bool {
|
|
37
|
+
return false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Move (or copy+delete fallback for cross-volume moves) a file
|
|
41
|
+
/// from `from` to `to`. Both paths can be bare or `file://`-prefixed
|
|
42
|
+
/// — bridge normalises internally. Creates the destination's
|
|
43
|
+
/// parent directory tree if missing. Resolves to the bare
|
|
44
|
+
/// destination path.
|
|
45
|
+
@objc(moveFile:to:resolver:rejecter:)
|
|
46
|
+
public func moveFile(_ from: String,
|
|
47
|
+
to dst: String,
|
|
48
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
49
|
+
rejecter: @escaping RCTPromiseRejectBlock) {
|
|
50
|
+
let fm = FileManager.default
|
|
51
|
+
let cleanFrom = from.hasPrefix("file://") ? String(from.dropFirst(7)) : from
|
|
52
|
+
let cleanTo = dst.hasPrefix("file://") ? String(dst.dropFirst(7)) : dst
|
|
53
|
+
do {
|
|
54
|
+
let dstDir = (cleanTo as NSString).deletingLastPathComponent
|
|
55
|
+
if !fm.fileExists(atPath: dstDir) {
|
|
56
|
+
try fm.createDirectory(
|
|
57
|
+
atPath: dstDir,
|
|
58
|
+
withIntermediateDirectories: true,
|
|
59
|
+
attributes: nil,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
if fm.fileExists(atPath: cleanTo) {
|
|
63
|
+
try fm.removeItem(atPath: cleanTo)
|
|
64
|
+
}
|
|
65
|
+
// Cheap rename first (same volume). iOS caches + tmp are on the
|
|
66
|
+
// same APFS volume so this is fast; the catch is for the
|
|
67
|
+
// theoretical cross-volume move that copyItem can still handle.
|
|
68
|
+
do {
|
|
69
|
+
try fm.moveItem(atPath: cleanFrom, toPath: cleanTo)
|
|
70
|
+
} catch {
|
|
71
|
+
try fm.copyItem(atPath: cleanFrom, toPath: cleanTo)
|
|
72
|
+
try? fm.removeItem(atPath: cleanFrom)
|
|
73
|
+
}
|
|
74
|
+
resolver(cleanTo)
|
|
75
|
+
} catch {
|
|
76
|
+
rejecter(
|
|
77
|
+
"FILE_MOVE_FAILED",
|
|
78
|
+
"Failed to move \(cleanFrom) → \(cleanTo): \(error.localizedDescription)",
|
|
79
|
+
error,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Resolve the lib's canonical default capture dir, creating it on
|
|
85
|
+
/// demand. Returns a bare absolute path.
|
|
86
|
+
@objc(defaultCaptureDir:rejecter:)
|
|
87
|
+
public func defaultCaptureDir(_ resolver: @escaping RCTPromiseResolveBlock,
|
|
88
|
+
rejecter: @escaping RCTPromiseRejectBlock) {
|
|
89
|
+
let caches = NSSearchPathForDirectoriesInDomains(
|
|
90
|
+
.cachesDirectory, .userDomainMask, true,
|
|
91
|
+
).first ?? NSTemporaryDirectory()
|
|
92
|
+
let dir = (caches as NSString).appendingPathComponent("react-native-image-stitcher")
|
|
93
|
+
do {
|
|
94
|
+
if !FileManager.default.fileExists(atPath: dir) {
|
|
95
|
+
try FileManager.default.createDirectory(
|
|
96
|
+
atPath: dir,
|
|
97
|
+
withIntermediateDirectories: true,
|
|
98
|
+
attributes: nil,
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
resolver(dir)
|
|
102
|
+
} catch {
|
|
103
|
+
rejecter(
|
|
104
|
+
"DIR_CREATE_FAILED",
|
|
105
|
+
"Failed to create canonical capture dir \(dir): \(error.localizedDescription)",
|
|
106
|
+
error,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-image-stitcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Pose-aware panorama capture + stitching for React Native. One <Camera> component, both tap-to-photo and hold-to-pan modes, both AR-backed and IMU-fallback capture paths.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|