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.
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.
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.