react-native-rectangle-doc-scanner 13.0.0 → 13.2.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.
|
@@ -123,6 +123,7 @@ class CameraController(
|
|
|
123
123
|
Log.d(TAG, "[CAMERAX] Setting target rotation to ROTATION_0 (portrait-only app)")
|
|
124
124
|
|
|
125
125
|
preview = Preview.Builder()
|
|
126
|
+
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
126
127
|
.setTargetRotation(targetRotation) // Force portrait
|
|
127
128
|
.build()
|
|
128
129
|
.also { previewUseCase ->
|
|
@@ -185,7 +186,8 @@ class CameraController(
|
|
|
185
186
|
// ImageAnalysis UseCase for document detection
|
|
186
187
|
imageAnalyzer = ImageAnalysis.Builder()
|
|
187
188
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
|
188
|
-
|
|
189
|
+
// Match preview aspect ratio to avoid square analysis frames on some devices.
|
|
190
|
+
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
189
191
|
.setTargetRotation(targetRotation) // Match preview rotation
|
|
190
192
|
.build()
|
|
191
193
|
.also {
|
|
@@ -201,6 +203,7 @@ class CameraController(
|
|
|
201
203
|
// ImageCapture UseCase
|
|
202
204
|
imageCapture = ImageCapture.Builder()
|
|
203
205
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
|
|
206
|
+
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
204
207
|
.setTargetRotation(targetRotation) // Match preview rotation
|
|
205
208
|
.build()
|
|
206
209
|
|
|
@@ -265,10 +268,11 @@ class CameraController(
|
|
|
265
268
|
return
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
269
271
|
val imageWidth = imageProxy.width
|
|
270
272
|
val imageHeight = imageProxy.height
|
|
271
273
|
|
|
274
|
+
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
275
|
+
|
|
272
276
|
// Calculate rotation using the same logic as TextureView transform
|
|
273
277
|
val sensorOrientation = getCameraSensorOrientation()
|
|
274
278
|
val displayRotationDegrees = when (textureView.display?.rotation ?: Surface.ROTATION_0) {
|
|
@@ -281,16 +285,16 @@ class CameraController(
|
|
|
281
285
|
|
|
282
286
|
// Use the same rotation logic as updateTextureViewTransform
|
|
283
287
|
val tabletUpsideDownFix = if (sensorOrientation == 0 && displayRotationDegrees == 90) 180 else 0
|
|
284
|
-
val effectiveRotation =
|
|
285
|
-
(displayRotationDegrees + tabletUpsideDownFix) % 360
|
|
286
|
-
} else {
|
|
287
|
-
sensorOrientation
|
|
288
|
-
}
|
|
288
|
+
val effectiveRotation = (displayRotationDegrees + tabletUpsideDownFix) % 360
|
|
289
289
|
|
|
290
|
-
Log.d(
|
|
290
|
+
Log.d(
|
|
291
|
+
TAG,
|
|
292
|
+
"[ANALYZE] Sensor: $sensorOrientation°, Display: $displayRotationDegrees°, " +
|
|
293
|
+
"ImageProxy: $rotationDegrees°, Effective: $effectiveRotation°"
|
|
294
|
+
)
|
|
291
295
|
|
|
292
|
-
// Try ML Kit first
|
|
293
|
-
val inputImage = InputImage.fromMediaImage(mediaImage,
|
|
296
|
+
// Try ML Kit first (use the same rotation as preview/OpenCV)
|
|
297
|
+
val inputImage = InputImage.fromMediaImage(mediaImage, effectiveRotation)
|
|
294
298
|
|
|
295
299
|
objectDetector.process(inputImage)
|
|
296
300
|
.addOnSuccessListener { objects ->
|
|
@@ -490,10 +494,10 @@ class CameraController(
|
|
|
490
494
|
val finalWidth = imageWidth
|
|
491
495
|
val finalHeight = imageHeight
|
|
492
496
|
|
|
493
|
-
// Apply
|
|
497
|
+
// Apply center-crop scaling to match TextureView display.
|
|
494
498
|
val scaleX = viewWidth / finalWidth.toFloat()
|
|
495
499
|
val scaleY = viewHeight / finalHeight.toFloat()
|
|
496
|
-
val scale = scaleX.
|
|
500
|
+
val scale = scaleX.coerceAtLeast(scaleY)
|
|
497
501
|
|
|
498
502
|
val scaledWidth = finalWidth * scale
|
|
499
503
|
val scaledHeight = finalHeight * scale
|
|
@@ -579,26 +583,17 @@ class CameraController(
|
|
|
579
583
|
bufferHeight
|
|
580
584
|
}
|
|
581
585
|
|
|
582
|
-
// Scale to
|
|
586
|
+
// Scale to fill the view while maintaining aspect ratio (center-crop)
|
|
583
587
|
val scaleX = viewWidth.toFloat() / rotatedBufferWidth.toFloat()
|
|
584
588
|
val scaleY = viewHeight.toFloat() / rotatedBufferHeight.toFloat()
|
|
585
|
-
val scale = scaleX.
|
|
589
|
+
val scale = scaleX.coerceAtLeast(scaleY) // Use max to fill
|
|
586
590
|
|
|
587
591
|
Log.d(TAG, "[TRANSFORM] Rotated buffer: ${rotatedBufferWidth}x${rotatedBufferHeight}, ScaleX: $scaleX, ScaleY: $scaleY, Using: $scale")
|
|
588
592
|
|
|
589
593
|
matrix.postScale(scale, scale, centerX, centerY)
|
|
590
594
|
|
|
591
|
-
//
|
|
592
|
-
|
|
593
|
-
val scaledHeight = rotatedBufferHeight * scale
|
|
594
|
-
val offsetX = (viewWidth - scaledWidth) / 2f
|
|
595
|
-
val offsetY = (viewHeight - scaledHeight) / 2f
|
|
596
|
-
previewViewport = android.graphics.RectF(
|
|
597
|
-
offsetX,
|
|
598
|
-
offsetY,
|
|
599
|
-
offsetX + scaledWidth,
|
|
600
|
-
offsetY + scaledHeight
|
|
601
|
-
)
|
|
595
|
+
// With center-crop, the preview fully covers the view bounds.
|
|
596
|
+
previewViewport = android.graphics.RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
|
|
602
597
|
|
|
603
598
|
textureView.setTransform(matrix)
|
|
604
599
|
Log.d(TAG, "[TRANSFORM] Transform applied successfully")
|
package/android/src/camera2/kotlin/com/reactnativerectangledocscanner/DocumentScannerView.kt
CHANGED
|
@@ -67,6 +67,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
67
67
|
|
|
68
68
|
companion object {
|
|
69
69
|
private const val TAG = "DocumentScannerView"
|
|
70
|
+
private const val PREVIEW_ASPECT_RATIO = 3f / 4f // width:height (matches 3:4)
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
override val lifecycle: Lifecycle
|
|
@@ -111,6 +112,9 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
111
112
|
addView(overlayView, 1) // Add at index 1 (front)
|
|
112
113
|
Log.d(TAG, "[INIT] OverlayView added, childCount: $childCount")
|
|
113
114
|
|
|
115
|
+
// Match camera UI look with letterboxing when preview doesn't fill the view.
|
|
116
|
+
setBackgroundColor(android.graphics.Color.BLACK)
|
|
117
|
+
|
|
114
118
|
Log.d(TAG, "╔════════════════════════════════════════╗")
|
|
115
119
|
Log.d(TAG, "║ DocumentScannerView INIT COMPLETE ║")
|
|
116
120
|
Log.d(TAG, "╚════════════════════════════════════════╝")
|
|
@@ -137,11 +141,29 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
137
141
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
138
142
|
super.onLayout(changed, left, top, right, bottom)
|
|
139
143
|
if (changed) {
|
|
144
|
+
layoutPreviewAndOverlay(right - left, bottom - top)
|
|
140
145
|
Log.d(TAG, "[LAYOUT] View size: ${right - left}x${bottom - top}, PreviewView: ${previewView.width}x${previewView.height}")
|
|
141
146
|
cameraController?.refreshTransform()
|
|
142
147
|
}
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
private fun layoutPreviewAndOverlay(viewWidth: Int, viewHeight: Int) {
|
|
151
|
+
if (viewWidth <= 0 || viewHeight <= 0) return
|
|
152
|
+
|
|
153
|
+
// Always fill width to avoid left/right letterboxing.
|
|
154
|
+
val targetWidth = viewWidth
|
|
155
|
+
val targetHeight = (viewWidth / PREVIEW_ASPECT_RATIO).toInt()
|
|
156
|
+
|
|
157
|
+
val left = 0
|
|
158
|
+
// Center vertically; allows top/bottom crop when targetHeight > viewHeight.
|
|
159
|
+
val top = (viewHeight - targetHeight) / 2
|
|
160
|
+
val right = left + targetWidth
|
|
161
|
+
val bottom = top + targetHeight
|
|
162
|
+
|
|
163
|
+
previewView.layout(left, top, right, bottom)
|
|
164
|
+
overlayView.layout(left, top, right, bottom)
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
private fun initializeCameraWhenReady() {
|
|
146
168
|
// If view is already laid out, start camera immediately
|
|
147
169
|
if (width > 0 && height > 0) {
|
|
@@ -213,17 +235,26 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
|
|
|
213
235
|
lastDetectedImageHeight = imageHeight
|
|
214
236
|
}
|
|
215
237
|
|
|
216
|
-
val
|
|
238
|
+
val previewWidth = previewView.width
|
|
239
|
+
val previewHeight = previewView.height
|
|
240
|
+
|
|
241
|
+
val rectangleOnScreen = if (rectangle != null && previewWidth > 0 && previewHeight > 0) {
|
|
217
242
|
cameraController?.mapRectangleToView(rectangle, imageWidth, imageHeight)
|
|
218
|
-
?: DocumentDetector.transformRectangleToViewCoordinates(
|
|
243
|
+
?: DocumentDetector.transformRectangleToViewCoordinates(
|
|
244
|
+
rectangle,
|
|
245
|
+
imageWidth,
|
|
246
|
+
imageHeight,
|
|
247
|
+
previewWidth,
|
|
248
|
+
previewHeight
|
|
249
|
+
)
|
|
219
250
|
} else {
|
|
220
251
|
null
|
|
221
252
|
}
|
|
222
|
-
val smoothedRectangleOnScreen = smoothRectangle(rectangleOnScreen,
|
|
253
|
+
val smoothedRectangleOnScreen = smoothRectangle(rectangleOnScreen, previewWidth, previewHeight)
|
|
223
254
|
lastRectangleOnScreen = smoothedRectangleOnScreen
|
|
224
255
|
val quality = when {
|
|
225
|
-
smoothedRectangleOnScreen != null &&
|
|
226
|
-
DocumentDetector.evaluateRectangleQualityInView(smoothedRectangleOnScreen,
|
|
256
|
+
smoothedRectangleOnScreen != null && previewWidth > 0 && previewHeight > 0 ->
|
|
257
|
+
DocumentDetector.evaluateRectangleQualityInView(smoothedRectangleOnScreen, previewWidth, previewHeight)
|
|
227
258
|
rectangle != null -> DocumentDetector.evaluateRectangleQuality(rectangle, imageWidth, imageHeight)
|
|
228
259
|
else -> RectangleQuality.TOO_FAR
|
|
229
260
|
}
|