react-native-image-stitcher 0.15.2 → 0.16.1

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.
Files changed (146) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/README.md +131 -5
  3. package/android/src/main/cpp/image_stitcher_jni.cpp +154 -13
  4. package/android/src/main/cpp/keyframe_gate_jni.cpp +15 -8
  5. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +223 -1
  6. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +220 -87
  7. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
  8. package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +7 -36
  9. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +14 -8
  10. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +39 -1
  11. package/cpp/crop_quad.cpp +162 -0
  12. package/cpp/crop_quad.hpp +163 -0
  13. package/cpp/keyframe_gate.cpp +54 -15
  14. package/cpp/keyframe_gate.hpp +33 -0
  15. package/cpp/stitcher.cpp +1122 -132
  16. package/cpp/stitcher.hpp +62 -0
  17. package/cpp/warp_guard.hpp +212 -0
  18. package/dist/camera/Camera.d.ts +209 -12
  19. package/dist/camera/Camera.js +575 -36
  20. package/dist/camera/CameraView.js +35 -16
  21. package/dist/camera/CaptureCountdownOverlay.d.ts +70 -0
  22. package/dist/camera/CaptureCountdownOverlay.js +239 -0
  23. package/dist/camera/CaptureFrameCounterOverlay.d.ts +58 -0
  24. package/dist/camera/CaptureFrameCounterOverlay.js +153 -0
  25. package/dist/camera/CaptureMemoryPill.d.ts +24 -8
  26. package/dist/camera/CaptureMemoryPill.js +37 -12
  27. package/dist/camera/CapturePreview.js +2 -1
  28. package/dist/camera/CaptureStatusOverlay.d.ts +11 -4
  29. package/dist/camera/CaptureStatusOverlay.js +22 -5
  30. package/dist/camera/CaptureThumbnailStrip.js +2 -1
  31. package/dist/camera/LateralMotionModal.d.ts +85 -0
  32. package/dist/camera/LateralMotionModal.js +134 -0
  33. package/dist/camera/PanHowToOverlay.d.ts +76 -0
  34. package/dist/camera/PanHowToOverlay.js +222 -0
  35. package/dist/camera/PanoramaBandOverlay.d.ts +2 -1
  36. package/dist/camera/PanoramaBandOverlay.js +9 -3
  37. package/dist/camera/PanoramaSettings.d.ts +8 -6
  38. package/dist/camera/PanoramaSettings.js +19 -1
  39. package/dist/camera/PanoramaSettingsModal.js +4 -4
  40. package/dist/camera/RectCropPreview.d.ts +135 -0
  41. package/dist/camera/RectCropPreview.js +370 -0
  42. package/dist/camera/RotateToLandscapePrompt.d.ts +87 -0
  43. package/dist/camera/RotateToLandscapePrompt.js +138 -0
  44. package/dist/camera/buildPanoramaInitialSettings.d.ts +19 -2
  45. package/dist/camera/buildPanoramaInitialSettings.js +9 -0
  46. package/dist/camera/cameraErrorMessages.d.ts +30 -1
  47. package/dist/camera/cameraErrorMessages.js +26 -10
  48. package/dist/camera/cameraGuidanceCopy.d.ts +87 -0
  49. package/dist/camera/cameraGuidanceCopy.js +80 -0
  50. package/dist/camera/captureCountdown.d.ts +52 -0
  51. package/dist/camera/captureCountdown.js +76 -0
  52. package/dist/camera/captureWarnings.d.ts +90 -0
  53. package/dist/camera/captureWarnings.js +108 -0
  54. package/dist/camera/classifyStitchError.d.ts +30 -0
  55. package/dist/camera/classifyStitchError.js +42 -0
  56. package/dist/camera/cropGeometry.d.ts +136 -0
  57. package/dist/camera/cropGeometry.js +223 -0
  58. package/dist/camera/displayDecodeImageProps.d.ts +25 -0
  59. package/dist/camera/displayDecodeImageProps.js +29 -0
  60. package/dist/camera/guidanceGraphics.d.ts +58 -0
  61. package/dist/camera/guidanceGraphics.js +280 -0
  62. package/dist/camera/guidanceTokens.d.ts +54 -0
  63. package/dist/camera/guidanceTokens.js +58 -0
  64. package/dist/camera/panModeGate.d.ts +54 -0
  65. package/dist/camera/panModeGate.js +62 -0
  66. package/dist/camera/pickCaptureFormat.d.ts +71 -0
  67. package/dist/camera/pickCaptureFormat.js +85 -0
  68. package/dist/camera/stitchDebugInfo.d.ts +27 -0
  69. package/dist/camera/stitchDebugInfo.js +55 -0
  70. package/dist/camera/usePanMotion.d.ts +250 -0
  71. package/dist/camera/usePanMotion.js +451 -0
  72. package/dist/index.d.ts +24 -3
  73. package/dist/index.js +33 -2
  74. package/dist/stitching/computeInscribedRect.d.ts +40 -0
  75. package/dist/stitching/computeInscribedRect.js +55 -0
  76. package/dist/stitching/cropQuad.d.ts +78 -0
  77. package/dist/stitching/cropQuad.js +116 -0
  78. package/dist/stitching/incremental.d.ts +74 -0
  79. package/dist/stitching/useIncrementalStitcher.d.ts +7 -1
  80. package/dist/stitching/useIncrementalStitcher.js +7 -1
  81. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +154 -29
  82. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.m +4 -0
  83. package/ios/Sources/RNImageStitcher/IncrementalStitcherBridge.swift +15 -0
  84. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +2 -2
  85. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +48 -5
  86. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +27 -0
  87. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +211 -7
  88. package/ios/Sources/RNImageStitcher/RNSARSession.swift +25 -1
  89. package/ios/Sources/RNImageStitcher/Stitcher.swift +34 -1
  90. package/ios/Sources/RNImageStitcher/StitcherBridge.m +5 -0
  91. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +56 -0
  92. package/package.json +5 -1
  93. package/src/camera/Camera.tsx +945 -47
  94. package/src/camera/CameraView.tsx +48 -16
  95. package/src/camera/CaptureCountdownOverlay.tsx +272 -0
  96. package/src/camera/CaptureFrameCounterOverlay.tsx +197 -0
  97. package/src/camera/CaptureMemoryPill.tsx +50 -12
  98. package/src/camera/CapturePreview.tsx +5 -0
  99. package/src/camera/CaptureStatusOverlay.tsx +35 -7
  100. package/src/camera/CaptureThumbnailStrip.tsx +4 -0
  101. package/src/camera/LateralMotionModal.tsx +199 -0
  102. package/src/camera/PanHowToOverlay.tsx +246 -0
  103. package/src/camera/PanoramaBandOverlay.tsx +9 -1
  104. package/src/camera/PanoramaSettings.ts +27 -7
  105. package/src/camera/PanoramaSettingsModal.tsx +4 -4
  106. package/src/camera/RectCropPreview.tsx +638 -0
  107. package/src/camera/RotateToLandscapePrompt.tsx +188 -0
  108. package/src/camera/buildPanoramaInitialSettings.ts +30 -1
  109. package/src/camera/cameraErrorMessages.ts +39 -2
  110. package/src/camera/cameraGuidanceCopy.ts +145 -0
  111. package/src/camera/captureCountdown.ts +83 -0
  112. package/src/camera/captureWarnings.ts +190 -0
  113. package/src/camera/classifyStitchError.ts +68 -0
  114. package/src/camera/cropGeometry.ts +268 -0
  115. package/src/camera/displayDecodeImageProps.ts +25 -0
  116. package/src/camera/guidanceGraphics.tsx +347 -0
  117. package/src/camera/guidanceTokens.ts +57 -0
  118. package/src/camera/panModeGate.ts +81 -0
  119. package/src/camera/pickCaptureFormat.ts +130 -0
  120. package/src/camera/stitchDebugInfo.ts +71 -0
  121. package/src/camera/usePanMotion.ts +667 -0
  122. package/src/index.ts +66 -3
  123. package/src/stitching/computeInscribedRect.ts +81 -0
  124. package/src/stitching/cropQuad.ts +167 -0
  125. package/src/stitching/incremental.ts +74 -0
  126. package/src/stitching/useIncrementalStitcher.ts +13 -0
  127. package/android/src/main/java/io/imagestitcher/rn/TransferredNV21.kt +0 -100
  128. package/cpp/tests/CMakeLists.txt +0 -104
  129. package/cpp/tests/README.md +0 -86
  130. package/cpp/tests/keyframe_timebudget_test.cpp +0 -65
  131. package/cpp/tests/pose_test.cpp +0 -74
  132. package/cpp/tests/stitcher_frame_data_test.cpp +0 -132
  133. package/cpp/tests/stubs/jsi/jsi.h +0 -33
  134. package/cpp/tests/stubs/react-native-worklets-core/WKTJsiWorklet.h +0 -34
  135. package/cpp/tests/warp_guard_test.cpp +0 -48
  136. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +0 -190
  137. package/src/camera/__tests__/bandThumbRotation.test.ts +0 -120
  138. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +0 -160
  139. package/src/camera/__tests__/cameraErrorMessages.test.ts +0 -76
  140. package/src/camera/__tests__/homeIndicatorEdge.test.ts +0 -116
  141. package/src/camera/__tests__/lowMemDevice.test.ts +0 -52
  142. package/src/camera/__tests__/selectCaptureDevice.test.ts +0 -210
  143. package/src/camera/__tests__/useContentRotation.test.ts +0 -89
  144. package/src/camera/__tests__/useOrientationDrift.test.ts +0 -169
  145. package/src/stitching/__tests__/subscribeIncrementalState.refine.test.ts +0 -276
  146. package/src/stitching/__tests__/useStitcherWorklet.test.ts +0 -202
@@ -21,12 +21,41 @@ export interface UserFacingStitchError {
21
21
  /** One-paragraph, plain-language corrective guidance. */
22
22
  message: string;
23
23
  }
24
+ /**
25
+ * The four recoverable stitch outcomes, each with copy tuned to its
26
+ * actual root cause. `Partial<Record<...>>` keeps the keys
27
+ * compile-checked against the `CameraErrorCode` union — a renamed or
28
+ * dropped code breaks the build here rather than silently going
29
+ * unhandled.
30
+ */
31
+ /**
32
+ * A partial map of recoverable-error code → copy, for the `overrides`
33
+ * argument of {@link userFacingStitchError}. Hosts localising the SDK pass
34
+ * their translated strings here (typically built from their i18n catalogue,
35
+ * keyed by the same `CameraErrorCode`s exposed by {@link RECOVERABLE_STITCH_CODES}).
36
+ */
37
+ export type UserFacingStitchErrorOverrides = Partial<Record<CameraErrorCode, UserFacingStitchError>>;
38
+ export declare const RECOVERABLE_STITCH_GUIDANCE: Partial<Record<CameraErrorCode, UserFacingStitchError>>;
39
+ /**
40
+ * The recoverable stitch-error codes this module has built-in copy for.
41
+ * A host wiring i18n iterates these to know exactly which codes need a
42
+ * translation (every other `CameraErrorCode` maps to `null` and uses the
43
+ * host's generic error UI).
44
+ */
45
+ export declare const RECOVERABLE_STITCH_CODES: CameraErrorCode[];
24
46
  /**
25
47
  * Maps a `CameraErrorCode` to friendly, action-guiding alert copy.
26
48
  *
49
+ * Localisation: pass `overrides` (a partial code→copy map, typically from
50
+ * your i18n catalogue) and any code present there wins over the built-in
51
+ * English; codes you omit fall back to the bundled copy. This is the
52
+ * host-side mirror of the `guidanceCopy` prop — the recoverable-error alert
53
+ * is rendered by the HOST (in its `onError` handler), so it is localised
54
+ * here rather than through `<Camera>`.
55
+ *
27
56
  * @returns the title+message for a recoverable stitch failure, or `null`
28
57
  * if `code` has no single user-recoverable action (the host should
29
58
  * then show its generic error UI).
30
59
  */
31
- export declare function userFacingStitchError(code: CameraErrorCode): UserFacingStitchError | null;
60
+ export declare function userFacingStitchError(code: CameraErrorCode, overrides?: UserFacingStitchErrorOverrides): UserFacingStitchError | null;
32
61
  //# sourceMappingURL=cameraErrorMessages.d.ts.map
@@ -1,14 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RECOVERABLE_STITCH_CODES = exports.RECOVERABLE_STITCH_GUIDANCE = void 0;
3
4
  exports.userFacingStitchError = userFacingStitchError;
4
- /**
5
- * The four recoverable stitch outcomes, each with copy tuned to its
6
- * actual root cause. `Partial<Record<...>>` keeps the keys
7
- * compile-checked against the `CameraErrorCode` union — a renamed or
8
- * dropped code breaks the build here rather than silently going
9
- * unhandled.
10
- */
11
- const RECOVERABLE_STITCH_GUIDANCE = {
5
+ exports.RECOVERABLE_STITCH_GUIDANCE = {
12
6
  // cv::Stitcher ERR_NEED_MORE_IMGS / the manual pipeline's "0 valid
13
7
  // pairwise matches" — the frames simply don't overlap enough to chain.
14
8
  STITCH_NEED_MORE_IMGS: {
@@ -33,6 +27,14 @@ const RECOVERABLE_STITCH_GUIDANCE = {
33
27
  message: "The frames couldn't be aligned — keep the phone level and steady so "
34
28
  + 'each frame overlaps the one before it.',
35
29
  },
30
+ // v0.16 — the post-stitch validator rejected the output as disjoint /
31
+ // fragmented: the frames stitched but didn't form one coherent panorama
32
+ // (usually a too-fast or jerky sweep that broke alignment partway).
33
+ STITCH_LOW_QUALITY: {
34
+ title: "That didn't come out right",
35
+ message: "The panorama didn't stitch into one clean image — try again, panning "
36
+ + 'slowly and steadily in one direction so each frame overlaps the last.',
37
+ },
36
38
  // Ran out of memory finishing the stitch — usually an over-long sweep.
37
39
  STITCH_OOM: {
38
40
  title: 'Try a shorter sweep',
@@ -40,14 +42,28 @@ const RECOVERABLE_STITCH_GUIDANCE = {
40
42
  + '— a shorter, narrower sweep (or 1x for wide scenes) will fit.',
41
43
  },
42
44
  };
45
+ /**
46
+ * The recoverable stitch-error codes this module has built-in copy for.
47
+ * A host wiring i18n iterates these to know exactly which codes need a
48
+ * translation (every other `CameraErrorCode` maps to `null` and uses the
49
+ * host's generic error UI).
50
+ */
51
+ exports.RECOVERABLE_STITCH_CODES = Object.keys(exports.RECOVERABLE_STITCH_GUIDANCE);
43
52
  /**
44
53
  * Maps a `CameraErrorCode` to friendly, action-guiding alert copy.
45
54
  *
55
+ * Localisation: pass `overrides` (a partial code→copy map, typically from
56
+ * your i18n catalogue) and any code present there wins over the built-in
57
+ * English; codes you omit fall back to the bundled copy. This is the
58
+ * host-side mirror of the `guidanceCopy` prop — the recoverable-error alert
59
+ * is rendered by the HOST (in its `onError` handler), so it is localised
60
+ * here rather than through `<Camera>`.
61
+ *
46
62
  * @returns the title+message for a recoverable stitch failure, or `null`
47
63
  * if `code` has no single user-recoverable action (the host should
48
64
  * then show its generic error UI).
49
65
  */
50
- function userFacingStitchError(code) {
51
- return RECOVERABLE_STITCH_GUIDANCE[code] ?? null;
66
+ function userFacingStitchError(code, overrides) {
67
+ return overrides?.[code] ?? exports.RECOVERABLE_STITCH_GUIDANCE[code] ?? null;
52
68
  }
53
69
  //# sourceMappingURL=cameraErrorMessages.js.map
@@ -0,0 +1,87 @@
1
+ /**
2
+ * cameraGuidanceCopy — the single user-overridable copy surface for EVERY
3
+ * string the panorama capture UI renders itself: the rotate prompt, pan
4
+ * hint, too-fast cue, lateral-stop popup, the capture-status banner
5
+ * (recording / stitching) AND the crop-editor warning banners. Centralised
6
+ * so a host can localise or re-word the whole capture experience in one
7
+ * place via the `guidanceCopy` `<Camera>` prop (see the README's
8
+ * "Internationalization" section), and so the defaults live together.
9
+ *
10
+ * NOTE on coverage: the *recoverable stitch-error* alert copy
11
+ * (`userFacingStitchError`) is rendered by the HOST (it calls that helper
12
+ * in its `onError` handler), so it is localised there — see
13
+ * `cameraErrorMessages.ts`, which accepts an override map for the same
14
+ * reason. Everything the SDK draws on screen flows through THIS object.
15
+ *
16
+ * Mirrors the override pattern of `PanoramaGuidance.messages` and
17
+ * `cameraErrorMessages.ts`.
18
+ */
19
+ import { type CaptureWarningCopy } from './captureWarnings';
20
+ export interface GuidanceCopy {
21
+ /** Item 2 — caption pill while waiting for the user to rotate to landscape
22
+ * (panMode `'vertical'`). */
23
+ rotateToLandscape: string;
24
+ /** Item 2 — caption pill while waiting for the user to rotate to portrait
25
+ * (panMode `'horizontal'`). */
26
+ rotateToPortrait: string;
27
+ /** Item 3 — short hint shown with the how-to-pan animation. */
28
+ panHint: string;
29
+ /** Item 4 — transient warning when the pan is too fast. */
30
+ tooFast: string;
31
+ /** Item 6 — popup title when the user drifts laterally (cross-axis). */
32
+ lateralStopTitle: string;
33
+ /** Item 6 — popup body / guidance for the lateral-drift stop. */
34
+ lateralStopBody: string;
35
+ /** Item 6 — popup dismiss button label. */
36
+ lateralStopDismiss: string;
37
+ /**
38
+ * Item 6 — popup TITLE when lateral drift stopped the capture before
39
+ * enough frames were captured to stitch (the user panned the wrong way
40
+ * almost immediately). Nothing was produced, so the copy points them at
41
+ * the arrow instead of saying "we kept what you captured".
42
+ */
43
+ lateralWrongDirectionTitle: string;
44
+ /** Item 6 — popup BODY for the too-few-frames wrong-direction stop. */
45
+ lateralWrongDirectionBody: string;
46
+ /** Item 7 — confirm button on the crop editor. */
47
+ cropConfirm: string;
48
+ /** Item 7 — reset-corners button on the crop editor. */
49
+ cropReset: string;
50
+ /** Item 7 — "emit the stitch un-cropped" button on the crop editor. */
51
+ cropUseOriginal: string;
52
+ /** Item 7 — discard this capture and return to the camera. */
53
+ cropRetake: string;
54
+ /**
55
+ * Accept button in PREVIEW-ONLY mode (`showPreview` without `rectCrop`):
56
+ * the editor shows the stitched image with no crop box, and this confirms
57
+ * it as-is.
58
+ */
59
+ previewConfirm: string;
60
+ /** Banner while a capture is recording (the calm, green state). */
61
+ statusRecording: string;
62
+ /** Banner while the panorama is being stitched after release. */
63
+ statusStitching: string;
64
+ /**
65
+ * LOW_FRAME_UTILIZATION warning. TEMPLATE — keep the `{included}`,
66
+ * `{requested}` and `{percent}` placeholders (substituted at runtime).
67
+ */
68
+ warnLowFrameUtilization: string;
69
+ /** LATERAL_DRIFT_FINALIZE warning. */
70
+ warnLateralDriftFinalize: string;
71
+ /** HIGH_PAN_SPEED warning. */
72
+ warnHighPanSpeed: string;
73
+ }
74
+ export declare const DEFAULT_GUIDANCE_COPY: GuidanceCopy;
75
+ /**
76
+ * Project the warning keys of a resolved `GuidanceCopy` back onto the
77
+ * {@link CaptureWarningCopy} shape `buildCaptureWarnings` consumes. Keeps
78
+ * the two call sites in `<Camera>` from re-spelling the mapping (DRY).
79
+ */
80
+ export declare function captureWarningCopyFrom(g: GuidanceCopy): CaptureWarningCopy;
81
+ /**
82
+ * Merge a partial host override onto the defaults. Undefined / missing keys
83
+ * fall back to the default string; an empty-object / undefined override
84
+ * returns the defaults unchanged.
85
+ */
86
+ export declare function mergeGuidanceCopy(override?: Partial<GuidanceCopy>): GuidanceCopy;
87
+ //# sourceMappingURL=cameraGuidanceCopy.d.ts.map
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_GUIDANCE_COPY = void 0;
4
+ exports.captureWarningCopyFrom = captureWarningCopyFrom;
5
+ exports.mergeGuidanceCopy = mergeGuidanceCopy;
6
+ // SPDX-License-Identifier: Apache-2.0
7
+ /**
8
+ * cameraGuidanceCopy — the single user-overridable copy surface for EVERY
9
+ * string the panorama capture UI renders itself: the rotate prompt, pan
10
+ * hint, too-fast cue, lateral-stop popup, the capture-status banner
11
+ * (recording / stitching) AND the crop-editor warning banners. Centralised
12
+ * so a host can localise or re-word the whole capture experience in one
13
+ * place via the `guidanceCopy` `<Camera>` prop (see the README's
14
+ * "Internationalization" section), and so the defaults live together.
15
+ *
16
+ * NOTE on coverage: the *recoverable stitch-error* alert copy
17
+ * (`userFacingStitchError`) is rendered by the HOST (it calls that helper
18
+ * in its `onError` handler), so it is localised there — see
19
+ * `cameraErrorMessages.ts`, which accepts an override map for the same
20
+ * reason. Everything the SDK draws on screen flows through THIS object.
21
+ *
22
+ * Mirrors the override pattern of `PanoramaGuidance.messages` and
23
+ * `cameraErrorMessages.ts`.
24
+ */
25
+ const captureWarnings_1 = require("./captureWarnings");
26
+ exports.DEFAULT_GUIDANCE_COPY = {
27
+ rotateToLandscape: 'Rotate to landscape',
28
+ rotateToPortrait: 'Rotate to portrait',
29
+ panHint: 'Pan slowly top to bottom',
30
+ tooFast: 'Moving too fast — slow down',
31
+ lateralStopTitle: 'Keep the pan straight',
32
+ lateralStopBody: 'You moved sideways. Pan in one direction only — we stitched what you captured.',
33
+ lateralStopDismiss: 'Got it',
34
+ lateralWrongDirectionTitle: 'Follow the arrow',
35
+ lateralWrongDirectionBody: 'You moved the phone the wrong way. Pan slowly in the direction the '
36
+ + 'arrow shows, in one straight line.',
37
+ cropConfirm: 'Crop',
38
+ cropReset: 'Reset',
39
+ cropUseOriginal: 'Use original',
40
+ cropRetake: 'Retake',
41
+ previewConfirm: 'Confirm',
42
+ statusRecording: 'Hold steady — pan slowly',
43
+ statusStitching: 'Stitching panorama…',
44
+ // DRY: the English warning copy lives once, in captureWarnings.ts.
45
+ warnLowFrameUtilization: captureWarnings_1.DEFAULT_CAPTURE_WARNING_COPY.lowFrameUtilization,
46
+ warnLateralDriftFinalize: captureWarnings_1.DEFAULT_CAPTURE_WARNING_COPY.lateralDriftFinalize,
47
+ warnHighPanSpeed: captureWarnings_1.DEFAULT_CAPTURE_WARNING_COPY.highPanSpeed,
48
+ };
49
+ /**
50
+ * Project the warning keys of a resolved `GuidanceCopy` back onto the
51
+ * {@link CaptureWarningCopy} shape `buildCaptureWarnings` consumes. Keeps
52
+ * the two call sites in `<Camera>` from re-spelling the mapping (DRY).
53
+ */
54
+ function captureWarningCopyFrom(g) {
55
+ return {
56
+ lowFrameUtilization: g.warnLowFrameUtilization,
57
+ lateralDriftFinalize: g.warnLateralDriftFinalize,
58
+ highPanSpeed: g.warnHighPanSpeed,
59
+ };
60
+ }
61
+ /**
62
+ * Merge a partial host override onto the defaults. Undefined / missing keys
63
+ * fall back to the default string; an empty-object / undefined override
64
+ * returns the defaults unchanged.
65
+ */
66
+ function mergeGuidanceCopy(override) {
67
+ if (!override)
68
+ return exports.DEFAULT_GUIDANCE_COPY;
69
+ return { ...exports.DEFAULT_GUIDANCE_COPY, ...stripUndefined(override) };
70
+ }
71
+ /** Drop keys whose value is `undefined` so they don't clobber a default. */
72
+ function stripUndefined(o) {
73
+ const out = {};
74
+ Object.keys(o).forEach((k) => {
75
+ if (o[k] !== undefined)
76
+ out[k] = o[k];
77
+ });
78
+ return out;
79
+ }
80
+ //# sourceMappingURL=cameraGuidanceCopy.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * captureCountdown — pure timing helpers for the recording-time countdown
3
+ * and auto-finalize (guidance item 5).
4
+ *
5
+ * The non-AR panorama hold-and-pan has a hard recording ceiling (`maxMs`).
6
+ * As the user pans, a blinking countdown shows the whole seconds remaining;
7
+ * when it hits 0 the host auto-finalizes (stops recording and stitches what
8
+ * was captured — the FINALIZE-on-zero decision is handled by `<Camera>`,
9
+ * not here).
10
+ *
11
+ * Both functions are pure (no React, no timers, no `Date.now()` baked in —
12
+ * the caller threads `now` from its own animation frame / interval) so the
13
+ * boundary behaviour is unit-testable in the node jest env. Mirrors the
14
+ * pure-helper + `__tests__` pattern of `contentRotationDeg`.
15
+ *
16
+ * `maxMs <= 0` DISABLES the feature: `shouldAutoStop` never returns true
17
+ * (recording is unbounded) and the countdown is meant to be hidden by the
18
+ * caller. `countdownSecondsFrom` still returns a clamped, non-negative
19
+ * number in that case (0) so a caller that renders it regardless won't show
20
+ * a negative value.
21
+ */
22
+ /**
23
+ * Whole seconds remaining in the recording window, for the countdown UI.
24
+ *
25
+ * - While recording (`recordingStartedAt` non-null):
26
+ * `ceil((maxMs - elapsed) / 1000)`, where `elapsed = now - start`,
27
+ * clamped to `[0, round(maxMs / 1000)]`. `ceil` means the displayed
28
+ * number ticks to N only once strictly fewer than N seconds remain
29
+ * (e.g. at exactly 1 ms before the 1s boundary it still reads the
30
+ * higher value), and it reaches 0 exactly at `elapsed === maxMs`.
31
+ * - Before recording (`recordingStartedAt === null`): the full window,
32
+ * `round(maxMs / 1000)` — the at-rest value shown before the user
33
+ * starts the hold.
34
+ * - `maxMs <= 0` (feature disabled): returns 0.
35
+ *
36
+ * The result is always a whole, non-negative number.
37
+ */
38
+ export declare function countdownSecondsFrom(recordingStartedAt: number | null, now: number, maxMs: number): number;
39
+ /**
40
+ * True when the host should auto-finalize the recording NOW.
41
+ *
42
+ * Fires only when ALL of:
43
+ * 1. recording (`recordingStartedAt` non-null),
44
+ * 2. the window is enabled (`maxMs > 0`), AND
45
+ * 3. elapsed (`now - start`) has reached or passed the ceiling
46
+ * (`>= maxMs`).
47
+ *
48
+ * `maxMs <= 0` disables auto-stop entirely (unbounded recording), so this
49
+ * returns false regardless of how long the user has been recording.
50
+ */
51
+ export declare function shouldAutoStop(recordingStartedAt: number | null, now: number, maxMs: number): boolean;
52
+ //# sourceMappingURL=captureCountdown.d.ts.map
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * captureCountdown — pure timing helpers for the recording-time countdown
5
+ * and auto-finalize (guidance item 5).
6
+ *
7
+ * The non-AR panorama hold-and-pan has a hard recording ceiling (`maxMs`).
8
+ * As the user pans, a blinking countdown shows the whole seconds remaining;
9
+ * when it hits 0 the host auto-finalizes (stops recording and stitches what
10
+ * was captured — the FINALIZE-on-zero decision is handled by `<Camera>`,
11
+ * not here).
12
+ *
13
+ * Both functions are pure (no React, no timers, no `Date.now()` baked in —
14
+ * the caller threads `now` from its own animation frame / interval) so the
15
+ * boundary behaviour is unit-testable in the node jest env. Mirrors the
16
+ * pure-helper + `__tests__` pattern of `contentRotationDeg`.
17
+ *
18
+ * `maxMs <= 0` DISABLES the feature: `shouldAutoStop` never returns true
19
+ * (recording is unbounded) and the countdown is meant to be hidden by the
20
+ * caller. `countdownSecondsFrom` still returns a clamped, non-negative
21
+ * number in that case (0) so a caller that renders it regardless won't show
22
+ * a negative value.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.countdownSecondsFrom = countdownSecondsFrom;
26
+ exports.shouldAutoStop = shouldAutoStop;
27
+ /**
28
+ * Whole seconds remaining in the recording window, for the countdown UI.
29
+ *
30
+ * - While recording (`recordingStartedAt` non-null):
31
+ * `ceil((maxMs - elapsed) / 1000)`, where `elapsed = now - start`,
32
+ * clamped to `[0, round(maxMs / 1000)]`. `ceil` means the displayed
33
+ * number ticks to N only once strictly fewer than N seconds remain
34
+ * (e.g. at exactly 1 ms before the 1s boundary it still reads the
35
+ * higher value), and it reaches 0 exactly at `elapsed === maxMs`.
36
+ * - Before recording (`recordingStartedAt === null`): the full window,
37
+ * `round(maxMs / 1000)` — the at-rest value shown before the user
38
+ * starts the hold.
39
+ * - `maxMs <= 0` (feature disabled): returns 0.
40
+ *
41
+ * The result is always a whole, non-negative number.
42
+ */
43
+ function countdownSecondsFrom(recordingStartedAt, now, maxMs) {
44
+ if (maxMs <= 0)
45
+ return 0;
46
+ const maxSeconds = Math.round(maxMs / 1000);
47
+ // Not recording yet — show the full window at rest.
48
+ if (recordingStartedAt === null)
49
+ return maxSeconds;
50
+ const elapsed = now - recordingStartedAt;
51
+ const remainingSeconds = Math.ceil((maxMs - elapsed) / 1000);
52
+ // Clamp into [0, maxSeconds]: guards a clock that ran past the ceiling
53
+ // (negative remaining → 0) and a `now` before the start (`elapsed < 0`,
54
+ // remaining > maxSeconds → maxSeconds).
55
+ return Math.min(maxSeconds, Math.max(0, remainingSeconds));
56
+ }
57
+ /**
58
+ * True when the host should auto-finalize the recording NOW.
59
+ *
60
+ * Fires only when ALL of:
61
+ * 1. recording (`recordingStartedAt` non-null),
62
+ * 2. the window is enabled (`maxMs > 0`), AND
63
+ * 3. elapsed (`now - start`) has reached or passed the ceiling
64
+ * (`>= maxMs`).
65
+ *
66
+ * `maxMs <= 0` disables auto-stop entirely (unbounded recording), so this
67
+ * returns false regardless of how long the user has been recording.
68
+ */
69
+ function shouldAutoStop(recordingStartedAt, now, maxMs) {
70
+ if (recordingStartedAt === null)
71
+ return false;
72
+ if (maxMs <= 0)
73
+ return false;
74
+ return now - recordingStartedAt >= maxMs;
75
+ }
76
+ //# sourceMappingURL=captureCountdown.js.map
@@ -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