Capture Images with CameraX

Google at its 2019 I/O introduced CameraX, a camera library as part of the Jetpack support library. With CameraX, the developers can spend less time writing the logic to set up the preview to analyze the preview or capture an image and more on time business logic.



CameraX is built to simplify three use cases when building a camera application. These are showing a preview, taking a picture and analyzing the image.

In addition to that, CameraX provides what is called CameraX Extensions, which enables the developers to take advantage of the device-specific and vendor-specific effects like bokeh, HDR, Night, Beauty mode. With this, the features of the native camera application on the device can be easily extended to our application.

A temporary disadvantage is that the CameraX is available for the devices running API 21 (Lollipop or Android 5.0) and higher. This is because of its heavy use of Camera2 API's.

Android CameraX Preview

In this article, we will take a look at how to show camera preview and take a picture using CameraX.

Add UI elements

1. Add TextureView to show the camera preview.

<TextureView
    android:id="@+id/cameraTextureView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

2. Add an image button at the bottom of the screen which when tapped takes a picture.

<androidx.appcompat.widget.AppCompatImageButton
    android:id="@+id/captureImageButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    style="@style/Widget.AppCompat.Button.Borderless"
    android:layout_marginBottom="@dimen/capture_image_vertical_margin"
    android:minWidth="@dimen/capture_button_min_width"
    android:minHeight="@dimen/capture_button_min_height"
    app:srcCompat="@drawable/circle_photo_camera"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>

The image button drawable (circle_photo_camera.xml) is a layer list with vector shaped camera on top of a white background.

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape android:shape="oval">
            <size android:width="48dp"
                  android:height="48dp"/>
            <solid android:color="@color/colorWhite"/>
        </shape>
    </item>


    <item>
        <inset android:drawable="@drawable/ic_photo_camera"
               android:insetLeft="8dp"
               android:insetBottom="8dp"
               android:insetRight="8dp"
               android:insetTop="8dp"/>
    </item>

</layer-list>

Request Runtime Permissions:

Every camera application at least needs Camera permission to show a camera preview and Storage permission to save the image to the external storage. So, add android.permission.CAMERA, android.permission.WRITE_EXTERNAL_STORAGE to the AndroidManifest.xml and request them at Runtime.

Set up Preview

Configure a preview with a back facing camera and rotation. Using the configuration, create a camera preview.

val rotation = activity?.windowManager?.defaultDisplay?.rotation ?: 0

val previewConfig = PreviewConfig.Builder()
    .setLensFacing(CameraX.LensFacing.BACK)
    .setTargetRotation(rotation)
    .build()
preview = Preview(previewConfig)

Create an image configuration with max image quality and auto flash functionality. Using the configuration, create an image capture object which will be used to take pictures.

val imageCaptureConfig = ImageCaptureConfig.Builder()
    .setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
    .setFlashMode(FlashMode.AUTO)
    .build()
imageCapture = ImageCapture(imageCaptureConfig)


// Listen to the preview data updates
preview.setOnPreviewOutputUpdateListener {
    cameraTextureView.surfaceTexture = it.surfaceTexture
}

Capture the image

Specify the location in the external storage for the image to be saved when the picture is taken and take the picture.

private fun captureImage() {
    // Decide the location of the picture to be saved to
    val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "IMG_${System.currentTimeMillis()}.jpg")
    if (DBG) Log.d(TAG, "File Path: ${file.path}")
    // Create the file
    if (!file.exists()) {
        try {
            val isCreated = file.createNewFile()
            if (DBG) Log.d(TAG, "File Created: $isCreated")
        } catch (e: IOException) {
            Snackbar.make(
                main, "Failed to create the file",
                LENGTH_LONG
            ).show()
            e.printStackTrace()
        }
    }

    // Take a picture and save it
    imageCapture.takePicture(file, imageSavedListener)
}

Pass a listener to the takePicture method so that it notifies whether the image is saved successfully or failed. In case if failed, show an error message and if saved we can display the image to the user.

// Listen to the image save or fail status
private val imageSavedListener =
    object : ImageCapture.OnImageSavedListener {
        override fun onImageSaved(file: File) {
            Snackbar.make(
                main, "Image saved successfully at ${file.path}",
                LENGTH_SHORT
            ).show()
        }


        override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
            Snackbar.make(
                main, "Image capture failed: $message",
                LENGTH_LONG
            ).show()
            cause?.printStackTrace()
        }

    }

Finally, bind the camera preview to a lifecycle, which helps the developer to less worry about pausing, resuming the previews and releasing the camera when the Activity lifecycle changes.

CameraX.bindToLifecycle(this, preview, imageCapture)

When the user taps on the camera button, a picture is taken and saved to the DCIM folder in the external storage. This finishes setting up the camera preview and taking pictures.

Popular posts from this blog

How to Read Metadata from AndriodManifest File

Mr Phone - Find The Next Best Phone You Want To Buy

Add Spacing to Recycler View Linear Layout Manager Using Item Decoration

Add Options Menu to Activity and Fragment

Create Assets Folder, Add Files and Read Data From It