react-native-rectangle-doc-scanner 3.211.0 → 3.212.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
|
|
@@ -46,6 +52,14 @@ class CameraController(
|
|
|
46
52
|
private const val ANALYSIS_HEIGHT = 720
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
private data class LastFrame(
|
|
56
|
+
val nv21: ByteArray,
|
|
57
|
+
val width: Int,
|
|
58
|
+
val height: Int,
|
|
59
|
+
val rotationDegrees: Int,
|
|
60
|
+
val isFront: Boolean
|
|
61
|
+
)
|
|
62
|
+
|
|
49
63
|
fun startCamera(
|
|
50
64
|
useFrontCam: Boolean = false,
|
|
51
65
|
enableDetection: Boolean = true
|
|
@@ -83,30 +97,35 @@ class CameraController(
|
|
|
83
97
|
onImageCaptured: (File) -> Unit,
|
|
84
98
|
onError: (Exception) -> Unit
|
|
85
99
|
) {
|
|
86
|
-
val
|
|
87
|
-
if (
|
|
88
|
-
onError(IllegalStateException("
|
|
100
|
+
val frame = lastFrame.get()
|
|
101
|
+
if (frame == null) {
|
|
102
|
+
onError(IllegalStateException("No frame available for capture"))
|
|
89
103
|
return
|
|
90
104
|
}
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
cameraExecutor.execute {
|
|
107
|
+
try {
|
|
108
|
+
val photoFile = File(outputDirectory, "doc_scan_${System.currentTimeMillis()}.jpg")
|
|
109
|
+
val jpegBytes = nv21ToJpeg(frame.nv21, frame.width, frame.height, 95)
|
|
110
|
+
val bitmap = BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
|
111
|
+
?: throw IllegalStateException("Failed to decode JPEG")
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
object : ImageCapture.OnImageSavedCallback {
|
|
99
|
-
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
|
100
|
-
Log.d(TAG, "[CAMERAX] Photo capture succeeded: ${photoFile.absolutePath}")
|
|
101
|
-
onImageCaptured(photoFile)
|
|
113
|
+
val rotated = rotateAndMirror(bitmap, frame.rotationDegrees, frame.isFront)
|
|
114
|
+
FileOutputStream(photoFile).use { out ->
|
|
115
|
+
rotated.compress(Bitmap.CompressFormat.JPEG, 95, out)
|
|
102
116
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Log.e(TAG, "[CAMERAX] Photo capture failed", exception)
|
|
106
|
-
onError(exception)
|
|
117
|
+
if (rotated != bitmap) {
|
|
118
|
+
rotated.recycle()
|
|
107
119
|
}
|
|
120
|
+
bitmap.recycle()
|
|
121
|
+
|
|
122
|
+
Log.d(TAG, "[CAMERAX] Photo capture succeeded: ${photoFile.absolutePath}")
|
|
123
|
+
onImageCaptured(photoFile)
|
|
124
|
+
} catch (e: Exception) {
|
|
125
|
+
Log.e(TAG, "[CAMERAX] Photo capture failed", e)
|
|
126
|
+
onError(e)
|
|
108
127
|
}
|
|
109
|
-
|
|
128
|
+
}
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
fun setTorchEnabled(enabled: Boolean) {
|
|
@@ -153,12 +172,6 @@ class CameraController(
|
|
|
153
172
|
it.setAnalyzer(cameraExecutor, DocumentAnalyzer())
|
|
154
173
|
}
|
|
155
174
|
|
|
156
|
-
imageCapture = ImageCapture.Builder()
|
|
157
|
-
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
158
|
-
.setTargetRotation(rotation)
|
|
159
|
-
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
|
|
160
|
-
.build()
|
|
161
|
-
|
|
162
175
|
val cameraSelector = if (useFrontCamera) {
|
|
163
176
|
CameraSelector.DEFAULT_FRONT_CAMERA
|
|
164
177
|
} else {
|
|
@@ -170,8 +183,7 @@ class CameraController(
|
|
|
170
183
|
lifecycleOwner,
|
|
171
184
|
cameraSelector,
|
|
172
185
|
preview,
|
|
173
|
-
imageAnalysis
|
|
174
|
-
imageCapture
|
|
186
|
+
imageAnalysis
|
|
175
187
|
)
|
|
176
188
|
Log.d(TAG, "[CAMERAX] Camera bound successfully")
|
|
177
189
|
} catch (e: Exception) {
|
|
@@ -184,6 +196,15 @@ class CameraController(
|
|
|
184
196
|
try {
|
|
185
197
|
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
|
|
186
198
|
val nv21 = imageProxy.toNv21()
|
|
199
|
+
lastFrame.set(
|
|
200
|
+
LastFrame(
|
|
201
|
+
nv21,
|
|
202
|
+
imageProxy.width,
|
|
203
|
+
imageProxy.height,
|
|
204
|
+
rotationDegrees,
|
|
205
|
+
useFrontCamera
|
|
206
|
+
)
|
|
207
|
+
)
|
|
187
208
|
|
|
188
209
|
val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
|
|
189
210
|
imageProxy.height
|
|
@@ -216,6 +237,27 @@ class CameraController(
|
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
239
|
|
|
240
|
+
private fun nv21ToJpeg(nv21: ByteArray, width: Int, height: Int, quality: Int): ByteArray {
|
|
241
|
+
val yuv = YuvImage(nv21, android.graphics.ImageFormat.NV21, width, height, null)
|
|
242
|
+
val out = ByteArrayOutputStream()
|
|
243
|
+
yuv.compressToJpeg(Rect(0, 0, width, height), quality, out)
|
|
244
|
+
return out.toByteArray()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private fun rotateAndMirror(bitmap: Bitmap, rotationDegrees: Int, mirror: Boolean): Bitmap {
|
|
248
|
+
if (rotationDegrees == 0 && !mirror) {
|
|
249
|
+
return bitmap
|
|
250
|
+
}
|
|
251
|
+
val matrix = Matrix()
|
|
252
|
+
if (mirror) {
|
|
253
|
+
matrix.postScale(-1f, 1f, bitmap.width / 2f, bitmap.height / 2f)
|
|
254
|
+
}
|
|
255
|
+
if (rotationDegrees != 0) {
|
|
256
|
+
matrix.postRotate(rotationDegrees.toFloat(), bitmap.width / 2f, bitmap.height / 2f)
|
|
257
|
+
}
|
|
258
|
+
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
259
|
+
}
|
|
260
|
+
|
|
219
261
|
private fun hasCameraPermission(): Boolean {
|
|
220
262
|
return ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
|
|
221
263
|
}
|
package/package.json
CHANGED