17 December 2024
Get ready to level up your Android camera apps! CameraX 1.4.0 just dropped with a load of awesome new features and improvements. We're talking expanded HDR capabilities, preview stabilization and the versatile effect framework, and a whole lot of cool stuff to explore. We will also explore how to seamlessly integrate CameraX with Jetpack Compose! Let's dive in and see how these enhancements can take your camera app to the next level.
High Dynamic Range (HDR) is a game-changer for photography, capturing a wider range of light and detail to create stunningly realistic images. With CameraX 1.3.0, we brought you HDR video recording capabilities, and now in 1.4.0, we're taking it even further! Get ready for HDR Preview and Ultra HDR. These exciting additions empower you to deliver an even richer visual experience to your users.
This new feature allows you to enable HDR on Preview without needing to bind a VideoCapture use case. This is especially useful for apps that use a single preview stream for both showing preview on display and video recording with an OpenGL pipeline.
To fully enable the HDR, you need to ensure your OpenGL pipeline is capable of processing the specific dynamic range format and then check the camera capability.
See following code snippet as an example to enable HLG10 which is the baseline HDR standard that device makers must support on cameras with 10-bit output.
// Declare your OpenGL pipeline supported dynamic range format. val openGLPipelineSupportedDynamicRange = setOf( DynamicRange.SDR, DynamicRange.HLG_10_BIT ) // Check camera dynamic range capabilities. val isHlg10Supported = cameraProvider.getCameraInfo(cameraSelector) .querySupportedDynamicRanges(openGLPipelineSupportedDynamicRange) .contains(DynamicRange.HLG_10_BIT) val preview = Preview.Builder().apply { if (isHlg10Supported) { setDynamicRange(DynamicRange.HLG_10_BIT) } }
Introducing Ultra HDR, a new format in Android 14 that lets users capture stunningly realistic photos with incredible dynamic range. And the best part? CameraX 1.4.0 makes it incredibly easy to add Ultra HDR capture to your app with just a few lines of code:
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA val cameraInfo = cameraProvider.getCameraInfo(cameraSelector) val isUltraHdrSupported = ImageCapture.getImageCaptureCapabilities(cameraInfo) .supportedOutputFormats .contains(ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR) val imageCapture = ImageCapture.Builder().apply { if (isUltraHdrSupported) { setOutputFormat(ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR) } }.build()
While this post focuses on 1.4.0, we're excited to announce the Jetpack Compose support in CameraX 1.5.0 alpha. We’re adding support for a Composable Viewfinder built on top of AndroidExternalSurface and AndroidEmbeddedExternalSurface. The CameraXViewfinder Composable hooks up a display surface to a CameraX Preview use case, handling the complexities of rotation, scaling and Surface lifecycle so you don’t need to.
// in build.gradle implementation ("androidx.camera:camera-compose:1.5.0-alpha03") class PreviewViewModel : ViewModel() { private val _surfaceRequests = MutableStateFlow<SurfaceRequest?>(null) val surfaceRequests: StateFlow<SurfaceRequest?> get() = _surfaceRequests.asStateFlow() private fun produceSurfaceRequests(previewUseCase: Preview) { // Always publish new SurfaceRequests from Preview previewUseCase.setSurfaceProvider { newSurfaceRequest -> _surfaceRequests.value = newSurfaceRequest } } // ... } @Composable fun MyCameraViewfinder( viewModel: PreviewViewModel, modifier: Modifier = Modifier ) { val currentSurfaceRequest: SurfaceRequest? by viewModel.surfaceRequests.collectAsState() currentSurfaceRequest?.let { surfaceRequest -> CameraXViewfinder( surfaceRequest = surfaceRequest, implementationMode = ImplementationMode.EXTERNAL, // Or EMBEDDED modifier = modifier ) } }
CameraX is getting even more Kotlin-friendly! In 1.4.0, we've introduced two new suspend functions to streamline camera initialization and image capture.
// CameraX initialization val cameraProvider = ProcessCameraProvider.awaitInstance() val imageProxy = imageCapture.takePicture() // Processing imageProxy imageProxy.close()
Preview stabilization mode was added in Android 13 to enable the stabilization on all non-RAW streams, including previews and MediaCodec input surfaces. Compared to the previous video stabilization mode, which may have inconsistent FoV (Field of View) between the preview and recorded video, this new preview stabilization mode ensures consistency and thus provides a better user experience. For apps that record the preview directly for video recording, this mode is also the only way to enable stabilization.
Follow the code below to enable preview stabilization. Please note that once preview stabilization is turned on, it is not only applied to the Preview but also to the VideoCapture if it is bound as well.
val isPreviewStabilizationSupported = Preview.getPreviewCapabilities(cameraProvider.getCameraInfo(cameraSelector)) .isStabilizationSupported val preview = Preview.Builder().apply { if (isPreviewStabilizationSupported) { setPreviewStabilizationEnabled(true) } }.build()
While CameraX 1.3.0 introduced mirror mode for VideoCapture, we've now brought this handy feature to Preview in 1.4.0. This is especially useful for devices with outer displays, allowing you to create a more natural selfie experience when using the rear camera.
To enable the mirror mode, simply call Preview.Builder.setMirrorMode APIs. This feature is supported for Android 13 and above.
CameraX 1.3.0 introduced the CameraEffect framework, giving you the power to customize your camera output with OpenGL. Now, in 1.4.0, we're taking it a step further. In addition to applying your own custom effects, you can now leverage a set of pre-built effects provided by CameraX and Media3, making it easier than ever to enhance your app's camera features.
The new camera-effects artifact aims to provide ready-to-use effect implementations, starting with the OverlayEffect. This effect lets you draw overlays on top of camera frames using the familiar Canvas API.
The following sample code shows how to detect the QR code and draw the shape of the QR code once it is detected.
By default, drawing is performed in surface frame coordinates. But what if you need to use camera sensor coordinates? No problem! OverlayEffect provides the Frame#getSensorToBufferTransform function, allowing you to apply the necessary transformation matrix to your overlayCanvas.
In this example, we use CameraX's MLKit Vision APIs (MlKitAnalyzer) and specify COORDINATE_SYSTEM_SENSOR to obtain QR code corner points in sensor coordinates. This ensures accurate overlay placement regardless of device orientation or screen aspect ratio.
// in build.gradle implementation ("androidx.camera:camera-effects:1.4.1}") implementation ("androidx.camera:camera-mlkit-vision:1.4.1") var qrcodePoints: Array<Point>? = null val qrcodeBoxEffect = OverlayEffect( PREVIEW /* applied on the preview only */, 0, /* queueDepth */, Handler(Looper.getMainLooper()), {} ) fun initCamera() { qrcodeBoxEffect.setOnDrawListener { frame -> frame.overlayCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR) qrcodePoints?.let { // Using sensor coordinates to draw. frame.overlayCanvas.setMatrix(frame.sensorToBufferTransform) val path = android.graphics.Path().apply { it.forEachIndexed { index, point -> if (index == 0) { moveTo(point.x.toFloat(), point.y.toFloat()) } else { lineTo(point.x.toFloat(), point.y.toFloat()) } } lineTo(it[0].x.toFloat(), it[0].y.toFloat()) } frame.overlayCanvas.drawPath(path, paint) } true } val imageAnalysis = ImageAnalysis.Builder() .build() .apply { setAnalyzer(executor, MlKitAnalyzer( listOf(barcodeScanner!!), COORDINATE_SYSTEM_SENSOR, executor ) { result -> val barcodes = result.getValue(barcodeScanner!!) qrcodePoints = barcodes?.takeIf { it.size > 0}?.get(0)?.cornerPoints } ) } val useCaseGroup = UseCaseGroup.Builder() .addUseCase(preview) .addUseCase(imageAnalysis) .addEffect(qrcodeBoxEffect) .build() cameraProvider.bindToLifecycle( lifecycleOwner, cameraSelector, usecaseGroup) }
Want to add stunning camera effects to your CameraX app? Now you can tap into the power of Media3's rich effects framework! This exciting integration allows you to apply Media3 effects to your CameraX output, including Preview, VideoCapture, and ImageCapture.
This means you can easily enhance your app with a wide range of professional-grade effects, from blurs and color filters to transitions and more. To get started, simply use the new androidx.camera:media3:media3-effect artifact.
Here's a quick example of how to apply a Gaussian blur to your camera output:
// in build.gradle implementation ("androidx.camera.media3:media3-effect:1.0.0-alpha01") implementation ("androidx.media3:media3-effect:1.5.0") import androidx.camera.media3.effect.Media3Effect val media3Effect = Media3Effect( requireContext(), PREVIEW or VIDEO_CAPTURE or IMAGE_CAPTURE, mainThreadExecutor(), {} ) // use grayscale effect media3Effect.setEffects(listOf(RgbFilter.createGrayscaleFilter()) cameraController.setEffects(setOf(media3Effect)) // or using UseCaseGroup API
Here is what the effect looks like:
Taking selfies in low light just got easier with CameraX 1.4.0! This release introduces a powerful new feature: screen flash. Instead of relying on a traditional LED flash which most selfie cameras don’t have, screen flash cleverly utilizes your phone's display. By momentarily turning the screen bright white, it provides a burst of illumination that helps capture clear and vibrant selfies even in challenging lighting conditions.
Integrating screen flash into your CameraX app is flexible and straightforward. You have two main options:
1. Implement the ScreenFlash interface: This gives you full control over the screen flash behavior. You can customize the color, intensity, duration, and any other aspect of the flash. This is ideal if you need a highly tailored solution.
2. Use the built-in implementation: For a quick and easy solution, leverage the pre-built screen flash functionality in ScreenFlashView or PreviewView. This implementation handles all the heavy lifting for you.
If you're already using PreviewView in your app, enabling screen flash is incredibly simple. Just enable it directly on the PreviewView instance. If you need more control or aren't using PreviewView, you can use ScreenFlashView directly.
Here's a code example demonstrating how to enable screen flash:
// case 1: PreviewView + CameraX core API. previewView.setScreenFlashWindow(activity.getWindow()); imageCapture.screenFlash = previewView.screenFlash imageCapture.setFlashMode(ImageCapture.FLASH_MODE_SCREEN) // case 2: PreviewView + CameraController previewView.setScreenFlashWindow(activity.getWindow()); cameraController.setImageCaptureFlashMode(ImageCapture.FLASH_MODE_SCREEN); // case 3 : use ScreenFlashView screenFlashView.setScreenFlashWindow(activity.getWindow()); imageCapture.setScreenFlash(screenFlashView.getScreenFlash()); imageCapture.setFlashMode(ImageCapture.FLASH_MODE_SCREEN);
Camera Extensions APIs aim to help apps to access the cutting-edge capabilities previously available only on built-in camera apps. And the ecosystem is growing rapidly! In 2024, we've seen major players like Pixel, Samsung, Xiaomi, Oppo, OnePlus, Vivo, and Honor all embrace Camera Extensions, particularly for Night Mode and Bokeh Mode. CameraX 1.4.0 takes this even further by adding support for brand-new Android 15 Camera Extensions features, including:
Below is an example of the improved UX that uses postview and capture process progress features on Samsung S24 Ultra.
Interested to know how this can be implemented? See the sample code below:
val extensionsCameraSelector = extensionsManager .getExtensionEnabledCameraSelector(DEFAULT_BACK_CAMERA, extensionMode) val isPostviewSupported = ImageCapture.getImageCaptureCapabilities( cameraProvider.getCameraInfo(extensionsCameraSelector) ).isPostviewSupported val imageCapture = ImageCapture.Builder().apply { setPostviewEnabled(isPostviewSupported) }.build() imageCapture.takePicture(outputfileOptions, executor, object : OnImageSavedCallback { override fun onImageSaved(outputFileResults: OutputFileResults) { // final image saved. } override fun onPostviewBitmapAvailable(bitmap: Bitmap) { // Postview bitmap is available. } override fun onCaptureProcessProgressed(progress: Int) { // capture process progress update } }
Important: If your app ran into the CameraX Extensions issue on Pixel 9 series devices, please use CameraX 1.4.1 instead. This release fixes a critical issue that prevented Night Mode from working correctly with takePicture.
We hope you enjoy this new release. Our mission is to make camera development a joy, removing the friction and pain points so you can focus on innovation. With CameraX, you can easily harness the power of Android's camera capabilities and build truly amazing app experiences.
Have questions or want to connect with the CameraX team? Join the CameraX developer discussion group or file a bug report:
We can’t wait to see what you create!