Camera app: improve default setting handling, only save when non-default

This commit is contained in:
Thomas Farstrike
2025-12-02 15:00:56 +01:00
parent 27d1af9931
commit 52a4fccd9e
4 changed files with 217 additions and 80 deletions
+1
View File
@@ -4,6 +4,7 @@
- Fri3d Camp 2024 Badge: improve battery monitor calibration to fix 0.1V delta
- API: improve and cleanup animations
- API: SharedPreferences: add erase_all() function
- API: add defaults handling to SharedPreferences and only save non-defaults
- About app: add free, used and total storage space info
- AppStore app: remove unnecessary scrollbar over publisher's name
- Camera app: massive overhaul!
+72
View File
@@ -446,6 +446,78 @@ prefs.edit().put_int("brightness", -1).commit()
# brightness is no longer stored in config.json, saves space
```
**Multi-mode apps with merged defaults**:
Apps with multiple operating modes can define separate defaults dictionaries and merge them based on the current mode. The camera app demonstrates this pattern with normal and QR scanning modes:
```python
# Define defaults in your settings class
class CameraSettingsActivity:
# Common defaults shared by all modes
COMMON_DEFAULTS = {
"brightness": 1,
"contrast": 0,
"saturation": 0,
"hmirror": False,
"vflip": True,
# ... 20 more common settings
}
# Normal mode specific defaults
NORMAL_DEFAULTS = {
"resolution_width": 240,
"resolution_height": 240,
"colormode": True,
"ae_level": 0,
"raw_gma": True,
}
# QR scanning mode specific defaults
SCANQR_DEFAULTS = {
"resolution_width": 960,
"resolution_height": 960,
"colormode": False, # Grayscale for better QR detection
"ae_level": 2, # Higher exposure
"raw_gma": False, # Better contrast
}
# Merge defaults based on mode when initializing
def load_settings(self):
if self.scanqr_mode:
# Merge common + scanqr defaults
scanqr_defaults = {}
scanqr_defaults.update(CameraSettingsActivity.COMMON_DEFAULTS)
scanqr_defaults.update(CameraSettingsActivity.SCANQR_DEFAULTS)
self.prefs = SharedPreferences(
self.PACKAGE,
filename="config_scanqr.json",
defaults=scanqr_defaults
)
else:
# Merge common + normal defaults
normal_defaults = {}
normal_defaults.update(CameraSettingsActivity.COMMON_DEFAULTS)
normal_defaults.update(CameraSettingsActivity.NORMAL_DEFAULTS)
self.prefs = SharedPreferences(
self.PACKAGE,
defaults=normal_defaults
)
# Now all get_*() calls can omit default arguments
width = self.prefs.get_int("resolution_width") # Mode-specific default
brightness = self.prefs.get_int("brightness") # Common default
```
**Benefits of this pattern**:
- Single source of truth for all 30 camera settings defaults
- Mode-specific config files (`config.json`, `config_scanqr.json`)
- ~90% reduction in config file size (only non-default values stored)
- Eliminates hardcoded defaults throughout the codebase
- No need to pass defaults to every `get_int()`/`get_bool()` call
- Self-documenting code with clear defaults dictionaries
**Note**: Use `dict.update()` instead of `{**dict1, **dict2}` for MicroPython compatibility (dictionary unpacking syntax not supported).
**Intent system**: Launch activities and pass data
```python
from mpos.content.intent import Intent
@@ -188,16 +188,30 @@ class CameraApp(Activity):
if self.scanqr_mode:
print("loading scanqr settings...")
if not self.scanqr_prefs:
self.scanqr_prefs = SharedPreferences(self.PACKAGE, filename=self.SCANQR_CONFIG)
self.width = self.scanqr_prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_SCANQR_WIDTH)
self.height = self.scanqr_prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_SCANQR_HEIGHT)
self.colormode = self.scanqr_prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_SCANQR_COLORMODE)
# Merge common and scanqr-specific defaults
scanqr_defaults = {}
scanqr_defaults.update(CameraSettingsActivity.COMMON_DEFAULTS)
scanqr_defaults.update(CameraSettingsActivity.SCANQR_DEFAULTS)
self.scanqr_prefs = SharedPreferences(
self.PACKAGE,
filename=self.SCANQR_CONFIG,
defaults=scanqr_defaults
)
# Defaults come from constructor, no need to pass them here
self.width = self.scanqr_prefs.get_int("resolution_width")
self.height = self.scanqr_prefs.get_int("resolution_height")
self.colormode = self.scanqr_prefs.get_bool("colormode")
else:
if not self.prefs:
self.prefs = SharedPreferences(self.PACKAGE)
self.width = self.prefs.get_int("resolution_width", CameraSettingsActivity.DEFAULT_WIDTH)
self.height = self.prefs.get_int("resolution_height", CameraSettingsActivity.DEFAULT_HEIGHT)
self.colormode = self.prefs.get_bool("colormode", CameraSettingsActivity.DEFAULT_COLORMODE)
# Merge common and normal-specific defaults
normal_defaults = {}
normal_defaults.update(CameraSettingsActivity.COMMON_DEFAULTS)
normal_defaults.update(CameraSettingsActivity.NORMAL_DEFAULTS)
self.prefs = SharedPreferences(self.PACKAGE, defaults=normal_defaults)
# Defaults come from constructor, no need to pass them here
self.width = self.prefs.get_int("resolution_width")
self.height = self.prefs.get_int("resolution_height")
self.colormode = self.prefs.get_bool("colormode")
def update_preview_image(self):
self.image_dsc = lv.image_dsc_t({
@@ -467,93 +481,95 @@ class CameraApp(Activity):
try:
# Basic image adjustments
brightness = prefs.get_int("brightness", CameraSettingsActivity.DEFAULTS.get("brightness"))
brightness = prefs.get_int("brightness")
cam.set_brightness(brightness)
contrast = prefs.get_int("contrast", 0)
contrast = prefs.get_int("contrast")
cam.set_contrast(contrast)
saturation = prefs.get_int("saturation", 0)
saturation = prefs.get_int("saturation")
cam.set_saturation(saturation)
# Orientation
hmirror = prefs.get_bool("hmirror", False)
hmirror = prefs.get_bool("hmirror")
cam.set_hmirror(hmirror)
vflip = prefs.get_bool("vflip", True)
vflip = prefs.get_bool("vflip")
cam.set_vflip(vflip)
# Special effect
special_effect = prefs.get_int("special_effect", 0)
special_effect = prefs.get_int("special_effect")
cam.set_special_effect(special_effect)
# Exposure control (apply master switch first, then manual value)
exposure_ctrl = prefs.get_bool("exposure_ctrl", True)
exposure_ctrl = prefs.get_bool("exposure_ctrl")
cam.set_exposure_ctrl(exposure_ctrl)
if not exposure_ctrl:
aec_value = prefs.get_int("aec_value", 300)
aec_value = prefs.get_int("aec_value")
cam.set_aec_value(aec_value)
ae_level = prefs.get_int("ae_level", 2 if self.scanqr_mode else 0)
# Mode-specific default comes from constructor
ae_level = prefs.get_int("ae_level")
cam.set_ae_level(ae_level)
aec2 = prefs.get_bool("aec2", False)
aec2 = prefs.get_bool("aec2")
cam.set_aec2(aec2)
# Gain control (apply master switch first, then manual value)
gain_ctrl = prefs.get_bool("gain_ctrl", True)
gain_ctrl = prefs.get_bool("gain_ctrl")
cam.set_gain_ctrl(gain_ctrl)
if not gain_ctrl:
agc_gain = prefs.get_int("agc_gain", 0)
agc_gain = prefs.get_int("agc_gain")
cam.set_agc_gain(agc_gain)
gainceiling = prefs.get_int("gainceiling", 0)
gainceiling = prefs.get_int("gainceiling")
cam.set_gainceiling(gainceiling)
# White balance (apply master switch first, then mode)
whitebal = prefs.get_bool("whitebal", True)
whitebal = prefs.get_bool("whitebal")
cam.set_whitebal(whitebal)
if not whitebal:
wb_mode = prefs.get_int("wb_mode", 0)
wb_mode = prefs.get_int("wb_mode")
cam.set_wb_mode(wb_mode)
awb_gain = prefs.get_bool("awb_gain", True)
awb_gain = prefs.get_bool("awb_gain")
cam.set_awb_gain(awb_gain)
# Sensor-specific settings (try/except for unsupported sensors)
try:
sharpness = prefs.get_int("sharpness", 0)
sharpness = prefs.get_int("sharpness")
cam.set_sharpness(sharpness)
except:
pass # Not supported on OV2640?
try:
denoise = prefs.get_int("denoise", 0)
denoise = prefs.get_int("denoise")
cam.set_denoise(denoise)
except:
pass # Not supported on OV2640?
# Advanced corrections
colorbar = prefs.get_bool("colorbar", False)
colorbar = prefs.get_bool("colorbar")
cam.set_colorbar(colorbar)
dcw = prefs.get_bool("dcw", True)
dcw = prefs.get_bool("dcw")
cam.set_dcw(dcw)
bpc = prefs.get_bool("bpc", False)
bpc = prefs.get_bool("bpc")
cam.set_bpc(bpc)
wpc = prefs.get_bool("wpc", True)
wpc = prefs.get_bool("wpc")
cam.set_wpc(wpc)
raw_gma = prefs.get_bool("raw_gma", False if self.scanqr_mode else True)
# Mode-specific default comes from constructor
raw_gma = prefs.get_bool("raw_gma")
print(f"applying raw_gma: {raw_gma}")
cam.set_raw_gma(raw_gma)
lenc = prefs.get_bool("lenc", True)
lenc = prefs.get_bool("lenc")
cam.set_lenc(lenc)
# JPEG quality (only relevant for JPEG format)
@@ -31,8 +31,56 @@ class CameraSettingsActivity(Activity):
scale_default=False
binning_default=False
DEFAULTS = {
"brightness": 1,
# Common defaults shared by both normal and scanqr modes (25 settings)
COMMON_DEFAULTS = {
# Basic image adjustments
"brightness": 0,
"contrast": 0,
"saturation": 0,
# Orientation
"hmirror": False,
"vflip": True,
# Visual effects
"special_effect": 0,
# Exposure control
"exposure_ctrl": True,
"aec_value": 300,
"aec2": False,
# Gain control
"gain_ctrl": True,
"agc_gain": 0,
"gainceiling": 0,
# White balance
"whitebal": True,
"wb_mode": 0,
"awb_gain": True,
# Sensor-specific
"sharpness": 0,
"denoise": 0,
# Advanced corrections
"colorbar": False,
"dcw": True,
"bpc": False,
"wpc": True,
"lenc": True,
}
# Normal mode specific defaults (5 settings)
NORMAL_DEFAULTS = {
"resolution_width": DEFAULT_WIDTH, # 240
"resolution_height": DEFAULT_HEIGHT, # 240
"colormode": DEFAULT_COLORMODE, # True
"ae_level": 0,
"raw_gma": True,
}
# Scanqr mode specific defaults (5 settings, optimized for QR detection)
SCANQR_DEFAULTS = {
"resolution_width": DEFAULT_SCANQR_WIDTH, # 960
"resolution_height": DEFAULT_SCANQR_HEIGHT, # 960
"colormode": DEFAULT_SCANQR_COLORMODE, # False (grayscale)
"ae_level": 2, # Higher exposure compensation
"raw_gma": False, # Disable gamma for better contrast
}
# Resolution options for desktop/webcam
@@ -273,14 +321,14 @@ class CameraSettingsActivity(Activity):
tab.set_style_pad_all(1, 0)
# Color Mode
colormode = prefs.get_bool("colormode", False if self.scanqr_mode else True)
colormode = prefs.get_bool("colormode")
checkbox, cont = self.create_checkbox(tab, "Color Mode (slower)", colormode, "colormode")
self.ui_controls["colormode"] = checkbox
# Resolution dropdown
print(f"self.scanqr_mode: {self.scanqr_mode}")
current_resolution_width = prefs.get_string("resolution_width", self.DEFAULT_SCANQR_WIDTH if self.scanqr_mode else self.DEFAULT_WIDTH)
current_resolution_height = prefs.get_string("resolution_height", self.DEFAULT_SCANQR_HEIGHT if self.scanqr_mode else self.DEFAULT_HEIGHT)
current_resolution_width = prefs.get_int("resolution_width")
current_resolution_height = prefs.get_int("resolution_height")
dropdown_value = f"{current_resolution_width}x{current_resolution_height}"
print(f"looking for {dropdown_value}")
resolution_idx = 0
@@ -295,27 +343,27 @@ class CameraSettingsActivity(Activity):
self.ui_controls["resolution"] = dropdown
# Brightness
brightness = prefs.get_int("brightness", self.DEFAULTS.get("brightness"))
brightness = prefs.get_int("brightness")
slider, label, cont = self.create_slider(tab, "Brightness", -2, 2, brightness, "brightness")
self.ui_controls["brightness"] = slider
# Contrast
contrast = prefs.get_int("contrast", 0)
contrast = prefs.get_int("contrast")
slider, label, cont = self.create_slider(tab, "Contrast", -2, 2, contrast, "contrast")
self.ui_controls["contrast"] = slider
# Saturation
saturation = prefs.get_int("saturation", 0)
saturation = prefs.get_int("saturation")
slider, label, cont = self.create_slider(tab, "Saturation", -2, 2, saturation, "saturation")
self.ui_controls["saturation"] = slider
# Horizontal Mirror
hmirror = prefs.get_bool("hmirror", False)
hmirror = prefs.get_bool("hmirror")
checkbox, cont = self.create_checkbox(tab, "Horizontal Mirror", hmirror, "hmirror")
self.ui_controls["hmirror"] = checkbox
# Vertical Flip
vflip = prefs.get_bool("vflip", True)
vflip = prefs.get_bool("vflip")
checkbox, cont = self.create_checkbox(tab, "Vertical Flip", vflip, "vflip")
self.ui_controls["vflip"] = checkbox
@@ -327,17 +375,17 @@ class CameraSettingsActivity(Activity):
tab.set_style_pad_all(1, 0)
# Auto Exposure Control (master switch)
exposure_ctrl = prefs.get_bool("exposure_ctrl", True)
exposure_ctrl = prefs.get_bool("exposure_ctrl")
aec_checkbox, cont = self.create_checkbox(tab, "Auto Exposure", exposure_ctrl, "exposure_ctrl")
self.ui_controls["exposure_ctrl"] = aec_checkbox
# Manual Exposure Value (dependent)
aec_value = prefs.get_int("aec_value", 300)
aec_value = prefs.get_int("aec_value")
me_slider, label, me_cont = self.create_slider(tab, "Manual Exposure", 0, 1200, aec_value, "aec_value")
self.ui_controls["aec_value"] = me_slider
# Auto Exposure Level (dependent)
ae_level = prefs.get_int("ae_level", 0)
ae_level = prefs.get_int("ae_level")
ae_slider, label, ae_cont = self.create_slider(tab, "Auto Exposure Level", -2, 2, ae_level, "ae_level")
self.ui_controls["ae_level"] = ae_slider
@@ -355,17 +403,17 @@ class CameraSettingsActivity(Activity):
exposure_ctrl_changed()
# Night Mode (AEC2)
aec2 = prefs.get_bool("aec2", False)
aec2 = prefs.get_bool("aec2")
checkbox, cont = self.create_checkbox(tab, "Night Mode (AEC2)", aec2, "aec2")
self.ui_controls["aec2"] = checkbox
# Auto Gain Control (master switch)
gain_ctrl = prefs.get_bool("gain_ctrl", True)
gain_ctrl = prefs.get_bool("gain_ctrl")
agc_checkbox, cont = self.create_checkbox(tab, "Auto Gain", gain_ctrl, "gain_ctrl")
self.ui_controls["gain_ctrl"] = agc_checkbox
# Manual Gain Value (dependent)
agc_gain = prefs.get_int("agc_gain", 0)
agc_gain = prefs.get_int("agc_gain")
slider, label, agc_cont = self.create_slider(tab, "Manual Gain", 0, 30, agc_gain, "agc_gain")
self.ui_controls["agc_gain"] = slider
@@ -385,12 +433,12 @@ class CameraSettingsActivity(Activity):
("2X", 0), ("4X", 1), ("8X", 2), ("16X", 3),
("32X", 4), ("64X", 5), ("128X", 6)
]
gainceiling = prefs.get_int("gainceiling", 0)
gainceiling = prefs.get_int("gainceiling")
dropdown, cont = self.create_dropdown(tab, "Gain Ceiling:", gainceiling_options, gainceiling, "gainceiling")
self.ui_controls["gainceiling"] = dropdown
# Auto White Balance (master switch)
whitebal = prefs.get_bool("whitebal", True)
whitebal = prefs.get_bool("whitebal")
wbcheckbox, cont = self.create_checkbox(tab, "Auto White Balance", whitebal, "whitebal")
self.ui_controls["whitebal"] = wbcheckbox
@@ -398,7 +446,7 @@ class CameraSettingsActivity(Activity):
wb_mode_options = [
("Auto", 0), ("Sunny", 1), ("Cloudy", 2), ("Office", 3), ("Home", 4)
]
wb_mode = prefs.get_int("wb_mode", 0)
wb_mode = prefs.get_int("wb_mode")
wb_dropdown, wb_cont = self.create_dropdown(tab, "WB Mode:", wb_mode_options, wb_mode, "wb_mode")
self.ui_controls["wb_mode"] = wb_dropdown
@@ -412,7 +460,7 @@ class CameraSettingsActivity(Activity):
whitebal_changed()
# AWB Gain
awb_gain = prefs.get_bool("awb_gain", True)
awb_gain = prefs.get_bool("awb_gain")
checkbox, cont = self.create_checkbox(tab, "AWB Gain", awb_gain, "awb_gain")
self.ui_controls["awb_gain"] = checkbox
@@ -423,7 +471,7 @@ class CameraSettingsActivity(Activity):
("None", 0), ("Negative", 1), ("Grayscale", 2),
("Reddish", 3), ("Greenish", 4), ("Blue", 5), ("Retro", 6)
]
special_effect = prefs.get_int("special_effect", 0)
special_effect = prefs.get_int("special_effect")
dropdown, cont = self.create_dropdown(tab, "Special Effect:", special_effect_options,
special_effect, "special_effect")
self.ui_controls["special_effect"] = dropdown
@@ -435,12 +483,12 @@ class CameraSettingsActivity(Activity):
tab.set_style_pad_all(1, 0)
# Sharpness
sharpness = prefs.get_int("sharpness", 0)
sharpness = prefs.get_int("sharpness")
slider, label, cont = self.create_slider(tab, "Sharpness", -3, 3, sharpness, "sharpness")
self.ui_controls["sharpness"] = slider
# Denoise
denoise = prefs.get_int("denoise", 0)
denoise = prefs.get_int("denoise")
slider, label, cont = self.create_slider(tab, "Denoise", 0, 8, denoise, "denoise")
self.ui_controls["denoise"] = slider
@@ -451,32 +499,32 @@ class CameraSettingsActivity(Activity):
#self.ui_controls["quality"] = slider
# Color Bar
colorbar = prefs.get_bool("colorbar", False)
colorbar = prefs.get_bool("colorbar")
checkbox, cont = self.create_checkbox(tab, "Color Bar Test", colorbar, "colorbar")
self.ui_controls["colorbar"] = checkbox
# DCW Mode
dcw = prefs.get_bool("dcw", True)
dcw = prefs.get_bool("dcw")
checkbox, cont = self.create_checkbox(tab, "Downsize Crop Window", dcw, "dcw")
self.ui_controls["dcw"] = checkbox
# Black Point Compensation
bpc = prefs.get_bool("bpc", False)
bpc = prefs.get_bool("bpc")
checkbox, cont = self.create_checkbox(tab, "Black Point Compensation", bpc, "bpc")
self.ui_controls["bpc"] = checkbox
# White Point Compensation
wpc = prefs.get_bool("wpc", True)
wpc = prefs.get_bool("wpc")
checkbox, cont = self.create_checkbox(tab, "White Point Compensation", wpc, "wpc")
self.ui_controls["wpc"] = checkbox
# Raw Gamma Mode
raw_gma = prefs.get_bool("raw_gma", True)
raw_gma = prefs.get_bool("raw_gma")
checkbox, cont = self.create_checkbox(tab, "Raw Gamma Mode", raw_gma, "raw_gma")
self.ui_controls["raw_gma"] = checkbox
# Lens Correction
lenc = prefs.get_bool("lenc", True)
lenc = prefs.get_bool("lenc")
checkbox, cont = self.create_checkbox(tab, "Lens Correction", lenc, "lenc")
self.ui_controls["lenc"] = checkbox