react-native-rectangle-doc-scanner 3.221.0 → 3.223.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,17 +171,15 @@ 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 ONLY - this device cannot handle 2 simultaneous surfaces
161
179
  preview = Preview.Builder()
162
180
  .setTargetResolution(Size(1280, 720))
163
181
  .setTargetRotation(rotation)
164
182
  .build()
165
- .also {
166
- it.setSurfaceProvider(previewView.surfaceProvider)
167
- }
168
183
 
169
184
  val cameraSelector = if (useFrontCamera) {
170
185
  CameraSelector.DEFAULT_FRONT_CAMERA
@@ -172,119 +187,128 @@ class CameraController(
172
187
  CameraSelector.DEFAULT_BACK_CAMERA
173
188
  }
174
189
 
175
- // TEMPORARY: Bind Preview ONLY to test if Preview works without ImageAnalysis
190
+ // Bind Preview ONLY first
176
191
  try {
177
192
  camera = provider.bindToLifecycle(
178
193
  lifecycleOwner,
179
194
  cameraSelector,
180
195
  preview
181
196
  )
182
- Log.d(TAG, "[CAMERAX-DEBUG] Preview ONLY bound successfully - NO ImageAnalysis")
183
- analysisBound = false
184
197
 
185
- // TODO: If Preview works, we need to find alternative for document detection
186
- // Options:
187
- // 1. Use ImageCapture instead of ImageAnalysis
188
- // 2. Use lower resolution for ImageAnalysis (already tried)
189
- // 3. Use Camera2 API directly instead of CameraX
198
+ // Set surface provider AFTER binding
199
+ preview?.setSurfaceProvider(previewView.surfaceProvider)
200
+
201
+ Log.d(TAG, "[CAMERAX-V7] Preview ONLY bound successfully")
202
+
203
+ // Wait for preview to stabilize, then try adding ImageCapture
204
+ android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
205
+ tryAddImageCapture(provider, cameraSelector, rotation)
206
+ }, 3000)
190
207
 
191
208
  } catch (e: Exception) {
192
- Log.e(TAG, "[CAMERAX-DEBUG] Failed to bind preview", e)
193
- analysisBound = false
209
+ Log.e(TAG, "[CAMERAX-V7] Failed to bind preview", e)
194
210
  }
195
211
  }
196
212
 
197
- private fun bindImageAnalysis(provider: ProcessCameraProvider, cameraSelector: CameraSelector, rotation: Int) {
198
- if (analysisBound) return
213
+ private fun tryAddImageCapture(provider: ProcessCameraProvider, cameraSelector: CameraSelector, rotation: Int) {
214
+ Log.d(TAG, "[CAMERAX-V7] Attempting to add ImageCapture...")
199
215
 
200
- Log.d(TAG, "[CAMERAX-FIX-V5] Starting to add ImageAnalysis...")
216
+ // Build ImageCapture with minimal resolution
217
+ imageCapture = ImageCapture.Builder()
218
+ .setTargetResolution(Size(320, 240))
219
+ .setTargetRotation(rotation)
220
+ .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
221
+ .build()
201
222
 
202
223
  try {
203
- // Build ImageAnalysis with very low resolution
204
- imageAnalysis = ImageAnalysis.Builder()
205
- .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
206
- .setTargetRotation(rotation)
207
- .setTargetResolution(Size(480, 360))
208
- .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
209
- .build()
210
- .also {
211
- it.setAnalyzer(cameraExecutor, DocumentAnalyzer())
212
- }
213
-
214
- // IMPORTANT: Unbind all first, then rebind together to avoid session reconfiguration timeout
224
+ // Unbind and rebind with both
215
225
  provider.unbindAll()
216
226
 
217
- // Rebind BOTH at the same time
218
227
  camera = provider.bindToLifecycle(
219
228
  lifecycleOwner,
220
229
  cameraSelector,
221
230
  preview,
222
- imageAnalysis
231
+ imageCapture
223
232
  )
224
- analysisBound = true
225
- Log.d(TAG, "[CAMERAX-FIX-V5] ImageAnalysis added successfully after unbind/rebind")
233
+
234
+ preview?.setSurfaceProvider(previewView.surfaceProvider)
235
+
236
+ Log.d(TAG, "[CAMERAX-V7] ImageCapture added successfully")
237
+
238
+ // Start periodic frame capture
239
+ if (detectionEnabled) {
240
+ isAnalysisActive = true
241
+ analysisHandler.postDelayed(analysisRunnable, 500)
242
+ Log.d(TAG, "[CAMERAX-V7] Started periodic frame capture")
243
+ }
244
+
226
245
  } catch (e: Exception) {
227
- Log.e(TAG, "[CAMERAX-FIX-V5] Failed to add ImageAnalysis", e)
228
- analysisBound = false
246
+ Log.e(TAG, "[CAMERAX-V7] Failed to add ImageCapture, keeping Preview only", e)
229
247
 
230
- // Fallback: rebind preview only
248
+ // Fallback: Keep preview only and disable detection
231
249
  try {
232
250
  camera = provider.bindToLifecycle(
233
251
  lifecycleOwner,
234
252
  cameraSelector,
235
253
  preview
236
254
  )
237
- Log.d(TAG, "[CAMERAX-FIX-V5] Fallback: Preview only")
255
+ preview?.setSurfaceProvider(previewView.surfaceProvider)
256
+ Log.d(TAG, "[CAMERAX-V7] Fallback: Preview only mode (detection disabled)")
238
257
  } catch (fallbackException: Exception) {
239
- Log.e(TAG, "[CAMERAX-FIX-V5] Fallback also failed", fallbackException)
258
+ Log.e(TAG, "[CAMERAX-V7] Fallback failed", fallbackException)
240
259
  }
241
260
  }
242
261
  }
243
262
 
244
- private inner class DocumentAnalyzer : ImageAnalysis.Analyzer {
245
- override fun analyze(imageProxy: androidx.camera.core.ImageProxy) {
246
- try {
247
- val rotationDegrees = imageProxy.imageInfo.rotationDegrees
248
- val nv21 = imageProxy.toNv21()
249
- lastFrame.set(
250
- LastFrame(
251
- nv21,
252
- imageProxy.width,
253
- imageProxy.height,
254
- rotationDegrees,
255
- useFrontCamera
263
+ private fun captureFrameForAnalysis() {
264
+ val capture = imageCapture ?: return
265
+
266
+ capture.takePicture(cameraExecutor, object : ImageCapture.OnImageCapturedCallback() {
267
+ override fun onCaptureSuccess(image: androidx.camera.core.ImageProxy) {
268
+ try {
269
+ val rotationDegrees = image.imageInfo.rotationDegrees
270
+ val nv21 = image.toNv21()
271
+
272
+ lastFrame.set(
273
+ LastFrame(
274
+ nv21,
275
+ image.width,
276
+ image.height,
277
+ rotationDegrees,
278
+ useFrontCamera
279
+ )
256
280
  )
257
- )
258
281
 
259
- val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
260
- imageProxy.height
261
- } else {
262
- imageProxy.width
263
- }
282
+ val frameWidth = if (rotationDegrees == 90 || rotationDegrees == 270) {
283
+ image.height
284
+ } else {
285
+ image.width
286
+ }
264
287
 
265
- val frameHeight = if (rotationDegrees == 90 || rotationDegrees == 270) {
266
- imageProxy.width
267
- } else {
268
- imageProxy.height
269
- }
288
+ val frameHeight = if (rotationDegrees == 90 || rotationDegrees == 270) {
289
+ image.width
290
+ } else {
291
+ image.height
292
+ }
270
293
 
271
- if (detectionEnabled) {
272
294
  val rectangle = DocumentDetector.detectRectangleInYUV(
273
295
  nv21,
274
- imageProxy.width,
275
- imageProxy.height,
296
+ image.width,
297
+ image.height,
276
298
  rotationDegrees
277
299
  )
278
300
  onFrameAnalyzed?.invoke(rectangle, frameWidth, frameHeight)
279
- } else {
280
- onFrameAnalyzed?.invoke(null, frameWidth, frameHeight)
301
+ } catch (e: Exception) {
302
+ Log.e(TAG, "[CAMERAX-V6] Error analyzing frame", e)
303
+ } finally {
304
+ image.close()
281
305
  }
282
- } catch (e: Exception) {
283
- Log.e(TAG, "[CAMERAX] Error analyzing frame", e)
284
- } finally {
285
- imageProxy.close()
286
306
  }
287
- }
307
+
308
+ override fun onError(exception: ImageCaptureException) {
309
+ Log.e(TAG, "[CAMERAX-V6] Frame capture for analysis failed", exception)
310
+ }
311
+ })
288
312
  }
289
313
 
290
314
  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.221.0",
3
+ "version": "3.223.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",