react-native-image-stitcher 0.15.1 → 0.16.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 +147 -1
- package/README.md +116 -5
- package/android/src/main/cpp/image_stitcher_jni.cpp +107 -11
- package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +223 -1
- package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +87 -30
- package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +14 -8
- package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +39 -1
- package/cpp/crop_quad.cpp +162 -0
- package/cpp/crop_quad.hpp +163 -0
- package/cpp/stitcher.cpp +651 -55
- package/cpp/stitcher.hpp +10 -0
- package/cpp/warp_guard.hpp +212 -0
- package/dist/camera/Camera.d.ts +196 -12
- package/dist/camera/Camera.js +629 -35
- package/dist/camera/CameraView.js +62 -5
- package/dist/camera/CaptureCountdownOverlay.d.ts +70 -0
- package/dist/camera/CaptureCountdownOverlay.js +239 -0
- package/dist/camera/CaptureFrameCounterOverlay.d.ts +58 -0
- package/dist/camera/CaptureFrameCounterOverlay.js +142 -0
- package/dist/camera/CaptureMemoryPill.d.ts +9 -1
- package/dist/camera/CaptureMemoryPill.js +3 -3
- package/dist/camera/CapturePreview.js +2 -1
- package/dist/camera/CaptureStatusOverlay.d.ts +11 -4
- package/dist/camera/CaptureStatusOverlay.js +22 -5
- package/dist/camera/CaptureThumbnailStrip.js +2 -1
- package/dist/camera/LateralMotionModal.d.ts +85 -0
- package/dist/camera/LateralMotionModal.js +134 -0
- package/dist/camera/PanHowToOverlay.d.ts +76 -0
- package/dist/camera/PanHowToOverlay.js +222 -0
- package/dist/camera/PanoramaSettings.d.ts +8 -6
- package/dist/camera/PanoramaSettings.js +26 -5
- package/dist/camera/PanoramaSettingsModal.js +4 -4
- package/dist/camera/RectCropPreview.d.ts +161 -0
- package/dist/camera/RectCropPreview.js +480 -0
- package/dist/camera/RotateToLandscapePrompt.d.ts +87 -0
- package/dist/camera/RotateToLandscapePrompt.js +138 -0
- package/dist/camera/buildPanoramaInitialSettings.d.ts +19 -2
- package/dist/camera/buildPanoramaInitialSettings.js +9 -0
- package/dist/camera/cameraErrorMessages.d.ts +30 -1
- package/dist/camera/cameraErrorMessages.js +26 -10
- package/dist/camera/cameraGuidanceCopy.d.ts +87 -0
- package/dist/camera/cameraGuidanceCopy.js +80 -0
- package/dist/camera/captureCountdown.d.ts +52 -0
- package/dist/camera/captureCountdown.js +76 -0
- package/dist/camera/captureWarnings.d.ts +90 -0
- package/dist/camera/captureWarnings.js +108 -0
- package/dist/camera/classifyStitchError.d.ts +30 -0
- package/dist/camera/classifyStitchError.js +42 -0
- package/dist/camera/cropGeometry.d.ts +136 -0
- package/dist/camera/cropGeometry.js +223 -0
- package/dist/camera/displayDecodeImageProps.d.ts +25 -0
- package/dist/camera/displayDecodeImageProps.js +29 -0
- package/dist/camera/guidanceGraphics.d.ts +58 -0
- package/dist/camera/guidanceGraphics.js +280 -0
- package/dist/camera/guidanceTokens.d.ts +54 -0
- package/dist/camera/guidanceTokens.js +58 -0
- package/dist/camera/panModeGate.d.ts +54 -0
- package/dist/camera/panModeGate.js +62 -0
- package/dist/camera/pickCaptureFormat.d.ts +71 -0
- package/dist/camera/pickCaptureFormat.js +85 -0
- package/dist/camera/stitchDebugInfo.d.ts +27 -0
- package/dist/camera/stitchDebugInfo.js +55 -0
- package/dist/camera/usePanMotion.d.ts +250 -0
- package/dist/camera/usePanMotion.js +451 -0
- package/dist/index.d.ts +24 -3
- package/dist/index.js +33 -2
- package/dist/stitching/computeInscribedRect.d.ts +40 -0
- package/dist/stitching/computeInscribedRect.js +55 -0
- package/dist/stitching/cropQuad.d.ts +78 -0
- package/dist/stitching/cropQuad.js +116 -0
- package/dist/stitching/incremental.d.ts +45 -0
- package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +56 -8
- package/ios/Sources/RNImageStitcher/KeyframeGate.swift +2 -2
- package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +48 -5
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +27 -0
- package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +191 -7
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +25 -1
- package/ios/Sources/RNImageStitcher/Stitcher.swift +34 -1
- package/ios/Sources/RNImageStitcher/StitcherBridge.m +5 -0
- package/ios/Sources/RNImageStitcher/StitcherBridge.swift +56 -0
- package/package.json +5 -1
- package/src/camera/Camera.tsx +994 -47
- package/src/camera/CameraView.tsx +75 -5
- package/src/camera/CaptureCountdownOverlay.tsx +272 -0
- package/src/camera/CaptureFrameCounterOverlay.tsx +183 -0
- package/src/camera/CaptureMemoryPill.tsx +17 -3
- package/src/camera/CapturePreview.tsx +5 -0
- package/src/camera/CaptureStatusOverlay.tsx +35 -7
- package/src/camera/CaptureThumbnailStrip.tsx +4 -0
- package/src/camera/LateralMotionModal.tsx +199 -0
- package/src/camera/PanHowToOverlay.tsx +246 -0
- package/src/camera/PanoramaSettings.ts +34 -11
- package/src/camera/PanoramaSettingsModal.tsx +4 -4
- package/src/camera/RectCropPreview.tsx +820 -0
- package/src/camera/RotateToLandscapePrompt.tsx +188 -0
- package/src/camera/buildPanoramaInitialSettings.ts +30 -1
- package/src/camera/cameraErrorMessages.ts +39 -2
- package/src/camera/cameraGuidanceCopy.ts +145 -0
- package/src/camera/captureCountdown.ts +83 -0
- package/src/camera/captureWarnings.ts +190 -0
- package/src/camera/classifyStitchError.ts +68 -0
- package/src/camera/cropGeometry.ts +268 -0
- package/src/camera/displayDecodeImageProps.ts +25 -0
- package/src/camera/guidanceGraphics.tsx +347 -0
- package/src/camera/guidanceTokens.ts +57 -0
- package/src/camera/panModeGate.ts +81 -0
- package/src/camera/pickCaptureFormat.ts +130 -0
- package/src/camera/stitchDebugInfo.ts +71 -0
- package/src/camera/usePanMotion.ts +667 -0
- package/src/index.ts +66 -3
- package/src/stitching/computeInscribedRect.ts +81 -0
- package/src/stitching/cropQuad.ts +167 -0
- package/src/stitching/incremental.ts +45 -0
- package/cpp/tests/CMakeLists.txt +0 -104
- package/cpp/tests/README.md +0 -86
- package/cpp/tests/keyframe_timebudget_test.cpp +0 -65
- package/cpp/tests/pose_test.cpp +0 -74
- package/cpp/tests/stitcher_frame_data_test.cpp +0 -132
- package/cpp/tests/stubs/jsi/jsi.h +0 -33
- package/cpp/tests/stubs/react-native-worklets-core/WKTJsiWorklet.h +0 -34
- package/cpp/tests/warp_guard_test.cpp +0 -48
- package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +0 -190
- package/src/camera/__tests__/bandThumbRotation.test.ts +0 -120
- package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +0 -160
- package/src/camera/__tests__/cameraErrorMessages.test.ts +0 -76
- package/src/camera/__tests__/homeIndicatorEdge.test.ts +0 -116
- package/src/camera/__tests__/lowMemDevice.test.ts +0 -52
- package/src/camera/__tests__/selectCaptureDevice.test.ts +0 -210
- package/src/camera/__tests__/useContentRotation.test.ts +0 -89
- package/src/camera/__tests__/useOrientationDrift.test.ts +0 -169
- package/src/stitching/__tests__/subscribeIncrementalState.refine.test.ts +0 -276
- package/src/stitching/__tests__/useStitcherWorklet.test.ts +0 -202
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* captureWarnings — non-fatal quality / behaviour signals attached to a
|
|
3
|
+
* SUCCESSFUL capture result. A stitch that *failed* surfaces a
|
|
4
|
+
* `CameraError` (via the `ok:false` result + `onError`); these warnings
|
|
5
|
+
* cover the "it succeeded but the host/user should know something" cases:
|
|
6
|
+
*
|
|
7
|
+
* • LOW_FRAME_UTILIZATION — fewer than `threshold` (default 70 %) of the
|
|
8
|
+
* captured frames survived the confidence filter, so the panorama may
|
|
9
|
+
* be patchy / shorter than intended.
|
|
10
|
+
* • LATERAL_DRIFT_FINALIZE — the capture was auto-finalized early because
|
|
11
|
+
* the phone drifted sideways (item 6); only the pre-drift portion was
|
|
12
|
+
* stitched.
|
|
13
|
+
* • HIGH_PAN_SPEED — the pan exceeded the recommended pace at some point
|
|
14
|
+
* during the capture (the live "too fast" cue fired), so motion blur /
|
|
15
|
+
* thin overlap may have hurt the result.
|
|
16
|
+
*
|
|
17
|
+
* `<Camera>` builds these at finalize and threads them into BOTH the
|
|
18
|
+
* `onCapture` result payload (so any host — not just the example app —
|
|
19
|
+
* learns of degraded output programmatically) AND the crop editor's banner
|
|
20
|
+
* (so the user sees it before accepting the crop).
|
|
21
|
+
*
|
|
22
|
+
* Pure + dependency-free so it's unit-testable in isolation (the lib's jest
|
|
23
|
+
* config is pure-TS and can't mount `<Camera>`), mirroring
|
|
24
|
+
* `classifyStitchError` / `buildPanoramaInitialSettings`.
|
|
25
|
+
*/
|
|
26
|
+
/** Stable codes a host can branch on (in addition to the message). */
|
|
27
|
+
export type CaptureWarningCode = 'LOW_FRAME_UTILIZATION' | 'LATERAL_DRIFT_FINALIZE' | 'HIGH_PAN_SPEED';
|
|
28
|
+
export interface CaptureWarning {
|
|
29
|
+
/** Stable, host-switchable code. */
|
|
30
|
+
code: CaptureWarningCode;
|
|
31
|
+
/** Plain-language default message (shown in the crop banner). */
|
|
32
|
+
message: string;
|
|
33
|
+
/** Frames the engine tried to use (LOW_FRAME_UTILIZATION only). */
|
|
34
|
+
framesRequested?: number;
|
|
35
|
+
/** Frames that survived the confidence filter (LOW_FRAME_UTILIZATION). */
|
|
36
|
+
framesIncluded?: number;
|
|
37
|
+
/** included / requested in [0, 1] (LOW_FRAME_UTILIZATION only). */
|
|
38
|
+
utilization?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Default trip point for LOW_FRAME_UTILIZATION: warn when fewer than 70 %
|
|
42
|
+
* of captured frames survived. Matches the threshold the user specified.
|
|
43
|
+
*/
|
|
44
|
+
export declare const LOW_FRAME_UTILIZATION_THRESHOLD = 0.7;
|
|
45
|
+
/**
|
|
46
|
+
* The overridable message strings for the three capture warnings. This is
|
|
47
|
+
* the SINGLE SOURCE OF TRUTH for the default English warning copy — the
|
|
48
|
+
* `GuidanceCopy` surface re-uses these defaults (see `cameraGuidanceCopy`),
|
|
49
|
+
* so a host that localises via the `guidanceCopy` `<Camera>` prop re-words
|
|
50
|
+
* these too.
|
|
51
|
+
*
|
|
52
|
+
* `lowFrameUtilization` is a TEMPLATE: the placeholders `{included}`,
|
|
53
|
+
* `{requested}` and `{percent}` are substituted at build time with the
|
|
54
|
+
* actual frame counts. A translation must keep the placeholders (any it
|
|
55
|
+
* omits is simply not interpolated; an unknown placeholder is left as-is).
|
|
56
|
+
*/
|
|
57
|
+
export interface CaptureWarningCopy {
|
|
58
|
+
/** LOW_FRAME_UTILIZATION — template; `{included}`/`{requested}`/`{percent}`. */
|
|
59
|
+
lowFrameUtilization: string;
|
|
60
|
+
/** LATERAL_DRIFT_FINALIZE. */
|
|
61
|
+
lateralDriftFinalize: string;
|
|
62
|
+
/** HIGH_PAN_SPEED. */
|
|
63
|
+
highPanSpeed: string;
|
|
64
|
+
}
|
|
65
|
+
export declare const DEFAULT_CAPTURE_WARNING_COPY: CaptureWarningCopy;
|
|
66
|
+
export interface BuildCaptureWarningsInput {
|
|
67
|
+
/** `framesRequested` from the native finalize result. */
|
|
68
|
+
framesRequested?: number;
|
|
69
|
+
/** `framesIncluded` from the native finalize result. */
|
|
70
|
+
framesIncluded?: number;
|
|
71
|
+
/** True when this finalize was triggered by lateral-drift auto-stop. */
|
|
72
|
+
lateralFinalize?: boolean;
|
|
73
|
+
/** True when the pan exceeded the recommended pace during the capture. */
|
|
74
|
+
highPanSpeed?: boolean;
|
|
75
|
+
/** Override the LOW_FRAME_UTILIZATION trip point (fraction in (0, 1]). */
|
|
76
|
+
lowFrameUtilizationThreshold?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Localised / re-worded warning messages. Missing keys fall back to
|
|
79
|
+
* {@link DEFAULT_CAPTURE_WARNING_COPY}. `<Camera>` threads the resolved
|
|
80
|
+
* `guidanceCopy` here so the crop-banner warnings honour the host's i18n.
|
|
81
|
+
*/
|
|
82
|
+
copy?: Partial<CaptureWarningCopy>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the warning list for a successful capture. Order is by cause →
|
|
86
|
+
* symptom: a lateral-drift stop (the reason a capture is short) is listed
|
|
87
|
+
* before the low-utilization symptom it usually produces.
|
|
88
|
+
*/
|
|
89
|
+
export declare function buildCaptureWarnings(input: BuildCaptureWarningsInput): CaptureWarning[];
|
|
90
|
+
//# sourceMappingURL=captureWarnings.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* captureWarnings — non-fatal quality / behaviour signals attached to a
|
|
5
|
+
* SUCCESSFUL capture result. A stitch that *failed* surfaces a
|
|
6
|
+
* `CameraError` (via the `ok:false` result + `onError`); these warnings
|
|
7
|
+
* cover the "it succeeded but the host/user should know something" cases:
|
|
8
|
+
*
|
|
9
|
+
* • LOW_FRAME_UTILIZATION — fewer than `threshold` (default 70 %) of the
|
|
10
|
+
* captured frames survived the confidence filter, so the panorama may
|
|
11
|
+
* be patchy / shorter than intended.
|
|
12
|
+
* • LATERAL_DRIFT_FINALIZE — the capture was auto-finalized early because
|
|
13
|
+
* the phone drifted sideways (item 6); only the pre-drift portion was
|
|
14
|
+
* stitched.
|
|
15
|
+
* • HIGH_PAN_SPEED — the pan exceeded the recommended pace at some point
|
|
16
|
+
* during the capture (the live "too fast" cue fired), so motion blur /
|
|
17
|
+
* thin overlap may have hurt the result.
|
|
18
|
+
*
|
|
19
|
+
* `<Camera>` builds these at finalize and threads them into BOTH the
|
|
20
|
+
* `onCapture` result payload (so any host — not just the example app —
|
|
21
|
+
* learns of degraded output programmatically) AND the crop editor's banner
|
|
22
|
+
* (so the user sees it before accepting the crop).
|
|
23
|
+
*
|
|
24
|
+
* Pure + dependency-free so it's unit-testable in isolation (the lib's jest
|
|
25
|
+
* config is pure-TS and can't mount `<Camera>`), mirroring
|
|
26
|
+
* `classifyStitchError` / `buildPanoramaInitialSettings`.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.DEFAULT_CAPTURE_WARNING_COPY = exports.LOW_FRAME_UTILIZATION_THRESHOLD = void 0;
|
|
30
|
+
exports.buildCaptureWarnings = buildCaptureWarnings;
|
|
31
|
+
/**
|
|
32
|
+
* Default trip point for LOW_FRAME_UTILIZATION: warn when fewer than 70 %
|
|
33
|
+
* of captured frames survived. Matches the threshold the user specified.
|
|
34
|
+
*/
|
|
35
|
+
exports.LOW_FRAME_UTILIZATION_THRESHOLD = 0.7;
|
|
36
|
+
exports.DEFAULT_CAPTURE_WARNING_COPY = {
|
|
37
|
+
lowFrameUtilization: 'Only {included} of {requested} captured frames ({percent}%) could be '
|
|
38
|
+
+ 'used — the panorama may be incomplete. Pan more slowly and steadily '
|
|
39
|
+
+ 'next time.',
|
|
40
|
+
lateralDriftFinalize: 'Capture stopped early because the phone drifted sideways — only the '
|
|
41
|
+
+ 'part captured before the drift was stitched.',
|
|
42
|
+
highPanSpeed: 'The capture was taken faster than the recommended pace — the result '
|
|
43
|
+
+ 'may not be the best. Pan more slowly next time.',
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Substitute `{name}` placeholders in a template with `vars[name]`. An
|
|
47
|
+
* unknown placeholder is left verbatim (so a malformed translation degrades
|
|
48
|
+
* to showing `{percent}` rather than throwing).
|
|
49
|
+
*/
|
|
50
|
+
function fillTemplate(tpl, vars) {
|
|
51
|
+
return tpl.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the warning list for a successful capture. Order is by cause →
|
|
55
|
+
* symptom: a lateral-drift stop (the reason a capture is short) is listed
|
|
56
|
+
* before the low-utilization symptom it usually produces.
|
|
57
|
+
*/
|
|
58
|
+
function buildCaptureWarnings(input) {
|
|
59
|
+
const { framesRequested, framesIncluded, lateralFinalize = false, highPanSpeed = false, lowFrameUtilizationThreshold = exports.LOW_FRAME_UTILIZATION_THRESHOLD, } = input;
|
|
60
|
+
const copy = {
|
|
61
|
+
...exports.DEFAULT_CAPTURE_WARNING_COPY,
|
|
62
|
+
...stripUndefinedCopy(input.copy),
|
|
63
|
+
};
|
|
64
|
+
const warnings = [];
|
|
65
|
+
if (lateralFinalize) {
|
|
66
|
+
warnings.push({
|
|
67
|
+
code: 'LATERAL_DRIFT_FINALIZE',
|
|
68
|
+
message: copy.lateralDriftFinalize,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (highPanSpeed) {
|
|
72
|
+
warnings.push({
|
|
73
|
+
code: 'HIGH_PAN_SPEED',
|
|
74
|
+
message: copy.highPanSpeed,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (typeof framesRequested === 'number'
|
|
78
|
+
&& typeof framesIncluded === 'number'
|
|
79
|
+
&& framesRequested > 0
|
|
80
|
+
&& framesIncluded >= 0
|
|
81
|
+
&& framesIncluded < framesRequested * lowFrameUtilizationThreshold) {
|
|
82
|
+
const utilization = framesIncluded / framesRequested;
|
|
83
|
+
warnings.push({
|
|
84
|
+
code: 'LOW_FRAME_UTILIZATION',
|
|
85
|
+
message: fillTemplate(copy.lowFrameUtilization, {
|
|
86
|
+
included: framesIncluded,
|
|
87
|
+
requested: framesRequested,
|
|
88
|
+
percent: Math.round(utilization * 100),
|
|
89
|
+
}),
|
|
90
|
+
framesRequested,
|
|
91
|
+
framesIncluded,
|
|
92
|
+
utilization,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return warnings;
|
|
96
|
+
}
|
|
97
|
+
/** Drop `undefined` values so a partial override never clobbers a default. */
|
|
98
|
+
function stripUndefinedCopy(o) {
|
|
99
|
+
if (!o)
|
|
100
|
+
return {};
|
|
101
|
+
const out = {};
|
|
102
|
+
Object.keys(o).forEach((k) => {
|
|
103
|
+
if (o[k] !== undefined)
|
|
104
|
+
out[k] = o[k];
|
|
105
|
+
});
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=captureWarnings.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* classifyStitchError — map a raw native stitch-failure message to a
|
|
3
|
+
* `CameraErrorCode`.
|
|
4
|
+
*
|
|
5
|
+
* This is the load-bearing C++↔JS contract: the native pipeline reports
|
|
6
|
+
* failures only as exception strings (see cpp/stitcher.cpp — the warp /
|
|
7
|
+
* cumulative-canvas guards throw "warpRoi too large … degenerate camera
|
|
8
|
+
* params", cv::Stitcher reports "need more images", etc.), and this
|
|
9
|
+
* function is the single place that turns those strings into the typed
|
|
10
|
+
* codes `<Camera onError>` surfaces. The code then drives the friendly
|
|
11
|
+
* copy in {@link userFacingStitchError} (cameraErrorMessages.ts) — e.g.
|
|
12
|
+
* STITCH_CAMERA_PARAMS_FAIL → "Please pan more slowly".
|
|
13
|
+
*
|
|
14
|
+
* Extracted from the inline chain in Camera.tsx so the contract is
|
|
15
|
+
* unit-testable against the actual native strings (the lib's jest config
|
|
16
|
+
* is pure-TS and can't mount <Camera>).
|
|
17
|
+
*
|
|
18
|
+
* Ordering matters — the branches are checked top-to-bottom and the first
|
|
19
|
+
* match wins:
|
|
20
|
+
* 1. need-more-images (insufficient overlap) — most specific.
|
|
21
|
+
* 2. homography estimation.
|
|
22
|
+
* 3. degenerate camera params / warp-canvas guard (the divergent-warp
|
|
23
|
+
* OOM path, now converted to a clean throw by the canvas guard).
|
|
24
|
+
* 4. low-quality / disjoint output (the post-stitch validator, v0.16).
|
|
25
|
+
* 5. out-of-memory (incl. the pre-stitch memory abort).
|
|
26
|
+
* 6. fallback — an unclassified finalize failure.
|
|
27
|
+
*/
|
|
28
|
+
import type { CameraErrorCode } from './Camera';
|
|
29
|
+
export declare function classifyStitchError(message: string): CameraErrorCode;
|
|
30
|
+
//# sourceMappingURL=classifyStitchError.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyStitchError = classifyStitchError;
|
|
4
|
+
function classifyStitchError(message) {
|
|
5
|
+
// Insufficient overlap surfaces two ways: cv::Stitcher's
|
|
6
|
+
// ERR_NEED_MORE_IMGS ("need more images") and the manual pipeline's
|
|
7
|
+
// "0 valid pairwise matches / frames may not overlap enough" — both are
|
|
8
|
+
// the same recoverable "pan more slowly" case.
|
|
9
|
+
if (/need more images|pairwise match|overlap enough/i.test(message)) {
|
|
10
|
+
return 'STITCH_NEED_MORE_IMGS';
|
|
11
|
+
}
|
|
12
|
+
if (/homography/i.test(message)) {
|
|
13
|
+
return 'STITCH_HOMOGRAPHY_FAIL';
|
|
14
|
+
}
|
|
15
|
+
// Degenerate camera params — the rapid/wide-pan divergent-warp path.
|
|
16
|
+
// Broadened beyond the original "camera params" so a future reword of
|
|
17
|
+
// the native throw can't silently drop the "pan more slowly" copy: the
|
|
18
|
+
// per-frame warp guard and the cumulative-canvas guard carry "warpRoi"
|
|
19
|
+
// / "canvas too large" / "degenerate" respectively (see cpp/stitcher.cpp
|
|
20
|
+
// degenerateFrameException / degenerateCanvasException). Kept AFTER the
|
|
21
|
+
// homography branch and BEFORE the OOM branch so a true OOM string still
|
|
22
|
+
// routes to STITCH_OOM.
|
|
23
|
+
if (/camera params|warpRoi|degenerate|canvas too large/i.test(message)) {
|
|
24
|
+
return 'STITCH_CAMERA_PARAMS_FAIL';
|
|
25
|
+
}
|
|
26
|
+
// v0.16 — the native post-stitch validator rejected the output as
|
|
27
|
+
// disjoint / fragmented / wildly mis-proportioned (frames didn't connect
|
|
28
|
+
// into one coherent panorama). The cpp throw carries "stitch validation"
|
|
29
|
+
// / "disjoint" / "fragmented" (see cpp/stitcher.cpp validateStitchOutput).
|
|
30
|
+
// Kept BEFORE the OOM branch so its distinct message isn't swallowed.
|
|
31
|
+
if (/stitch validation|disjoint|fragmented|low-quality stitch/i.test(message)) {
|
|
32
|
+
return 'STITCH_LOW_QUALITY';
|
|
33
|
+
}
|
|
34
|
+
// OOM, including the pre-stitch headroom abort ("pre-stitch memory abort"
|
|
35
|
+
// / "memory abort") that fires when even the minimal streaming config
|
|
36
|
+
// won't fit — same user remedy (shorter sweep), so same code.
|
|
37
|
+
if (/out of memory|oom|memory abort/i.test(message)) {
|
|
38
|
+
return 'STITCH_OOM';
|
|
39
|
+
}
|
|
40
|
+
return 'PANORAMA_FINALIZE_FAILED';
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=classifyStitchError.js.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cropGeometry — pure coordinate + quad helpers behind the item-7
|
|
3
|
+
* draggable-corner crop editor (`RectCropPreview`).
|
|
4
|
+
*
|
|
5
|
+
* The editor shows the full result image with a `resizeMode="contain"`
|
|
6
|
+
* letterbox: the image is centred and uniformly scaled to fit the layout
|
|
7
|
+
* box, leaving symmetric bars on one axis. The 4 draggable corners live in
|
|
8
|
+
* ON-SCREEN coordinates (the touch space PanResponder reports), but the
|
|
9
|
+
* native crop needs IMAGE-PIXEL coordinates. These helpers are the
|
|
10
|
+
* letterbox transform and its inverse — extracted verbatim from
|
|
11
|
+
* `example/InscribedRectDebug.tsx` (~lines 178-204), which mapped an
|
|
12
|
+
* inscribed-rect from image px → screen the same way.
|
|
13
|
+
*
|
|
14
|
+
* Everything here is pure (no React, no native) so it's unit-testable
|
|
15
|
+
* without booting a render — same posture as `contentRotationDeg` and
|
|
16
|
+
* `buildPanoramaInitialSettings`.
|
|
17
|
+
*
|
|
18
|
+
* Coordinate conventions:
|
|
19
|
+
* - A `Point` is `{ x, y }`. Screen points are in the layout box's
|
|
20
|
+
* local space (origin = box top-left); image points are in pixel
|
|
21
|
+
* space (origin = image top-left, range [0..imageW] × [0..imageH]).
|
|
22
|
+
* - A `Quad` is exactly 4 points. `orderQuadCorners` canonicalises
|
|
23
|
+
* winding to [TL, TR, BR, BL] so downstream native perspective
|
|
24
|
+
* rectify gets corners in the order it expects.
|
|
25
|
+
*/
|
|
26
|
+
/** A 2-D point in either screen-local or image-pixel space. */
|
|
27
|
+
export interface Point {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
}
|
|
31
|
+
/** The contain-fit letterbox layout of the image inside its box. */
|
|
32
|
+
export interface ContainLayout {
|
|
33
|
+
/** Layout box width (on-screen px). */
|
|
34
|
+
width: number;
|
|
35
|
+
/** Layout box height (on-screen px). */
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
/** Exactly four points (corners of a crop quad). */
|
|
39
|
+
export type Quad = [Point, Point, Point, Point];
|
|
40
|
+
/**
|
|
41
|
+
* The contain-fit transform: uniform scale + centring offsets that map
|
|
42
|
+
* image-pixel space into the on-screen layout box. Returns `null` when
|
|
43
|
+
* any dimension is non-positive (nothing to lay out).
|
|
44
|
+
*
|
|
45
|
+
* `scale` is `min(box.w / imageW, box.h / imageH)` — the same
|
|
46
|
+
* `resizeMode="contain"` math RN's <Image> applies — and `offX`/`offY`
|
|
47
|
+
* centre the scaled image, producing the letterbox bars.
|
|
48
|
+
*/
|
|
49
|
+
export declare function containFit(layout: ContainLayout, imageW: number, imageH: number): {
|
|
50
|
+
scale: number;
|
|
51
|
+
offX: number;
|
|
52
|
+
offY: number;
|
|
53
|
+
} | null;
|
|
54
|
+
/**
|
|
55
|
+
* Map an on-screen point (layout-box local coords) → image-pixel coords.
|
|
56
|
+
* Inverse of {@link imageToScreen}. The result is clamped to
|
|
57
|
+
* `[0..imageW] × [0..imageH]` so a corner dragged onto / past the
|
|
58
|
+
* letterbox bar still yields a valid in-bounds pixel for the native crop
|
|
59
|
+
* (the user can't pick pixels that don't exist).
|
|
60
|
+
*
|
|
61
|
+
* Returns the un-mapped point unchanged when the layout is degenerate
|
|
62
|
+
* (see {@link containFit}) — the caller has no valid letterbox yet.
|
|
63
|
+
*/
|
|
64
|
+
export declare function screenToImage(point: Point, layout: ContainLayout, imageW: number, imageH: number): Point;
|
|
65
|
+
/**
|
|
66
|
+
* Map an image-pixel point → on-screen point (layout-box local coords).
|
|
67
|
+
* Inverse of {@link screenToImage}. Used to seed the draggable corners
|
|
68
|
+
* from an image-space initial rect and to keep the overlay aligned to the
|
|
69
|
+
* letterboxed image.
|
|
70
|
+
*
|
|
71
|
+
* Returns the un-mapped point unchanged when the layout is degenerate.
|
|
72
|
+
*/
|
|
73
|
+
export declare function imageToScreen(point: Point, layout: ContainLayout, imageW: number, imageH: number): Point;
|
|
74
|
+
/**
|
|
75
|
+
* Re-order 4 arbitrary corner points into canonical
|
|
76
|
+
* [TL, TR, BR, BL] (clockwise from top-left) winding.
|
|
77
|
+
*
|
|
78
|
+
* Strategy (robust to slight perspective skew, no trig):
|
|
79
|
+
* - Top two = the two points with the smallest `y`; bottom two = the
|
|
80
|
+
* largest `y`. Within each pair, the smaller `x` is left.
|
|
81
|
+
* Ties on `y` (a perfectly axis-aligned rect) resolve deterministically
|
|
82
|
+
* because the sort is stable and we then split by `x`.
|
|
83
|
+
*
|
|
84
|
+
* This matches the corner order the native `cropToQuad` perspective
|
|
85
|
+
* rectify expects (dst rect: TL→TR→BR→BL).
|
|
86
|
+
*/
|
|
87
|
+
export declare function orderQuadCorners(pts: Quad): Quad;
|
|
88
|
+
/**
|
|
89
|
+
* 2× the signed area of a polygon via the shoelace formula. Positive for
|
|
90
|
+
* counter-clockwise winding, negative for clockwise, ~0 for degenerate.
|
|
91
|
+
* Exported for tests + reused by {@link isQuadValid}.
|
|
92
|
+
*/
|
|
93
|
+
export declare function signedArea2(pts: Quad): number;
|
|
94
|
+
/**
|
|
95
|
+
* True when the 4 points form a usable crop quad:
|
|
96
|
+
* 1. **Non-degenerate area** — `|signed area|` ≥ `minArea` (default
|
|
97
|
+
* `1`, i.e. at least 1 px²). Rejects all-collinear / zero-size.
|
|
98
|
+
* 2. **Convex** — every cross-product of consecutive edges shares one
|
|
99
|
+
* sign (allowing zero for a straight, axis-aligned corner). Rejects
|
|
100
|
+
* self-intersecting / "bowtie" quads, which the native perspective
|
|
101
|
+
* warp can't rectify.
|
|
102
|
+
*
|
|
103
|
+
* Operates on the points in their given winding (call `orderQuadCorners`
|
|
104
|
+
* first if you need canonical order); convexity is winding-agnostic.
|
|
105
|
+
*/
|
|
106
|
+
export declare function isQuadValid(pts: Quad, minArea?: number): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Target rectangle size for the perspective `dst` quad, derived from the
|
|
109
|
+
* 4 ORDERED ([TL, TR, BR, BL]) image-pixel corners:
|
|
110
|
+
* - `w` = average of the top edge (TL→TR) and bottom edge (BL→BR)
|
|
111
|
+
* lengths.
|
|
112
|
+
* - `h` = average of the left edge (TL→BL) and right edge (TR→BR)
|
|
113
|
+
* lengths.
|
|
114
|
+
* Averaging opposite edges gives a stable output size for a skewed quad
|
|
115
|
+
* (each pair of opposite edges differs under perspective; the mean is the
|
|
116
|
+
* least-distorting target). Rounds to whole pixels — the native crop
|
|
117
|
+
* allocates an integer-sized bitmap.
|
|
118
|
+
*
|
|
119
|
+
* Caller must pass corners already in [TL, TR, BR, BL] order (use
|
|
120
|
+
* {@link orderQuadCorners}); the math assumes that winding.
|
|
121
|
+
*/
|
|
122
|
+
export declare function rectSizeForQuad(orderedImagePts: Quad): {
|
|
123
|
+
w: number;
|
|
124
|
+
h: number;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* True when an ORDERED ([TL, TR, BR, BL]) image-pixel quad is, within
|
|
128
|
+
* `tolerancePx`, an axis-aligned rectangle — i.e. the cheap axis-aligned
|
|
129
|
+
* `cropToRect` path applies and no perspective warp is needed. The parent
|
|
130
|
+
* uses this to choose `cropToRect` vs `cropToQuad`.
|
|
131
|
+
*
|
|
132
|
+
* Checks the two top/bottom corners share a `y` and the two left/right
|
|
133
|
+
* corners share an `x`, all within tolerance.
|
|
134
|
+
*/
|
|
135
|
+
export declare function isAxisAlignedRect(orderedImagePts: Quad, tolerancePx?: number): boolean;
|
|
136
|
+
//# sourceMappingURL=cropGeometry.d.ts.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* cropGeometry — pure coordinate + quad helpers behind the item-7
|
|
5
|
+
* draggable-corner crop editor (`RectCropPreview`).
|
|
6
|
+
*
|
|
7
|
+
* The editor shows the full result image with a `resizeMode="contain"`
|
|
8
|
+
* letterbox: the image is centred and uniformly scaled to fit the layout
|
|
9
|
+
* box, leaving symmetric bars on one axis. The 4 draggable corners live in
|
|
10
|
+
* ON-SCREEN coordinates (the touch space PanResponder reports), but the
|
|
11
|
+
* native crop needs IMAGE-PIXEL coordinates. These helpers are the
|
|
12
|
+
* letterbox transform and its inverse — extracted verbatim from
|
|
13
|
+
* `example/InscribedRectDebug.tsx` (~lines 178-204), which mapped an
|
|
14
|
+
* inscribed-rect from image px → screen the same way.
|
|
15
|
+
*
|
|
16
|
+
* Everything here is pure (no React, no native) so it's unit-testable
|
|
17
|
+
* without booting a render — same posture as `contentRotationDeg` and
|
|
18
|
+
* `buildPanoramaInitialSettings`.
|
|
19
|
+
*
|
|
20
|
+
* Coordinate conventions:
|
|
21
|
+
* - A `Point` is `{ x, y }`. Screen points are in the layout box's
|
|
22
|
+
* local space (origin = box top-left); image points are in pixel
|
|
23
|
+
* space (origin = image top-left, range [0..imageW] × [0..imageH]).
|
|
24
|
+
* - A `Quad` is exactly 4 points. `orderQuadCorners` canonicalises
|
|
25
|
+
* winding to [TL, TR, BR, BL] so downstream native perspective
|
|
26
|
+
* rectify gets corners in the order it expects.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.containFit = containFit;
|
|
30
|
+
exports.screenToImage = screenToImage;
|
|
31
|
+
exports.imageToScreen = imageToScreen;
|
|
32
|
+
exports.orderQuadCorners = orderQuadCorners;
|
|
33
|
+
exports.signedArea2 = signedArea2;
|
|
34
|
+
exports.isQuadValid = isQuadValid;
|
|
35
|
+
exports.rectSizeForQuad = rectSizeForQuad;
|
|
36
|
+
exports.isAxisAlignedRect = isAxisAlignedRect;
|
|
37
|
+
/**
|
|
38
|
+
* The contain-fit transform: uniform scale + centring offsets that map
|
|
39
|
+
* image-pixel space into the on-screen layout box. Returns `null` when
|
|
40
|
+
* any dimension is non-positive (nothing to lay out).
|
|
41
|
+
*
|
|
42
|
+
* `scale` is `min(box.w / imageW, box.h / imageH)` — the same
|
|
43
|
+
* `resizeMode="contain"` math RN's <Image> applies — and `offX`/`offY`
|
|
44
|
+
* centre the scaled image, producing the letterbox bars.
|
|
45
|
+
*/
|
|
46
|
+
function containFit(layout, imageW, imageH) {
|
|
47
|
+
if (layout.width <= 0
|
|
48
|
+
|| layout.height <= 0
|
|
49
|
+
|| imageW <= 0
|
|
50
|
+
|| imageH <= 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const scale = Math.min(layout.width / imageW, layout.height / imageH);
|
|
54
|
+
const dispW = imageW * scale;
|
|
55
|
+
const dispH = imageH * scale;
|
|
56
|
+
const offX = (layout.width - dispW) / 2;
|
|
57
|
+
const offY = (layout.height - dispH) / 2;
|
|
58
|
+
return { scale, offX, offY };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Map an on-screen point (layout-box local coords) → image-pixel coords.
|
|
62
|
+
* Inverse of {@link imageToScreen}. The result is clamped to
|
|
63
|
+
* `[0..imageW] × [0..imageH]` so a corner dragged onto / past the
|
|
64
|
+
* letterbox bar still yields a valid in-bounds pixel for the native crop
|
|
65
|
+
* (the user can't pick pixels that don't exist).
|
|
66
|
+
*
|
|
67
|
+
* Returns the un-mapped point unchanged when the layout is degenerate
|
|
68
|
+
* (see {@link containFit}) — the caller has no valid letterbox yet.
|
|
69
|
+
*/
|
|
70
|
+
function screenToImage(point, layout, imageW, imageH) {
|
|
71
|
+
const fit = containFit(layout, imageW, imageH);
|
|
72
|
+
if (!fit)
|
|
73
|
+
return point;
|
|
74
|
+
const x = (point.x - fit.offX) / fit.scale;
|
|
75
|
+
const y = (point.y - fit.offY) / fit.scale;
|
|
76
|
+
return {
|
|
77
|
+
x: clamp(x, 0, imageW),
|
|
78
|
+
y: clamp(y, 0, imageH),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Map an image-pixel point → on-screen point (layout-box local coords).
|
|
83
|
+
* Inverse of {@link screenToImage}. Used to seed the draggable corners
|
|
84
|
+
* from an image-space initial rect and to keep the overlay aligned to the
|
|
85
|
+
* letterboxed image.
|
|
86
|
+
*
|
|
87
|
+
* Returns the un-mapped point unchanged when the layout is degenerate.
|
|
88
|
+
*/
|
|
89
|
+
function imageToScreen(point, layout, imageW, imageH) {
|
|
90
|
+
const fit = containFit(layout, imageW, imageH);
|
|
91
|
+
if (!fit)
|
|
92
|
+
return point;
|
|
93
|
+
return {
|
|
94
|
+
x: fit.offX + point.x * fit.scale,
|
|
95
|
+
y: fit.offY + point.y * fit.scale,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Re-order 4 arbitrary corner points into canonical
|
|
100
|
+
* [TL, TR, BR, BL] (clockwise from top-left) winding.
|
|
101
|
+
*
|
|
102
|
+
* Strategy (robust to slight perspective skew, no trig):
|
|
103
|
+
* - Top two = the two points with the smallest `y`; bottom two = the
|
|
104
|
+
* largest `y`. Within each pair, the smaller `x` is left.
|
|
105
|
+
* Ties on `y` (a perfectly axis-aligned rect) resolve deterministically
|
|
106
|
+
* because the sort is stable and we then split by `x`.
|
|
107
|
+
*
|
|
108
|
+
* This matches the corner order the native `cropToQuad` perspective
|
|
109
|
+
* rectify expects (dst rect: TL→TR→BR→BL).
|
|
110
|
+
*/
|
|
111
|
+
function orderQuadCorners(pts) {
|
|
112
|
+
// Sort a copy by y ascending so [0,1] are the top pair, [2,3] bottom.
|
|
113
|
+
const byY = [...pts].sort((a, b) => a.y - b.y);
|
|
114
|
+
const [t0, t1, b0, b1] = byY;
|
|
115
|
+
// Within each horizontal pair, smaller x is the left corner.
|
|
116
|
+
const [tl, tr] = t0.x <= t1.x ? [t0, t1] : [t1, t0];
|
|
117
|
+
const [bl, br] = b0.x <= b1.x ? [b0, b1] : [b1, b0];
|
|
118
|
+
return [tl, tr, br, bl];
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 2× the signed area of a polygon via the shoelace formula. Positive for
|
|
122
|
+
* counter-clockwise winding, negative for clockwise, ~0 for degenerate.
|
|
123
|
+
* Exported for tests + reused by {@link isQuadValid}.
|
|
124
|
+
*/
|
|
125
|
+
function signedArea2(pts) {
|
|
126
|
+
let sum = 0;
|
|
127
|
+
for (let i = 0; i < pts.length; i++) {
|
|
128
|
+
const a = pts[i];
|
|
129
|
+
const b = pts[(i + 1) % pts.length];
|
|
130
|
+
sum += a.x * b.y - b.x * a.y;
|
|
131
|
+
}
|
|
132
|
+
return sum;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* True when the 4 points form a usable crop quad:
|
|
136
|
+
* 1. **Non-degenerate area** — `|signed area|` ≥ `minArea` (default
|
|
137
|
+
* `1`, i.e. at least 1 px²). Rejects all-collinear / zero-size.
|
|
138
|
+
* 2. **Convex** — every cross-product of consecutive edges shares one
|
|
139
|
+
* sign (allowing zero for a straight, axis-aligned corner). Rejects
|
|
140
|
+
* self-intersecting / "bowtie" quads, which the native perspective
|
|
141
|
+
* warp can't rectify.
|
|
142
|
+
*
|
|
143
|
+
* Operates on the points in their given winding (call `orderQuadCorners`
|
|
144
|
+
* first if you need canonical order); convexity is winding-agnostic.
|
|
145
|
+
*/
|
|
146
|
+
function isQuadValid(pts, minArea = 1) {
|
|
147
|
+
if (Math.abs(signedArea2(pts)) < minArea * 2)
|
|
148
|
+
return false;
|
|
149
|
+
return isConvex(pts);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Target rectangle size for the perspective `dst` quad, derived from the
|
|
153
|
+
* 4 ORDERED ([TL, TR, BR, BL]) image-pixel corners:
|
|
154
|
+
* - `w` = average of the top edge (TL→TR) and bottom edge (BL→BR)
|
|
155
|
+
* lengths.
|
|
156
|
+
* - `h` = average of the left edge (TL→BL) and right edge (TR→BR)
|
|
157
|
+
* lengths.
|
|
158
|
+
* Averaging opposite edges gives a stable output size for a skewed quad
|
|
159
|
+
* (each pair of opposite edges differs under perspective; the mean is the
|
|
160
|
+
* least-distorting target). Rounds to whole pixels — the native crop
|
|
161
|
+
* allocates an integer-sized bitmap.
|
|
162
|
+
*
|
|
163
|
+
* Caller must pass corners already in [TL, TR, BR, BL] order (use
|
|
164
|
+
* {@link orderQuadCorners}); the math assumes that winding.
|
|
165
|
+
*/
|
|
166
|
+
function rectSizeForQuad(orderedImagePts) {
|
|
167
|
+
const [tl, tr, br, bl] = orderedImagePts;
|
|
168
|
+
const top = dist(tl, tr);
|
|
169
|
+
const bottom = dist(bl, br);
|
|
170
|
+
const left = dist(tl, bl);
|
|
171
|
+
const right = dist(tr, br);
|
|
172
|
+
return {
|
|
173
|
+
w: Math.round((top + bottom) / 2),
|
|
174
|
+
h: Math.round((left + right) / 2),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* True when an ORDERED ([TL, TR, BR, BL]) image-pixel quad is, within
|
|
179
|
+
* `tolerancePx`, an axis-aligned rectangle — i.e. the cheap axis-aligned
|
|
180
|
+
* `cropToRect` path applies and no perspective warp is needed. The parent
|
|
181
|
+
* uses this to choose `cropToRect` vs `cropToQuad`.
|
|
182
|
+
*
|
|
183
|
+
* Checks the two top/bottom corners share a `y` and the two left/right
|
|
184
|
+
* corners share an `x`, all within tolerance.
|
|
185
|
+
*/
|
|
186
|
+
function isAxisAlignedRect(orderedImagePts, tolerancePx = 1) {
|
|
187
|
+
const [tl, tr, br, bl] = orderedImagePts;
|
|
188
|
+
return (Math.abs(tl.y - tr.y) <= tolerancePx
|
|
189
|
+
&& Math.abs(bl.y - br.y) <= tolerancePx
|
|
190
|
+
&& Math.abs(tl.x - bl.x) <= tolerancePx
|
|
191
|
+
&& Math.abs(tr.x - br.x) <= tolerancePx);
|
|
192
|
+
}
|
|
193
|
+
/** Convexity test: all consecutive edge cross-products share a sign. */
|
|
194
|
+
function isConvex(pts) {
|
|
195
|
+
let sign = 0;
|
|
196
|
+
for (let i = 0; i < pts.length; i++) {
|
|
197
|
+
const a = pts[i];
|
|
198
|
+
const b = pts[(i + 1) % pts.length];
|
|
199
|
+
const c = pts[(i + 2) % pts.length];
|
|
200
|
+
const cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
|
|
201
|
+
if (cross !== 0) {
|
|
202
|
+
const s = cross > 0 ? 1 : -1;
|
|
203
|
+
if (sign === 0)
|
|
204
|
+
sign = s;
|
|
205
|
+
else if (s !== sign)
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
/** Euclidean distance between two points. */
|
|
212
|
+
function dist(a, b) {
|
|
213
|
+
return Math.hypot(a.x - b.x, a.y - b.y);
|
|
214
|
+
}
|
|
215
|
+
/** Clamp `v` into the inclusive `[lo, hi]` range. */
|
|
216
|
+
function clamp(v, lo, hi) {
|
|
217
|
+
if (v < lo)
|
|
218
|
+
return lo;
|
|
219
|
+
if (v > hi)
|
|
220
|
+
return hi;
|
|
221
|
+
return v;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=cropGeometry.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DISPLAY_DECODE_IMAGE_PROPS — props every <Image> that displays a
|
|
3
|
+
* FULL-RES capture (a stitched panorama or a photo file) must spread.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists (the accumulation half of the OOM crash):
|
|
6
|
+
* On Android 8+, decoded bitmap pixels live in the NATIVE heap, and the
|
|
7
|
+
* source here is the full-resolution capture file — a wide panorama can
|
|
8
|
+
* be tens of megapixels. Without `resizeMethod="resize"`, Android/Fresco
|
|
9
|
+
* decodes the source at FULL resolution into a native bitmap that the
|
|
10
|
+
* mounted <Image> pins (not LRU-evictable), and Fresco's URI-keyed cache
|
|
11
|
+
* keeps it even after the view unmounts. Each capture (especially wide
|
|
12
|
+
* panoramas) then accumulates tens of MB of native heap until lmkd
|
|
13
|
+
* OOM-kills the app. 'resize' decodes at the on-screen (~device-width)
|
|
14
|
+
* size instead, making per-image memory tiny and panorama-size-
|
|
15
|
+
* independent. No-op on iOS (harmless).
|
|
16
|
+
*
|
|
17
|
+
* Centralised (rather than a bare `resizeMethod="resize"` at each call
|
|
18
|
+
* site) so the decode strategy + its rationale have one home, and so the
|
|
19
|
+
* contract is unit-testable without mounting a component. Spread it:
|
|
20
|
+
* <Image source={...} resizeMode="cover" {...DISPLAY_DECODE_IMAGE_PROPS} />
|
|
21
|
+
*/
|
|
22
|
+
export declare const DISPLAY_DECODE_IMAGE_PROPS: {
|
|
23
|
+
readonly resizeMethod: "resize";
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=displayDecodeImageProps.d.ts.map
|