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
|
|
87
|
-
if (
|
|
88
|
-
onError(IllegalStateException("
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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