Skip to content

Commit 34f32dc

Browse files
committed
overlay feature and example
1 parent 00691dd commit 34f32dc

File tree

7 files changed

+182
-5
lines changed

7 files changed

+182
-5
lines changed

adafruit_pycamera/__init__.py

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
#
44
# SPDX-License-Identifier: MIT
55
"""Library for the Adafruit PyCamera with OV5640 autofocus module"""
6-
6+
# pylint: disable=too-many-lines
7+
import gc
78
import os
89
import struct
910
import time
@@ -32,6 +33,11 @@
3233
from adafruit_display_text import label
3334
from digitalio import DigitalInOut, Pull
3435
from rainbowio import colorwheel
36+
from displayio import Bitmap, ColorConverter, Colorspace
37+
from jpegio import JpegDecoder
38+
import ulab.numpy as np
39+
from adafruit_bitmapsaver import save_pixels
40+
import adafruit_imageload
3541

3642
__version__ = "0.0.0-auto.0"
3743
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyCamera.git"
@@ -147,7 +153,7 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub
147153
espcamera.FrameSize.QVGA, # 320x240
148154
# espcamera.FrameSize.CIF, # 400x296
149155
# espcamera.FrameSize.HVGA, # 480x320
150-
espcamera.FrameSize.VGA, # 640x480
156+
espcamera.FrameSize.VGA, # 640x480
151157
espcamera.FrameSize.SVGA, # 800x600
152158
espcamera.FrameSize.XGA, # 1024x768
153159
espcamera.FrameSize.HD, # 1280x720
@@ -230,6 +236,12 @@ def __init__(self) -> None: # pylint: disable=too-many-statements
230236
self.display = None
231237
self.pixels = None
232238
self.sdcard = None
239+
self._last_saved_image_filename = None
240+
self.decoder = None
241+
self._overlay = None
242+
self.overlay_transparency_color = None
243+
self.overlay_bmp = None
244+
self.combined_bmp = None
233245
self.splash = displayio.Group()
234246

235247
# Reset display and I/O expander
@@ -797,6 +809,7 @@ def open_next_image(self, extension="jpg"):
797809
os.stat(filename)
798810
except OSError:
799811
break
812+
self._last_saved_image_filename = filename
800813
print("Writing to", filename)
801814
return open(filename, "wb")
802815

@@ -827,6 +840,78 @@ def capture_jpeg(self):
827840
else:
828841
print("# frame capture failed")
829842

843+
@property
844+
def overlay(self) -> str:
845+
"""
846+
The overlay file to be used. A filepath string that points
847+
to a .bmp file that has 24bit RGB888 Colorspace.
848+
The overlay image will be shown in the camera preview,
849+
and combined to create a modified version of the
850+
final photo.
851+
"""
852+
return self._overlay
853+
854+
@overlay.setter
855+
def overlay(self, new_overlay_file: str) -> None:
856+
if self.overlay_bmp is not None:
857+
self.overlay_bmp.deinit()
858+
self._overlay = new_overlay_file
859+
cc888 = ColorConverter(input_colorspace=Colorspace.RGB888)
860+
self.overlay_bmp, _ = adafruit_imageload.load(new_overlay_file, palette=cc888)
861+
862+
arr = np.frombuffer(self.overlay_bmp, dtype=np.uint16)
863+
arr.byteswap(inplace=True)
864+
865+
del arr
866+
867+
def _init_jpeg_decoder(self):
868+
"""
869+
Initialize the JpegDecoder if it hasn't been already.
870+
Only needed if overlay is used.
871+
"""
872+
if self.decoder is None:
873+
self.decoder = JpegDecoder()
874+
875+
def blit_overlay_into_last_capture(self):
876+
"""
877+
Create a modified version of the last photo taken that pastes
878+
the overlay image on top of the photo and saves the new version
879+
in a separate but similarly named .bmp file on the SDCard.
880+
"""
881+
if self.overlay_bmp is None:
882+
raise ValueError(
883+
"Must set overlay before calling blit_overlay_into_last_capture"
884+
)
885+
886+
self._init_jpeg_decoder()
887+
888+
width, height = self.decoder.open(self._last_saved_image_filename)
889+
photo_bitmap = Bitmap(width, height, 65535)
890+
891+
self.decoder.decode(photo_bitmap, scale=0, x=0, y=0)
892+
893+
bitmaptools.blit(
894+
photo_bitmap,
895+
self.overlay_bmp,
896+
0,
897+
0,
898+
skip_source_index=self.overlay_transparency_color,
899+
skip_dest_index=None,
900+
)
901+
902+
cc565_swapped = ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED)
903+
save_pixels(
904+
self._last_saved_image_filename.replace(".jpg", "_modified.bmp"),
905+
photo_bitmap,
906+
cc565_swapped,
907+
)
908+
909+
# RAM cleanup
910+
photo_bitmap.deinit()
911+
del photo_bitmap
912+
del cc565_swapped
913+
gc.collect()
914+
830915
def continuous_capture_start(self):
831916
"""Switch the camera to continuous-capture mode"""
832917
pass # pylint: disable=unnecessary-pass
@@ -872,6 +957,20 @@ def blit(self, bitmap, x_offset=0, y_offset=32):
872957
for status information.
873958
"""
874959

960+
if self.overlay_bmp is not None:
961+
if self.combined_bmp is None:
962+
self.combined_bmp = Bitmap(bitmap.width, bitmap.height, 65535)
963+
964+
bitmaptools.blit(self.combined_bmp, bitmap, 0, 0)
965+
966+
bitmaptools.rotozoom(
967+
self.combined_bmp,
968+
self.overlay_bmp,
969+
scale=0.75,
970+
skip_index=self.overlay_transparency_color,
971+
)
972+
bitmap = self.combined_bmp
973+
875974
self._display_bus.send(
876975
42, struct.pack(">hh", 80 + x_offset, 80 + x_offset + bitmap.width - 1)
877976
)

examples/basic_camera/code.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
22
#
33
# SPDX-License-Identifier: MIT
4-
''' simple point-and-shoot camera example. No bells! Zero whistles! '''
4+
""" simple point-and-shoot camera example. No bells! Zero whistles! """
55

66
import time
7-
import adafruit_pycamera # pylint: disable=import-error
7+
import adafruit_pycamera # pylint: disable=import-error
88

99
pycam = adafruit_pycamera.PyCamera()
1010
pycam.mode = 0 # only mode 0 (JPEG) will work in this example
1111

1212
# User settings - try changing these:
13-
pycam.resolution = 8 # 0-12 preset resolutions:
13+
pycam.resolution = 8 # 0-12 preset resolutions:
1414
# 0: 240x240, 1: 320x240, 2: 640x480, 3: 800x600, 4: 1024x768,
1515
# 5: 1280x720, 6: 1280x1024, 7: 1600x1200, 8: 1920x1080, 9: 2048x1536,
1616
# 10: 2560x1440, 11: 2560x1600, 12: 2560x1920

examples/overlay/code.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
2+
# SPDX-FileCopyrightText: Copyright (c) 202 Tim Cocks for Adafruit Industries
3+
#
4+
# SPDX-License-Identifier: MIT
5+
""" simple point-and-shoot camera example, with an overlay frame image. """
6+
7+
import time
8+
import traceback
9+
import adafruit_pycamera # pylint: disable=import-error
10+
11+
pycam = adafruit_pycamera.PyCamera()
12+
pycam.mode = 0 # only mode 0 (JPEG) will work in this example
13+
14+
# User settings - try changing these:
15+
pycam.resolution = 1 # 0-12 preset resolutions:
16+
# 0: 240x240, 1: 320x240, 2: 640x480
17+
18+
pycam.led_level = 1 # 0-4 preset brightness levels
19+
pycam.led_color = 0 # 0-7 preset colors: 0: white, 1: green, 2: yellow, 3: red,
20+
# 4: pink, 5: blue, 6: teal, 7: rainbow
21+
pycam.effect = 0 # 0-7 preset FX: 0: normal, 1: invert, 2: b&w, 3: red,
22+
# 4: green, 5: blue, 6: sepia, 7: solarize
23+
24+
print("Overlay example camera ready.")
25+
pycam.tone(800, 0.1)
26+
pycam.tone(1200, 0.05)
27+
28+
pycam.overlay = "/heart_frame_rgb888.bmp"
29+
pycam.overlay_transparency_color = 0xE007
30+
31+
while True:
32+
pycam.blit(pycam.continuous_capture())
33+
pycam.keys_debounce()
34+
35+
if pycam.shutter.short_count:
36+
print("Shutter released")
37+
pycam.tone(1200, 0.05)
38+
pycam.tone(1600, 0.05)
39+
try:
40+
pycam.display_message("snap", color=0x00DD00)
41+
pycam.capture_jpeg()
42+
pycam.display_message("overlay", color=0x00DD00)
43+
pycam.blit_overlay_into_last_capture()
44+
pycam.live_preview_mode()
45+
except TypeError as exception:
46+
traceback.print_exception(exception)
47+
pycam.display_message("Failed", color=0xFF0000)
48+
time.sleep(0.5)
49+
pycam.live_preview_mode()
50+
except RuntimeError as exception:
51+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
52+
time.sleep(0.5)
53+
54+
if pycam.card_detect.fell:
55+
print("SD card removed")
56+
pycam.unmount_sd_card()
57+
pycam.display.refresh()
58+
59+
if pycam.card_detect.rose:
60+
print("SD card inserted")
61+
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
62+
for _ in range(3):
63+
try:
64+
print("Mounting card")
65+
pycam.mount_sd_card()
66+
print("Success!")
67+
break
68+
except OSError as exception:
69+
print("Retrying!", exception)
70+
time.sleep(0.5)
71+
else:
72+
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
73+
time.sleep(0.5)
74+
pycam.display.refresh()
225 KB
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
300 KB
Binary file not shown.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT

0 commit comments

Comments
 (0)