capacitor-camera-view 1.0.0 → 1.0.1
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,8 +3,6 @@ package com.michaelwolz.capacitorcameraview
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.Context.CAMERA_SERVICE
|
|
5
5
|
import android.graphics.Bitmap
|
|
6
|
-
import android.graphics.BitmapFactory
|
|
7
|
-
import android.graphics.Matrix
|
|
8
6
|
import android.hardware.camera2.CameraCharacteristics
|
|
9
7
|
import android.hardware.camera2.CameraManager
|
|
10
8
|
import android.util.Base64
|
|
@@ -18,13 +16,13 @@ import androidx.camera.core.CameraSelector
|
|
|
18
16
|
import androidx.camera.core.ImageAnalysis
|
|
19
17
|
import androidx.camera.core.ImageCapture
|
|
20
18
|
import androidx.camera.core.ImageCaptureException
|
|
21
|
-
import androidx.camera.core.ImageProxy
|
|
22
19
|
import androidx.camera.core.resolutionselector.AspectRatioStrategy
|
|
23
20
|
import androidx.camera.core.resolutionselector.ResolutionSelector
|
|
24
21
|
import androidx.camera.mlkit.vision.MlKitAnalyzer
|
|
25
22
|
import androidx.camera.view.LifecycleCameraController
|
|
26
23
|
import androidx.camera.view.PreviewView
|
|
27
24
|
import androidx.core.content.ContextCompat
|
|
25
|
+
import androidx.exifinterface.media.ExifInterface
|
|
28
26
|
import androidx.lifecycle.LifecycleOwner
|
|
29
27
|
import com.getcapacitor.Plugin
|
|
30
28
|
import com.google.mlkit.vision.barcode.BarcodeScanner
|
|
@@ -36,6 +34,8 @@ import com.michaelwolz.capacitorcameraview.model.CameraDevice
|
|
|
36
34
|
import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
|
|
37
35
|
import com.michaelwolz.capacitorcameraview.model.ZoomFactors
|
|
38
36
|
import java.io.ByteArrayOutputStream
|
|
37
|
+
import java.io.File
|
|
38
|
+
import java.util.UUID
|
|
39
39
|
import java.util.concurrent.ExecutorService
|
|
40
40
|
import java.util.concurrent.Executors
|
|
41
41
|
|
|
@@ -124,32 +124,30 @@ class CameraView(plugin: Plugin) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/** Capture a photo with the current camera configuration */
|
|
127
|
-
fun capturePhoto(quality: Int
|
|
128
|
-
val controller =
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
127
|
+
fun capturePhoto(quality: Int, callback: (String?, Exception?) -> Unit) {
|
|
128
|
+
val controller = this.cameraController
|
|
129
|
+
?: run {
|
|
130
|
+
callback(null, Exception("Camera controller not initialized"))
|
|
131
|
+
return
|
|
132
|
+
}
|
|
134
133
|
|
|
135
134
|
mainHandler.post {
|
|
136
135
|
try {
|
|
136
|
+
// Create temporary file for the captured image
|
|
137
|
+
val tempFile =
|
|
138
|
+
File.createTempFile(UUID.randomUUID().toString(), ".jpg", context.cacheDir)
|
|
139
|
+
val outputOptions = ImageCapture.OutputFileOptions.Builder(tempFile).build()
|
|
140
|
+
|
|
137
141
|
controller.takePicture(
|
|
142
|
+
outputOptions,
|
|
138
143
|
cameraExecutor,
|
|
139
|
-
object : ImageCapture.
|
|
140
|
-
override fun
|
|
141
|
-
|
|
142
|
-
val base64String = imageProxyToBase64(image, quality)
|
|
143
|
-
callback(base64String, null)
|
|
144
|
-
} catch (e: Exception) {
|
|
145
|
-
Log.e(TAG, "Error processing captured image", e)
|
|
146
|
-
callback(null, e)
|
|
147
|
-
} finally {
|
|
148
|
-
image.close()
|
|
149
|
-
}
|
|
144
|
+
object : ImageCapture.OnImageSavedCallback {
|
|
145
|
+
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
|
146
|
+
handleImageSaved(tempFile, quality, callback)
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
override fun onError(exception: ImageCaptureException) {
|
|
150
|
+
tempFile.delete()
|
|
153
151
|
Log.e(TAG, "Error capturing image", exception)
|
|
154
152
|
callback(null, exception)
|
|
155
153
|
}
|
|
@@ -162,6 +160,61 @@ class CameraView(plugin: Plugin) {
|
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Handles the image saved callback, re-encodes the JPEG if quality is specified
|
|
165
|
+
* and returns the Base64 encoded string as the callback result.
|
|
166
|
+
*/
|
|
167
|
+
private fun handleImageSaved(
|
|
168
|
+
tempFile: File,
|
|
169
|
+
quality: Int,
|
|
170
|
+
callback: (String?, Exception?) -> Unit
|
|
171
|
+
) {
|
|
172
|
+
val startTime = System.currentTimeMillis()
|
|
173
|
+
try {
|
|
174
|
+
val jpegBytes = tempFile.readBytes()
|
|
175
|
+
val base64String = if (quality == 100) {
|
|
176
|
+
// If quality is 100, return the original JPEG without re-encoding
|
|
177
|
+
Log.d(TAG, "Encoding original JPEG (quality 100)")
|
|
178
|
+
Base64.encodeToString(jpegBytes, Base64.NO_WRAP)
|
|
179
|
+
} else {
|
|
180
|
+
// Otherwise, re-encode the JPEG with the specified quality
|
|
181
|
+
// which is a little bit more expensive
|
|
182
|
+
Log.d(TAG, "Re-encoding JPEG with quality $quality")
|
|
183
|
+
val originalExif = ExifInterface(tempFile.absolutePath)
|
|
184
|
+
val orientation = originalExif.getAttributeInt(
|
|
185
|
+
ExifInterface.TAG_ORIENTATION,
|
|
186
|
+
ExifInterface.ORIENTATION_UNDEFINED
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
val bitmap =
|
|
190
|
+
android.graphics.BitmapFactory.decodeByteArray(jpegBytes, 0, jpegBytes.size)
|
|
191
|
+
val compressedFile =
|
|
192
|
+
File.createTempFile(UUID.randomUUID().toString(), ".jpg", context.cacheDir)
|
|
193
|
+
val outputStream = compressedFile.outputStream()
|
|
194
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
|
|
195
|
+
outputStream.close()
|
|
196
|
+
|
|
197
|
+
val newExif = ExifInterface(compressedFile.absolutePath)
|
|
198
|
+
newExif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation.toString())
|
|
199
|
+
newExif.saveAttributes()
|
|
200
|
+
|
|
201
|
+
val compressedBytes = compressedFile.readBytes()
|
|
202
|
+
compressedFile.delete()
|
|
203
|
+
Base64.encodeToString(compressedBytes, Base64.NO_WRAP)
|
|
204
|
+
}
|
|
205
|
+
val endTime = System.currentTimeMillis()
|
|
206
|
+
Log.d(
|
|
207
|
+
TAG,
|
|
208
|
+
"Image processing took ${endTime - startTime} ms (quality: ${quality ?: 100})"
|
|
209
|
+
)
|
|
210
|
+
tempFile.delete()
|
|
211
|
+
callback(base64String, null)
|
|
212
|
+
} catch (e: Exception) {
|
|
213
|
+
tempFile.delete()
|
|
214
|
+
callback(null, e)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
165
218
|
/**
|
|
166
219
|
* Capture a frame directly from the preview without using the full photo pipeline which is
|
|
167
220
|
* faster but has lower quality.
|
|
@@ -494,36 +547,6 @@ class CameraView(plugin: Plugin) {
|
|
|
494
547
|
lastBarcodeDetectionTime = now
|
|
495
548
|
}
|
|
496
549
|
|
|
497
|
-
/** Converts an ImageProxy to a Base64 encoded string */
|
|
498
|
-
private fun imageProxyToBase64(image: ImageProxy, quality: Int?): String {
|
|
499
|
-
val buffer = image.planes[0].buffer
|
|
500
|
-
val bytes = ByteArray(buffer.remaining())
|
|
501
|
-
buffer.get(bytes)
|
|
502
|
-
|
|
503
|
-
var bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
|
|
504
|
-
|
|
505
|
-
try {
|
|
506
|
-
// Apply rotation if needed
|
|
507
|
-
if (image.imageInfo.rotationDegrees != 0) {
|
|
508
|
-
val matrix = Matrix()
|
|
509
|
-
matrix.postRotate(image.imageInfo.rotationDegrees.toFloat())
|
|
510
|
-
val rotatedBitmap =
|
|
511
|
-
Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
|
|
512
|
-
// Recycle the original bitmap to prevent memory leaks
|
|
513
|
-
bitmap.recycle()
|
|
514
|
-
bitmap = rotatedBitmap
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
val outputStream = ByteArrayOutputStream()
|
|
518
|
-
bitmap.compress(Bitmap.CompressFormat.JPEG, quality ?: 90, outputStream)
|
|
519
|
-
val byteArray = outputStream.toByteArray()
|
|
520
|
-
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
|
521
|
-
} finally {
|
|
522
|
-
// Ensure bitmap is always recycled
|
|
523
|
-
bitmap.recycle()
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
|
|
527
550
|
private fun notifyBarcodeDetected(result: BarcodeDetectionResult) {
|
|
528
551
|
pluginDelegate.let { plugin ->
|
|
529
552
|
if (plugin is CameraViewPlugin) {
|
|
@@ -73,7 +73,7 @@ class CameraViewPlugin : Plugin() {
|
|
|
73
73
|
|
|
74
74
|
@PluginMethod
|
|
75
75
|
fun capture(call: PluginCall) {
|
|
76
|
-
val quality = call.getInt("quality"
|
|
76
|
+
val quality = call.getInt("quality") ?: 90
|
|
77
77
|
|
|
78
78
|
if (quality !in 0..100) {
|
|
79
79
|
call.reject("Quality must be between 0 and 100")
|
package/package.json
CHANGED