diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt
index 9f11ea197..84b11d297 100644
--- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt
+++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt
@@ -129,6 +129,8 @@ class CamConfig(private val mActivity: MainActivity) {
const val WAIT_FOR_FOCUS_LOCK = "wait_for_focus_lock"
+ const val MAX_IMAGE_LONG_EDGE = "max_image_long_edge"
+
const val SELF_TIMER_DURATION = "self_timer_duration"
}
@@ -175,6 +177,9 @@ class CamConfig(private val mActivity: MainActivity) {
const val WAIT_FOR_FOCUS_LOCK = false
+ // 0 = no cap (use the largest resolution for the chosen aspect ratio)
+ const val MAX_IMAGE_LONG_EDGE = 0
+
const val SELF_TIMER_DURATION = 0
}
}
@@ -574,6 +579,21 @@ class CamConfig(private val mActivity: MainActivity) {
editor.apply()
}
+ // Cap for the long edge of captured photos, in pixels. 0 = no cap. Keeps documentation
+ // photos small independently of the aspect ratio / sensor.
+ var maxImageLongEdge: Int
+ get() {
+ return commonPref.getInt(
+ SettingValues.Key.MAX_IMAGE_LONG_EDGE,
+ SettingValues.Default.MAX_IMAGE_LONG_EDGE
+ )
+ }
+ set(value) {
+ val editor = commonPref.edit()
+ editor.putInt(SettingValues.Key.MAX_IMAGE_LONG_EDGE, value)
+ editor.apply()
+ }
+
var removeExifAfterCapture: Boolean
get() {
return commonPref.getBoolean(
@@ -1578,10 +1598,29 @@ class CamConfig(private val mActivity: MainActivity) {
val resolutionSelectorBuilder = ResolutionSelector.Builder()
.setAspectRatioStrategy(aspectRatioStrategy)
- if (selectHighestResolution) {
+ // The cap and "highest resolution" are mutually exclusive in the UI; if both
+ // are somehow set, the cap below takes precedence.
+ if (selectHighestResolution && maxImageLongEdge == 0) {
resolutionSelectorBuilder.setAllowedResolutionMode(ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE)
}
+ // Cap the capture resolution when configured (0 = no cap). Picks the largest
+ // supported size whose long edge is <= the cap for the current aspect ratio.
+ val longEdgeCap = maxImageLongEdge
+ if (longEdgeCap > 0) {
+ val target = if (aspectRatio == AspectRatio.RATIO_16_9) {
+ Size(longEdgeCap, longEdgeCap * 9 / 16)
+ } else {
+ Size(longEdgeCap, longEdgeCap * 3 / 4)
+ }
+ resolutionSelectorBuilder.setResolutionStrategy(
+ ResolutionStrategy(
+ target,
+ ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER
+ )
+ )
+ }
+
it.setResolutionSelector(resolutionSelectorBuilder.build())
it.setFlashMode(flashMode)
diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt
index ef5c03b77..12d8710c5 100644
--- a/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt
+++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MoreSettings.kt
@@ -41,6 +41,7 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener {
private lateinit var rootView: View
private lateinit var pQField: EditText
+ private lateinit var mleField: EditText
private val dirPickerHandler = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
@@ -149,6 +150,10 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener {
pQField.filters = arrayOf(NumInputFilter(this))
pQField.setOnEditorActionListener(this)
+ mleField = binding.maxLongEdge
+ mleField.setText(camConfig.maxImageLongEdge.toString())
+ mleField.setOnEditorActionListener(this)
+
val exifToggle = binding.removeExifToggle
val exifToggleSetting = binding.removeExifSetting
@@ -241,11 +246,21 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener {
highResToggle.isChecked = camConfig.selectHighestResolution
highResToggle.setOnClickListener {
- camConfig.selectHighestResolution = !camConfig.selectHighestResolution
+ camConfig.selectHighestResolution = highResToggle.isChecked
+ // "Highest resolution" and the resolution cap contradict each other; keep them
+ // mutually exclusive by clearing the cap when the highest-res mode is enabled.
+ if (highResToggle.isChecked && camConfig.maxImageLongEdge > 0) {
+ camConfig.maxImageLongEdge = 0
+ mleField.setText("0")
+ }
+ syncResolutionControls()
}
highResSetting.setOnClickListener {
- highResToggle.performClick()
+ // Ignore taps while a resolution cap keeps this row disabled.
+ if (camConfig.maxImageLongEdge == 0) {
+ highResToggle.performClick()
+ }
}
if (!showStorageSettings) {
@@ -268,6 +283,8 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener {
v.setPadding(cutouts.left, 0, cutouts.right, 0)
insets
}
+
+ syncResolutionControls()
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
@@ -327,6 +344,37 @@ open class MoreSettings : AppCompatActivity(), TextView.OnEditorActionListener {
} else {
camConfig.photoQuality = quality
}
+
+ // Dump state of max image resolution (long edge). 0 = no cap; empty/invalid -> revert.
+ val longEdge = mleField.text.toString().toIntOrNull()
+ if (longEdge == null || longEdge < 0) {
+ mleField.setText(camConfig.maxImageLongEdge.toString())
+ if (notifyOnInvalidValue) {
+ showMessage(getString(R.string.invalid_max_long_edge_value))
+ }
+ } else {
+ camConfig.maxImageLongEdge = longEdge
+ // Mutually exclusive with "highest resolution": a cap takes precedence.
+ if (longEdge > 0 && camConfig.selectHighestResolution) {
+ camConfig.selectHighestResolution = false
+ binding.highestResSettingToggle.isChecked = false
+ }
+ }
+
+ syncResolutionControls()
+ }
+
+ // "Highest resolution" and the resolution cap are mutually exclusive; reflect that by
+ // disabling whichever control the other one currently overrides.
+ private fun syncResolutionControls() {
+ val capActive = camConfig.maxImageLongEdge > 0
+ val highestActive = camConfig.selectHighestResolution
+
+ mleField.isEnabled = !highestActive
+ binding.maxLongEdgeSetting.alpha = if (highestActive) 0.5f else 1f
+
+ binding.highestResSettingToggle.isEnabled = !capActive
+ binding.highestResSetting.alpha = if (capActive) 0.5f else 1f
}
override fun onEditorAction(p0: TextView?, id: Int, p2: KeyEvent?): Boolean {
diff --git a/app/src/main/res/layout/more_settings.xml b/app/src/main/res/layout/more_settings.xml
index 4ec526cbb..1c04e845b 100644
--- a/app/src/main/res/layout/more_settings.xml
+++ b/app/src/main/res/layout/more_settings.xml
@@ -388,6 +388,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Wait for Focus Lock
Invalid value set for photo quality
+
+
+ Max resolution
+ Longest edge in pixels for captured photos. 0 = no limit. If the value is below the smallest size the camera supports, that smallest size is used.
+ px
+ Invalid value set for maximum resolution