CameraX: Capturing photo as bitmap
Asked Answered
C

2

9

I am experimenting with Googles CameraX sample app (CameraXBasic, can be found on Github) and would like to capture the image as a Bitmap to be able to make some modifications to the image before saving it. Does anyone have a suggestion on how to achieve this?

Please see below Google's original code to capture and save the image:

// Listener for button used to capture photo
        controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {

            // Get a stable reference of the modifiable image capture use case
            imageCapture?.let { imageCapture ->

                // Create output file to hold the image
                val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)

                    // Create output options object which contains file + metadata
                    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
                            .build()

                    // Setup image capture listener which is triggered after photo has been taken
                    imageCapture.takePicture(
                            outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
                        override fun onError(exc: ImageCaptureException) {
                            Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                        }

                        override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                            val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                            Log.d(TAG, "Photo capture succeeded: $savedUri")

                            // We can only change the foreground Drawable using API level 23+ API
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                // Update the gallery thumbnail with latest picture taken
                                setGalleryThumbnail(savedUri)
                            }

                            // Implicit broadcasts will be ignored for devices running API level >= 24
                            // so if you only target API level 24+ you can remove this statement
                            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                                requireActivity().sendBroadcast(
                                        Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)
                                )
                            }

                            // If the folder selected is an external media directory, this is
                            // unnecessary but otherwise other apps will not be able to access our
                            // images unless we scan them using [MediaScannerConnection]
                            val mimeType = MimeTypeMap.getSingleton()
                                    .getMimeTypeFromExtension(savedUri.toFile().extension)
                            MediaScannerConnection.scanFile(
                                    context,
                                    arrayOf(savedUri.toFile().absolutePath),
                                    arrayOf(mimeType)
                            ) { _, uri ->
                                Log.d(TAG, "Image capture scanned into media store: $uri")
                            }
                        }
                    })
                }

                // We can only change the foreground Drawable using API level 23+ API
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                    // Display flash animation to indicate that photo was captured
                    container.postDelayed({
                        container.foreground = ColorDrawable(Color.WHITE)
                        container.postDelayed(
                                { container.foreground = null }, ANIMATION_FAST_MILLIS)
                    }, ANIMATION_SLOW_MILLIS)
                }
            }

Thanks in advance for your help :)

Coyote answered 18/5, 2020 at 18:17 Comment(1)
You can use the in-memory method for taking a picture with ImageCaputre, this provides an ImageProxy, you can convert it to a Bitmap, apply your changes to it then store it.Malony
O
14

There is another callback OnImageCapturedCallback available for takePicture which return ImageProxy in onCaptureSuccess, You can get bitmap from that ImageProxy object

 imageCapture.takePicture(cameraExecutor, object : 
 ImageCapture.OnImageCapturedCallback() {
                override fun onCaptureSuccess(image: ImageProxy) {
                    //get bitmap from image 
                    val bitmap = imageProxyToBitmap(image)
                    super.onCaptureSuccess(image)
                }

                override fun onError(exception: ImageCaptureException) {
                    super.onError(exception)
                }

 })

/**
 *  convert image proxy to bitmap
 *  @param image
 */
private fun imageProxyToBitmap(image: ImageProxy): Bitmap {
    val planeProxy = image.planes[0]
    val buffer: ByteBuffer = planeProxy.buffer
    val bytes = ByteArray(buffer.remaining())
    buffer.get(bytes)
    return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
}
Obregon answered 10/9, 2020 at 5:44 Comment(6)
Thanks for your answer, it works very well! However, I once had a NullPointerExeption in the line " return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)". Any idea what caused it and how to resolve it? Thanks again for your helpCoyote
Beware of the format of the ImageProxy. You can check it with getFormat() method. The method for converting to bitmap displayed here is for JPEG (255) formats, if I am not wrong.Vetiver
@Vetiver is correct that this imageProxyToBitmap code works for JPEG format. A minor correction - the constant for JPEG format is 256.Culberson
did any one encounter image rotation issue using this methods ?Ladonnalady
For some reason this is not working on some devices, Do anyone know the reason?Crewelwork
@Ladonnalady I found it. Don't know why. Digging deeper into that.Kinase
G
0

Addition to kishore answer Rotation issue: CameraX image bitmap was appearing horizontally instead of verticle. issue fixed by using metadata to fix orientation. Tested on Xiaomi Poco X3

    private fun imageProxyToBitmap(image: ImageProxy): Bitmap {
    val planeProxy = image.planes[0]
    val buffer: ByteBuffer = planeProxy.buffer
    val bytes = ByteArray(buffer.remaining())
    buffer.get(bytes)

    // Decode the image with EXIF orientation information
    val options = BitmapFactory.Options()
    options.inSampleSize = 1
    val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)

    // Check and handle EXIF orientation
    val exif = ExifInterface(bytes.inputStream())
    val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
    val rotatedBitmap = when (orientation) {
        ExifInterface.ORIENTATION_ROTATE_90 -> rotateBitmap(bitmap, 90)
        ExifInterface.ORIENTATION_ROTATE_180 -> rotateBitmap(bitmap, 180)
        ExifInterface.ORIENTATION_ROTATE_270 -> rotateBitmap(bitmap, 270)
        else -> bitmap
    }

    return rotatedBitmap
}

// Rotate the bitmap to the desired orientation
private fun rotateBitmap(source: Bitmap, degrees: Int): Bitmap {
    val matrix = Matrix()
    matrix.postRotate(degrees.toFloat())
    return Bitmap.createBitmap(source, 0, 0, source.width, source.height, matrix, true)
}
Garry answered 18/10, 2023 at 12:15 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.