react-native-image-stitcher 0.14.1 → 0.15.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 +160 -0
- package/README.md +35 -0
- package/RNImageStitcher.podspec +8 -1
- package/android/build.gradle +0 -16
- package/android/src/main/cpp/CMakeLists.txt +2 -63
- package/android/src/main/cpp/image_stitcher_jni.cpp +14 -0
- package/android/src/main/cpp/keyframe_gate_jni.cpp +13 -0
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +285 -3
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +180 -1162
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +29 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +0 -4
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +13 -64
- package/cpp/keyframe_gate.cpp +82 -23
- package/cpp/keyframe_gate.hpp +31 -2
- package/cpp/stitcher.cpp +208 -28
- package/cpp/tests/CMakeLists.txt +18 -12
- package/cpp/tests/keyframe_timebudget_test.cpp +65 -0
- package/cpp/tests/warp_guard_test.cpp +48 -0
- package/cpp/warp_guard.hpp +41 -0
- package/dist/ar/useARSession.d.ts +9 -0
- package/dist/ar/useARSession.js +24 -2
- package/dist/camera/Camera.d.ts +31 -16
- package/dist/camera/Camera.js +27 -4
- package/dist/camera/CaptureStitchStatsToast.d.ts +15 -2
- package/dist/camera/CaptureStitchStatsToast.js +27 -7
- package/dist/camera/PanoramaSettings.d.ts +10 -223
- package/dist/camera/PanoramaSettings.js +6 -28
- package/dist/camera/PanoramaSettingsBridge.d.ts +1 -24
- package/dist/camera/PanoramaSettingsBridge.js +3 -102
- package/dist/camera/PanoramaSettingsModal.js +7 -1
- package/dist/camera/buildPanoramaInitialSettings.d.ts +11 -0
- package/dist/camera/buildPanoramaInitialSettings.js +4 -0
- package/dist/camera/cameraErrorMessages.d.ts +32 -0
- package/dist/camera/cameraErrorMessages.js +53 -0
- package/dist/camera/selectCaptureDevice.d.ts +5 -1
- package/dist/camera/selectCaptureDevice.js +22 -2
- package/dist/camera/useCapture.js +38 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +11 -34
- package/dist/stitching/incremental.d.ts +1 -117
- package/dist/stitching/stitchVideo.d.ts +0 -35
- package/dist/types.d.ts +0 -87
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +96 -674
- package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +9 -12
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +14 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.h +7 -0
- package/ios/Sources/RNImageStitcher/KeyframeGateBridge.mm +6 -0
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.h +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +3 -3
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +28 -60
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +180 -921
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +10 -35
- package/ios/Sources/RNImageStitcher/Stitcher.swift +84 -35
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +13 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +132 -5
- package/package.json +3 -2
- package/src/ar/useARSession.ts +35 -5
- package/src/camera/Camera.tsx +63 -24
- package/src/camera/CaptureStitchStatsToast.tsx +58 -14
- package/src/camera/PanoramaSettings.ts +16 -289
- package/src/camera/PanoramaSettingsBridge.ts +3 -114
- package/src/camera/PanoramaSettingsModal.tsx +14 -1
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +3 -188
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +41 -0
- package/src/camera/__tests__/cameraErrorMessages.test.ts +76 -0
- package/src/camera/__tests__/selectCaptureDevice.test.ts +33 -0
- package/src/camera/buildPanoramaInitialSettings.ts +17 -0
- package/src/camera/cameraErrorMessages.ts +84 -0
- package/src/camera/selectCaptureDevice.ts +28 -3
- package/src/camera/useCapture.ts +44 -1
- package/src/index.ts +11 -40
- package/src/stitching/incremental.ts +3 -140
- package/src/stitching/stitchVideo.ts +0 -26
- package/src/types.ts +0 -95
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +0 -227
- package/android/src/main/java/io/imagestitcher/rn/IncrementalFirstwinsEngine.kt +0 -1081
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +0 -103
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +0 -256
- package/cpp/stitcher_frame_jsi.cpp +0 -214
- package/cpp/stitcher_frame_jsi.hpp +0 -108
- package/cpp/stitcher_proxy_jsi.cpp +0 -109
- package/cpp/stitcher_proxy_jsi.hpp +0 -46
- package/cpp/stitcher_worklet_dispatch.cpp +0 -103
- package/cpp/stitcher_worklet_dispatch.hpp +0 -71
- package/cpp/stitcher_worklet_registry.cpp +0 -91
- package/cpp/stitcher_worklet_registry.hpp +0 -146
- package/cpp/tests/stitcher_worklet_registry_test.cpp +0 -195
- package/dist/stitching/IncrementalStitcherView.d.ts +0 -41
- package/dist/stitching/IncrementalStitcherView.js +0 -157
- package/dist/stitching/StitcherWorkletRegistry.d.ts +0 -117
- package/dist/stitching/StitcherWorkletRegistry.js +0 -78
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +0 -8
- package/dist/stitching/ensureStitcherProxyInstalled.js +0 -81
- package/dist/stitching/useFrameProcessor.d.ts +0 -119
- package/dist/stitching/useFrameProcessor.js +0 -196
- package/dist/stitching/useFrameStream.d.ts +0 -34
- package/dist/stitching/useFrameStream.js +0 -234
- package/dist/stitching/useThrottledFrameProcessor.d.ts +0 -33
- package/dist/stitching/useThrottledFrameProcessor.js +0 -132
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.h +0 -474
- package/ios/Sources/RNImageStitcher/OpenCVIncrementalStitcher.mm +0 -1328
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.h +0 -103
- package/ios/Sources/RNImageStitcher/OpenCVSlitScanStitcher.mm +0 -3285
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +0 -128
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +0 -313
- package/ios/Sources/RNImageStitcher/SaveFrameAsJpegPlugin.mm +0 -185
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.h +0 -60
- package/ios/Sources/RNImageStitcher/StitcherFrameHostObject.mm +0 -214
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +0 -42
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +0 -160
- package/src/stitching/IncrementalStitcherView.tsx +0 -198
- package/src/stitching/StitcherWorkletRegistry.ts +0 -156
- package/src/stitching/__tests__/StitcherWorkletRegistry.test.ts +0 -176
- package/src/stitching/__tests__/ensureStitcherProxyInstalled.test.ts +0 -94
- package/src/stitching/__tests__/useThrottledFrameProcessor.test.ts +0 -178
- package/src/stitching/ensureStitcherProxyInstalled.ts +0 -141
- package/src/stitching/useFrameProcessor.ts +0 -226
- package/src/stitching/useFrameStream.ts +0 -271
- package/src/stitching/useThrottledFrameProcessor.ts +0 -145
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
// StitcherFrameHostObject.h — Obj-C facade for the v0.8.0
|
|
4
|
-
// `StitcherFrame` JSI host object. Header is intentionally
|
|
5
|
-
// Obj-C-only (no `<jsi/jsi.h>` import) so this can land in the
|
|
6
|
-
// public CocoaPods umbrella without breaking `use_frameworks!` hosts
|
|
7
|
-
// (same rationale as `KeyframeGateBridge.h`).
|
|
8
|
-
//
|
|
9
|
-
// The C++ JSI host object class lives in the .mm; this facade
|
|
10
|
-
// exposes only what cross-module callers need:
|
|
11
|
-
//
|
|
12
|
-
// - Factory `+ fromARFrame:pose:` that the AR worklet runtime
|
|
13
|
-
// calls per ARFrame to construct a host object backed by the
|
|
14
|
-
// current AR session's frame.
|
|
15
|
-
// - Opaque accessor `- (void *)jsiHostObjectPtr` returning the
|
|
16
|
-
// `std::shared_ptr<facebook::jsi::HostObject> *` (boxed) that
|
|
17
|
-
// the worklet runtime hands to `jsi::Object::createFromHostObject`.
|
|
18
|
-
//
|
|
19
|
-
// Lifetime: the Obj-C wrapper holds the C++ shared_ptr; ARC frees
|
|
20
|
-
// the wrapper when nothing references it. Worklet runtime
|
|
21
|
-
// invalidates the underlying ARFrame retain when the dispatch
|
|
22
|
-
// returns; after invalidation, JSI access throws.
|
|
23
|
-
|
|
24
|
-
#pragma once
|
|
25
|
-
|
|
26
|
-
#import <Foundation/Foundation.h>
|
|
27
|
-
#import <ARKit/ARKit.h>
|
|
28
|
-
|
|
29
|
-
@class RNSARFramePose;
|
|
30
|
-
|
|
31
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
32
|
-
|
|
33
|
-
NS_SWIFT_NAME(StitcherFrameHostObject)
|
|
34
|
-
@interface StitcherFrameHostObject : NSObject
|
|
35
|
-
|
|
36
|
-
/// Construct a host object backed by the supplied ARFrame + pose.
|
|
37
|
-
/// Retains the ARFrame for the host object's lifetime — caller can
|
|
38
|
-
/// safely release their reference.
|
|
39
|
-
///
|
|
40
|
-
/// Thread: safe to call from the ARSession delegate queue; the
|
|
41
|
-
/// resulting host object's JSI access must happen on the worklet
|
|
42
|
-
/// runtime's thread (separate queue).
|
|
43
|
-
+ (instancetype)fromARFrame:(ARFrame *)arFrame pose:(RNSARFramePose *)pose;
|
|
44
|
-
|
|
45
|
-
/// Mark the host object's underlying ARFrame as no longer accessible.
|
|
46
|
-
/// Subsequent JSI property reads return `undefined` or throw,
|
|
47
|
-
/// depending on the property. Idempotent.
|
|
48
|
-
- (void)invalidate;
|
|
49
|
-
|
|
50
|
-
/// Opaque pointer to a `std::shared_ptr<facebook::jsi::HostObject>`.
|
|
51
|
-
/// The worklet runtime (Obj-C++ context with JSI available) casts
|
|
52
|
-
/// this back via `*reinterpret_cast<std::shared_ptr<facebook::jsi::HostObject>*>(ptr)`
|
|
53
|
-
/// to hand to `jsi::Object::createFromHostObject`.
|
|
54
|
-
///
|
|
55
|
-
/// Returns `NULL` if the host object has been invalidated.
|
|
56
|
-
- (nullable void *)jsiHostObjectPtr;
|
|
57
|
-
|
|
58
|
-
@end
|
|
59
|
-
|
|
60
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
// StitcherFrameHostObject.mm — iOS-specific wrapper for the shared
|
|
4
|
-
// `retailens::StitcherFrameJsiHostObject` (defined in
|
|
5
|
-
// `cpp/stitcher_frame_jsi.{hpp,cpp}`).
|
|
6
|
-
//
|
|
7
|
-
// Owns:
|
|
8
|
-
// - The Obj-C facade callable from Swift / other Obj-C / .mm files.
|
|
9
|
-
// - The iOS-specific `PixelBufferReader` impl (wraps a
|
|
10
|
-
// `CVPixelBufferRef` from `ARFrame.capturedImage`; lock / memcpy
|
|
11
|
-
// / unlock pattern).
|
|
12
|
-
// - The Obj-C → C++ extraction logic that builds a
|
|
13
|
-
// `retailens::StitcherFrameData` from an `ARFrame` + the lib's
|
|
14
|
-
// `RNSARFramePose`.
|
|
15
|
-
//
|
|
16
|
-
// Does NOT own:
|
|
17
|
-
// - The JSI `get` / `getPropertyNames` dispatch. That lives in
|
|
18
|
-
// `cpp/stitcher_frame_jsi.cpp` and is identical to the Android
|
|
19
|
-
// implementation (DRY across platforms).
|
|
20
|
-
|
|
21
|
-
#import "StitcherFrameHostObject.h"
|
|
22
|
-
|
|
23
|
-
#import <Foundation/Foundation.h>
|
|
24
|
-
#import <CoreVideo/CVPixelBuffer.h>
|
|
25
|
-
#import <CoreMedia/CoreMedia.h>
|
|
26
|
-
#import <os/log.h>
|
|
27
|
-
|
|
28
|
-
#include <jsi/jsi.h>
|
|
29
|
-
|
|
30
|
-
#include <algorithm>
|
|
31
|
-
#include <cstring>
|
|
32
|
-
#include <memory>
|
|
33
|
-
#include <string>
|
|
34
|
-
#include <utility>
|
|
35
|
-
|
|
36
|
-
#include "stitcher_frame_data.hpp"
|
|
37
|
-
#include "stitcher_frame_jsi.hpp"
|
|
38
|
-
|
|
39
|
-
using namespace facebook;
|
|
40
|
-
|
|
41
|
-
// Forward-declare the Swift `RNSARFramePose` Obj-C surface we need.
|
|
42
|
-
// This matches the pattern in `KeyframeGateFrameProcessor.mm`
|
|
43
|
-
// (forward-declaring `IncrementalStitcher`) — avoids depending on
|
|
44
|
-
// the autogenerated `RNImageStitcher-Swift.h`, which is created at
|
|
45
|
-
// build time and not always available to .mm files in this pod.
|
|
46
|
-
//
|
|
47
|
-
// MUST stay in sync with `RNSARSession.swift::RNSARFramePose` —
|
|
48
|
-
// adding a new field there means adding it here too.
|
|
49
|
-
@class RNSARFramePose;
|
|
50
|
-
@interface RNSARFramePose : NSObject
|
|
51
|
-
@property (nonatomic, readonly) double tx;
|
|
52
|
-
@property (nonatomic, readonly) double ty;
|
|
53
|
-
@property (nonatomic, readonly) double tz;
|
|
54
|
-
@property (nonatomic, readonly) double qx;
|
|
55
|
-
@property (nonatomic, readonly) double qy;
|
|
56
|
-
@property (nonatomic, readonly) double qz;
|
|
57
|
-
@property (nonatomic, readonly) double qw;
|
|
58
|
-
@property (nonatomic, readonly) NSInteger imageWidth;
|
|
59
|
-
@property (nonatomic, readonly) NSInteger imageHeight;
|
|
60
|
-
@property (nonatomic, readonly) double timestampMs;
|
|
61
|
-
@end
|
|
62
|
-
|
|
63
|
-
#pragma mark - iOS PixelBufferReader
|
|
64
|
-
|
|
65
|
-
namespace {
|
|
66
|
-
|
|
67
|
-
/// iOS-specific `retailens::PixelBufferReader` impl. See the base
|
|
68
|
-
/// class docstring for the general contract (thread-affinity,
|
|
69
|
-
/// invalidation semantics, Y-plane-only constraint). This subclass
|
|
70
|
-
/// adds:
|
|
71
|
-
/// - `CVPixelBuffer` lock/memcpy/unlock per copyTo
|
|
72
|
-
/// - `CFBridgingRetain` of the parent `ARFrame` so ARKit's
|
|
73
|
-
/// pool can't reclaim the underlying buffer mid-read
|
|
74
|
-
class IOSPixelBufferReader : public retailens::PixelBufferReader {
|
|
75
|
-
public:
|
|
76
|
-
explicit IOSPixelBufferReader(ARFrame* arFrame) {
|
|
77
|
-
// Retain the ARFrame for our lifetime. CFBridgingRetain hands
|
|
78
|
-
// ARC ownership to our void*. Released in destructor.
|
|
79
|
-
_retainedFrame = (void*)CFBridgingRetain(arFrame);
|
|
80
|
-
CVPixelBufferRef pixelBuffer = arFrame.capturedImage;
|
|
81
|
-
if (pixelBuffer != NULL) {
|
|
82
|
-
_bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
83
|
-
_height = CVPixelBufferGetHeight(pixelBuffer);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
~IOSPixelBufferReader() override {
|
|
88
|
-
// Transfer ownership back to ARC, which then releases.
|
|
89
|
-
if (_retainedFrame != nullptr) {
|
|
90
|
-
ARFrame* frame = CFBridgingRelease(_retainedFrame);
|
|
91
|
-
(void)frame;
|
|
92
|
-
_retainedFrame = nullptr;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
std::size_t byteSize() const override {
|
|
97
|
-
return _bytesPerRow * _height;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
std::size_t copyTo(uint8_t* dst, std::size_t maxBytes) override {
|
|
101
|
-
if (_retainedFrame == nullptr) return 0;
|
|
102
|
-
ARFrame* frame = (__bridge ARFrame*)_retainedFrame;
|
|
103
|
-
CVPixelBufferRef pixelBuffer = frame.capturedImage;
|
|
104
|
-
if (pixelBuffer == NULL) return 0;
|
|
105
|
-
|
|
106
|
-
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
|
107
|
-
const uint8_t* src = (const uint8_t*)CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
108
|
-
std::size_t toCopy = std::min<std::size_t>(byteSize(), maxBytes);
|
|
109
|
-
if (src != nullptr && toCopy > 0) {
|
|
110
|
-
std::memcpy(dst, src, toCopy);
|
|
111
|
-
} else {
|
|
112
|
-
toCopy = 0;
|
|
113
|
-
}
|
|
114
|
-
CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
|
|
115
|
-
return toCopy;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private:
|
|
119
|
-
void* _retainedFrame = nullptr; // CFBridgingRetain'd ARFrame
|
|
120
|
-
std::size_t _bytesPerRow = 0;
|
|
121
|
-
std::size_t _height = 0;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
} // anonymous namespace
|
|
125
|
-
|
|
126
|
-
#pragma mark - Obj-C facade
|
|
127
|
-
|
|
128
|
-
@implementation StitcherFrameHostObject {
|
|
129
|
-
std::shared_ptr<retailens::StitcherFrameJsiHostObject> _hostObject;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
+ (instancetype)fromARFrame:(ARFrame*)arFrame pose:(RNSARFramePose*)pose {
|
|
133
|
-
StitcherFrameHostObject* obj = [[self alloc] init];
|
|
134
|
-
|
|
135
|
-
retailens::StitcherFrameData data;
|
|
136
|
-
data.source = "ar";
|
|
137
|
-
data.width = static_cast<int32_t>(pose.imageWidth);
|
|
138
|
-
data.height = static_cast<int32_t>(pose.imageHeight);
|
|
139
|
-
// ARKit's `kCVPixelFormatType_420YpCbCr8BiPlanarFullRange` (NV12)
|
|
140
|
-
// is reported as "yuv". Other formats (rare in ARKit; possible if
|
|
141
|
-
// ARWorldTrackingConfiguration.videoFormat is overridden to BGRA)
|
|
142
|
-
// → "unknown" + os_log warning so worklets that gate on
|
|
143
|
-
// `pixelFormat === 'yuv'` can be debugged without a screen recording.
|
|
144
|
-
OSType pf = CVPixelBufferGetPixelFormatType(arFrame.capturedImage);
|
|
145
|
-
if (pf == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
|
|
146
|
-
pf == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) {
|
|
147
|
-
data.pixelFormat = "yuv";
|
|
148
|
-
} else {
|
|
149
|
-
data.pixelFormat = "unknown";
|
|
150
|
-
os_log_error(OS_LOG_DEFAULT,
|
|
151
|
-
"[StitcherFrame] unexpected ARKit pixel format 0x%x; "
|
|
152
|
-
"worklet receives pixelFormat='unknown' and toArrayBuffer() "
|
|
153
|
-
"bytes are first-plane only (layout undefined for unknown "
|
|
154
|
-
"formats). See StitcherFrame.ts docstring.", (unsigned int)pf);
|
|
155
|
-
}
|
|
156
|
-
// ARKit doesn't have a `Frame.orientation` per se; pose carries
|
|
157
|
-
// the imageWidth >= imageHeight discriminator the lib uses
|
|
158
|
-
// elsewhere (`isLandscape`). v0.8.0 ships a coarse mapping;
|
|
159
|
-
// worklets that need exact UI orientation can read it from
|
|
160
|
-
// device-orientation sensors.
|
|
161
|
-
data.orientation =
|
|
162
|
-
(pose.imageWidth >= pose.imageHeight) ? "landscape-right" : "portrait";
|
|
163
|
-
// `ARFrame.timestamp` is CFAbsoluteTime (seconds since epoch).
|
|
164
|
-
// Convert to ns to match vc Frame.timestamp.
|
|
165
|
-
data.timestampNs = arFrame.timestamp * 1e9;
|
|
166
|
-
|
|
167
|
-
data.qx = pose.qx;
|
|
168
|
-
data.qy = pose.qy;
|
|
169
|
-
data.qz = pose.qz;
|
|
170
|
-
data.qw = pose.qw;
|
|
171
|
-
data.tx = pose.tx;
|
|
172
|
-
data.ty = pose.ty;
|
|
173
|
-
data.tz = pose.tz;
|
|
174
|
-
data.hasTranslation = true; // AR mode always has translation
|
|
175
|
-
|
|
176
|
-
switch (arFrame.camera.trackingState) {
|
|
177
|
-
case ARTrackingStateNotAvailable:
|
|
178
|
-
data.arTrackingState = "notAvailable";
|
|
179
|
-
break;
|
|
180
|
-
case ARTrackingStateLimited:
|
|
181
|
-
data.arTrackingState = "limited";
|
|
182
|
-
break;
|
|
183
|
-
case ARTrackingStateNormal:
|
|
184
|
-
data.arTrackingState = "normal";
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
data.pixelReader = std::make_shared<IOSPixelBufferReader>(arFrame);
|
|
189
|
-
|
|
190
|
-
// Use the static factory (private ctor enforces shared_ptr
|
|
191
|
-
// ownership — required for `shared_from_this()` inside the JSI
|
|
192
|
-
// `toArrayBuffer` lambda).
|
|
193
|
-
obj->_hostObject =
|
|
194
|
-
retailens::StitcherFrameJsiHostObject::create(std::move(data));
|
|
195
|
-
return obj;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
- (void)invalidate {
|
|
199
|
-
if (_hostObject) {
|
|
200
|
-
_hostObject->invalidate();
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
- (void*)jsiHostObjectPtr {
|
|
205
|
-
if (!_hostObject) return NULL;
|
|
206
|
-
// Box a heap-allocated copy of the shared_ptr to the abstract
|
|
207
|
-
// `jsi::HostObject` base. Caller (worklet runtime) does:
|
|
208
|
-
// auto sp = static_cast<std::shared_ptr<jsi::HostObject>*>(ptr);
|
|
209
|
-
// auto jsObj = jsi::Object::createFromHostObject(rt, *sp);
|
|
210
|
-
// delete sp;
|
|
211
|
-
return new std::shared_ptr<jsi::HostObject>(_hostObject);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
@end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
// StitcherJsiInstaller.h — RN module that installs the
|
|
4
|
-
// `globalThis.__stitcherProxy` JSI host object on the main JS
|
|
5
|
-
// runtime. Called once at lib boot from the TS layer
|
|
6
|
-
// (`src/index.ts` or the `useFrameProcessor` hook) via a
|
|
7
|
-
// synchronous JS bridge call.
|
|
8
|
-
//
|
|
9
|
-
// The proxy exposes two host functions:
|
|
10
|
-
//
|
|
11
|
-
// __stitcherProxy.install(workletFn) → string ID
|
|
12
|
-
// __stitcherProxy.uninstall(id) → undefined
|
|
13
|
-
//
|
|
14
|
-
// `install` wraps the worklet function into a
|
|
15
|
-
// `RNWorklet::WorkletInvoker` and stores it in the C++
|
|
16
|
-
// `retailens::StitcherWorkletRegistry` singleton (in
|
|
17
|
-
// `cpp/stitcher_worklet_registry.{hpp,cpp}`). The AR worklet
|
|
18
|
-
// runtime's per-frame dispatch reads from that registry to fan
|
|
19
|
-
// out invocations.
|
|
20
|
-
//
|
|
21
|
-
// Why a RN module (not a vanilla NSObject installable):
|
|
22
|
-
// - Hosts can't reliably reach into the JSI runtime from JS
|
|
23
|
-
// without a native sync method to broker the install.
|
|
24
|
-
// - `RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD` is the documented
|
|
25
|
-
// pattern for "JS calls a native method synchronously to
|
|
26
|
-
// install JSI bindings on the main runtime". vision-camera
|
|
27
|
-
// uses the same pattern (`VisionCameraInstaller.mm`).
|
|
28
|
-
// - In RN's bridgeless mode the legacy `RCTCxxBridge.runtime`
|
|
29
|
-
// accessor still works (vc has a comment to migrate but it
|
|
30
|
-
// hasn't been needed yet — same applies to us).
|
|
31
|
-
|
|
32
|
-
#pragma once
|
|
33
|
-
|
|
34
|
-
#import <Foundation/Foundation.h>
|
|
35
|
-
#import <React/RCTBridgeModule.h>
|
|
36
|
-
|
|
37
|
-
NS_ASSUME_NONNULL_BEGIN
|
|
38
|
-
|
|
39
|
-
@interface StitcherJsiInstaller : NSObject <RCTBridgeModule>
|
|
40
|
-
@end
|
|
41
|
-
|
|
42
|
-
NS_ASSUME_NONNULL_END
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
3
|
-
// StitcherJsiInstaller.mm — implementation. Installs
|
|
4
|
-
// `globalThis.__stitcherProxy` on the main JS runtime.
|
|
5
|
-
//
|
|
6
|
-
// ## Why a host object rather than two globalThis functions
|
|
7
|
-
//
|
|
8
|
-
// We could install `__stitcherProxy_install` + `__stitcherProxy_uninstall`
|
|
9
|
-
// directly on `globalThis`. Wrapping them in a host object is
|
|
10
|
-
// slightly more code but:
|
|
11
|
-
// - Namespaces the proxy under a single global property
|
|
12
|
-
// (easier to feature-detect; one `if (globalThis.__stitcherProxy)`
|
|
13
|
-
// instead of two).
|
|
14
|
-
// - Matches vc's pattern (`global.VisionCameraProxy`), so future
|
|
15
|
-
// readers recognise the shape.
|
|
16
|
-
// - Keeps room to grow (e.g., add `__stitcherProxy.snapshot()` for
|
|
17
|
-
// diagnostics) without polluting globalThis further.
|
|
18
|
-
|
|
19
|
-
#import "StitcherJsiInstaller.h"
|
|
20
|
-
|
|
21
|
-
#import <Foundation/Foundation.h>
|
|
22
|
-
#import <React/RCTBridge.h>
|
|
23
|
-
#import <React/RCTBridge+Private.h>
|
|
24
|
-
#import <React/RCTUtils.h>
|
|
25
|
-
#import <ReactCommon/CallInvoker.h>
|
|
26
|
-
// `RCTCxxBridge` (and its bridgeless-mode `RCTBridgeProxy` forwarder)
|
|
27
|
-
// exposes `-jsCallInvoker` returning `std::shared_ptr<CallInvoker>`,
|
|
28
|
-
// but the property declaration lives in `<ReactCommon/RCTTurboModule.h>`
|
|
29
|
-
// which isn't on our pod's HEADER_SEARCH_PATHS (worklets-core gets it
|
|
30
|
-
// via its own ReactCommon dep). Rather than enlarging our pod's
|
|
31
|
-
// dependency surface, forward-declare the property in an anonymous
|
|
32
|
-
// category — the runtime dispatches to RN's actual implementation.
|
|
33
|
-
// Pattern matches `WKTJsiWorkletContext.cpp`'s approach to keep the
|
|
34
|
-
// pod self-contained.
|
|
35
|
-
@interface RCTCxxBridge ()
|
|
36
|
-
@property (nonatomic, readonly) std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker;
|
|
37
|
-
@end
|
|
38
|
-
#import <os/log.h>
|
|
39
|
-
|
|
40
|
-
#include <jsi/jsi.h>
|
|
41
|
-
|
|
42
|
-
#include "stitcher_proxy_jsi.hpp"
|
|
43
|
-
// v0.11.1 — worklets-core JsiWorkletContext. We initialize the
|
|
44
|
-
// SINGLETON default instance here so that other contexts in this
|
|
45
|
-
// library that use the 2-arg `JsiWorkletContext(name, workletInvoker)`
|
|
46
|
-
// constructor inherit a working `_jsCallInvoker` (and thus their
|
|
47
|
-
// `runOnJS` / `Worklets.createRunOnJS` callbacks actually route back
|
|
48
|
-
// to the main JS thread). Specifically: `RNSARWorkletRuntime`'s AR-
|
|
49
|
-
// side worklet context (see `RNSARWorkletRuntime.mm:155`) uses the
|
|
50
|
-
// 2-arg ctor; pre-v0.11.1 that left its inherited `_jsCallInvoker`
|
|
51
|
-
// nullptr, and `invokeOnJsThread` silently no-op'd (see
|
|
52
|
-
// `WKTJsiWorkletContext.cpp:124-131`). Test 2 of the v0.11.0
|
|
53
|
-
// manual-verification checklist surfaced this as "AR-mode host
|
|
54
|
-
// worklets register but their runOnJS callbacks never fire."
|
|
55
|
-
#include "WKTJsiWorkletContext.h"
|
|
56
|
-
|
|
57
|
-
using namespace facebook;
|
|
58
|
-
|
|
59
|
-
// The host object class + install logic moved to shared C++ in
|
|
60
|
-
// `cpp/stitcher_proxy_jsi.{hpp,cpp}` (v0.8.0 Phase 4b.ii). The
|
|
61
|
-
// Android JNI installer reuses the same `install` / `uninstall` /
|
|
62
|
-
// `count` host functions verbatim — the JSI dispatch is identical
|
|
63
|
-
// across platforms (matches the StitcherFrame host object's design).
|
|
64
|
-
|
|
65
|
-
#pragma mark - RN module
|
|
66
|
-
|
|
67
|
-
@implementation StitcherJsiInstaller
|
|
68
|
-
|
|
69
|
-
// RN injects `_bridge` at module init (legacy bridge → RCTBridge*;
|
|
70
|
-
// bridgeless / new arch → RCTBridgeProxy*, which forwards `runtime`
|
|
71
|
-
// access via NSProxy `forwardInvocation:`). Using the injected
|
|
72
|
-
// `_bridge` instead of `[RCTBridge currentBridge]` is the
|
|
73
|
-
// bridgeless-compatible idiom — `currentBridge` is nil under new
|
|
74
|
-
// arch. Pattern lifted from `react-native-worklets-core/ios/Worklets.mm`.
|
|
75
|
-
@synthesize bridge = _bridge;
|
|
76
|
-
|
|
77
|
-
RCT_EXPORT_MODULE()
|
|
78
|
-
|
|
79
|
-
+ (BOOL)requiresMainQueueSetup {
|
|
80
|
-
return YES;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
- (void)setBridge:(RCTBridge*)bridge {
|
|
84
|
-
_bridge = bridge;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Synchronous install method. JS calls this once at lib bootstrap
|
|
88
|
-
// to install the global proxy on the main JS runtime. Returns
|
|
89
|
-
// `@YES` on success or `@NO` if the JSI runtime wasn't reachable
|
|
90
|
-
// (remote debug mode pre-Hermes; bridge not yet ready; etc.).
|
|
91
|
-
//
|
|
92
|
-
// `RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD` is the documented
|
|
93
|
-
// pattern for "run native code synchronously on the JS thread to
|
|
94
|
-
// install JSI bindings." Same pattern worklets-core + vision-camera
|
|
95
|
-
// use for their installs.
|
|
96
|
-
//
|
|
97
|
-
// **Bridgeless mode:** `_bridge` is an `RCTBridgeProxy` (NSProxy
|
|
98
|
-
// subclass) that forwards `-runtime` / `-jsCallInvoker` invocations
|
|
99
|
-
// to the underlying RCTHost-backed runtime. The `(RCTCxxBridge*)`
|
|
100
|
-
// cast is a no-op at runtime (NSProxy ignores static type) but
|
|
101
|
-
// keeps the Obj-C compiler happy about property access.
|
|
102
|
-
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
103
|
-
if (_bridge == nil) {
|
|
104
|
-
os_log_error(OS_LOG_DEFAULT,
|
|
105
|
-
"[StitcherJsiInstaller] _bridge is nil; the module was "
|
|
106
|
-
"instantiated without bridge injection. Cannot install "
|
|
107
|
-
"__stitcherProxy.");
|
|
108
|
-
return @NO;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
RCTCxxBridge* cxxBridge = (RCTCxxBridge*)_bridge;
|
|
112
|
-
if (cxxBridge.runtime == nullptr) {
|
|
113
|
-
os_log_error(OS_LOG_DEFAULT,
|
|
114
|
-
"[StitcherJsiInstaller] _bridge.runtime is nullptr; the JS "
|
|
115
|
-
"runtime hasn't been initialized yet OR remote debugger is "
|
|
116
|
-
"attached. Cannot install __stitcherProxy.");
|
|
117
|
-
return @NO;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
jsi::Runtime& runtime = *(jsi::Runtime*)cxxBridge.runtime;
|
|
121
|
-
retailens::installStitcherProxy(runtime);
|
|
122
|
-
|
|
123
|
-
// v0.11.1 — initialize the singleton default JsiWorkletContext so
|
|
124
|
-
// that downstream 2-arg ctors (RNSARWorkletRuntime) inherit a
|
|
125
|
-
// working `_jsCallInvoker`. Without this, AR-mode host worklets'
|
|
126
|
-
// `runOnJS` / `Worklets.createRunOnJS` callbacks silently no-op
|
|
127
|
-
// (`WKTJsiWorkletContext.cpp:124-131` early-returns when
|
|
128
|
-
// `_jsCallInvoker == nullptr`). See file-top comment for the full
|
|
129
|
-
// diagnosis (Test 2 of v0.11.0 manual-verification checklist).
|
|
130
|
-
//
|
|
131
|
-
// Idempotent at the worklets-core level: re-initialization is
|
|
132
|
-
// tolerated; the default instance is a process-scope singleton
|
|
133
|
-
// and we're called once per JS-runtime bootstrap. In bridgeless
|
|
134
|
-
// mode `cxxBridge.jsCallInvoker` is forwarded via RCTBridgeProxy
|
|
135
|
-
// to the underlying RCTHost's `CallInvoker` (same forwarding
|
|
136
|
-
// pattern as `cxxBridge.runtime` above).
|
|
137
|
-
auto jsCallInvoker = cxxBridge.jsCallInvoker;
|
|
138
|
-
if (jsCallInvoker == nullptr) {
|
|
139
|
-
os_log_error(OS_LOG_DEFAULT,
|
|
140
|
-
"[StitcherJsiInstaller] cxxBridge.jsCallInvoker is nullptr; "
|
|
141
|
-
"AR-mode host worklets' runOnJS will not fire. Proxy installed "
|
|
142
|
-
"but worklet-bridging is impaired.");
|
|
143
|
-
// Proxy is still installed; only the runOnJS path is impaired.
|
|
144
|
-
// Return @YES so JS callers don't fall back to the JS-side registry.
|
|
145
|
-
return @YES;
|
|
146
|
-
}
|
|
147
|
-
auto jsInvokerAdapter =
|
|
148
|
-
[jsCallInvoker](std::function<void()>&& fp) {
|
|
149
|
-
jsCallInvoker->invokeAsync(std::move(fp));
|
|
150
|
-
};
|
|
151
|
-
RNWorklet::JsiWorkletContext::getDefaultInstance()->initialize(
|
|
152
|
-
"stitcher.default", &runtime, jsInvokerAdapter);
|
|
153
|
-
|
|
154
|
-
os_log_info(OS_LOG_DEFAULT,
|
|
155
|
-
"[StitcherJsiInstaller] installed globalThis.__stitcherProxy "
|
|
156
|
-
"AND initialized default JsiWorkletContext on main JS runtime.");
|
|
157
|
-
return @YES;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
@end
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
/**
|
|
3
|
-
* IncrementalStitcherView — live preview component for the panorama
|
|
4
|
-
* engine. Renders the latest snapshot JPEG written by the native
|
|
5
|
-
* side, with confidence + hint overlays.
|
|
6
|
-
*
|
|
7
|
-
* Why <Image> + cache-bust query string instead of a custom native
|
|
8
|
-
* view: per the design doc's open question, the JPEG-write approach
|
|
9
|
-
* is V1; if perf measurements show we're hitting RN's image cache
|
|
10
|
-
* too hard, swap in an `Animated.Image` or a native UIView with
|
|
11
|
-
* an in-memory bitmap. Until then, the simple path keeps the
|
|
12
|
-
* cross-platform surface tiny.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import React, { useMemo } from 'react';
|
|
16
|
-
import {
|
|
17
|
-
ActivityIndicator,
|
|
18
|
-
Image,
|
|
19
|
-
StyleSheet,
|
|
20
|
-
Text,
|
|
21
|
-
View,
|
|
22
|
-
type ViewStyle,
|
|
23
|
-
} from 'react-native';
|
|
24
|
-
import type { IncrementalState } from './incremental';
|
|
25
|
-
import type { IncrementalHint } from './useIncrementalStitcher';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export interface IncrementalStitcherViewProps {
|
|
29
|
-
/** Latest engine state — typically `useIncrementalStitcher().state`. */
|
|
30
|
-
state: IncrementalState | null;
|
|
31
|
-
/**
|
|
32
|
-
* Active hint to surface as a banner overlay. Pass
|
|
33
|
-
* `useIncrementalStitcher().hint` directly; the view picks the
|
|
34
|
-
* right wording.
|
|
35
|
-
*/
|
|
36
|
-
hint: IncrementalHint;
|
|
37
|
-
/**
|
|
38
|
-
* Confidence ring colour driver — typically
|
|
39
|
-
* `useIncrementalStitcher().confidenceLevel`.
|
|
40
|
-
*/
|
|
41
|
-
confidenceLevel?: 'high' | 'medium' | null;
|
|
42
|
-
/** Outer container style (size, position). Required: the view
|
|
43
|
-
* has no intrinsic size since the panorama dimensions vary. */
|
|
44
|
-
style?: ViewStyle;
|
|
45
|
-
/**
|
|
46
|
-
* Optional override for the spinner shown before the first frame
|
|
47
|
-
* is accepted. Default is a subtle "Pan to begin" caption.
|
|
48
|
-
*/
|
|
49
|
-
emptyText?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
function hintMessage(hint: IncrementalHint): string | null {
|
|
54
|
-
switch (hint) {
|
|
55
|
-
case 'slow-down': return 'Slow down — alignment lost';
|
|
56
|
-
case 'scene-uniform': return 'Pan to a textured area';
|
|
57
|
-
case 'alignment-lost': return 'Slow down — re-acquiring alignment';
|
|
58
|
-
case 'tracking-poor': return 'Hold steady — AR re-acquiring';
|
|
59
|
-
default: return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
export function IncrementalStitcherView({
|
|
65
|
-
state,
|
|
66
|
-
hint,
|
|
67
|
-
confidenceLevel,
|
|
68
|
-
style,
|
|
69
|
-
emptyText = 'Pan to begin capturing',
|
|
70
|
-
}: IncrementalStitcherViewProps): React.JSX.Element {
|
|
71
|
-
// Cache-bust the panorama URI. The native side rotates through
|
|
72
|
-
// 4 filenames so the path itself changes between snapshots, plus
|
|
73
|
-
// we tag with acceptedCount as belt-and-suspenders since RN's
|
|
74
|
-
// image cache on iOS sometimes ignores file:// query strings.
|
|
75
|
-
const imageUri = useMemo(() => {
|
|
76
|
-
if (!state?.panoramaPath) return null;
|
|
77
|
-
return `file://${state.panoramaPath}?v=${state.acceptedCount}`;
|
|
78
|
-
}, [state?.panoramaPath, state?.acceptedCount]);
|
|
79
|
-
|
|
80
|
-
// Use the panorama's NATURAL aspect ratio so the strip widens as
|
|
81
|
-
// the user pans across. Falls back to 4:3 (a single frame's
|
|
82
|
-
// shape) before any snapshot has been written. Without this the
|
|
83
|
-
// PiP was forced into a 3:1 letterbox, cropping the actual
|
|
84
|
-
// panorama to a thin slice across the middle.
|
|
85
|
-
const naturalAspect = state?.width && state?.height && state.height > 0
|
|
86
|
-
? state.width / state.height
|
|
87
|
-
: 4 / 3;
|
|
88
|
-
|
|
89
|
-
const ringColor = confidenceLevel === 'high'
|
|
90
|
-
? '#1aaf5d'
|
|
91
|
-
: confidenceLevel === 'medium'
|
|
92
|
-
? '#e6b800'
|
|
93
|
-
: 'rgba(255,255,255,0.35)';
|
|
94
|
-
|
|
95
|
-
const message = hintMessage(hint);
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<View style={[styles.container, { aspectRatio: naturalAspect }, style]}>
|
|
99
|
-
{imageUri ? (
|
|
100
|
-
// `contain` so the FULL panorama is visible inside the
|
|
101
|
-
// strip, not cropped to a slice. Background fills the
|
|
102
|
-
// letterbox edges. Key={acceptedCount} forces RN to
|
|
103
|
-
// remount the Image component each accept — the surest
|
|
104
|
-
// way to defeat the native image cache on file:// URIs.
|
|
105
|
-
<Image
|
|
106
|
-
key={state?.acceptedCount ?? 0}
|
|
107
|
-
source={{ uri: imageUri }}
|
|
108
|
-
style={StyleSheet.absoluteFill}
|
|
109
|
-
resizeMode="contain"
|
|
110
|
-
fadeDuration={0}
|
|
111
|
-
/>
|
|
112
|
-
) : (
|
|
113
|
-
<View style={styles.empty}>
|
|
114
|
-
<ActivityIndicator color="#fff" />
|
|
115
|
-
<Text style={styles.emptyText}>{emptyText}</Text>
|
|
116
|
-
</View>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
|
-
{/* Confidence ring — subtle border that picks up colour for
|
|
120
|
-
medium-confidence accepts. Always visible (white-translucent
|
|
121
|
-
when no confidence signal) so the operator can see exactly
|
|
122
|
-
where the live preview is on screen. */}
|
|
123
|
-
<View
|
|
124
|
-
pointerEvents="none"
|
|
125
|
-
style={[styles.ring, { borderColor: ringColor }]}
|
|
126
|
-
/>
|
|
127
|
-
|
|
128
|
-
{message ? (
|
|
129
|
-
<View pointerEvents="none" style={styles.hintBanner}>
|
|
130
|
-
<Text style={styles.hintText}>{message}</Text>
|
|
131
|
-
</View>
|
|
132
|
-
) : null}
|
|
133
|
-
|
|
134
|
-
{state ? (
|
|
135
|
-
<View pointerEvents="none" style={styles.counterPill}>
|
|
136
|
-
<Text style={styles.counterText}>
|
|
137
|
-
{state.acceptedCount} frame{state.acceptedCount === 1 ? '' : 's'}
|
|
138
|
-
</Text>
|
|
139
|
-
</View>
|
|
140
|
-
) : null}
|
|
141
|
-
</View>
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const styles = StyleSheet.create({
|
|
147
|
-
container: {
|
|
148
|
-
backgroundColor: 'rgba(0,0,0,0.7)',
|
|
149
|
-
overflow: 'hidden',
|
|
150
|
-
borderRadius: 8,
|
|
151
|
-
},
|
|
152
|
-
empty: {
|
|
153
|
-
...StyleSheet.absoluteFillObject,
|
|
154
|
-
alignItems: 'center',
|
|
155
|
-
justifyContent: 'center',
|
|
156
|
-
gap: 6,
|
|
157
|
-
},
|
|
158
|
-
emptyText: {
|
|
159
|
-
color: '#fff',
|
|
160
|
-
fontSize: 12,
|
|
161
|
-
opacity: 0.85,
|
|
162
|
-
},
|
|
163
|
-
ring: {
|
|
164
|
-
...StyleSheet.absoluteFillObject,
|
|
165
|
-
borderRadius: 8,
|
|
166
|
-
borderWidth: 2,
|
|
167
|
-
},
|
|
168
|
-
hintBanner: {
|
|
169
|
-
position: 'absolute',
|
|
170
|
-
left: 8,
|
|
171
|
-
right: 8,
|
|
172
|
-
bottom: 8,
|
|
173
|
-
paddingVertical: 4,
|
|
174
|
-
paddingHorizontal: 8,
|
|
175
|
-
backgroundColor: 'rgba(220, 53, 69, 0.92)',
|
|
176
|
-
borderRadius: 6,
|
|
177
|
-
},
|
|
178
|
-
hintText: {
|
|
179
|
-
color: '#fff',
|
|
180
|
-
fontSize: 11,
|
|
181
|
-
textAlign: 'center',
|
|
182
|
-
fontWeight: '500',
|
|
183
|
-
},
|
|
184
|
-
counterPill: {
|
|
185
|
-
position: 'absolute',
|
|
186
|
-
top: 6,
|
|
187
|
-
right: 6,
|
|
188
|
-
paddingVertical: 2,
|
|
189
|
-
paddingHorizontal: 8,
|
|
190
|
-
backgroundColor: 'rgba(0, 0, 0, 0.55)',
|
|
191
|
-
borderRadius: 10,
|
|
192
|
-
},
|
|
193
|
-
counterText: {
|
|
194
|
-
color: '#fff',
|
|
195
|
-
fontSize: 10,
|
|
196
|
-
fontWeight: '500',
|
|
197
|
-
},
|
|
198
|
-
});
|