react-native-rectangle-doc-scanner 7.8.0 → 7.10.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.
@@ -24,12 +24,14 @@ import android.util.Size
24
24
  import android.view.Surface
25
25
  import android.view.TextureView
26
26
  import androidx.core.content.ContextCompat
27
+ import androidx.exifinterface.media.ExifInterface
27
28
  import com.google.mlkit.vision.common.InputImage
28
29
  import com.google.mlkit.vision.objects.ObjectDetection
29
30
  import com.google.mlkit.vision.objects.defaults.ObjectDetectorOptions
30
31
  import org.opencv.core.Point
31
32
  import java.io.File
32
33
  import java.io.FileOutputStream
34
+ import java.io.ByteArrayInputStream
33
35
  import java.util.concurrent.atomic.AtomicReference
34
36
  import java.util.concurrent.atomic.AtomicBoolean
35
37
  import kotlin.math.abs
@@ -118,10 +120,10 @@ class CameraController(
118
120
  return
119
121
  }
120
122
 
123
+ // Always set the listener so we get size-change callbacks for transform updates.
124
+ previewView.surfaceTextureListener = textureListener
121
125
  if (previewView.isAvailable) {
122
126
  openCamera()
123
- } else {
124
- previewView.surfaceTextureListener = textureListener
125
127
  }
126
128
  }
127
129
 
@@ -131,6 +133,10 @@ class CameraController(
131
133
  closeSession()
132
134
  }
133
135
 
136
+ fun refreshTransform() {
137
+ configureTransform()
138
+ }
139
+
134
140
  fun capturePhoto(
135
141
  outputDirectory: File,
136
142
  onImageCaptured: (File) -> Unit,
@@ -150,8 +156,8 @@ class CameraController(
150
156
  }
151
157
 
152
158
  try {
153
- // Use 90 degrees for back camera in portrait mode
154
- val jpegOrientation = 90
159
+ // Match JPEG orientation to current device rotation and sensor orientation.
160
+ val jpegOrientation = computeRotationDegrees()
155
161
  Log.d(TAG, "[CAPTURE] Setting JPEG_ORIENTATION to $jpegOrientation")
156
162
 
157
163
  val requestBuilder = device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
@@ -240,9 +246,10 @@ class CameraController(
240
246
  val previewSizes = streamConfigMap.getOutputSizes(SurfaceTexture::class.java)
241
247
  Log.d(TAG, "[CAMERA2] Available preview sizes: ${previewSizes?.take(10)?.joinToString { "${it.width}x${it.height}" }}")
242
248
 
243
- // Use the largest available preview size to fill the screen (like iOS uses full image extent)
244
- previewSize = previewSizes?.maxByOrNull { it.width * it.height }
245
- Log.d(TAG, "[CAMERA2] Selected LARGEST preview size: ${previewSize?.width}x${previewSize?.height}")
249
+ // Prefer a preview size that matches the view aspect to avoid letterboxing.
250
+ previewSize = chooseBestSize(previewSizes, viewAspect, null, preferClosestAspect = true)
251
+ ?: previewSizes?.maxByOrNull { it.width * it.height }
252
+ Log.d(TAG, "[CAMERA2] Selected preview size: ${previewSize?.width}x${previewSize?.height}")
246
253
 
247
254
  val previewAspect = previewSize?.let { it.width.toDouble() / it.height.toDouble() } ?: viewAspect
248
255
  val analysisSizes = streamConfigMap.getOutputSizes(ImageFormat.YUV_420_888)
@@ -474,10 +481,11 @@ class CameraController(
474
481
  val buffer = image.planes[0].buffer
475
482
  val bytes = ByteArray(buffer.remaining())
476
483
  buffer.get(bytes)
484
+ val exifRotation = readExifRotation(bytes)
477
485
  val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
478
486
  ?: throw IllegalStateException("Failed to decode JPEG")
479
487
 
480
- val rotated = rotateAndMirror(bitmap, computeRotationDegrees(), useFrontCamera)
488
+ val rotated = rotateAndMirror(bitmap, exifRotation, useFrontCamera)
481
489
  val photoFile = File(pending.outputDirectory, "doc_scan_${System.currentTimeMillis()}.jpg")
482
490
  FileOutputStream(photoFile).use { out ->
483
491
  rotated.compress(Bitmap.CompressFormat.JPEG, 95, out)
@@ -648,22 +656,37 @@ class CameraController(
648
656
  private fun rotateAndMirror(bitmap: Bitmap, rotationDegrees: Int, mirror: Boolean): Bitmap {
649
657
  Log.d(TAG, "[ROTATE_MIRROR] rotationDegrees=$rotationDegrees mirror=$mirror bitmap=${bitmap.width}x${bitmap.height}")
650
658
 
651
- // JPEG_ORIENTATION is already set to 90, so the image should already be rotated correctly
652
- // We only need to apply mirror for front camera
653
- if (!mirror) {
654
- // Back camera: no additional processing needed since JPEG_ORIENTATION handles rotation
655
- Log.d(TAG, "[ROTATE_MIRROR] Back camera: returning bitmap as-is (JPEG_ORIENTATION=90 already applied)")
659
+ if (rotationDegrees == 0 && !mirror) {
660
+ Log.d(TAG, "[ROTATE_MIRROR] No rotation/mirror needed, returning bitmap as-is")
656
661
  return bitmap
657
662
  }
658
663
 
659
- // Front camera: apply horizontal mirror
660
664
  val matrix = Matrix()
661
- matrix.postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f)
662
- Log.d(TAG, "[ROTATE_MIRROR] Front camera: applied horizontal mirror")
665
+ if (rotationDegrees != 0) {
666
+ matrix.postRotate(rotationDegrees.toFloat(), bitmap.width / 2f, bitmap.height / 2f)
667
+ }
668
+ if (mirror) {
669
+ matrix.postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f)
670
+ }
663
671
 
664
672
  return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
665
673
  }
666
674
 
675
+ private fun readExifRotation(bytes: ByteArray): Int {
676
+ return try {
677
+ val exif = ExifInterface(ByteArrayInputStream(bytes))
678
+ when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) {
679
+ ExifInterface.ORIENTATION_ROTATE_90 -> 90
680
+ ExifInterface.ORIENTATION_ROTATE_180 -> 180
681
+ ExifInterface.ORIENTATION_ROTATE_270 -> 270
682
+ else -> 0
683
+ }
684
+ } catch (e: Exception) {
685
+ Log.w(TAG, "[CAMERA2] Failed to read EXIF rotation", e)
686
+ 0
687
+ }
688
+ }
689
+
667
690
  private fun refineWithOpenCv(
668
691
  nv21: ByteArray,
669
692
  imageWidth: Int,
@@ -135,6 +135,7 @@ class DocumentScannerView(context: ThemedReactContext) : FrameLayout(context), L
135
135
  super.onLayout(changed, left, top, right, bottom)
136
136
  if (changed) {
137
137
  Log.d(TAG, "[LAYOUT] View size: ${right - left}x${bottom - top}, PreviewView: ${previewView.width}x${previewView.height}")
138
+ cameraController?.refreshTransform()
138
139
  }
139
140
  }
140
141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "7.8.0",
3
+ "version": "7.10.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",