From 5b710a65bc5d9d5c2d3acf582985965f31a8d0dd Mon Sep 17 00:00:00 2001 From: Emilian Peev Date: Mon, 3 Feb 2025 15:49:24 -0800 Subject: [PATCH] Add support for HEIC_ULTRAHDR --- .../com/example/platform/app/SampleDemo.kt | 14 ++++++++- samples/camera/camera2/build.gradle.kts | 4 +-- .../Camera2HeicUltraHDRCapture.kt | 26 ++++++++++++++++ .../imagecapture/Camera2UltraHDRCapture.kt | 31 ++++++++++--------- 4 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2HeicUltraHDRCapture.kt diff --git a/app/src/main/java/com/example/platform/app/SampleDemo.kt b/app/src/main/java/com/example/platform/app/SampleDemo.kt index 0bee52b8..e5b46ac0 100644 --- a/app/src/main/java/com/example/platform/app/SampleDemo.kt +++ b/app/src/main/java/com/example/platform/app/SampleDemo.kt @@ -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 @@ -209,7 +210,18 @@ val SAMPLE_DEMOS by lazy { content = { AndroidFragment() }, ), 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() }, + ), + ComposableSampleDemo( + id = "image-preview", name = "Camera2 Preview", description = "Demonstrates displaying processed pixel data directly from the camera sensor " + "to the screen using Camera2.", diff --git a/samples/camera/camera2/build.gradle.kts b/samples/camera/camera2/build.gradle.kts index 1f540082..bd23dcaa 100644 --- a/samples/camera/camera2/build.gradle.kts +++ b/samples/camera/camera2/build.gradle.kts @@ -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" diff --git a/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2HeicUltraHDRCapture.kt b/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2HeicUltraHDRCapture.kt new file mode 100644 index 00000000..1b819d49 --- /dev/null +++ b/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2HeicUltraHDRCapture.kt @@ -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 +} diff --git a/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2UltraHDRCapture.kt b/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2UltraHDRCapture.kt index 3b0d008e..86b83665 100644 --- a/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2UltraHDRCapture.kt +++ b/samples/camera/camera2/src/main/java/com/example/platform/camera/imagecapture/Camera2UltraHDRCapture.kt @@ -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 @@ -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). */ @@ -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, ) } } @@ -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) { @@ -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 } @@ -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(), @@ -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) { @@ -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) } } } \ No newline at end of file