@@ -3,7 +3,11 @@ package dev.steenbakker.mobile_scanner
3
3
import android.app.Activity
4
4
import android.content.Context
5
5
import android.graphics.Bitmap
6
+ import android.graphics.Canvas
7
+ import android.graphics.ColorMatrix
8
+ import android.graphics.ColorMatrixColorFilter
6
9
import android.graphics.Matrix
10
+ import android.graphics.Paint
7
11
import android.graphics.Rect
8
12
import android.hardware.display.DisplayManager
9
13
import android.net.Uri
@@ -34,6 +38,10 @@ import dev.steenbakker.mobile_scanner.objects.MobileScannerErrorCodes
34
38
import dev.steenbakker.mobile_scanner.objects.MobileScannerStartParameters
35
39
import dev.steenbakker.mobile_scanner.utils.YuvToRgbConverter
36
40
import io.flutter.view.TextureRegistry
41
+ import kotlinx.coroutines.CoroutineScope
42
+ import kotlinx.coroutines.Dispatchers
43
+ import kotlinx.coroutines.coroutineScope
44
+ import kotlinx.coroutines.launch
37
45
import java.io.ByteArrayOutputStream
38
46
import java.io.IOException
39
47
import kotlin.math.roundToInt
@@ -59,6 +67,7 @@ class MobileScanner(
59
67
60
68
// / Configurable variables
61
69
var scanWindow: List <Float >? = null
70
+ private var invertImage: Boolean = false
62
71
private var detectionSpeed: DetectionSpeed = DetectionSpeed .NO_DUPLICATES
63
72
private var detectionTimeout: Long = 250
64
73
private var returnImage = false
@@ -79,7 +88,12 @@ class MobileScanner(
79
88
@ExperimentalGetImage
80
89
val captureOutput = ImageAnalysis .Analyzer { imageProxy -> // YUV_420_888 format
81
90
val mediaImage = imageProxy.image ? : return @Analyzer
82
- val inputImage = InputImage .fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
91
+
92
+ val inputImage = if (invertImage) {
93
+ invertInputImage(imageProxy)
94
+ } else {
95
+ InputImage .fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
96
+ }
83
97
84
98
if (detectionSpeed == DetectionSpeed .NORMAL && scannerTimeout) {
85
99
imageProxy.close()
@@ -97,6 +111,7 @@ class MobileScanner(
97
111
98
112
if (newScannedBarcodes == lastScanned) {
99
113
// New scanned is duplicate, returning
114
+ imageProxy.close()
100
115
return @addOnSuccessListener
101
116
}
102
117
if (newScannedBarcodes.isNotEmpty()) {
@@ -118,45 +133,52 @@ class MobileScanner(
118
133
}
119
134
120
135
if (barcodeMap.isEmpty()) {
136
+ imageProxy.close()
121
137
return @addOnSuccessListener
122
138
}
123
139
124
140
val portrait = (camera?.cameraInfo?.sensorRotationDegrees ? : 0 ) % 180 == 0
125
141
126
142
if (! returnImage) {
143
+ imageProxy.close()
127
144
mobileScannerCallback(
128
145
barcodeMap,
129
146
null ,
130
- if (portrait) mediaImage .width else mediaImage .height,
131
- if (portrait) mediaImage .height else mediaImage .width)
147
+ if (portrait) inputImage .width else inputImage .height,
148
+ if (portrait) inputImage .height else inputImage .width)
132
149
return @addOnSuccessListener
133
150
}
134
151
135
- val bitmap = Bitmap .createBitmap(mediaImage.width, mediaImage.height, Bitmap .Config .ARGB_8888 )
136
- val imageFormat = YuvToRgbConverter (activity.applicationContext)
152
+ CoroutineScope (Dispatchers .IO ).launch {
153
+ val bitmap = Bitmap .createBitmap(mediaImage.width, mediaImage.height, Bitmap .Config .ARGB_8888 )
154
+ val imageFormat = YuvToRgbConverter (activity.applicationContext)
137
155
138
- imageFormat.yuvToRgb(mediaImage, bitmap)
156
+ imageFormat.yuvToRgb(mediaImage, bitmap)
139
157
140
- val bmResult = rotateBitmap(bitmap, camera?.cameraInfo?.sensorRotationDegrees?.toFloat() ? : 90f )
158
+ val bmResult = rotateBitmap(bitmap, camera?.cameraInfo?.sensorRotationDegrees?.toFloat() ? : 90f )
141
159
142
- val stream = ByteArrayOutputStream ()
143
- bmResult.compress(Bitmap .CompressFormat .PNG , 100 , stream)
144
- val byteArray = stream.toByteArray()
145
- val bmWidth = bmResult.width
146
- val bmHeight = bmResult.height
147
- bmResult.recycle()
160
+ val stream = ByteArrayOutputStream ()
161
+ bmResult.compress(Bitmap .CompressFormat .PNG , 100 , stream)
162
+ val byteArray = stream.toByteArray()
163
+ val bmWidth = bmResult.width
164
+ val bmHeight = bmResult.height
165
+
166
+ bmResult.recycle()
167
+ imageProxy.close()
168
+
169
+ mobileScannerCallback(
170
+ barcodeMap,
171
+ byteArray,
172
+ bmWidth,
173
+ bmHeight
174
+ )
175
+ }
148
176
149
- mobileScannerCallback(
150
- barcodeMap,
151
- byteArray,
152
- bmWidth,
153
- bmHeight
154
- )
155
177
}.addOnFailureListener { e ->
156
178
mobileScannerErrorCallback(
157
179
e.localizedMessage ? : e.toString()
158
180
)
159
- }.addOnCompleteListener { imageProxy.close() }
181
+ }
160
182
}
161
183
162
184
if (detectionSpeed == DetectionSpeed .NORMAL ) {
@@ -219,11 +241,13 @@ class MobileScanner(
219
241
mobileScannerStartedCallback : MobileScannerStartedCallback ,
220
242
mobileScannerErrorCallback : (exception: Exception ) -> Unit ,
221
243
detectionTimeout : Long ,
222
- cameraResolutionWanted : Size ?
244
+ cameraResolutionWanted : Size ? ,
245
+ invertImage : Boolean ,
223
246
) {
224
247
this .detectionSpeed = detectionSpeed
225
248
this .detectionTimeout = detectionTimeout
226
249
this .returnImage = returnImage
250
+ this .invertImage = invertImage
227
251
228
252
if (camera?.cameraInfo != null && preview != null && textureEntry != null && ! isPaused) {
229
253
@@ -416,14 +440,14 @@ class MobileScanner(
416
440
isPaused = true
417
441
}
418
442
419
- private fun resumeCamera () {
420
- // Resume camera by rebinding use cases
421
- cameraProvider?.let { provider ->
422
- val owner = activity as LifecycleOwner
423
- cameraSelector?.let { provider.bindToLifecycle(owner, it, preview) }
424
- }
425
- isPaused = false
426
- }
443
+ // private fun resumeCamera() {
444
+ // // Resume camera by rebinding use cases
445
+ // cameraProvider?.let { provider ->
446
+ // val owner = activity as LifecycleOwner
447
+ // cameraSelector?.let { provider.bindToLifecycle(owner, it, preview) }
448
+ // }
449
+ // isPaused = false
450
+ // }
427
451
428
452
private fun releaseCamera () {
429
453
if (displayListener != null ) {
@@ -472,6 +496,50 @@ class MobileScanner(
472
496
}
473
497
}
474
498
499
+ /* *
500
+ * Inverts the image colours respecting the alpha channel
501
+ */
502
+ @ExperimentalGetImage
503
+ fun invertInputImage (imageProxy : ImageProxy ): InputImage {
504
+ val image = imageProxy.image ? : throw IllegalArgumentException (" Image is null" )
505
+
506
+ // Convert YUV_420_888 image to RGB Bitmap
507
+ val bitmap = Bitmap .createBitmap(image.width, image.height, Bitmap .Config .ARGB_8888 )
508
+ try {
509
+ val imageFormat = YuvToRgbConverter (activity.applicationContext)
510
+ imageFormat.yuvToRgb(image, bitmap)
511
+
512
+ // Create an inverted bitmap
513
+ val invertedBitmap = invertBitmapColors(bitmap)
514
+ imageFormat.release()
515
+
516
+ return InputImage .fromBitmap(invertedBitmap, imageProxy.imageInfo.rotationDegrees)
517
+ } finally {
518
+ // Release resources
519
+ bitmap.recycle() // Free up bitmap memory
520
+ imageProxy.close() // Close ImageProxy
521
+ }
522
+ }
523
+
524
+ // Efficiently invert bitmap colors using ColorMatrix
525
+ private fun invertBitmapColors (bitmap : Bitmap ): Bitmap {
526
+ val colorMatrix = ColorMatrix ().apply {
527
+ set(floatArrayOf(
528
+ - 1f , 0f , 0f , 0f , 255f , // Red
529
+ 0f , - 1f , 0f , 0f , 255f , // Green
530
+ 0f , 0f , - 1f , 0f , 255f , // Blue
531
+ 0f , 0f , 0f , 1f , 0f // Alpha
532
+ ))
533
+ }
534
+ val paint = Paint ().apply { colorFilter = ColorMatrixColorFilter (colorMatrix) }
535
+
536
+ val invertedBitmap = Bitmap .createBitmap(bitmap.width, bitmap.height, bitmap.config)
537
+ val canvas = Canvas (invertedBitmap)
538
+ canvas.drawBitmap(bitmap, 0f , 0f , paint)
539
+
540
+ return invertedBitmap
541
+ }
542
+
475
543
/* *
476
544
* Analyze a single image.
477
545
*/
@@ -517,6 +585,11 @@ class MobileScanner(
517
585
camera?.cameraControl?.setLinearZoom(scale.toFloat())
518
586
}
519
587
588
+ fun setZoomRatio (zoomRatio : Double ) {
589
+ if (camera == null ) throw ZoomWhenStopped ()
590
+ camera?.cameraControl?.setZoomRatio(zoomRatio.toFloat())
591
+ }
592
+
520
593
/* *
521
594
* Reset the zoom rate of the camera.
522
595
*/
0 commit comments