Skip to content

Commit 1457ede

Browse files
Work Camera app
- Add 1280x1280 resolution - Fix dependent settings enablement - Use grayscale for now
1 parent a3db12f commit 1457ede

File tree

1 file changed

+50
-47
lines changed
  • internal_filesystem/apps/com.micropythonos.camera/assets

1 file changed

+50
-47
lines changed

internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,21 @@
2020

2121
class CameraApp(Activity):
2222

23+
DEFAULT_WIDTH = 320 # 240 would be better but webcam doesn't support this (yet)
24+
DEFAULT_HEIGHT = 240
25+
2326
button_width = 60
2427
button_height = 45
25-
width = 320
26-
height = 240
28+
graymode = True
2729

2830
status_label_text = "No camera found."
2931
status_label_text_searching = "Searching QR codes...\n\nHold still and try varying scan distance (10-25cm) and QR size (4-12cm). Ensure proper lighting."
3032
status_label_text_found = "Decoding QR..."
3133

3234
cam = None
3335
current_cam_buffer = None # Holds the current memoryview to prevent garbage collection
34-
current_cam_buffer_copy = None # Holds a copy so that the memoryview can be free'd
36+
width = None
37+
height = None
3538

3639
image = None
3740
image_dsc = None
@@ -52,16 +55,16 @@ class CameraApp(Activity):
5255
def load_resolution_preference(self):
5356
"""Load resolution preference from SharedPreferences and update width/height."""
5457
prefs = SharedPreferences("com.micropythonos.camera")
55-
resolution_str = prefs.get_string("resolution", "320x240")
58+
resolution_str = prefs.get_string("resolution", f"{self.DEFAULT_WIDTH}x{self.DEFAULT_HEIGHT}")
5659
try:
5760
width_str, height_str = resolution_str.split('x')
5861
self.width = int(width_str)
5962
self.height = int(height_str)
6063
print(f"Camera resolution loaded: {self.width}x{self.height}")
6164
except Exception as e:
6265
print(f"Error parsing resolution '{resolution_str}': {e}, using default 320x240")
63-
self.width = 320
64-
self.height = 240
66+
self.width = self.DEFAULT_WIDTH
67+
self.height = self.DEFAULT_HEIGHT
6568

6669
def onCreate(self):
6770
self.load_resolution_preference()
@@ -88,7 +91,6 @@ def onCreate(self):
8891
settings_label.set_text(lv.SYMBOL.SETTINGS)
8992
settings_label.center()
9093
settings_button.add_event_cb(lambda e: self.open_settings(),lv.EVENT.CLICKED,None)
91-
9294
self.snap_button = lv.button(self.main_screen)
9395
self.snap_button.set_size(self.button_width, self.button_height)
9496
self.snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0)
@@ -104,8 +106,6 @@ def onCreate(self):
104106
zoom_label = lv.label(self.zoom_button)
105107
zoom_label.set_text("Z")
106108
zoom_label.center()
107-
108-
109109
self.qr_button = lv.button(self.main_screen)
110110
self.qr_button.set_size(self.button_width, self.button_height)
111111
self.qr_button.add_flag(lv.obj.FLAG.HIDDEN)
@@ -161,7 +161,6 @@ def onResume(self, screen):
161161
if self.scanqr_mode:
162162
self.finish()
163163

164-
165164
def onPause(self, screen):
166165
print("camera app backgrounded, cleaning up...")
167166
if self.capture_timer:
@@ -208,11 +207,13 @@ def create_preview_image(self):
208207
"magic": lv.IMAGE_HEADER_MAGIC,
209208
"w": self.width,
210209
"h": self.height,
211-
"stride": self.width * 2,
212-
"cf": lv.COLOR_FORMAT.RGB565
213-
#"cf": lv.COLOR_FORMAT.L8
210+
#"stride": self.width * 2, # RGB565
211+
"stride": self.width, # RGB565
212+
#"cf": lv.COLOR_FORMAT.RGB565
213+
"cf": lv.COLOR_FORMAT.L8
214214
},
215-
'data_size': self.width * self.height * 2,
215+
#'data_size': self.width * self.height * 2, # RGB565
216+
'data_size': self.width * self.height, # gray
216217
'data': None # Will be updated per frame
217218
})
218219
self.image.set_src(self.image_dsc)
@@ -224,7 +225,7 @@ def qrdecode_one(self):
224225
import qrdecode
225226
import utime
226227
before = utime.ticks_ms()
227-
result = qrdecode.qrdecode_rgb565(self.current_cam_buffer, self.width, self.height)
228+
result = qrdecode.qrdecode(self.current_cam_buffer, self.width, self.height)
228229
after = utime.ticks_ms()
229230
#result = bytearray("INSERT_QR_HERE", "utf-8")
230231
if not result:
@@ -343,8 +344,10 @@ def handle_settings_result(self, result):
343344
# Note: image_dsc is an LVGL struct, use attribute access not dictionary access
344345
self.image_dsc.header.w = self.width
345346
self.image_dsc.header.h = self.height
346-
self.image_dsc.header.stride = self.width * 2
347-
self.image_dsc.data_size = self.width * self.height * 2
347+
#self.image_dsc.header.stride = self.width * 2 # RGB565
348+
#self.image_dsc.data_size = self.width * self.height * 2 #RGB565
349+
self.image_dsc.header.stride = self.width
350+
self.image_dsc.data_size = self.width * self.height
348351
print(f"Image descriptor updated to {self.width}x{self.height}")
349352

350353
# Reconfigure camera if active
@@ -379,25 +382,23 @@ def try_capture(self, event):
379382
try:
380383
if self.use_webcam:
381384
self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565")
382-
#self.current_cam_buffer_copy = bytes(self.current_cam_buffer)
383385
elif self.cam.frame_available():
384386
self.cam.free_buffer()
385387
self.current_cam_buffer = self.cam.capture()
386-
#self.current_cam_buffer_copy = bytes(self.current_cam_buffer)
387-
self.cam.free_buffer()
388+
#self.cam.free_buffer()
388389

389390
if self.current_cam_buffer and len(self.current_cam_buffer):
390391
# Defensive check: verify buffer size matches expected dimensions
391-
expected_size = self.width * self.height * 2 # RGB565 = 2 bytes per pixel
392+
#expected_size = self.width * self.height * 2 # RGB565 = 2 bytes per pixel
393+
expected_size = self.width * self.height # Grayscale = 1 byte per pixel
392394
actual_size = len(self.current_cam_buffer)
393395

394396
if actual_size == expected_size:
395-
#self.image_dsc.data = self.current_cam_buffer_copy
396397
self.image_dsc.data = self.current_cam_buffer
397398
#image.invalidate() # does not work so do this:
398399
self.image.set_src(self.image_dsc)
399-
if not self.use_webcam:
400-
self.cam.free_buffer() # Free the old buffer
400+
#if not self.use_webcam:
401+
# self.cam.free_buffer() # Free the old buffer
401402
try:
402403
if self.keepliveqrdecoding:
403404
self.qrdecode_one()
@@ -443,6 +444,7 @@ def init_internal_cam(width, height):
443444
(1024,1024): FrameSize.R1024X1024,
444445
(1280, 720): FrameSize.HD,
445446
(1280, 1024): FrameSize.SXGA,
447+
(1280, 1280): FrameSize.R1280X1280,
446448
(1600, 1200): FrameSize.UXGA,
447449
(1920, 1080): FrameSize.FHD,
448450
}
@@ -465,7 +467,8 @@ def init_internal_cam(width, height):
465467
xclk_freq=20000000,
466468
powerdown_pin=-1,
467469
reset_pin=-1,
468-
pixel_format=PixelFormat.RGB565,
470+
#pixel_format=PixelFormat.RGB565,
471+
pixel_format=PixelFormat.GRAYSCALE,
469472
frame_size=frame_size,
470473
grab_mode=GrabMode.WHEN_EMPTY,
471474
fb_count=1
@@ -674,6 +677,7 @@ class CameraSettingsActivity(Activity):
674677
("1024x1024","1024x1024"),
675678
("1280x720", "1280x720"), # binned 2x2 (in default ov5640.c)
676679
("1280x1024", "1280x1024"),
680+
("1280x1280", "1280x1280"),
677681
("1600x1200", "1600x1200"),
678682
("1920x1080", "1920x1080"),
679683
]
@@ -933,30 +937,30 @@ def create_advanced_tab(self, tab, prefs):
933937

934938
# Auto Exposure Control (master switch)
935939
exposure_ctrl = prefs.get_bool("exposure_ctrl", True)
936-
checkbox, cont = self.create_checkbox(tab, "Auto Exposure", exposure_ctrl, "exposure_ctrl")
937-
self.ui_controls["exposure_ctrl"] = checkbox
940+
aec_checkbox, cont = self.create_checkbox(tab, "Auto Exposure", exposure_ctrl, "exposure_ctrl")
941+
self.ui_controls["exposure_ctrl"] = aec_checkbox
938942

939943
# Manual Exposure Value (dependent)
940944
aec_value = prefs.get_int("aec_value", 300)
941-
slider, label, cont = self.create_slider(tab, "Manual Exposure", 0, 1200, aec_value, "aec_value")
942-
self.ui_controls["aec_value"] = slider
945+
me_slider, label, cont = self.create_slider(tab, "Manual Exposure", 0, 1200, aec_value, "aec_value")
946+
self.ui_controls["aec_value"] = me_slider
943947

944948
# Set initial state
945949
if exposure_ctrl:
946-
slider.add_state(lv.STATE.DISABLED)
947-
slider.set_style_bg_opa(128, 0)
950+
me_slider.add_state(lv.STATE.DISABLED)
951+
me_slider.set_style_bg_opa(128, 0)
948952

949953
# Add dependency handler
950954
def exposure_ctrl_changed(e):
951-
is_auto = checkbox.get_state() & lv.STATE.CHECKED
955+
is_auto = aec_checkbox.get_state() & lv.STATE.CHECKED
952956
if is_auto:
953-
slider.add_state(lv.STATE.DISABLED)
954-
slider.set_style_bg_opa(128, 0)
957+
me_slider.add_state(lv.STATE.DISABLED)
958+
me_slider.set_style_bg_opa(128, 0)
955959
else:
956-
slider.remove_state(lv.STATE.DISABLED)
957-
slider.set_style_bg_opa(255, 0)
960+
me_slider.remove_state(lv.STATE.DISABLED)
961+
me_slider.set_style_bg_opa(255, 0)
958962

959-
checkbox.add_event_cb(exposure_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
963+
aec_checkbox.add_event_cb(exposure_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
960964

961965
# Auto Exposure Level
962966
ae_level = prefs.get_int("ae_level", 0)
@@ -970,8 +974,8 @@ def exposure_ctrl_changed(e):
970974

971975
# Auto Gain Control (master switch)
972976
gain_ctrl = prefs.get_bool("gain_ctrl", True)
973-
checkbox, cont = self.create_checkbox(tab, "Auto Gain", gain_ctrl, "gain_ctrl")
974-
self.ui_controls["gain_ctrl"] = checkbox
977+
agc_checkbox, cont = self.create_checkbox(tab, "Auto Gain", gain_ctrl, "gain_ctrl")
978+
self.ui_controls["gain_ctrl"] = agc_checkbox
975979

976980
# Manual Gain Value (dependent)
977981
agc_gain = prefs.get_int("agc_gain", 0)
@@ -983,7 +987,7 @@ def exposure_ctrl_changed(e):
983987
slider.set_style_bg_opa(128, 0)
984988

985989
def gain_ctrl_changed(e):
986-
is_auto = checkbox.get_state() & lv.STATE.CHECKED
990+
is_auto = agc_checkbox.get_state() & lv.STATE.CHECKED
987991
gain_slider = self.ui_controls["agc_gain"]
988992
if is_auto:
989993
gain_slider.add_state(lv.STATE.DISABLED)
@@ -992,22 +996,21 @@ def gain_ctrl_changed(e):
992996
gain_slider.remove_state(lv.STATE.DISABLED)
993997
gain_slider.set_style_bg_opa(255, 0)
994998

995-
checkbox.add_event_cb(gain_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
999+
agc_checkbox.add_event_cb(gain_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
9961000

9971001
# Gain Ceiling
9981002
gainceiling_options = [
9991003
("2X", 0), ("4X", 1), ("8X", 2), ("16X", 3),
10001004
("32X", 4), ("64X", 5), ("128X", 6)
10011005
]
10021006
gainceiling = prefs.get_int("gainceiling", 0)
1003-
dropdown, cont = self.create_dropdown(tab, "Gain Ceiling:", gainceiling_options,
1004-
gainceiling, "gainceiling")
1007+
dropdown, cont = self.create_dropdown(tab, "Gain Ceiling:", gainceiling_options, gainceiling, "gainceiling")
10051008
self.ui_controls["gainceiling"] = dropdown
10061009

10071010
# Auto White Balance (master switch)
10081011
whitebal = prefs.get_bool("whitebal", True)
1009-
checkbox, cont = self.create_checkbox(tab, "Auto White Balance", whitebal, "whitebal")
1010-
self.ui_controls["whitebal"] = checkbox
1012+
wbcheckbox, cont = self.create_checkbox(tab, "Auto White Balance", whitebal, "whitebal")
1013+
self.ui_controls["whitebal"] = wbcheckbox
10111014

10121015
# White Balance Mode (dependent)
10131016
wb_mode_options = [
@@ -1021,14 +1024,14 @@ def gain_ctrl_changed(e):
10211024
dropdown.add_state(lv.STATE.DISABLED)
10221025

10231026
def whitebal_changed(e):
1024-
is_auto = checkbox.get_state() & lv.STATE.CHECKED
1027+
is_auto = wbcheckbox.get_state() & lv.STATE.CHECKED
10251028
wb_dropdown = self.ui_controls["wb_mode"]
10261029
if is_auto:
10271030
wb_dropdown.add_state(lv.STATE.DISABLED)
10281031
else:
10291032
wb_dropdown.remove_state(lv.STATE.DISABLED)
10301033

1031-
checkbox.add_event_cb(whitebal_changed, lv.EVENT.VALUE_CHANGED, None)
1034+
wbcheckbox.add_event_cb(whitebal_changed, lv.EVENT.VALUE_CHANGED, None)
10321035

10331036
# AWB Gain
10341037
awb_gain = prefs.get_bool("awb_gain", True)

0 commit comments

Comments
 (0)