Skip to content

Add support for HEIC_ULTRAHDR #282

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 2 commits 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
3 changes: 2 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ plugins {

android {
namespace = "com.example.platform"
compileSdk = 35
compileSdkPreview = "Baklava"


defaultConfig {
applicationId = "com.example.platform"
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ android.defaults.buildfeatures.aidl=false
android.defaults.buildfeatures.renderscript=false
android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false
android.suppressUnsupportedCompileSdk=35
4 changes: 4 additions & 0 deletions samples/camera/camera2/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ android {
}

viewBinding.isEnabled = true
compileSdkPreview = "Baklava"
defaultConfig {
targetSdkPreview = "Baklava"
}
Comment on lines +36 to +39

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a comment explaining why compileSdkPreview and targetSdkPreview are being used. This can help future developers understand the purpose of these settings and avoid accidental removal or modification.

Also, ensure that using a preview SDK doesn't introduce unexpected behavior or compatibility issues on older devices. It might be useful to add a check to ensure that the app gracefully handles cases where the preview SDK is not available.

}

dependencies {
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)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The @RequiresApi annotation is good for ensuring that this class is only used on devices running Android Baklava or higher. However, consider adding a fallback mechanism or a check at runtime to handle cases where the API is not available. This could involve disabling the HEIC_ULTRAHDR capture option or providing a message to the user.

class Camera2HeicUltraHDRCapture : Camera2UltraHDRCapture() {
override val ULTRAHDR_FORMAT = ImageFormat.HEIC_ULTRAHDR
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,9 @@ import android.view.SurfaceView
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
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 +74,16 @@ import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
class Camera2UltraHDRCapture : Fragment() {
@RequiresApi(Build.VERSION_CODES.BAKLAVA)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the Camera2HeicUltraHDRCapture.kt file, ensure that the app gracefully handles cases where the API level is not met. Consider providing a fallback mechanism or a user-friendly message.

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,9 @@ 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
val output = saveResult(result)
Log.d(TAG, "Image saved: ${output.absolutePath}")
Comment on lines +327 to 329

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The EXIF metadata update is removed. Is this intentional? If so, please add a comment explaining why the EXIF metadata update is no longer needed for UltraHDR images. If it was unintentional, please re-add the EXIF metadata update logic.

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 +351,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 +495,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 +581,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 +632,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")
fileType = when (format) {
ImageFormat.JPEG_R -> ".jpg"
else -> ".heic"
}
return File(context.filesDir, "IMG_${sdf.format(Date())}" + fileType)
}
}
}
}