From 959d6a18e850796d626f90347ef4e8991afec038 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Wed, 12 Mar 2025 23:24:48 +0530 Subject: [PATCH 1/6] Add UI for image format mode --- app/src/main/res/layout/settings.xml | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/src/main/res/layout/settings.xml b/app/src/main/res/layout/settings.xml index 2a0671f6..cf76486b 100644 --- a/app/src/main/res/layout/settings.xml +++ b/app/src/main/res/layout/settings.xml @@ -280,6 +280,39 @@ + + + + + + + + + + + Date: Wed, 12 Mar 2025 23:27:01 +0530 Subject: [PATCH 2/6] Disable image format selection UI (while a video is being recorded) --- .../main/java/app/grapheneos/camera/capturer/VideoCapturer.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt index 6a505d0c..dc0086d6 100644 --- a/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt +++ b/app/src/main/java/app/grapheneos/camera/capturer/VideoCapturer.kt @@ -296,6 +296,7 @@ class VideoCapturer(private val mActivity: MainActivity) { animator.start() mActivity.settingsDialog.videoQualitySpinner.isEnabled = false + mActivity.settingsDialog.imageFormatSpinner.isEnabled = false mActivity.settingsDialog.enableEISToggle.isEnabled = false mActivity.flipCamIcon.setImageResource(R.drawable.pause) @@ -351,6 +352,7 @@ class VideoCapturer(private val mActivity: MainActivity) { mActivity.flipCamIcon.setImageResource(R.drawable.flip_camera) mActivity.settingsDialog.videoQualitySpinner.isEnabled = true + mActivity.settingsDialog.imageFormatSpinner.isEnabled = true mActivity.settingsDialog.enableEISToggle.isEnabled = true if (mActivity !is VideoCaptureActivity) { From 0c722bd0d6f447b0c04bd959cd8ad51da5076a47 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Wed, 12 Mar 2025 23:29:07 +0530 Subject: [PATCH 3/6] Add string resource (for image format feature) --- app/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa6ae1e..20257eff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -187,6 +187,7 @@ Use ZSL in Latency mode Uses Zero Shutter Lag (ZSL) in Latency mode for faster capture. Certain devices may have a buggy implementation for this. Unable to request for audio permission in between a recording + Image Format Tap to mute audio Tap to unmute audio From c277ef84402bc59dbb8c4a396d9028ff543c9de5 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Wed, 12 Mar 2025 23:29:46 +0530 Subject: [PATCH 4/6] Add code to support image format setting UI --- .../grapheneos/camera/ui/SettingsDialog.kt | 108 +++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt index 7a09d5c1..004c7e9e 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt @@ -63,6 +63,10 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : private var gridToggle: ImageView var videoQualitySpinner: Spinner private lateinit var vQAdapter: ArrayAdapter + + var imageFormatSpinner: Spinner + private lateinit var imageFormatAdapter: ArrayAdapter + private var focusTimeoutSpinner: Spinner private var timerSpinner: Spinner @@ -84,6 +88,7 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : private var enableEISSetting: View private var selfIlluminationSetting: View private var videoQualitySetting: View + private var imageFormatSetting: View private var timerSetting: View var settingsFrame: View @@ -226,19 +231,36 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( - p0: AdapterView<*>?, - p1: View?, + parent: AdapterView<*>?, + view: View?, position: Int, - p3: Long + id: Long ) { val choice = vQAdapter.getItem(position) as String updateVideoQuality(choice) } - override fun onNothingSelected(p0: AdapterView<*>?) {} + override fun onNothingSelected(parent: AdapterView<*>?) {} } + imageFormatSpinner = binding.imageFormatSpinner + + imageFormatSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val choice = imageFormatAdapter.getItem(position) as String + updateOutputImageFormat(choice) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + + } + qRadio = binding.qualityRadio lRadio = binding.latencyRadio @@ -333,6 +355,7 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : enableEISSetting = binding.enableEisSetting selfIlluminationSetting = binding.selfIlluminationSetting videoQualitySetting = binding.videoQualitySetting + imageFormatSetting = binding.imageFormatSetting timerSetting = binding.timerSetting includeAudioToggle = binding.includeAudioSwitch @@ -515,6 +538,21 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : } } + fun updateOutputImageFormat(choice: String, resCam: Boolean = true) { + + val imageFormat = titleToImageFormat(choice) + + if (imageFormat == camConfig.outputImageFormat) return + + camConfig.outputImageFormat = imageFormat + + if (resCam) { + camConfig.startCamera(true) + } else { + imageFormatSpinner.setSelection(getAvailableQTitles().indexOf(choice)) + } + } + fun titleToQuality(title: String): Quality { return when (title) { "2160p (UHD)" -> Quality.UHD @@ -740,6 +778,40 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : } } + private fun getAvailableImageFormatTitles(): List { + val titles = arrayListOf() + + camConfig.getAvailableImageFormats().forEach { format -> + titles.add(getTitleForImageFormat(format)) + } + + return titles + } + + fun getTitleForImageFormat(format: Int): String { + return when (format) { + ImageCapture.OUTPUT_FORMAT_RAW -> "DNG" + ImageCapture.OUTPUT_FORMAT_JPEG -> "JPEG" + ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR -> "JPEG (UHD)" + else -> { + Log.e("TAG", "Unknown image format constant: $format") + "Unknown" + } + } + } + + fun titleToImageFormat(title: String): Int { + return when (title) { + "DNG" -> ImageCapture.OUTPUT_FORMAT_RAW + "JPEG" -> ImageCapture.OUTPUT_FORMAT_JPEG + "JPEG (UHD)" -> ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR + else -> { + Log.e("TAG", "Unknown title for image format: $title") + ImageCapture.OUTPUT_FORMAT_JPEG + } + } + } + fun updateGridToggleUI() { mActivity.previewGrid.postInvalidate() gridToggle.setImageResource( @@ -788,6 +860,34 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) : slideDialogDown() } + fun reloadSettings() { + if (camConfig.isVideoMode) { + reloadQualities() + } + + reloadImageFormats() + } + + fun reloadImageFormats() { + val imageFormats = getAvailableImageFormatTitles() + + imageFormatAdapter = ArrayAdapter( + mActivity, + android.R.layout.simple_spinner_item, + imageFormats + ) + + imageFormatAdapter.setDropDownViewResource( + android.R.layout.simple_spinner_dropdown_item + ) + + imageFormatSpinner.adapter = imageFormatAdapter + + imageFormatSpinner.setSelection( + camConfig.getAvailableImageFormats().indexOf(camConfig.outputImageFormat) + ) + } + fun reloadQualities() { val titles = getAvailableQTitles() From 6f583fc763767c985180f59501e8d0eaccec8096 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Wed, 12 Mar 2025 23:22:38 +0530 Subject: [PATCH 5/6] Add fields and methods in CamConfig (to support image format mode) --- .../java/app/grapheneos/camera/CamConfig.kt | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 128205f5..23d94572 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -51,6 +51,7 @@ import app.grapheneos.camera.ui.showIgnoringShortEdgeMode import app.grapheneos.camera.util.edit import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.zxing.BarcodeFormat +import java.util.Collections import java.util.concurrent.ExecutionException import java.util.concurrent.Executors @@ -111,6 +112,8 @@ class CamConfig(private val mActivity: MainActivity) { const val ENABLE_ZSL = "enable_zsl" + const val OUTPUT_IMAGE_FORMAT = "output_image_format" + // const val IMAGE_FILE_FORMAT = "image_quality" // const val VIDEO_FILE_FORMAT = "video_quality" } @@ -156,6 +159,8 @@ class CamConfig(private val mActivity: MainActivity) { const val ENABLE_ZSL = false + const val OUTPUT_IMAGE_FORMAT = "JPEG" + // const val IMAGE_FILE_FORMAT = "" // const val VIDEO_FILE_FORMAT = "" } @@ -195,6 +200,13 @@ class CamConfig(private val mActivity: MainActivity) { val REAR_CAMERA_SELECTOR = CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build() + + // Array of image formats currently supported by the camera app + private val supportedImageFormats = arrayOf( + ImageCapture.OUTPUT_FORMAT_RAW, + ImageCapture.OUTPUT_FORMAT_JPEG, + ImageCapture.OUTPUT_FORMAT_JPEG_ULTRA_HDR, + ) } var camera: Camera? = null @@ -555,6 +567,40 @@ class CamConfig(private val mActivity: MainActivity) { editor.apply() } + var outputImageFormat : Int + get() { + return mActivity.settingsDialog.titleToImageFormat( + modePref.getString( + outputImageFormatKey, + SettingValues.Default.OUTPUT_IMAGE_FORMAT + )!! + ) + } + set(format) { + val formatTitle = mActivity.settingsDialog.getTitleForImageFormat(format) + + val editor = modePref.edit() + editor.putString(outputImageFormatKey, formatTitle) + editor.apply() + } + + private val outputImageFormatKey: String + get() { + val pf = if (lensFacing == CameraSelector.LENS_FACING_FRONT) { + "FRONT" + } else { + "BACK" + } + + return "${SettingValues.Key.OUTPUT_IMAGE_FORMAT}_$pf" + } + + fun getAvailableImageFormats() : Set { + val cameraInfo = camera?.cameraInfo ?: return Collections.emptySet() + return ImageCapture.getImageCaptureCapabilities(cameraInfo) + .supportedOutputFormats.filter { it in supportedImageFormats }.toSet() + } + val isZslSupported : Boolean by lazy { camera!!.cameraInfo.isZslSupported } @@ -710,9 +756,7 @@ class CamConfig(private val mActivity: MainActivity) { putBoolean(SettingValues.Key.GEO_TAGGING, SettingValues.Default.GEO_TAGGING) } - if (isVideoMode) { - mActivity.settingsDialog.reloadQualities() - } + mActivity.settingsDialog.reloadSettings() if (lensFacing == CameraSelector.LENS_FACING_FRONT) { if (!modePref.contains(SettingValues.Key.SELF_ILLUMINATION)) { @@ -1193,6 +1237,8 @@ class CamConfig(private val mActivity: MainActivity) { it.setJpegQuality(photoQuality) } + it.setOutputFormat(outputImageFormat) + it.build() } From 09e336801b91411f8e66af40f95d544dfffd8d72 Mon Sep 17 00:00:00 2001 From: MHShetty Date: Thu, 20 Mar 2025 00:37:32 +0530 Subject: [PATCH 6/6] Add support for JPEG Ultra HDR --- app/src/main/java/app/grapheneos/camera/capturer/ImageSaver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/grapheneos/camera/capturer/ImageSaver.kt b/app/src/main/java/app/grapheneos/camera/capturer/ImageSaver.kt index 6477716c..9f2e2f25 100644 --- a/app/src/main/java/app/grapheneos/camera/capturer/ImageSaver.kt +++ b/app/src/main/java/app/grapheneos/camera/capturer/ImageSaver.kt @@ -102,7 +102,7 @@ class ImageSaver( cropRect = if (ImageUtil.shouldCropImage(image)) image.cropRect else null val imageFormat = image.format - origJpegBytes = if (imageFormat == ImageFormat.JPEG) { + origJpegBytes = if (imageFormat == ImageFormat.JPEG || imageFormat == ImageFormat.JPEG_R) { ImageUtil.jpegImageToJpegByteArray(image) } else if (imageFormat == ImageFormat.YUV_420_888) { ImageUtil.yuvImageToJpegByteArray(image, cropRect, jpegQuality, 0)