Skip to content

Commit b06ccf5

Browse files
authored
Merge pull request adafruit#28 from FoamyGuy/overlays
Overlay feature and example
2 parents 491e88e + 1e75869 commit b06ccf5

10 files changed

+302
-2
lines changed

adafruit_pycamera/__init__.py

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""Library for the Adafruit PyCamera with OV5640 autofocus module"""
66

77
# pylint: disable=too-many-lines
8-
8+
import gc
99
import os
1010
import struct
1111
import time
@@ -149,7 +149,7 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub
149149
espcamera.FrameSize.QVGA, # 320x240
150150
# espcamera.FrameSize.CIF, # 400x296
151151
# espcamera.FrameSize.HVGA, # 480x320
152-
espcamera.FrameSize.VGA, # 640x480
152+
espcamera.FrameSize.VGA, # 640x480
153153
espcamera.FrameSize.SVGA, # 800x600
154154
espcamera.FrameSize.XGA, # 1024x768
155155
espcamera.FrameSize.HD, # 1280x720
@@ -232,6 +232,12 @@ def __init__(self) -> None: # pylint: disable=too-many-statements
232232
self.display = None
233233
self.pixels = None
234234
self.sdcard = None
235+
self._last_saved_image_filename = None
236+
self.decoder = None
237+
self._overlay = None
238+
self.overlay_transparency_color = None
239+
self.overlay_bmp = None
240+
self.combined_bmp = None
235241
self.splash = displayio.Group()
236242

237243
# Reset display and I/O expander
@@ -827,6 +833,7 @@ def open_next_image(self, extension="jpg"):
827833
os.stat(filename)
828834
except OSError:
829835
break
836+
self._last_saved_image_filename = filename
830837
print("Writing to", filename)
831838
return open(filename, "wb")
832839

@@ -857,6 +864,89 @@ def capture_jpeg(self):
857864
else:
858865
print("# frame capture failed")
859866

867+
@property
868+
def overlay(self) -> str:
869+
"""
870+
The overlay file to be used. A filepath string that points
871+
to a .bmp file that has 24bit RGB888 Colorspace.
872+
The overlay image will be shown in the camera preview,
873+
and combined to create a modified version of the
874+
final photo.
875+
"""
876+
return self._overlay
877+
878+
@overlay.setter
879+
def overlay(self, new_overlay_file: str) -> None:
880+
# pylint: disable=import-outside-toplevel
881+
from displayio import ColorConverter, Colorspace
882+
import ulab.numpy as np
883+
import adafruit_imageload
884+
885+
if self.overlay_bmp is not None:
886+
self.overlay_bmp.deinit()
887+
self._overlay = new_overlay_file
888+
cc888 = ColorConverter(input_colorspace=Colorspace.RGB888)
889+
self.overlay_bmp, _ = adafruit_imageload.load(new_overlay_file, palette=cc888)
890+
891+
arr = np.frombuffer(self.overlay_bmp, dtype=np.uint16)
892+
arr.byteswap(inplace=True)
893+
894+
del arr
895+
896+
def _init_jpeg_decoder(self):
897+
# pylint: disable=import-outside-toplevel
898+
from jpegio import JpegDecoder
899+
900+
"""
901+
Initialize the JpegDecoder if it hasn't been already.
902+
Only needed if overlay is used.
903+
"""
904+
if self.decoder is None:
905+
self.decoder = JpegDecoder()
906+
907+
def blit_overlay_into_last_capture(self):
908+
"""
909+
Create a modified version of the last photo taken that pastes
910+
the overlay image on top of the photo and saves the new version
911+
in a separate but similarly named .bmp file on the SDCard.
912+
"""
913+
if self.overlay_bmp is None:
914+
raise ValueError(
915+
"Must set overlay before calling blit_overlay_into_last_capture"
916+
)
917+
# pylint: disable=import-outside-toplevel
918+
from adafruit_bitmapsaver import save_pixels
919+
from displayio import Bitmap, ColorConverter, Colorspace
920+
921+
self._init_jpeg_decoder()
922+
923+
width, height = self.decoder.open(self._last_saved_image_filename)
924+
photo_bitmap = Bitmap(width, height, 65535)
925+
926+
self.decoder.decode(photo_bitmap, scale=0, x=0, y=0)
927+
928+
bitmaptools.blit(
929+
photo_bitmap,
930+
self.overlay_bmp,
931+
0,
932+
0,
933+
skip_source_index=self.overlay_transparency_color,
934+
skip_dest_index=None,
935+
)
936+
937+
cc565_swapped = ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED)
938+
save_pixels(
939+
self._last_saved_image_filename.replace(".jpg", "_modified.bmp"),
940+
photo_bitmap,
941+
cc565_swapped,
942+
)
943+
944+
# RAM cleanup
945+
photo_bitmap.deinit()
946+
del photo_bitmap
947+
del cc565_swapped
948+
gc.collect()
949+
860950
def continuous_capture_start(self):
861951
"""Switch the camera to continuous-capture mode"""
862952
pass # pylint: disable=unnecessary-pass
@@ -901,6 +991,22 @@ def blit(self, bitmap, x_offset=0, y_offset=32):
901991
The default preview capture is 240x176, leaving 32 pixel rows at the top and bottom
902992
for status information.
903993
"""
994+
# pylint: disable=import-outside-toplevel
995+
from displayio import Bitmap
996+
997+
if self.overlay_bmp is not None:
998+
if self.combined_bmp is None:
999+
self.combined_bmp = Bitmap(bitmap.width, bitmap.height, 65535)
1000+
1001+
bitmaptools.blit(self.combined_bmp, bitmap, 0, 0)
1002+
1003+
bitmaptools.rotozoom(
1004+
self.combined_bmp,
1005+
self.overlay_bmp,
1006+
scale=0.75,
1007+
skip_index=self.overlay_transparency_color,
1008+
)
1009+
bitmap = self.combined_bmp
9041010

9051011
self._display_bus.send(
9061012
42, struct.pack(">hh", 80 + x_offset, 80 + x_offset + bitmap.width - 1)

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"digitalio",
4242
"espcamera",
4343
"fourwire",
44+
"jpegio",
4445
"micropython",
4546
"neopixel",
4647
"sdcardio",

docs/mock/displayio.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,24 @@ def __init__(self, i):
99

1010
def __setitem__(self, idx, value):
1111
self._data[idx] = value
12+
13+
14+
class ColorConverter:
15+
def __init__(self, colorspace):
16+
self._colorspace = colorspace
17+
18+
def convert(self, color_value) -> int:
19+
pass
20+
21+
22+
class Bitmap:
23+
def __init__(self, width, height, color_count):
24+
pass
25+
26+
27+
class Colorspace:
28+
pass
29+
30+
31+
class Display:
32+
pass

examples/overlay/code_select.py

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

examples/overlay/code_simple.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) 2024 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

optional_requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
22
#
33
# SPDX-License-Identifier: Unlicense
4+
5+
adafruit-circuitpython-bitmapsaver
6+
adafruit-circuitpython-imageload

0 commit comments

Comments
 (0)