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.
Files changed (133) hide show
  1. package/CHANGELOG.md +147 -1
  2. package/README.md +116 -5
  3. package/android/src/main/cpp/image_stitcher_jni.cpp +107 -11
  4. package/android/src/main/java/io/imagestitcher/rn/BatchStitcher.kt +223 -1
  5. package/android/src/main/java/io/imagestitcher/rn/IncrementalStitcher.kt +87 -30
  6. package/android/src/main/java/io/imagestitcher/rn/KeyframeGate.kt +1 -1
  7. package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +14 -8
  8. package/android/src/main/java/io/imagestitcher/rn/ar/YuvImageConverter.kt +39 -1
  9. package/cpp/crop_quad.cpp +162 -0
  10. package/cpp/crop_quad.hpp +163 -0
  11. package/cpp/stitcher.cpp +651 -55
  12. package/cpp/stitcher.hpp +10 -0
  13. package/cpp/warp_guard.hpp +212 -0
  14. package/dist/camera/Camera.d.ts +196 -12
  15. package/dist/camera/Camera.js +629 -35
  16. package/dist/camera/CameraView.js +62 -5
  17. package/dist/camera/CaptureCountdownOverlay.d.ts +70 -0
  18. package/dist/camera/CaptureCountdownOverlay.js +239 -0
  19. package/dist/camera/CaptureFrameCounterOverlay.d.ts +58 -0
  20. package/dist/camera/CaptureFrameCounterOverlay.js +142 -0
  21. package/dist/camera/CaptureMemoryPill.d.ts +9 -1
  22. package/dist/camera/CaptureMemoryPill.js +3 -3
  23. package/dist/camera/CapturePreview.js +2 -1
  24. package/dist/camera/CaptureStatusOverlay.d.ts +11 -4
  25. package/dist/camera/CaptureStatusOverlay.js +22 -5
  26. package/dist/camera/CaptureThumbnailStrip.js +2 -1
  27. package/dist/camera/LateralMotionModal.d.ts +85 -0
  28. package/dist/camera/LateralMotionModal.js +134 -0
  29. package/dist/camera/PanHowToOverlay.d.ts +76 -0
  30. package/dist/camera/PanHowToOverlay.js +222 -0
  31. package/dist/camera/PanoramaSettings.d.ts +8 -6
  32. package/dist/camera/PanoramaSettings.js +26 -5
  33. package/dist/camera/PanoramaSettingsModal.js +4 -4
  34. package/dist/camera/RectCropPreview.d.ts +161 -0
  35. package/dist/camera/RectCropPreview.js +480 -0
  36. package/dist/camera/RotateToLandscapePrompt.d.ts +87 -0
  37. package/dist/camera/RotateToLandscapePrompt.js +138 -0
  38. package/dist/camera/buildPanoramaInitialSettings.d.ts +19 -2
  39. package/dist/camera/buildPanoramaInitialSettings.js +9 -0
  40. package/dist/camera/cameraErrorMessages.d.ts +30 -1
  41. package/dist/camera/cameraErrorMessages.js +26 -10
  42. package/dist/camera/cameraGuidanceCopy.d.ts +87 -0
  43. package/dist/camera/cameraGuidanceCopy.js +80 -0
  44. package/dist/camera/captureCountdown.d.ts +52 -0
  45. package/dist/camera/captureCountdown.js +76 -0
  46. package/dist/camera/captureWarnings.d.ts +90 -0
  47. package/dist/camera/captureWarnings.js +108 -0
  48. package/dist/camera/classifyStitchError.d.ts +30 -0
  49. package/dist/camera/classifyStitchError.js +42 -0
  50. package/dist/camera/cropGeometry.d.ts +136 -0
  51. package/dist/camera/cropGeometry.js +223 -0
  52. package/dist/camera/displayDecodeImageProps.d.ts +25 -0
  53. package/dist/camera/displayDecodeImageProps.js +29 -0
  54. package/dist/camera/guidanceGraphics.d.ts +58 -0
  55. package/dist/camera/guidanceGraphics.js +280 -0
  56. package/dist/camera/guidanceTokens.d.ts +54 -0
  57. package/dist/camera/guidanceTokens.js +58 -0
  58. package/dist/camera/panModeGate.d.ts +54 -0
  59. package/dist/camera/panModeGate.js +62 -0
  60. package/dist/camera/pickCaptureFormat.d.ts +71 -0
  61. package/dist/camera/pickCaptureFormat.js +85 -0
  62. package/dist/camera/stitchDebugInfo.d.ts +27 -0
  63. package/dist/camera/stitchDebugInfo.js +55 -0
  64. package/dist/camera/usePanMotion.d.ts +250 -0
  65. package/dist/camera/usePanMotion.js +451 -0
  66. package/dist/index.d.ts +24 -3
  67. package/dist/index.js +33 -2
  68. package/dist/stitching/computeInscribedRect.d.ts +40 -0
  69. package/dist/stitching/computeInscribedRect.js +55 -0
  70. package/dist/stitching/cropQuad.d.ts +78 -0
  71. package/dist/stitching/cropQuad.js +116 -0
  72. package/dist/stitching/incremental.d.ts +45 -0
  73. package/ios/Sources/RNImageStitcher/IncrementalStitcher.swift +56 -8
  74. package/ios/Sources/RNImageStitcher/KeyframeGate.swift +2 -2
  75. package/ios/Sources/RNImageStitcher/OpenCVKeyframeCollector.mm +48 -5
  76. package/ios/Sources/RNImageStitcher/OpenCVStitcher.h +27 -0
  77. package/ios/Sources/RNImageStitcher/OpenCVStitcher.mm +191 -7
  78. package/ios/Sources/RNImageStitcher/RNSARSession.swift +25 -1
  79. package/ios/Sources/RNImageStitcher/Stitcher.swift +34 -1
  80. package/ios/Sources/RNImageStitcher/StitcherBridge.m +5 -0
  81. package/ios/Sources/RNImageStitcher/StitcherBridge.swift +56 -0
  82. package/package.json +5 -1
  83. package/src/camera/Camera.tsx +994 -47
  84. package/src/camera/CameraView.tsx +75 -5
  85. package/src/camera/CaptureCountdownOverlay.tsx +272 -0
  86. package/src/camera/CaptureFrameCounterOverlay.tsx +183 -0
  87. package/src/camera/CaptureMemoryPill.tsx +17 -3
  88. package/src/camera/CapturePreview.tsx +5 -0
  89. package/src/camera/CaptureStatusOverlay.tsx +35 -7
  90. package/src/camera/CaptureThumbnailStrip.tsx +4 -0
  91. package/src/camera/LateralMotionModal.tsx +199 -0
  92. package/src/camera/PanHowToOverlay.tsx +246 -0
  93. package/src/camera/PanoramaSettings.ts +34 -11
  94. package/src/camera/PanoramaSettingsModal.tsx +4 -4
  95. package/src/camera/RectCropPreview.tsx +820 -0
  96. package/src/camera/RotateToLandscapePrompt.tsx +188 -0
  97. package/src/camera/buildPanoramaInitialSettings.ts +30 -1
  98. package/src/camera/cameraErrorMessages.ts +39 -2
  99. package/src/camera/cameraGuidanceCopy.ts +145 -0
  100. package/src/camera/captureCountdown.ts +83 -0
  101. package/src/camera/captureWarnings.ts +190 -0
  102. package/src/camera/classifyStitchError.ts +68 -0
  103. package/src/camera/cropGeometry.ts +268 -0
  104. package/src/camera/displayDecodeImageProps.ts +25 -0
  105. package/src/camera/guidanceGraphics.tsx +347 -0
  106. package/src/camera/guidanceTokens.ts +57 -0
  107. package/src/camera/panModeGate.ts +81 -0
  108. package/src/camera/pickCaptureFormat.ts +130 -0
  109. package/src/camera/stitchDebugInfo.ts +71 -0
  110. package/src/camera/usePanMotion.ts +667 -0
  111. package/src/index.ts +66 -3
  112. package/src/stitching/computeInscribedRect.ts +81 -0
  113. package/src/stitching/cropQuad.ts +167 -0
  114. package/src/stitching/incremental.ts +45 -0
  115. package/cpp/tests/CMakeLists.txt +0 -104
  116. package/cpp/tests/README.md +0 -86
  117. package/cpp/tests/keyframe_timebudget_test.cpp +0 -65
  118. package/cpp/tests/pose_test.cpp +0 -74
  119. package/cpp/tests/stitcher_frame_data_test.cpp +0 -132
  120. package/cpp/tests/stubs/jsi/jsi.h +0 -33
  121. package/cpp/tests/stubs/react-native-worklets-core/WKTJsiWorklet.h +0 -34
  122. package/cpp/tests/warp_guard_test.cpp +0 -48
  123. package/src/camera/__tests__/PanoramaSettingsBridge.test.ts +0 -190
  124. package/src/camera/__tests__/bandThumbRotation.test.ts +0 -120
  125. package/src/camera/__tests__/buildPanoramaInitialSettings.test.ts +0 -160
  126. package/src/camera/__tests__/cameraErrorMessages.test.ts +0 -76
  127. package/src/camera/__tests__/homeIndicatorEdge.test.ts +0 -116
  128. package/src/camera/__tests__/lowMemDevice.test.ts +0 -52
  129. package/src/camera/__tests__/selectCaptureDevice.test.ts +0 -210
  130. package/src/camera/__tests__/useContentRotation.test.ts +0 -89
  131. package/src/camera/__tests__/useOrientationDrift.test.ts +0 -169
  132. package/src/stitching/__tests__/subscribeIncrementalState.refine.test.ts +0 -276
  133. package/src/stitching/__tests__/useStitcherWorklet.test.ts +0 -202
@@ -0,0 +1,162 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // crop_quad.cpp — OpenCV implementation of the free-quad perspective
4
+ // crop (`cropToQuad`), the only net-new native code for item-7 of the
5
+ // first-time-user guidance flow.
6
+ //
7
+ // Pairs with the EXISTING axis-aligned crop (cropToRectAtPath on iOS /
8
+ // cropToRect on Android). Where that crop slices a sub-rectangle out of
9
+ // the panorama, this one takes 4 user-dragged corners (a skewed
10
+ // quadrilateral) and perspective-rectifies them into an upright rectangle
11
+ // — the editor in src/camera/RectCropPreview.tsx picks `cropToQuad` over
12
+ // `cropToRect` whenever the dragged quad isn't ~axis-aligned.
13
+ //
14
+ // Why a separate .cpp (not folded into stitcher.cpp): the geometry core
15
+ // (quadDstRect / isQuadAcceptable) is the OpenCV-FREE, unit-tested
16
+ // cpp/crop_quad.hpp; this file is JUST the thin cv:: warp around it, kept
17
+ // out of stitcher.cpp's translation unit so the test suite can link the
18
+ // header without pulling in the whole stitch pipeline.
19
+ //
20
+ // Both platform bridges (iOS OpenCVStitcher.mm, Android BatchStitcher.kt)
21
+ // duplicate this warp in their own native language today — see the
22
+ // integrator notes in the item-7 handoff — so this file is the shared
23
+ // C++ reference + the home of the canvasExceedsGuard wiring. Wire it
24
+ // into a translation unit (the pod / the JNI lib) when the native crop is
25
+ // routed through shared C++ rather than per-platform OpenCV bindings.
26
+
27
+ // OpenCV's headers redefine NO/YES on platforms whose prefix.pch already
28
+ // has the ObjC bool macros; undef defensively (no-op off iOS).
29
+ #ifdef NO
30
+ #undef NO
31
+ #endif
32
+ #ifdef YES
33
+ #undef YES
34
+ #endif
35
+
36
+ #include <opencv2/opencv.hpp>
37
+ #include <opencv2/imgproc.hpp>
38
+
39
+ #include <string>
40
+ #include <vector>
41
+
42
+ #include "crop_quad.hpp"
43
+ #include "warp_guard.hpp"
44
+
45
+ namespace retailens {
46
+
47
+ // Result of a cropToQuad call. `ok == false` carries a human-readable
48
+ // `error` (the bridge maps it to NSError / Promise.reject); on success
49
+ // `width`/`height` are the written output dimensions.
50
+ struct CropQuadResult {
51
+ bool ok = false;
52
+ std::string error;
53
+ int width = 0;
54
+ int height = 0;
55
+ };
56
+
57
+ // Perspective-rectify the quad `q` (4 ORDERED [TL, TR, BR, BL] image-pixel
58
+ // corners) out of the image at `inPath` into an upright w×h rectangle,
59
+ // written to `outPath` as a JPEG at `quality` (clamped to [1, 100]).
60
+ //
61
+ // Guards, in order (each is a hard reject — NO partial output is written):
62
+ // 1. inPath decodes (cv::imread non-empty).
63
+ // 2. isQuadAcceptable — convex + min-area + within the decoded image
64
+ // bounds. A degenerate / non-convex / out-of-bounds quad is rejected
65
+ // here, before any allocation.
66
+ // 3. quadDstRect yields a positive w×h.
67
+ // 4. canvasExceedsGuard(w, h) — the SAME blend-canvas guard the stitch
68
+ // pipeline uses. A near-collinear quad whose averaged opposite-edge
69
+ // lengths still multiply to a multi-gigapixel output (e.g. a 1 px ×
70
+ // 40000 px sliver dragged across a wide pano) can't OOM the device:
71
+ // the output Mat is never allocated when this fires.
72
+ //
73
+ // The warp itself is cv::getPerspectiveTransform(src → axis-aligned dst)
74
+ // + cv::warpPerspective to a w×h canvas. `outPath == inPath` is allowed
75
+ // (overwrite in place), matching cropToRect's contract.
76
+ CropQuadResult cropQuadToFile(const std::string& inPath,
77
+ const std::string& outPath,
78
+ const CropQuad& q,
79
+ int quality) {
80
+ CropQuadResult result;
81
+
82
+ cv::Mat img = cv::imread(inPath, cv::IMREAD_COLOR);
83
+ if (img.empty()) {
84
+ result.error = "Could not decode image at " + inPath;
85
+ return result;
86
+ }
87
+
88
+ // Guard 2 — geometry: convex, non-degenerate, inside the decoded image.
89
+ if (!isQuadAcceptable(q, static_cast<double>(img.cols),
90
+ static_cast<double>(img.rows))) {
91
+ result.error =
92
+ "Crop quad is degenerate (non-convex, zero-area, or out of bounds)";
93
+ return result;
94
+ }
95
+
96
+ // Guard 3 — positive destination size.
97
+ const QuadDstSize dst = quadDstRect(q);
98
+ if (dst.width <= 0 || dst.height <= 0) {
99
+ result.error = "Crop quad rectifies to a non-positive rectangle";
100
+ return result;
101
+ }
102
+
103
+ // Guard 4 — output-canvas OOM net (shared with the stitch pipeline).
104
+ if (canvasExceedsGuard(dst.width, dst.height)) {
105
+ result.error = "Crop quad output canvas exceeds the size guard (" +
106
+ std::to_string(dst.width) + "x" +
107
+ std::to_string(dst.height) + ")";
108
+ return result;
109
+ }
110
+
111
+ const cv::Point2f src[4] = {
112
+ cv::Point2f(static_cast<float>(q.tl.x), static_cast<float>(q.tl.y)),
113
+ cv::Point2f(static_cast<float>(q.tr.x), static_cast<float>(q.tr.y)),
114
+ cv::Point2f(static_cast<float>(q.br.x), static_cast<float>(q.br.y)),
115
+ cv::Point2f(static_cast<float>(q.bl.x), static_cast<float>(q.bl.y)),
116
+ };
117
+ const cv::Point2f dstPts[4] = {
118
+ cv::Point2f(0.0f, 0.0f),
119
+ cv::Point2f(static_cast<float>(dst.width), 0.0f),
120
+ cv::Point2f(static_cast<float>(dst.width),
121
+ static_cast<float>(dst.height)),
122
+ cv::Point2f(0.0f, static_cast<float>(dst.height)),
123
+ };
124
+
125
+ cv::Mat warped;
126
+ try {
127
+ const cv::Mat transform = cv::getPerspectiveTransform(src, dstPts);
128
+ cv::warpPerspective(img, warped, transform,
129
+ cv::Size(dst.width, dst.height),
130
+ cv::INTER_LINEAR);
131
+ } catch (const cv::Exception& e) {
132
+ result.error = std::string("Perspective warp failed: ") + e.what();
133
+ return result;
134
+ }
135
+ if (warped.empty()) {
136
+ result.error = "Perspective warp produced an empty image";
137
+ return result;
138
+ }
139
+
140
+ int q255 = quality;
141
+ if (q255 < 1) q255 = 1;
142
+ if (q255 > 100) q255 = 100;
143
+ const std::vector<int> writeParams = {cv::IMWRITE_JPEG_QUALITY, q255};
144
+ bool wrote = false;
145
+ try {
146
+ wrote = cv::imwrite(outPath, warped, writeParams);
147
+ } catch (const cv::Exception& e) {
148
+ result.error = std::string("Could not write cropped image: ") + e.what();
149
+ return result;
150
+ }
151
+ if (!wrote) {
152
+ result.error = "Could not write cropped image to " + outPath;
153
+ return result;
154
+ }
155
+
156
+ result.ok = true;
157
+ result.width = warped.cols;
158
+ result.height = warped.rows;
159
+ return result;
160
+ }
161
+
162
+ } // namespace retailens
@@ -0,0 +1,163 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ #pragma once
3
+ #include <cmath>
4
+ #include <cstdint>
5
+
6
+ // ─────────────────────────────────────────────────────────────────────
7
+ // Free-quad crop geometry — the OpenCV-FREE, unit-testable core of the
8
+ // item-7 draggable-corner crop (`cropToQuad`). The actual perspective
9
+ // warp (cv::getPerspectiveTransform + cv::warpPerspective) lives next to
10
+ // the existing axis-aligned crop in the platform bridges; this header is
11
+ // JUST the two pure predicates worth testing in isolation:
12
+ //
13
+ // - quadDstRect(quad) → the {w,h} of the upright destination
14
+ // rectangle the quad rectifies INTO.
15
+ // - isQuadAcceptable(quad) → convex + min-area + within-bounds gate
16
+ // the warp must pass before allocating.
17
+ //
18
+ // Header-only + zero cv:: dependency on purpose — same posture as
19
+ // warp_guard.hpp. These are the C++ twins of the JS-side helpers in
20
+ // src/camera/cropGeometry.ts (rectSizeForQuad / isQuadValid); the math
21
+ // is duplicated per the repo's "duplicate stage code, DRY when proven"
22
+ // convention so a native caller never has to round-trip through JS to
23
+ // validate a quad.
24
+ //
25
+ // Corner-order contract: every function below assumes the 4 points are
26
+ // in canonical [TL, TR, BR, BL] (clockwise from top-left) winding — the
27
+ // order src/camera/cropGeometry.ts:orderQuadCorners produces and the
28
+ // order RectCropResult.quad carries. Pass un-ordered points and the
29
+ // edge-length / convexity math is meaningless.
30
+ // ─────────────────────────────────────────────────────────────────────
31
+
32
+ namespace retailens {
33
+
34
+ // A single corner in image-pixel space (origin = image top-left).
35
+ struct QuadPoint {
36
+ double x = 0.0;
37
+ double y = 0.0;
38
+ };
39
+
40
+ // Exactly four corners, in [TL, TR, BR, BL] order.
41
+ struct CropQuad {
42
+ QuadPoint tl;
43
+ QuadPoint tr;
44
+ QuadPoint br;
45
+ QuadPoint bl;
46
+ };
47
+
48
+ // Integer destination-rectangle size the quad rectifies into.
49
+ struct QuadDstSize {
50
+ int width = 0;
51
+ int height = 0;
52
+ };
53
+
54
+ // Euclidean distance between two corners.
55
+ inline double quadPointDistance(const QuadPoint& a, const QuadPoint& b) {
56
+ const double dx = a.x - b.x;
57
+ const double dy = a.y - b.y;
58
+ return std::sqrt(dx * dx + dy * dy);
59
+ }
60
+
61
+ // Target rectangle size for the perspective `dst` quad, derived from the
62
+ // 4 ORDERED ([TL, TR, BR, BL]) image-pixel corners:
63
+ // - width = average of the top edge (TL→TR) and bottom edge (BL→BR).
64
+ // - height = average of the left edge (TL→BL) and right edge (TR→BR).
65
+ //
66
+ // Averaging opposite edges gives a stable output size for a skewed quad
67
+ // (each pair of opposite edges differs under perspective; the mean is the
68
+ // least-distorting target). Rounds to whole pixels — the warp allocates
69
+ // an integer-sized output Mat. Mirrors cropGeometry.ts:rectSizeForQuad
70
+ // so iOS / Android / JS agree on the output dimensions bit-for-bit.
71
+ //
72
+ // A degenerate quad (all-collinear, zero-size) yields a 0×0 or 1×N size;
73
+ // the caller GUARDS this with isQuadAcceptable + canvasExceedsGuard before
74
+ // allocating, so this function never has to reject — it only measures.
75
+ inline QuadDstSize quadDstRect(const CropQuad& q) {
76
+ const double top = quadPointDistance(q.tl, q.tr);
77
+ const double bottom = quadPointDistance(q.bl, q.br);
78
+ const double left = quadPointDistance(q.tl, q.bl);
79
+ const double right = quadPointDistance(q.tr, q.br);
80
+ QuadDstSize size;
81
+ size.width = static_cast<int>(std::lround((top + bottom) / 2.0));
82
+ size.height = static_cast<int>(std::lround((left + right) / 2.0));
83
+ return size;
84
+ }
85
+
86
+ // 2× the signed area of the quad via the shoelace formula. Positive for
87
+ // one winding, negative for the other, ~0 for a degenerate (collinear)
88
+ // quad. Sign is winding-dependent; callers that care about magnitude
89
+ // (min-area) take the absolute value.
90
+ inline double quadSignedArea2(const CropQuad& q) {
91
+ const QuadPoint p[4] = {q.tl, q.tr, q.br, q.bl};
92
+ double sum = 0.0;
93
+ for (int i = 0; i < 4; ++i) {
94
+ const QuadPoint& a = p[i];
95
+ const QuadPoint& b = p[(i + 1) % 4];
96
+ sum += a.x * b.y - b.x * a.y;
97
+ }
98
+ return sum;
99
+ }
100
+
101
+ // Convexity test: all four consecutive edge cross-products share one sign
102
+ // (zero allowed for a straight, axis-aligned corner). Rejects the self-
103
+ // intersecting "bowtie" quads a free-drag editor can produce, which a
104
+ // perspective warp can't rectify. Winding-agnostic. Mirrors the
105
+ // `isConvex` helper in cropGeometry.ts.
106
+ inline bool quadIsConvex(const CropQuad& q) {
107
+ const QuadPoint p[4] = {q.tl, q.tr, q.br, q.bl};
108
+ int sign = 0;
109
+ for (int i = 0; i < 4; ++i) {
110
+ const QuadPoint& a = p[i];
111
+ const QuadPoint& b = p[(i + 1) % 4];
112
+ const QuadPoint& c = p[(i + 2) % 4];
113
+ const double cross =
114
+ (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x);
115
+ if (cross != 0.0) {
116
+ const int s = (cross > 0.0) ? 1 : -1;
117
+ if (sign == 0) {
118
+ sign = s;
119
+ } else if (s != sign) {
120
+ return false;
121
+ }
122
+ }
123
+ }
124
+ return true;
125
+ }
126
+
127
+ // True when the 4 ordered ([TL, TR, BR, BL]) corners form a quad the
128
+ // perspective warp can safely rectify:
129
+ // 1. **Convex** — no self-intersection (quadIsConvex).
130
+ // 2. **Non-degenerate area** — |signed area| ≥ `minArea` (default 1 px²);
131
+ // rejects all-collinear / zero-size quads.
132
+ // 3. **Within bounds** — every corner lies inside [0..imageW]×[0..imageH]
133
+ // (a half-pixel epsilon absorbs the lround in the JS letterbox
134
+ // inverse). Pass imageW <= 0 OR imageH <= 0 to SKIP the bounds
135
+ // check (the caller doesn't know the image size yet).
136
+ //
137
+ // The companion to warp_guard.hpp:canvasExceedsGuard — that guards the
138
+ // OUTPUT canvas against an OOM; this guards the INPUT quad against being
139
+ // geometrically unwarpable. Both must pass before warpPerspective runs.
140
+ inline bool isQuadAcceptable(const CropQuad& q,
141
+ double imageW = 0.0,
142
+ double imageH = 0.0,
143
+ double minArea = 1.0) {
144
+ if (!quadIsConvex(q)) {
145
+ return false;
146
+ }
147
+ if (std::fabs(quadSignedArea2(q)) < minArea * 2.0) {
148
+ return false;
149
+ }
150
+ if (imageW > 0.0 && imageH > 0.0) {
151
+ const QuadPoint p[4] = {q.tl, q.tr, q.br, q.bl};
152
+ const double eps = 0.5;
153
+ for (int i = 0; i < 4; ++i) {
154
+ if (p[i].x < -eps || p[i].x > imageW + eps ||
155
+ p[i].y < -eps || p[i].y > imageH + eps) {
156
+ return false;
157
+ }
158
+ }
159
+ }
160
+ return true;
161
+ }
162
+
163
+ } // namespace retailens