|
1 | | -# This code grabs images from the camera in RGB565 format (2 bytes per pixel) |
2 | | -# and sends that to the QR decoder if QR decoding is enabled. |
3 | | -# The QR decoder then converts the RGB565 to grayscale, as that's what quirc operates on. |
4 | | -# It would be slightly more efficient to capture the images from the camera in L8/grayscale format, |
5 | | -# or in YUV format and discarding the U and V planes, but then the image will be gray (not great UX) |
6 | | -# and the performance impact of converting RGB565 to grayscale is probably minimal anyway. |
7 | | - |
8 | 1 | import lvgl as lv |
9 | 2 | from mpos.ui.keyboard import MposKeyboard |
10 | 3 |
|
@@ -76,7 +69,8 @@ def onCreate(self): |
76 | 69 | self.main_screen.set_size(lv.pct(100), lv.pct(100)) |
77 | 70 | self.main_screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) |
78 | 71 | # Initialize LVGL image widget |
79 | | - self.create_preview_image() |
| 72 | + self.image = lv.image(self.main_screen) |
| 73 | + self.image.align(lv.ALIGN.LEFT_MID, 0, 0) |
80 | 74 | close_button = lv.button(self.main_screen) |
81 | 75 | close_button.set_size(self.button_width, self.button_height) |
82 | 76 | close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0) |
@@ -149,6 +143,7 @@ def onResume(self, screen): |
149 | 143 | print(f"camera app: webcam exception: {e}") |
150 | 144 | if self.cam: |
151 | 145 | print("Camera app initialized, continuing...") |
| 146 | + self.create_preview_image() |
152 | 147 | self.set_image_size() |
153 | 148 | self.capture_timer = lv.timer_create(self.try_capture, 100, None) |
154 | 149 | self.status_label_cont.add_flag(lv.obj.FLAG.HIDDEN) |
@@ -200,25 +195,19 @@ def set_image_size(self): |
200 | 195 | self.image.set_scale(min(scale_factor_w,scale_factor_h)) |
201 | 196 |
|
202 | 197 | def create_preview_image(self): |
203 | | - self.image = lv.image(self.main_screen) |
204 | | - self.image.align(lv.ALIGN.LEFT_MID, 0, 0) |
205 | 198 | # Create image descriptor once |
206 | 199 | self.image_dsc = lv.image_dsc_t({ |
207 | 200 | "header": { |
208 | 201 | "magic": lv.IMAGE_HEADER_MAGIC, |
209 | 202 | "w": self.width, |
210 | 203 | "h": self.height, |
211 | | - #"stride": self.width * 2, # RGB565 |
212 | | - "stride": self.width, # RGB565 |
213 | | - #"cf": lv.COLOR_FORMAT.RGB565 |
214 | | - "cf": lv.COLOR_FORMAT.L8 |
| 204 | + "stride": self.width * (2 if self.colormode else 1), |
| 205 | + "cf": lv.COLOR_FORMAT.RGB565 if self.colormode else lv.COLOR_FORMAT.L8 |
215 | 206 | }, |
216 | | - #'data_size': self.width * self.height * 2, # RGB565 |
217 | | - 'data_size': self.width * self.height, # gray |
| 207 | + 'data_size': self.width * self.height * (2 if self.colormode else 1), |
218 | 208 | 'data': None # Will be updated per frame |
219 | 209 | }) |
220 | 210 | self.image.set_src(self.image_dsc) |
221 | | - #self.image.set_size(160, 120) |
222 | 211 |
|
223 | 212 |
|
224 | 213 | def qrdecode_one(self): |
@@ -263,7 +252,8 @@ def snap_button_click(self, e): |
263 | 252 | except OSError: |
264 | 253 | pass |
265 | 254 | if self.current_cam_buffer is not None: |
266 | | - filename=f"data/images/camera_capture_{mpos.time.epoch_seconds()}_{self.width}x{self.height}_RGB565.raw" |
| 255 | + colorname = "RGB565" if self.colormode else "GRAY" |
| 256 | + filename=f"data/images/camera_capture_{mpos.time.epoch_seconds()}_{self.width}x{self.height}_{colorname}.raw" |
267 | 257 | try: |
268 | 258 | with open(filename, 'wb') as f: |
269 | 259 | f.write(self.current_cam_buffer) |
@@ -345,10 +335,8 @@ def handle_settings_result(self, result): |
345 | 335 | # Note: image_dsc is an LVGL struct, use attribute access not dictionary access |
346 | 336 | self.image_dsc.header.w = self.width |
347 | 337 | self.image_dsc.header.h = self.height |
348 | | - #self.image_dsc.header.stride = self.width * 2 # RGB565 |
349 | | - #self.image_dsc.data_size = self.width * self.height * 2 #RGB565 |
350 | | - self.image_dsc.header.stride = self.width |
351 | | - self.image_dsc.data_size = self.width * self.height |
| 338 | + self.image_dsc.header.stride = self.width * (2 if self.colormode else 1) |
| 339 | + self.image_dsc.data_size = self.width * self.height * (2 if self.colormode else 1) |
352 | 340 | print(f"Image descriptor updated to {self.width}x{self.height}") |
353 | 341 |
|
354 | 342 | # Reconfigure camera if active |
@@ -376,27 +364,27 @@ def handle_settings_result(self, result): |
376 | 364 | self.status_label_cont.remove_flag(lv.obj.FLAG.HIDDEN) |
377 | 365 | return # Don't continue if camera failed |
378 | 366 |
|
| 367 | + self.create_preview_image() |
379 | 368 | self.set_image_size() |
380 | 369 |
|
381 | 370 | def try_capture(self, event): |
382 | 371 | #print("capturing camera frame") |
383 | 372 | try: |
384 | 373 | if self.use_webcam: |
385 | | - self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565") |
| 374 | + self.current_cam_buffer = webcam.capture_frame(self.cam, "rgb565" if self.colormode else "grayscale") |
386 | 375 | elif self.cam.frame_available(): |
387 | 376 | #self.cam.free_buffer() |
388 | 377 | self.current_cam_buffer = self.cam.capture() |
389 | 378 | #self.cam.free_buffer() |
390 | 379 |
|
391 | 380 | if self.current_cam_buffer and len(self.current_cam_buffer): |
392 | 381 | # Defensive check: verify buffer size matches expected dimensions |
393 | | - #expected_size = self.width * self.height * 2 # RGB565 = 2 bytes per pixel |
394 | | - expected_size = self.width * self.height # Grayscale = 1 byte per pixel |
| 382 | + expected_size = self.width * self.height * (2 if self.colormode else 1) |
395 | 383 | actual_size = len(self.current_cam_buffer) |
396 | 384 |
|
397 | 385 | if actual_size == expected_size: |
398 | 386 | self.image_dsc.data = self.current_cam_buffer |
399 | | - #image.invalidate() # does not work so do this: |
| 387 | + #self.image.invalidate() # does not work so do this: |
400 | 388 | self.image.set_src(self.image_dsc) |
401 | 389 | if not self.use_webcam: |
402 | 390 | self.cam.free_buffer() # Free the old buffer |
@@ -468,8 +456,7 @@ def init_internal_cam(width, height): |
468 | 456 | xclk_freq=20000000, |
469 | 457 | powerdown_pin=-1, |
470 | 458 | reset_pin=-1, |
471 | | - #pixel_format=PixelFormat.RGB565, |
472 | | - pixel_format=PixelFormat.GRAYSCALE, |
| 459 | + pixel_format=PixelFormat.RGB565 if self.colormode else PixelFormat.GRAYSCALE, |
473 | 460 | frame_size=frame_size, |
474 | 461 | #grab_mode=GrabMode.WHEN_EMPTY, |
475 | 462 | grab_mode=GrabMode.LATEST, |
|
0 commit comments