react-native-rectangle-doc-scanner 7.53.0 → 7.57.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/android/src/camera2/kotlin/com/reactnativerectangledocscanner/CameraController.kt +61 -17
- package/android/src/camera2/kotlin/com/reactnativerectangledocscanner/DocumentScannerView.kt +9 -0
- package/dist/DocScanner.d.ts +6 -0
- package/dist/DocScanner.js +2 -2
- package/dist/utils/overlay.d.ts +6 -0
- package/dist/utils/overlay.js +30 -7
- package/package.json +1 -1
- package/src/DocScanner.tsx +3 -0
- package/src/external.d.ts +1 -0
- package/src/utils/overlay.tsx +36 -7
|
@@ -70,6 +70,7 @@ class CameraController(
|
|
|
70
70
|
private var latestTransform: Matrix? = null
|
|
71
71
|
private var latestBufferWidth = 0
|
|
72
72
|
private var latestBufferHeight = 0
|
|
73
|
+
private var latestTransformRotation = 0
|
|
73
74
|
private val objectDetector = ObjectDetection.getClient(
|
|
74
75
|
ObjectDetectorOptions.Builder()
|
|
75
76
|
.setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
|
|
@@ -216,7 +217,7 @@ class CameraController(
|
|
|
216
217
|
if (rectangle == null || imageWidth <= 0 || imageHeight <= 0) return null
|
|
217
218
|
if (latestBufferWidth <= 0 || latestBufferHeight <= 0) return null
|
|
218
219
|
|
|
219
|
-
val rotationDegrees =
|
|
220
|
+
val rotationDegrees = latestTransformRotation
|
|
220
221
|
val inverseRotation = (360 - rotationDegrees) % 360
|
|
221
222
|
|
|
222
223
|
fun rotatePoint(point: Point): Point {
|
|
@@ -276,6 +277,30 @@ class CameraController(
|
|
|
276
277
|
)
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
fun getPreviewViewport(): RectF? {
|
|
281
|
+
val transform = latestTransform ?: return null
|
|
282
|
+
if (latestBufferWidth <= 0 || latestBufferHeight <= 0) return null
|
|
283
|
+
val rotation = latestTransformRotation
|
|
284
|
+
val isSwapped = rotation == 90 || rotation == 270
|
|
285
|
+
val bufferWidth = if (isSwapped) latestBufferHeight.toFloat() else latestBufferWidth.toFloat()
|
|
286
|
+
val bufferHeight = if (isSwapped) latestBufferWidth.toFloat() else latestBufferHeight.toFloat()
|
|
287
|
+
|
|
288
|
+
val pts = floatArrayOf(
|
|
289
|
+
0f, 0f,
|
|
290
|
+
bufferWidth, 0f,
|
|
291
|
+
0f, bufferHeight,
|
|
292
|
+
bufferWidth, bufferHeight
|
|
293
|
+
)
|
|
294
|
+
transform.mapPoints(pts)
|
|
295
|
+
|
|
296
|
+
val minX = min(min(pts[0], pts[2]), min(pts[4], pts[6]))
|
|
297
|
+
val maxX = max(max(pts[0], pts[2]), max(pts[4], pts[6]))
|
|
298
|
+
val minY = min(min(pts[1], pts[3]), min(pts[5], pts[7]))
|
|
299
|
+
val maxY = max(max(pts[1], pts[3]), max(pts[5], pts[7]))
|
|
300
|
+
|
|
301
|
+
return RectF(minX, minY, maxX, maxY)
|
|
302
|
+
}
|
|
303
|
+
|
|
279
304
|
private fun openCamera() {
|
|
280
305
|
if (cameraDevice != null) {
|
|
281
306
|
return
|
|
@@ -330,6 +355,13 @@ class CameraController(
|
|
|
330
355
|
captureSize = chooseBestSize(captureSizes, previewAspect, null, preferClosestAspect = true)
|
|
331
356
|
?: captureSizes?.maxByOrNull { it.width * it.height }
|
|
332
357
|
|
|
358
|
+
val previewDiff = previewSize?.let { abs(it.width.toDouble() / it.height.toDouble() - targetPreviewAspect) }
|
|
359
|
+
Log.d(
|
|
360
|
+
TAG,
|
|
361
|
+
"[SIZE_SELECTION] targetAspect=$targetPreviewAspect viewAspect=$viewAspect " +
|
|
362
|
+
"previewAspect=$previewAspect diff=$previewDiff selected=${previewSize?.width}x${previewSize?.height}"
|
|
363
|
+
)
|
|
364
|
+
|
|
333
365
|
setupImageReaders()
|
|
334
366
|
Log.d(
|
|
335
367
|
TAG,
|
|
@@ -429,6 +461,7 @@ class CameraController(
|
|
|
429
461
|
val preview = previewSize ?: return
|
|
430
462
|
|
|
431
463
|
surfaceTexture.setDefaultBufferSize(preview.width, preview.height)
|
|
464
|
+
Log.d(TAG, "[CAMERA2] SurfaceTexture defaultBufferSize=${preview.width}x${preview.height}")
|
|
432
465
|
val previewSurface = Surface(surfaceTexture)
|
|
433
466
|
|
|
434
467
|
val targets = mutableListOf(previewSurface)
|
|
@@ -628,9 +661,14 @@ class CameraController(
|
|
|
628
661
|
if (viewWidth == 0f || viewHeight == 0f) return
|
|
629
662
|
|
|
630
663
|
val rotationDegrees = computeRotationDegrees()
|
|
664
|
+
val transformRotation = if (useFrontCamera) {
|
|
665
|
+
rotationDegrees
|
|
666
|
+
} else {
|
|
667
|
+
(360 - rotationDegrees) % 360
|
|
668
|
+
}
|
|
631
669
|
Log.d(
|
|
632
670
|
TAG,
|
|
633
|
-
"[TRANSFORM] rotation=$
|
|
671
|
+
"[TRANSFORM] rotation=$transformRotation view=${viewWidth}x${viewHeight} preview=${preview.width}x${preview.height}"
|
|
634
672
|
)
|
|
635
673
|
|
|
636
674
|
val matrix = Matrix()
|
|
@@ -638,29 +676,37 @@ class CameraController(
|
|
|
638
676
|
val centerX = viewRect.centerX()
|
|
639
677
|
val centerY = viewRect.centerY()
|
|
640
678
|
|
|
641
|
-
val
|
|
642
|
-
val
|
|
679
|
+
val isSwapped = transformRotation == 90 || transformRotation == 270
|
|
680
|
+
val bufferWidth = if (isSwapped) preview.height.toFloat() else preview.width.toFloat()
|
|
681
|
+
val bufferHeight = if (isSwapped) preview.width.toFloat() else preview.height.toFloat()
|
|
643
682
|
val bufferRect = RectF(0f, 0f, bufferWidth, bufferHeight)
|
|
644
|
-
|
|
683
|
+
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
|
|
645
684
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
685
|
+
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
|
|
686
|
+
val scale = max(viewWidth / bufferWidth, viewHeight / bufferHeight)
|
|
687
|
+
matrix.postScale(scale, scale, centerX, centerY)
|
|
688
|
+
if (transformRotation != 0) {
|
|
689
|
+
matrix.postRotate(transformRotation.toFloat(), centerX, centerY)
|
|
649
690
|
}
|
|
650
691
|
|
|
651
|
-
val scale = max(viewWidth / rotatedRect.width(), viewHeight / rotatedRect.height())
|
|
652
|
-
matrix.postScale(scale, scale, rotatedRect.centerX(), rotatedRect.centerY())
|
|
653
|
-
matrix.postTranslate(centerX - rotatedRect.centerX(), centerY - rotatedRect.centerY())
|
|
654
|
-
|
|
655
692
|
previewView.setTransform(matrix)
|
|
656
693
|
latestTransform = Matrix(matrix)
|
|
657
694
|
latestBufferWidth = preview.width
|
|
658
695
|
latestBufferHeight = preview.height
|
|
696
|
+
latestTransformRotation = transformRotation
|
|
697
|
+
|
|
698
|
+
val pts = floatArrayOf(
|
|
699
|
+
0f, 0f,
|
|
700
|
+
bufferWidth, 0f,
|
|
701
|
+
0f, bufferHeight,
|
|
702
|
+
bufferWidth, bufferHeight
|
|
703
|
+
)
|
|
704
|
+
matrix.mapPoints(pts)
|
|
659
705
|
Log.d(
|
|
660
706
|
TAG,
|
|
661
707
|
"[TRANSFORM] viewClass=${previewView.javaClass.name} isTextureView=${previewView is TextureView} " +
|
|
662
|
-
"buffer=${
|
|
663
|
-
"
|
|
708
|
+
"buffer=${bufferWidth}x${bufferHeight} scale=$scale center=${centerX}x${centerY} matrix=$matrix " +
|
|
709
|
+
"pts=[${pts[0]},${pts[1]} ${pts[2]},${pts[3]} ${pts[4]},${pts[5]} ${pts[6]},${pts[7]}]"
|
|
664
710
|
)
|
|
665
711
|
Log.d(TAG, "[TRANSFORM] Matrix applied successfully")
|
|
666
712
|
}
|
|
@@ -696,9 +742,7 @@ class CameraController(
|
|
|
696
742
|
fun aspectDiff(size: Size): Double {
|
|
697
743
|
val w = size.width.toDouble()
|
|
698
744
|
val h = size.height.toDouble()
|
|
699
|
-
|
|
700
|
-
val inverted = abs(h / w - targetAspect)
|
|
701
|
-
return min(direct, inverted)
|
|
745
|
+
return abs(w / h - targetAspect)
|
|
702
746
|
}
|
|
703
747
|
|
|
704
748
|
if (preferClosestAspect) {
|
package/android/src/camera2/kotlin/com/reactnativerectangledocscanner/DocumentScannerView.kt
CHANGED
|
@@ -470,6 +470,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
470
470
|
imageHeight: Int
|
|
471
471
|
) {
|
|
472
472
|
val density = resources.displayMetrics.density.takeIf { it > 0f } ?: 1f
|
|
473
|
+
val previewViewport = cameraController?.getPreviewViewport()
|
|
473
474
|
val event = Arguments.createMap().apply {
|
|
474
475
|
putInt("stableCounter", stableCounter)
|
|
475
476
|
putInt("lastDetectionType", quality.ordinal)
|
|
@@ -480,6 +481,14 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
480
481
|
putMap("bottomLeft", mapPointToDp(getMap("bottomLeft"), density))
|
|
481
482
|
putMap("bottomRight", mapPointToDp(getMap("bottomRight"), density))
|
|
482
483
|
})
|
|
484
|
+
previewViewport?.let {
|
|
485
|
+
putMap("previewViewport", Arguments.createMap().apply {
|
|
486
|
+
putDouble("left", it.left / density)
|
|
487
|
+
putDouble("top", it.top / density)
|
|
488
|
+
putDouble("width", it.width() / density)
|
|
489
|
+
putDouble("height", it.height() / density)
|
|
490
|
+
})
|
|
491
|
+
}
|
|
483
492
|
putMap("previewSize", Arguments.createMap().apply {
|
|
484
493
|
putInt("width", (width / density).toInt())
|
|
485
494
|
putInt("height", (height / density).toInt())
|
package/dist/DocScanner.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ type PictureEvent = {
|
|
|
12
12
|
export type RectangleDetectEvent = Omit<RectangleEventPayload, 'rectangleCoordinates' | 'rectangleOnScreen'> & {
|
|
13
13
|
rectangleCoordinates?: Rectangle | null;
|
|
14
14
|
rectangleOnScreen?: Rectangle | null;
|
|
15
|
+
previewViewport?: {
|
|
16
|
+
left: number;
|
|
17
|
+
top: number;
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
};
|
|
15
21
|
};
|
|
16
22
|
export type DocScannerCapture = {
|
|
17
23
|
path: string;
|
package/dist/DocScanner.js
CHANGED
|
@@ -418,7 +418,7 @@ const VisionCameraScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor =
|
|
|
418
418
|
const overlayIsActive = autoCapture ? isAutoCapturing : (detectedRectangle?.stableCounter ?? 0) > 0;
|
|
419
419
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container, onLayout: handleLayout },
|
|
420
420
|
CameraComponent && device && hasPermission ? (react_1.default.createElement(CameraComponent, { ref: cameraRef, style: styles.scanner, device: device, isActive: true, photo: true, torch: enableTorch ? 'on' : 'off', frameProcessor: frameProcessor, frameProcessorFps: 10 })) : (react_1.default.createElement(react_native_1.View, { style: styles.scanner })),
|
|
421
|
-
showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon })),
|
|
421
|
+
showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: detectedRectangle?.previewViewport ?? null })),
|
|
422
422
|
showManualCaptureButton && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: () => captureVision('manual') })),
|
|
423
423
|
children));
|
|
424
424
|
});
|
|
@@ -705,7 +705,7 @@ const NativeScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAU
|
|
|
705
705
|
const detectionThreshold = autoCapture ? minStableFrames : 99999;
|
|
706
706
|
return (react_1.default.createElement(react_native_1.View, { style: styles.container },
|
|
707
707
|
react_1.default.createElement(react_native_document_scanner_1.default, { ref: scannerRef, style: styles.scanner, detectionCountBeforeCapture: detectionThreshold, overlayColor: overlayColor, enableTorch: enableTorch, quality: normalizedQuality, useBase64: useBase64, manualOnly: false, detectionConfig: detectionConfig, onPictureTaken: handlePictureTaken, onError: handleError, onRectangleDetect: handleRectangleDetect }),
|
|
708
|
-
showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon })),
|
|
708
|
+
showGrid && overlayPolygon && (react_1.default.createElement(overlay_1.ScannerOverlay, { active: overlayIsActive, color: gridColor ?? overlayColor, lineWidth: gridLineWidth, polygon: overlayPolygon, clipRect: detectedRectangle?.previewViewport ?? null })),
|
|
709
709
|
showManualCaptureButton && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: handleManualCapture })),
|
|
710
710
|
children));
|
|
711
711
|
});
|
package/dist/utils/overlay.d.ts
CHANGED
|
@@ -5,5 +5,11 @@ export interface ScannerOverlayProps {
|
|
|
5
5
|
color?: string;
|
|
6
6
|
lineWidth?: number;
|
|
7
7
|
polygon?: Rectangle | null;
|
|
8
|
+
clipRect?: {
|
|
9
|
+
left: number;
|
|
10
|
+
top: number;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
} | null;
|
|
8
14
|
}
|
|
9
15
|
export declare const ScannerOverlay: React.FC<ScannerOverlayProps>;
|
package/dist/utils/overlay.js
CHANGED
|
@@ -78,22 +78,45 @@ const getBounds = (polygon) => {
|
|
|
78
78
|
};
|
|
79
79
|
};
|
|
80
80
|
const ScannerOverlay = ({ active: _active, // kept for compatibility; no animation currently
|
|
81
|
-
color = '#0b7ef4', lineWidth = react_native_1.StyleSheet.hairlineWidth, polygon, }) => {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
color = '#0b7ef4', lineWidth = react_native_1.StyleSheet.hairlineWidth, polygon, clipRect, }) => {
|
|
82
|
+
const offset = (0, react_1.useMemo)(() => (clipRect ? { x: -clipRect.left, y: -clipRect.top } : { x: 0, y: 0 }), [clipRect]);
|
|
83
|
+
const shiftedPolygon = (0, react_1.useMemo)(() => {
|
|
84
|
+
if (!polygon)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
topLeft: { x: polygon.topLeft.x + offset.x, y: polygon.topLeft.y + offset.y },
|
|
88
|
+
topRight: { x: polygon.topRight.x + offset.x, y: polygon.topRight.y + offset.y },
|
|
89
|
+
bottomRight: { x: polygon.bottomRight.x + offset.x, y: polygon.bottomRight.y + offset.y },
|
|
90
|
+
bottomLeft: { x: polygon.bottomLeft.x + offset.x, y: polygon.bottomLeft.y + offset.y },
|
|
91
|
+
};
|
|
92
|
+
}, [polygon, offset]);
|
|
93
|
+
const points = (0, react_1.useMemo)(() => (shiftedPolygon ? createPointsString(shiftedPolygon) : null), [shiftedPolygon]);
|
|
94
|
+
const gridLines = (0, react_1.useMemo)(() => (shiftedPolygon ? createGridLines(shiftedPolygon) : []), [shiftedPolygon]);
|
|
95
|
+
const bounds = (0, react_1.useMemo)(() => (shiftedPolygon ? getBounds(shiftedPolygon) : null), [shiftedPolygon]);
|
|
96
|
+
if (!shiftedPolygon || !points || !bounds) {
|
|
86
97
|
return null;
|
|
87
98
|
}
|
|
99
|
+
const containerStyle = clipRect
|
|
100
|
+
? [
|
|
101
|
+
react_native_1.StyleSheet.absoluteFill,
|
|
102
|
+
{
|
|
103
|
+
left: clipRect.left,
|
|
104
|
+
top: clipRect.top,
|
|
105
|
+
width: clipRect.width,
|
|
106
|
+
height: clipRect.height,
|
|
107
|
+
overflow: 'hidden',
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
: react_native_1.StyleSheet.absoluteFill;
|
|
88
111
|
if (SvgModule) {
|
|
89
112
|
const { default: Svg, Polygon, Line } = SvgModule;
|
|
90
|
-
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style:
|
|
113
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: containerStyle },
|
|
91
114
|
react_1.default.createElement(Svg, { style: react_native_1.StyleSheet.absoluteFill },
|
|
92
115
|
react_1.default.createElement(Polygon, { points: points, fill: color, opacity: 0.15 }),
|
|
93
116
|
gridLines.map((line, index) => (react_1.default.createElement(Line, { key: `grid-${index}`, x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, stroke: color, strokeWidth: lineWidth, opacity: 0.5 }))),
|
|
94
117
|
react_1.default.createElement(Polygon, { points: points, stroke: color, strokeWidth: lineWidth, fill: "none" }))));
|
|
95
118
|
}
|
|
96
|
-
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style:
|
|
119
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: containerStyle },
|
|
97
120
|
react_1.default.createElement(react_native_1.View, { style: [
|
|
98
121
|
styles.fallbackBox,
|
|
99
122
|
{
|
package/package.json
CHANGED
package/src/DocScanner.tsx
CHANGED
|
@@ -35,6 +35,7 @@ type PictureEvent = {
|
|
|
35
35
|
export type RectangleDetectEvent = Omit<RectangleEventPayload, 'rectangleCoordinates' | 'rectangleOnScreen'> & {
|
|
36
36
|
rectangleCoordinates?: Rectangle | null;
|
|
37
37
|
rectangleOnScreen?: Rectangle | null;
|
|
38
|
+
previewViewport?: { left: number; top: number; width: number; height: number };
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
export type DocScannerCapture = {
|
|
@@ -600,6 +601,7 @@ const VisionCameraScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
600
601
|
color={gridColor ?? overlayColor}
|
|
601
602
|
lineWidth={gridLineWidth}
|
|
602
603
|
polygon={overlayPolygon}
|
|
604
|
+
clipRect={detectedRectangle?.previewViewport ?? null}
|
|
603
605
|
/>
|
|
604
606
|
)}
|
|
605
607
|
{showManualCaptureButton && (
|
|
@@ -1000,6 +1002,7 @@ const NativeScanner = forwardRef<DocScannerHandle, Props>(
|
|
|
1000
1002
|
color={gridColor ?? overlayColor}
|
|
1001
1003
|
lineWidth={gridLineWidth}
|
|
1002
1004
|
polygon={overlayPolygon}
|
|
1005
|
+
clipRect={detectedRectangle?.previewViewport ?? null}
|
|
1003
1006
|
/>
|
|
1004
1007
|
)}
|
|
1005
1008
|
{showManualCaptureButton && (
|
package/src/external.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ declare module 'react-native-document-scanner' {
|
|
|
46
46
|
lastDetectionType: number;
|
|
47
47
|
rectangleCoordinates?: Rectangle | null;
|
|
48
48
|
rectangleOnScreen?: Rectangle | null;
|
|
49
|
+
previewViewport?: { left: number; top: number; width: number; height: number };
|
|
49
50
|
previewSize?: { width: number; height: number };
|
|
50
51
|
imageSize?: { width: number; height: number };
|
|
51
52
|
};
|
package/src/utils/overlay.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
4
|
import type { Rectangle } from '../types';
|
|
4
5
|
|
|
5
6
|
let SvgModule: typeof import('react-native-svg') | null = null;
|
|
@@ -78,6 +79,7 @@ export interface ScannerOverlayProps {
|
|
|
78
79
|
color?: string;
|
|
79
80
|
lineWidth?: number;
|
|
80
81
|
polygon?: Rectangle | null;
|
|
82
|
+
clipRect?: { left: number; top: number; width: number; height: number } | null;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
|
|
@@ -85,20 +87,47 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
|
|
|
85
87
|
color = '#0b7ef4',
|
|
86
88
|
lineWidth = StyleSheet.hairlineWidth,
|
|
87
89
|
polygon,
|
|
90
|
+
clipRect,
|
|
88
91
|
}) => {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
const offset = useMemo(
|
|
93
|
+
() => (clipRect ? { x: -clipRect.left, y: -clipRect.top } : { x: 0, y: 0 }),
|
|
94
|
+
[clipRect],
|
|
95
|
+
);
|
|
96
|
+
const shiftedPolygon = useMemo(() => {
|
|
97
|
+
if (!polygon) return null;
|
|
98
|
+
return {
|
|
99
|
+
topLeft: { x: polygon.topLeft.x + offset.x, y: polygon.topLeft.y + offset.y },
|
|
100
|
+
topRight: { x: polygon.topRight.x + offset.x, y: polygon.topRight.y + offset.y },
|
|
101
|
+
bottomRight: { x: polygon.bottomRight.x + offset.x, y: polygon.bottomRight.y + offset.y },
|
|
102
|
+
bottomLeft: { x: polygon.bottomLeft.x + offset.x, y: polygon.bottomLeft.y + offset.y },
|
|
103
|
+
};
|
|
104
|
+
}, [polygon, offset]);
|
|
105
|
+
const points = useMemo(() => (shiftedPolygon ? createPointsString(shiftedPolygon) : null), [shiftedPolygon]);
|
|
106
|
+
const gridLines = useMemo(() => (shiftedPolygon ? createGridLines(shiftedPolygon) : []), [shiftedPolygon]);
|
|
107
|
+
const bounds = useMemo(() => (shiftedPolygon ? getBounds(shiftedPolygon) : null), [shiftedPolygon]);
|
|
108
|
+
|
|
109
|
+
if (!shiftedPolygon || !points || !bounds) {
|
|
94
110
|
return null;
|
|
95
111
|
}
|
|
96
112
|
|
|
113
|
+
const containerStyle: StyleProp<ViewStyle> = clipRect
|
|
114
|
+
? [
|
|
115
|
+
StyleSheet.absoluteFill as ViewStyle,
|
|
116
|
+
{
|
|
117
|
+
left: clipRect.left,
|
|
118
|
+
top: clipRect.top,
|
|
119
|
+
width: clipRect.width,
|
|
120
|
+
height: clipRect.height,
|
|
121
|
+
overflow: 'hidden',
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
: (StyleSheet.absoluteFill as ViewStyle);
|
|
125
|
+
|
|
97
126
|
if (SvgModule) {
|
|
98
127
|
const { default: Svg, Polygon, Line } = SvgModule;
|
|
99
128
|
|
|
100
129
|
return (
|
|
101
|
-
<View pointerEvents="none" style={
|
|
130
|
+
<View pointerEvents="none" style={containerStyle}>
|
|
102
131
|
<Svg style={StyleSheet.absoluteFill}>
|
|
103
132
|
<Polygon points={points} fill={color} opacity={0.15} />
|
|
104
133
|
{gridLines.map((line, index) => (
|
|
@@ -120,7 +149,7 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
|
|
|
120
149
|
}
|
|
121
150
|
|
|
122
151
|
return (
|
|
123
|
-
<View pointerEvents="none" style={
|
|
152
|
+
<View pointerEvents="none" style={containerStyle}>
|
|
124
153
|
<View
|
|
125
154
|
style={[
|
|
126
155
|
styles.fallbackBox,
|