react-native-rectangle-doc-scanner 3.220.0 → 3.222.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.
@@ -15,6 +15,8 @@ import androidx.camera.core.AspectRatio
15
15
  import androidx.camera.core.Camera
16
16
  import androidx.camera.core.CameraSelector
17
17
  import androidx.camera.core.ImageAnalysis
18
+ import androidx.camera.core.ImageCapture
19
+ import androidx.camera.core.ImageCaptureException
18
20
  import androidx.camera.core.Preview
19
21
  import androidx.camera.lifecycle.ProcessCameraProvider
20
22
  import androidx.camera.view.PreviewView
@@ -37,6 +39,7 @@ class CameraController(
37
39
  private var cameraProvider: ProcessCameraProvider? = null
38
40
  private var preview: Preview? = null
39
41
  private var imageAnalysis: ImageAnalysis? = null
42
+ private var imageCapture: ImageCapture? = null
40
43
  private var camera: Camera? = null
41
44
  private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
42
45
  private val lastFrame = AtomicReference<LastFrame?>()
@@ -45,6 +48,18 @@ class CameraController(
45
48
  private var useFrontCamera = false
46
49
  private var detectionEnabled = true
47
50
 
51
+ // For periodic frame capture
52
+ private var isAnalysisActive = false
53
+ private val analysisHandler = android.os.Handler(android.os.Looper.getMainLooper())
54
+ private val analysisRunnable = object : Runnable {
55
+ override fun run() {
56
+ if (isAnalysisActive && onFrameAnalyzed != null) {
57
+ captureFrameForAnalysis()
58
+ analysisHandler.postDelayed(this, 200) // Capture every 200ms
59
+ }
60
+ }
61
+ }
62
+
48
63
  var onFrameAnalyzed: ((Rectangle?, Int, Int) -> Unit)? = null
49
64
 
50
65
  companion object {
@@ -63,12 +78,12 @@ class CameraController(
63
78
  useFrontCam: Boolean = false,
64
79
  enableDetection: Boolean = true
65
80
  ) {
66
- Log.d(TAG, "[CAMERAX] startCamera called")
81
+ Log.d(TAG, "[CAMERAX-V6] startCamera called")
67
82
  this.useFrontCamera = useFrontCam
68
83
  this.detectionEnabled = enableDetection
69
84
 
70
85
  if (!hasCameraPermission()) {
71
- Log.e(TAG, "[CAMERAX] Camera permission not granted")
86
+ Log.e(TAG, "[CAMERAX-V6] Camera permission not granted")
72
87
  return
73
88
  }
74
89
 
@@ -81,13 +96,15 @@ class CameraController(
81
96
  cameraProvider = cameraProviderFuture?.get()
82
97
  bindCameraUseCases()
83
98
  } catch (e: Exception) {
84
- Log.e(TAG, "[CAMERAX] Failed to get camera provider", e)
99
+ Log.e(TAG, "[CAMERAX-V6] Failed to get camera provider", e)
85
100
  }
86
101
  }, ContextCompat.getMainExecutor(context))
87
102
  }
88
103
 
89
104
  fun stopCamera() {
90
- Log.d(TAG, "[CAMERAX] stopCamera called")
105
+ Log.d(TAG, "[CAMERAX-V6] stopCamera called")
106
+ isAnalysisActive = false
107
+ analysisHandler.removeCallbacks(analysisRunnable)
91
108
  cameraProvider?.unbindAll()
92
109
  analysisBound = false
93
110
  }
@@ -119,10 +136,10 @@ class CameraController(
119
136
  }
120
137
  bitmap.recycle()
121
138
 
122
- Log.d(TAG, "[CAMERAX] Photo capture succeeded: ${photoFile.absolutePath}")
139
+ Log.d(TAG, "[CAMERAX-V6] Photo capture succeeded: ${photoFile.absolutePath}")
123
140
  onImageCaptured(photoFile)
124
141
  } catch (e: Exception) {
125
- Log.e(TAG, "[CAMERAX] Photo capture failed", e)
142
+ Log.e(TAG, "[CAMERAX-V6] Photo capture failed", e)
126
143
  onError(e)
127
144
  }
128
145
  }
@@ -154,10 +171,11 @@ class CameraController(
154
171
  val provider = cameraProvider ?: return
155
172
  provider.unbindAll()
156
173
  analysisBound = false
174
+ isAnalysisActive = false
157
175
 
158
176
  val rotation = previewView.display?.rotation ?: Surface.ROTATION_0
159
177
 
160
- // Build Preview ONLY first
178
+ // Build Preview with lower resolution
161
179
  preview = Preview.Builder()
162
180
  .setTargetResolution(Size(1280, 720))
163
181
  .setTargetRotation(rotation)
@@ -166,123 +184,90 @@ class CameraController(
166
184
  it.setSurfaceProvider(previewView.surfaceProvider)
167
185
  }
168
186
 
187
+ // Build ImageCapture for periodic frame capture (instead of ImageAnalysis)
188
+ imageCapture = ImageCapture.Builder()
189
+ .setTargetResolution(Size(640, 480))
190
+ .setTargetRotation(rotation)
191
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
192
+ .build()
193
+
169
194
  val cameraSelector = if (useFrontCamera) {
170
195
  CameraSelector.DEFAULT_FRONT_CAMERA
171
196
  } else {
172
197
  CameraSelector.DEFAULT_BACK_CAMERA
173
198
  }
174
199
 
175
- // Step 1: Bind Preview ONLY first
200
+ // Bind Preview and ImageCapture together
176
201
  try {
177
- camera = provider.bindToLifecycle(
178
- lifecycleOwner,
179
- cameraSelector,
180
- preview
181
- )
182
- Log.d(TAG, "[CAMERAX-FIX-V5] Preview bound, waiting 2 seconds before adding analysis...")
183
-
184
- // Step 2: Add ImageAnalysis after a longer delay to let Preview session fully stabilize
185
- android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
186
- bindImageAnalysis(provider, cameraSelector, rotation)
187
- }, 2000)
188
-
189
- } catch (e: Exception) {
190
- Log.e(TAG, "[CAMERAX-FIX-V5] Failed to bind preview", e)
191
- analysisBound = false
192
- }
193
- }
194
-
195
- private fun bindImageAnalysis(provider: ProcessCameraProvider, cameraSelector: CameraSelector, rotation: Int) {
196
- if (analysisBound) return
197
-
198
- Log.d(TAG, "[CAMERAX-FIX-V5] Starting to add ImageAnalysis...")
199
-
200
- try {
201
- // Build ImageAnalysis with very low resolution
202
- imageAnalysis = ImageAnalysis.Builder()
203
- .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
204
- .setTargetRotation(rotation)
205
- .setTargetResolution(Size(480, 360))
206
- .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
207
- .build()
208
- .also {
209
- it.setAnalyzer(cameraExecutor, DocumentAnalyzer())
210
- }
211
-
212
- // IMPORTANT: Unbind all first, then rebind together to avoid session reconfiguration timeout
213
- provider.unbindAll()
214
-
215
- // Rebind BOTH at the same time
216
202
  camera = provider.bindToLifecycle(
217
203
  lifecycleOwner,
218
204
  cameraSelector,
219
205
  preview,
220
- imageAnalysis
206
+ imageCapture
221
207
  )
222
- analysisBound = true
223
- Log.d(TAG, "[CAMERAX-FIX-V5] ImageAnalysis added successfully after unbind/rebind")
224
- } catch (e: Exception) {
225
- Log.e(TAG, "[CAMERAX-FIX-V5] Failed to add ImageAnalysis", e)
226
- analysisBound = false
208
+ Log.d(TAG, "[CAMERAX-V6] Preview + ImageCapture bound successfully")
227
209
 
228
- // Fallback: rebind preview only
229
- try {
230
- camera = provider.bindToLifecycle(
231
- lifecycleOwner,
232
- cameraSelector,
233
- preview
234
- )
235
- Log.d(TAG, "[CAMERAX-FIX-V5] Fallback: Preview only")
236
- } catch (fallbackException: Exception) {
237
- Log.e(TAG, "[CAMERAX-FIX-V5] Fallback also failed", fallbackException)
210
+ // Start periodic frame capture for analysis
211
+ if (detectionEnabled) {
212
+ isAnalysisActive = true
213
+ analysisHandler.postDelayed(analysisRunnable, 500) // Start after 500ms
214
+ Log.d(TAG, "[CAMERAX-V6] Started periodic frame capture for analysis")
238
215
  }
216
+
217
+ } catch (e: Exception) {
218
+ Log.e(TAG, "[CAMERAX-V6] Failed to bind camera use cases", e)
239
219
  }
240
220
  }
241
221
 
242
- private inner class DocumentAnalyzer : ImageAnalysis.Analyzer {
243
- override fun analyze(imageProxy: androidx.camera.core.ImageProxy) {
244
- try {
245
- val rotationDegrees = imageProxy.imageInfo.rotationDegrees
246
- val nv21 = imageProxy.toNv21()
247
- lastFrame.set(
248
- LastFrame(
249
- nv21,
250
- imageProxy.width,
251
- imageProxy.height,
252
- rotationDegrees,
253
- useFrontCamera
222
+ private fun captureFrameForAnalysis() {
223
+ val capture = imageCapture ?: return
224
+
225
+ capture.takePicture(cameraExecutor, object : ImageCapture.OnImageCapturedCallback() {
226
+ override fun onCaptureSuccess(image: androidx.camera.core.ImageProxy) {
227
+ try {
228
+ val rotationDegrees = image.imageInfo.rotationDegrees
229
+ val nv21 = image.toNv21()
230
+
231
+ lastFrame.set(
232
+ LastFrame(
233
+ nv21,
234
+ image.width,
235
+ image.height,
236
+ rotationDegrees,
237
+ useFrontCamera
238
+ )
254
239
  )
255
- )
256
240
 
257
- val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
258
- imageProxy.height
259
- } else {
260
- imageProxy.width
261
- }
241
+ val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
242
+ image.height
243
+ } else {
244
+ image.width
245
+ }
262
246
 
263
- val frameHeight = if (rotationDegrees == 90 || rotationDegrees == 270) {
264
- imageProxy.width
265
- } else {
266
- imageProxy.height
267
- }
247
+ val frameHeight = if (rotationDegrees == 90 || rotationDegrees == 270) {
248
+ image.width
249
+ } else {
250
+ image.height
251
+ }
268
252
 
269
- if (detectionEnabled) {
270
253
  val rectangle = DocumentDetector.detectRectangleInYUV(
271
254
  nv21,
272
- imageProxy.width,
273
- imageProxy.height,
255
+ image.width,
256
+ image.height,
274
257
  rotationDegrees
275
258
  )
276
259
  onFrameAnalyzed?.invoke(rectangle, frameWidth, frameHeight)
277
- } else {
278
- onFrameAnalyzed?.invoke(null, frameWidth, frameHeight)
260
+ } catch (e: Exception) {
261
+ Log.e(TAG, "[CAMERAX-V6] Error analyzing frame", e)
262
+ } finally {
263
+ image.close()
279
264
  }
280
- } catch (e: Exception) {
281
- Log.e(TAG, "[CAMERAX] Error analyzing frame", e)
282
- } finally {
283
- imageProxy.close()
284
265
  }
285
- }
266
+
267
+ override fun onError(exception: ImageCaptureException) {
268
+ Log.e(TAG, "[CAMERAX-V6] Frame capture for analysis failed", exception)
269
+ }
270
+ })
286
271
  }
287
272
 
288
273
  private fun nv21ToJpeg(nv21: ByteArray, width: Int, height: Int, quality: Int): ByteArray {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.220.0",
3
+ "version": "3.222.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",