react-native-rectangle-doc-scanner 13.0.0 → 13.1.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
- .setTargetResolution(android.util.Size(1920, 1440)) // Higher resolution for better small-edge detection
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 = if (sensorOrientation == 0) {
285
- (displayRotationDegrees + tabletUpsideDownFix) % 360
286
- } else {
287
- sensorOrientation
288
- }
288
+ val effectiveRotation = (displayRotationDegrees + tabletUpsideDownFix) % 360
289
289
 
290
- Log.d(TAG, "[ANALYZE] Sensor: $sensorOrientation°, Display: $displayRotationDegrees°, Effective: $effectiveRotation°")
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, rotationDegrees)
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 ->
@@ -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,35 @@ 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
+ val targetWidth: Int
154
+ val targetHeight: Int
155
+ val aspectHeight = (viewWidth / PREVIEW_ASPECT_RATIO).toInt()
156
+ if (aspectHeight <= viewHeight) {
157
+ targetWidth = viewWidth
158
+ targetHeight = aspectHeight
159
+ } else {
160
+ targetWidth = (viewHeight * PREVIEW_ASPECT_RATIO).toInt()
161
+ targetHeight = viewHeight
162
+ }
163
+
164
+ val left = (viewWidth - targetWidth) / 2
165
+ val top = if (targetHeight < viewHeight) 0 else (viewHeight - targetHeight) / 2
166
+ val right = left + targetWidth
167
+ val bottom = top + targetHeight
168
+
169
+ previewView.layout(left, top, right, bottom)
170
+ overlayView.layout(left, top, right, bottom)
171
+ }
172
+
145
173
  private fun initializeCameraWhenReady() {
146
174
  // If view is already laid out, start camera immediately
147
175
  if (width > 0 && height > 0) {
@@ -213,17 +241,26 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
213
241
  lastDetectedImageHeight = imageHeight
214
242
  }
215
243
 
216
- val rectangleOnScreen = if (rectangle != null && width > 0 && height > 0) {
244
+ val previewWidth = previewView.width
245
+ val previewHeight = previewView.height
246
+
247
+ val rectangleOnScreen = if (rectangle != null && previewWidth > 0 && previewHeight > 0) {
217
248
  cameraController?.mapRectangleToView(rectangle, imageWidth, imageHeight)
218
- ?: DocumentDetector.transformRectangleToViewCoordinates(rectangle, imageWidth, imageHeight, width, height)
249
+ ?: DocumentDetector.transformRectangleToViewCoordinates(
250
+ rectangle,
251
+ imageWidth,
252
+ imageHeight,
253
+ previewWidth,
254
+ previewHeight
255
+ )
219
256
  } else {
220
257
  null
221
258
  }
222
- val smoothedRectangleOnScreen = smoothRectangle(rectangleOnScreen, width, height)
259
+ val smoothedRectangleOnScreen = smoothRectangle(rectangleOnScreen, previewWidth, previewHeight)
223
260
  lastRectangleOnScreen = smoothedRectangleOnScreen
224
261
  val quality = when {
225
- smoothedRectangleOnScreen != null && width > 0 && height > 0 ->
226
- DocumentDetector.evaluateRectangleQualityInView(smoothedRectangleOnScreen, width, height)
262
+ smoothedRectangleOnScreen != null && previewWidth > 0 && previewHeight > 0 ->
263
+ DocumentDetector.evaluateRectangleQualityInView(smoothedRectangleOnScreen, previewWidth, previewHeight)
227
264
  rectangle != null -> DocumentDetector.evaluateRectangleQuality(rectangle, imageWidth, imageHeight)
228
265
  else -> RectangleQuality.TOO_FAR
229
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "13.0.0",
3
+ "version": "13.1.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",