@@ -3,6 +3,8 @@ package app.grapheneos.camera
33import android.annotation.SuppressLint
44import android.content.Context
55import android.content.SharedPreferences
6+ import android.graphics.ImageFormat
7+ import android.hardware.camera2.CameraCharacteristics
68import android.net.Uri
79import android.os.Build
810import android.provider.MediaStore
@@ -32,6 +34,7 @@ import androidx.camera.core.featuregroup.GroupableFeature
3234import androidx.camera.core.resolutionselector.AspectRatioStrategy
3335import androidx.camera.core.resolutionselector.ResolutionSelector
3436import androidx.camera.core.resolutionselector.ResolutionStrategy
37+ import androidx.camera.camera2.interop.Camera2CameraInfo
3538import androidx.camera.extensions.ExtensionMode
3639import androidx.camera.extensions.ExtensionsManager
3740import androidx.camera.lifecycle.ProcessCameraProvider
@@ -56,6 +59,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
5659import com.google.zxing.BarcodeFormat
5760import java.util.concurrent.ExecutionException
5861import java.util.concurrent.Executors
62+ import kotlin.math.log
5963
6064// note that enum constant name is used as a name of a SharedPreferences instance
6165enum class CameraMode (val extensionMode : Int , val uiName : Int ) {
@@ -119,6 +123,8 @@ class CamConfig(private val mActivity: MainActivity) {
119123
120124 const val WAIT_FOR_FOCUS_LOCK = " wait_for_focus_lock"
121125
126+ const val CAPTURE_RESOLUTION = " capture_resolution"
127+
122128 // const val IMAGE_FILE_FORMAT = "image_quality"
123129 // const val VIDEO_FILE_FORMAT = "video_quality"
124130 }
@@ -179,6 +185,14 @@ class CamConfig(private val mActivity: MainActivity) {
179185
180186 const val DEFAULT_LENS_FACING = CameraSelector .LENS_FACING_BACK
181187
188+ fun aspectRatioToWidthHeight (aspectRatio : Int ): Pair <Int , Int > {
189+ return when (aspectRatio) {
190+ AspectRatio .RATIO_16_9 -> Pair (16 , 9 )
191+ AspectRatio .RATIO_4_3 -> Pair (4 , 3 )
192+ else -> throw IllegalArgumentException (" Unknown aspect ratio: $aspectRatio " )
193+ }
194+ }
195+
182196 val commonFormats = arrayOf(
183197 BarcodeFormat .AZTEC ,
184198 BarcodeFormat .QR_CODE ,
@@ -722,6 +736,8 @@ class CamConfig(private val mActivity: MainActivity) {
722736
723737 if (isVideoMode) {
724738 mActivity.settingsDialog.reloadQualities()
739+ } else {
740+ mActivity.settingsDialog.reloadResolutions()
725741 }
726742
727743 if (lensFacing == CameraSelector .LENS_FACING_FRONT ) {
@@ -923,6 +939,27 @@ class CamConfig(private val mActivity: MainActivity) {
923939 }
924940 }
925941
942+ var captureResolution: Size ?
943+ get() {
944+ val value = commonPref.getString(SettingValues .Key .CAPTURE_RESOLUTION , null )
945+ if (value.isNullOrEmpty()) return null
946+ return try {
947+ val parts = value.split(" x" )
948+ Size (parts[0 ].toInt(), parts[1 ].toInt())
949+ } catch (e: Exception ) {
950+ null
951+ }
952+ }
953+ set(value) {
954+ commonPref.edit {
955+ if (value == null ) {
956+ remove(SettingValues .Key .CAPTURE_RESOLUTION )
957+ } else {
958+ putString(SettingValues .Key .CAPTURE_RESOLUTION , " ${value.width} x${value.height} " )
959+ }
960+ }
961+ }
962+
926963 var selectHighestResolution: Boolean
927964 get() {
928965 return commonPref.getBoolean(
@@ -989,13 +1026,37 @@ class CamConfig(private val mActivity: MainActivity) {
9891026 } else {
9901027 AspectRatio .RATIO_16_9
9911028 }
1029+ // Clear capture resolution since available resolutions depend on aspect ratio
1030+ captureResolution = null
9921031 startCamera(true )
9931032 }
9941033
9951034 private fun getCurrentCameraInfo () : CameraInfo {
9961035 return cameraProvider!! .getCameraInfo(cameraSelector)
9971036 }
9981037
1038+ fun getAvailableImageResolutions (): List <Size > {
1039+ val cameraInfo = camera?.cameraInfo ? : return emptyList()
1040+ val camera2Info = Camera2CameraInfo .from(cameraInfo)
1041+ val characteristics = camera2Info.getCameraCharacteristic(
1042+ CameraCharacteristics .SCALER_STREAM_CONFIGURATION_MAP
1043+ ) ? : return emptyList()
1044+
1045+ val sizes = characteristics.getOutputSizes(ImageFormat .JPEG ) ? : return emptyList()
1046+
1047+ // Filter by current aspect ratio with 2% tolerance
1048+ val targetRatio = when (aspectRatio) {
1049+ AspectRatio .RATIO_16_9 -> 16.0 / 9.0
1050+ AspectRatio .RATIO_4_3 -> 4.0 / 3.0
1051+ else -> 4.0 / 3.0
1052+ }
1053+
1054+ return sizes.filter { size ->
1055+ val ratio = size.width.toDouble() / size.height.toDouble()
1056+ kotlin.math.abs(ratio - targetRatio) / targetRatio < 0.02
1057+ }.sortedByDescending { it.width * it.height }
1058+ }
1059+
9991060 fun toggleCameraSelector () {
10001061
10011062 // Manually switch to the opposite lens facing
@@ -1229,6 +1290,12 @@ class CamConfig(private val mActivity: MainActivity) {
12291290 resolutionSelectorBuilder.setAllowedResolutionMode(ResolutionSelector .PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE )
12301291 }
12311292
1293+ captureResolution?.let { size ->
1294+ resolutionSelectorBuilder.setResolutionStrategy(
1295+ ResolutionStrategy (size, ResolutionStrategy .FALLBACK_RULE_NONE )
1296+ )
1297+ }
1298+
12321299 it.setResolutionSelector(resolutionSelectorBuilder.build())
12331300
12341301 it.setFlashMode(flashMode)
0 commit comments