react-native-rectangle-doc-scanner 3.211.0 → 3.213.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.
@@ -3,6 +3,11 @@ package com.reactnativerectangledocscanner
3
3
  import android.Manifest
4
4
  import android.content.Context
5
5
  import android.content.pm.PackageManager
6
+ import android.graphics.Bitmap
7
+ import android.graphics.BitmapFactory
8
+ import android.graphics.Matrix
9
+ import android.graphics.Rect
10
+ import android.graphics.YuvImage
6
11
  import android.util.Log
7
12
  import android.util.Size
8
13
  import android.view.Surface
@@ -10,17 +15,18 @@ import androidx.camera.core.AspectRatio
10
15
  import androidx.camera.core.Camera
11
16
  import androidx.camera.core.CameraSelector
12
17
  import androidx.camera.core.ImageAnalysis
13
- import androidx.camera.core.ImageCapture
14
- import androidx.camera.core.ImageCaptureException
15
18
  import androidx.camera.core.Preview
16
19
  import androidx.camera.lifecycle.ProcessCameraProvider
17
20
  import androidx.camera.view.PreviewView
18
21
  import androidx.core.content.ContextCompat
19
22
  import androidx.lifecycle.LifecycleOwner
20
23
  import com.google.common.util.concurrent.ListenableFuture
24
+ import java.io.ByteArrayOutputStream
21
25
  import java.io.File
26
+ import java.io.FileOutputStream
22
27
  import java.util.concurrent.ExecutorService
23
28
  import java.util.concurrent.Executors
29
+ import java.util.concurrent.atomic.AtomicReference
24
30
 
25
31
  class CameraController(
26
32
  private val context: Context,
@@ -31,9 +37,9 @@ class CameraController(
31
37
  private var cameraProvider: ProcessCameraProvider? = null
32
38
  private var preview: Preview? = null
33
39
  private var imageAnalysis: ImageAnalysis? = null
34
- private var imageCapture: ImageCapture? = null
35
40
  private var camera: Camera? = null
36
41
  private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
42
+ private val lastFrame = AtomicReference<LastFrame?>()
37
43
 
38
44
  private var useFrontCamera = false
39
45
  private var detectionEnabled = true
@@ -42,10 +48,16 @@ class CameraController(
42
48
 
43
49
  companion object {
44
50
  private const val TAG = "CameraController"
45
- private const val ANALYSIS_WIDTH = 1280
46
- private const val ANALYSIS_HEIGHT = 720
47
51
  }
48
52
 
53
+ private data class LastFrame(
54
+ val nv21: ByteArray,
55
+ val width: Int,
56
+ val height: Int,
57
+ val rotationDegrees: Int,
58
+ val isFront: Boolean
59
+ )
60
+
49
61
  fun startCamera(
50
62
  useFrontCam: Boolean = false,
51
63
  enableDetection: Boolean = true
@@ -83,30 +95,35 @@ class CameraController(
83
95
  onImageCaptured: (File) -> Unit,
84
96
  onError: (Exception) -> Unit
85
97
  ) {
86
- val capture = imageCapture
87
- if (capture == null) {
88
- onError(IllegalStateException("ImageCapture not initialized"))
98
+ val frame = lastFrame.get()
99
+ if (frame == null) {
100
+ onError(IllegalStateException("No frame available for capture"))
89
101
  return
90
102
  }
91
103
 
92
- val photoFile = File(outputDirectory, "doc_scan_${System.currentTimeMillis()}.jpg")
93
- val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
104
+ cameraExecutor.execute {
105
+ try {
106
+ val photoFile = File(outputDirectory, "doc_scan_${System.currentTimeMillis()}.jpg")
107
+ val jpegBytes = nv21ToJpeg(frame.nv21, frame.width, frame.height, 95)
108
+ val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
109
+ ?: throw IllegalStateException("Failed to decode JPEG")
94
110
 
95
- capture.takePicture(
96
- outputOptions,
97
- cameraExecutor,
98
- object : ImageCapture.OnImageSavedCallback {
99
- override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
100
- Log.d(TAG, "[CAMERAX] Photo capture succeeded: ${photoFile.absolutePath}")
101
- onImageCaptured(photoFile)
111
+ val rotated = rotateAndMirror(bitmap, frame.rotationDegrees, frame.isFront)
112
+ FileOutputStream(photoFile).use { out ->
113
+ rotated.compress(Bitmap.CompressFormat.JPEG, 95, out)
102
114
  }
103
-
104
- override fun onError(exception: ImageCaptureException) {
105
- Log.e(TAG, "[CAMERAX] Photo capture failed", exception)
106
- onError(exception)
115
+ if (rotated != bitmap) {
116
+ rotated.recycle()
107
117
  }
118
+ bitmap.recycle()
119
+
120
+ Log.d(TAG, "[CAMERAX] Photo capture succeeded: ${photoFile.absolutePath}")
121
+ onImageCaptured(photoFile)
122
+ } catch (e: Exception) {
123
+ Log.e(TAG, "[CAMERAX] Photo capture failed", e)
124
+ onError(e)
108
125
  }
109
- )
126
+ }
110
127
  }
111
128
 
112
129
  fun setTorchEnabled(enabled: Boolean) {
@@ -137,6 +154,7 @@ class CameraController(
137
154
 
138
155
  val rotation = previewView.display?.rotation ?: Surface.ROTATION_0
139
156
  preview = Preview.Builder()
157
+ .setTargetAspectRatio(AspectRatio.RATIO_4_3)
140
158
  .setTargetRotation(rotation)
141
159
  .build()
142
160
  .also {
@@ -145,20 +163,14 @@ class CameraController(
145
163
 
146
164
  imageAnalysis = ImageAnalysis.Builder()
147
165
  .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
148
- .setTargetResolution(Size(ANALYSIS_WIDTH, ANALYSIS_HEIGHT))
149
166
  .setTargetRotation(rotation)
167
+ .setTargetAspectRatio(AspectRatio.RATIO_4_3)
150
168
  .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
151
169
  .build()
152
170
  .also {
153
171
  it.setAnalyzer(cameraExecutor, DocumentAnalyzer())
154
172
  }
155
173
 
156
- imageCapture = ImageCapture.Builder()
157
- .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
158
- .setTargetRotation(rotation)
159
- .setTargetAspectRatio(AspectRatio.RATIO_4_3)
160
- .build()
161
-
162
174
  val cameraSelector = if (useFrontCamera) {
163
175
  CameraSelector.DEFAULT_FRONT_CAMERA
164
176
  } else {
@@ -170,8 +182,7 @@ class CameraController(
170
182
  lifecycleOwner,
171
183
  cameraSelector,
172
184
  preview,
173
- imageAnalysis,
174
- imageCapture
185
+ imageAnalysis
175
186
  )
176
187
  Log.d(TAG, "[CAMERAX] Camera bound successfully")
177
188
  } catch (e: Exception) {
@@ -184,6 +195,15 @@ class CameraController(
184
195
  try {
185
196
  val rotationDegrees = imageProxy.imageInfo.rotationDegrees
186
197
  val nv21 = imageProxy.toNv21()
198
+ lastFrame.set(
199
+ LastFrame(
200
+ nv21,
201
+ imageProxy.width,
202
+ imageProxy.height,
203
+ rotationDegrees,
204
+ useFrontCamera
205
+ )
206
+ )
187
207
 
188
208
  val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
189
209
  imageProxy.height
@@ -216,6 +236,27 @@ class CameraController(
216
236
  }
217
237
  }
218
238
 
239
+ private fun nv21ToJpeg(nv21: ByteArray, width: Int, height: Int, quality: Int): ByteArray {
240
+ val yuv = YuvImage(nv21, android.graphics.ImageFormat.NV21, width, height, null)
241
+ val out = ByteArrayOutputStream()
242
+ yuv.compressToJpeg(Rect(0, 0, width, height), quality, out)
243
+ return out.toByteArray()
244
+ }
245
+
246
+ private fun rotateAndMirror(bitmap: Bitmap, rotationDegrees: Int, mirror: Boolean): Bitmap {
247
+ if (rotationDegrees == 0 && !mirror) {
248
+ return bitmap
249
+ }
250
+ val matrix = Matrix()
251
+ if (mirror) {
252
+ matrix.postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f)
253
+ }
254
+ if (rotationDegrees != 0) {
255
+ matrix.postRotate(rotationDegrees.toFloat(), bitmap.width / 2f, bitmap.height / 2f)
256
+ }
257
+ return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
258
+ }
259
+
219
260
  private fun hasCameraPermission(): Boolean {
220
261
  return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
221
262
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.211.0",
3
+ "version": "3.213.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",