Skip to content

Add support for HEIC_ULTRAHDR #310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion app/src/main/java/com/example/platform/app/SampleDemo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.example.android.pip.PiPSampleActivity
import com.example.platform.accessibility.ColorContrast
import com.example.platform.accessibility.LiveRegionView
import com.example.platform.accessibility.SpeakableText
import com.example.platform.camera.imagecapture.Camera2HeicUltraHDRCapture
import com.example.platform.camera.imagecapture.Camera2ImageCapture
import com.example.platform.camera.imagecapture.Camera2UltraHDRCapture
import com.example.platform.camera.preview.Camera2Preview
Expand Down Expand Up @@ -209,7 +210,18 @@ val SAMPLE_DEMOS by lazy {
content = { AndroidFragment<Camera2UltraHDRCapture>() },
),
ComposableSampleDemo(
id = "ultrahdr-image-capture",
id = "ultrahdr-heic-image-capture",
name = "HEIC UltraHDR Image Capture",
description = "This sample demonstrates how to capture a 10-bit compressed still image and " +
"store it using the new UltraHDR image format using Camera2.",
documentation = "https://developer.android.com/guide/topics/media/hdr-image-format",
minSdk = android.os.Build.VERSION_CODES.BAKLAVA, // Added minSdk for HEIC_ULTRAHDR
apiSurface = CameraCamera2ApiSurface,
tags = listOf("UltraHDR", "Camera2"),
content = { AndroidFragment<Camera2HeicUltraHDRCapture>() },
),
ComposableSampleDemo(
id = "image-preview",
name = "Camera2 Preview",
description = "Demonstrates displaying processed pixel data directly from the camera sensor "
+ "to the screen using Camera2.",
Expand Down
4 changes: 2 additions & 2 deletions samples/camera/camera2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ plugins {

android {
namespace = "com.example.platform.camera"
compileSdk = 35
compileSdk = 36

defaultConfig {
minSdk = 21
targetSdk = 35
targetSdk = 36
}
kotlinOptions {
jvmTarget = "1.8"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.platform.camera.imagecapture

import android.graphics.ImageFormat
import android.os.Build
import androidx.annotation.RequiresApi

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
class Camera2HeicUltraHDRCapture : Camera2UltraHDRCapture() {
override val ULTRAHDR_FORMAT = ImageFormat.HEIC_ULTRAHDR
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toDrawable
import androidx.core.os.bundleOf
import androidx.exifinterface.media.ExifInterface
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
Expand Down Expand Up @@ -76,14 +75,15 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class Camera2UltraHDRCapture : Fragment() {
open class Camera2UltraHDRCapture : Fragment() {
/**
* Android ViewBinding.
*/
private var _binding: Camera2UltrahdrCaptureBinding? = null
private val binding get() = _binding!!

protected open val ULTRAHDR_FORMAT = ImageFormat.JPEG_R

/**
* Detects, characterizes, and connects to a CameraDevice (used for all camera operations).
*/
Expand Down Expand Up @@ -303,13 +303,13 @@ class Camera2UltraHDRCapture : Fragment() {

private fun setUpImageReader() {
// Initialize an image reader which will be used to capture still photos
val pixelFormat = ImageFormat.JPEG_R
val pixelFormat = ULTRAHDR_FORMAT
val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
configMap?.let { config ->
config.getOutputSizes(pixelFormat).maxByOrNull { it.height * it.width }
?.let { size ->
imageReader = ImageReader.newInstance(
size.width, size.height, pixelFormat, IMAGE_BUFFER_SIZE,
size.width, size.height, ULTRAHDR_FORMAT, IMAGE_BUFFER_SIZE,
)
}
}
Expand All @@ -324,13 +324,11 @@ class Camera2UltraHDRCapture : Fragment() {
takePhoto().use { result ->
Log.d(TAG, "Result received: $result")

// Save the result to disk, update EXIF metadata with orientation info
// Save the result to disk, the previous EXIF orientation approach
// will not work in case of HEIC images so use the corresponding
// Camera2 control path instead.
val output = saveResult(result)
Log.d(TAG, "Image saved: ${output.absolutePath}")
val exif = ExifInterface(output.absolutePath)
exif.setAttribute(ExifInterface.TAG_ORIENTATION, result.orientation.toString())
exif.saveAttributes()
Log.d(TAG, "EXIF metadata saved: ${output.absolutePath}")

// Display the photo taken to user
lifecycleScope.launch(Dispatchers.Main) {
Expand All @@ -355,7 +353,7 @@ class Camera2UltraHDRCapture : Fragment() {
// Query the available output formats.
val formats = c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)?.outputFormats

val canEncodeUltraHDR = formats?.contains(ImageFormat.JPEG_R) ?: false
val canEncodeUltraHDR = formats?.contains(ULTRAHDR_FORMAT) ?: false

return canEncodeUltraHDR
}
Expand Down Expand Up @@ -499,6 +497,7 @@ class Camera2UltraHDRCapture : Fragment() {

val request = session.device.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
.apply { addTarget(imageReader.surface) }
request.set(CaptureRequest.JPEG_ORIENTATION, relativeOrientation.value ?: 0)

session.capture(
request.build(),
Expand Down Expand Up @@ -584,7 +583,7 @@ class Camera2UltraHDRCapture : Fragment() {
val buffer = result.image.planes[0].buffer
val bytes = ByteArray(buffer.remaining()).apply { buffer.get(this) }
try {
val output = createFile(requireContext())
val output = createFile(requireContext(), ULTRAHDR_FORMAT)
FileOutputStream(output).use { it.write(bytes) }
cont.resume(output)
} catch (exc: IOException) {
Expand Down Expand Up @@ -635,9 +634,13 @@ class Camera2UltraHDRCapture : Fragment() {
*
* @return [File] created.
*/
private fun createFile(context: Context): File {
private fun createFile(context: Context, format: Int): File {
val sdf = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", Locale.US)
return File(context.filesDir, "IMG_${sdf.format(Date())}.jpg")
val fileType = when (format) {
ImageFormat.JPEG_R -> ".jpg"
else -> ".heic"
}
return File(context.filesDir, "IMG_${sdf.format(Date())}" + fileType)
}
}
}
Loading