Skip to content

Commit

Permalink
Merge pull request #28 from FoamyGuy/overlays
Browse files Browse the repository at this point in the history
Overlay feature and example
  • Loading branch information
dhalbert committed Feb 22, 2024
2 parents 491e88e + 1e75869 commit b06ccf5
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 2 deletions.
110 changes: 108 additions & 2 deletions adafruit_pycamera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""Library for the Adafruit PyCamera with OV5640 autofocus module"""

# pylint: disable=too-many-lines

import gc
import os
import struct
import time
Expand Down Expand Up @@ -149,7 +149,7 @@ class PyCameraBase: # pylint: disable=too-many-instance-attributes,too-many-pub
espcamera.FrameSize.QVGA, # 320x240
# espcamera.FrameSize.CIF, # 400x296
# espcamera.FrameSize.HVGA, # 480x320
espcamera.FrameSize.VGA, # 640x480
espcamera.FrameSize.VGA, # 640x480
espcamera.FrameSize.SVGA, # 800x600
espcamera.FrameSize.XGA, # 1024x768
espcamera.FrameSize.HD, # 1280x720
Expand Down Expand Up @@ -232,6 +232,12 @@ def __init__(self) -> None: # pylint: disable=too-many-statements
self.display = None
self.pixels = None
self.sdcard = None
self._last_saved_image_filename = None
self.decoder = None
self._overlay = None
self.overlay_transparency_color = None
self.overlay_bmp = None
self.combined_bmp = None
self.splash = displayio.Group()

# Reset display and I/O expander
Expand Down Expand Up @@ -827,6 +833,7 @@ def open_next_image(self, extension="jpg"):
os.stat(filename)
except OSError:
break
self._last_saved_image_filename = filename
print("Writing to", filename)
return open(filename, "wb")

Expand Down Expand Up @@ -857,6 +864,89 @@ def capture_jpeg(self):
else:
print("# frame capture failed")

@property
def overlay(self) -> str:
"""
The overlay file to be used. A filepath string that points
to a .bmp file that has 24bit RGB888 Colorspace.
The overlay image will be shown in the camera preview,
and combined to create a modified version of the
final photo.
"""
return self._overlay

@overlay.setter
def overlay(self, new_overlay_file: str) -> None:
# pylint: disable=import-outside-toplevel
from displayio import ColorConverter, Colorspace
import ulab.numpy as np
import adafruit_imageload

if self.overlay_bmp is not None:
self.overlay_bmp.deinit()
self._overlay = new_overlay_file
cc888 = ColorConverter(input_colorspace=Colorspace.RGB888)
self.overlay_bmp, _ = adafruit_imageload.load(new_overlay_file, palette=cc888)

arr = np.frombuffer(self.overlay_bmp, dtype=np.uint16)
arr.byteswap(inplace=True)

del arr

def _init_jpeg_decoder(self):
# pylint: disable=import-outside-toplevel
from jpegio import JpegDecoder

"""
Initialize the JpegDecoder if it hasn't been already.
Only needed if overlay is used.
"""
if self.decoder is None:
self.decoder = JpegDecoder()

def blit_overlay_into_last_capture(self):
"""
Create a modified version of the last photo taken that pastes
the overlay image on top of the photo and saves the new version
in a separate but similarly named .bmp file on the SDCard.
"""
if self.overlay_bmp is None:
raise ValueError(
"Must set overlay before calling blit_overlay_into_last_capture"
)
# pylint: disable=import-outside-toplevel
from adafruit_bitmapsaver import save_pixels
from displayio import Bitmap, ColorConverter, Colorspace

self._init_jpeg_decoder()

width, height = self.decoder.open(self._last_saved_image_filename)
photo_bitmap = Bitmap(width, height, 65535)

self.decoder.decode(photo_bitmap, scale=0, x=0, y=0)

bitmaptools.blit(
photo_bitmap,
self.overlay_bmp,
0,
0,
skip_source_index=self.overlay_transparency_color,
skip_dest_index=None,
)

cc565_swapped = ColorConverter(input_colorspace=Colorspace.RGB565_SWAPPED)
save_pixels(
self._last_saved_image_filename.replace(".jpg", "_modified.bmp"),
photo_bitmap,
cc565_swapped,
)

# RAM cleanup
photo_bitmap.deinit()
del photo_bitmap
del cc565_swapped
gc.collect()

def continuous_capture_start(self):
"""Switch the camera to continuous-capture mode"""
pass # pylint: disable=unnecessary-pass
Expand Down Expand Up @@ -901,6 +991,22 @@ def blit(self, bitmap, x_offset=0, y_offset=32):
The default preview capture is 240x176, leaving 32 pixel rows at the top and bottom
for status information.
"""
# pylint: disable=import-outside-toplevel
from displayio import Bitmap

if self.overlay_bmp is not None:
if self.combined_bmp is None:
self.combined_bmp = Bitmap(bitmap.width, bitmap.height, 65535)

bitmaptools.blit(self.combined_bmp, bitmap, 0, 0)

bitmaptools.rotozoom(
self.combined_bmp,
self.overlay_bmp,
scale=0.75,
skip_index=self.overlay_transparency_color,
)
bitmap = self.combined_bmp

self._display_bus.send(
42, struct.pack(">hh", 80 + x_offset, 80 + x_offset + bitmap.width - 1)
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"digitalio",
"espcamera",
"fourwire",
"jpegio",
"micropython",
"neopixel",
"sdcardio",
Expand Down
21 changes: 21 additions & 0 deletions docs/mock/displayio.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,24 @@ def __init__(self, i):

def __setitem__(self, idx, value):
self._data[idx] = value


class ColorConverter:
def __init__(self, colorspace):
self._colorspace = colorspace

def convert(self, color_value) -> int:
pass


class Bitmap:
def __init__(self, width, height, color_count):
pass


class Colorspace:
pass


class Display:
pass
91 changes: 91 additions & 0 deletions examples/overlay/code_select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
""" simple point-and-shoot camera example, with overly selecting using select button.
Place all overlay files inside /sd/overlays/ directory.
"""
import os
import time
import traceback
import adafruit_pycamera # pylint: disable=import-error


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

# User settings - try changing these:
pycam.resolution = 1 # 0-12 preset resolutions:
# 0: 240x240, 1: 320x240, 2: 640x480

pycam.led_level = 1 # 0-4 preset brightness levels
pycam.led_color = 0 # 0-7 preset colors: 0: white, 1: green, 2: yellow, 3: red,
# 4: pink, 5: blue, 6: teal, 7: rainbow
pycam.effect = 0 # 0-7 preset FX: 0: normal, 1: invert, 2: b&w, 3: red,
# 4: green, 5: blue, 6: sepia, 7: solarize

print("Overlay example camera ready.")
pycam.tone(800, 0.1)
pycam.tone(1200, 0.05)

overlay_files = os.listdir("/sd/overlays/")
cur_overlay_idx = 0

pycam.overlay = f"/sd/overlays/{overlay_files[cur_overlay_idx]}"
pycam.overlay_transparency_color = 0xE007

overlay_files = os.listdir("/sd/overlays/")
cur_overlay_idx = 0

while True:
pycam.blit(pycam.continuous_capture())
pycam.keys_debounce()
# print(dir(pycam.select))
if pycam.select.fell:
cur_overlay_idx += 1
if cur_overlay_idx >= len(overlay_files):
cur_overlay_idx = 0
print(f"changing overlay to {overlay_files[cur_overlay_idx]}")
pycam.overlay = f"/sd/overlays/{overlay_files[cur_overlay_idx]}"

if pycam.shutter.short_count:
print("Shutter released")
pycam.tone(1200, 0.05)
pycam.tone(1600, 0.05)
try:
pycam.display_message("snap", color=0x00DD00)
pycam.capture_jpeg()
pycam.display_message("overlay", color=0x00DD00)
pycam.blit_overlay_into_last_capture()
pycam.live_preview_mode()
except TypeError as exception:
traceback.print_exception(exception)
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
except RuntimeError as exception:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)

if pycam.card_detect.fell:
print("SD card removed")
pycam.unmount_sd_card()
pycam.display.refresh()

if pycam.card_detect.rose:
print("SD card inserted")
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
for _ in range(3):
try:
print("Mounting card")
pycam.mount_sd_card()
print("Success!")
break
except OSError as exception:
print("Retrying!", exception)
time.sleep(0.5)
else:
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
time.sleep(0.5)
pycam.display.refresh()
74 changes: 74 additions & 0 deletions examples/overlay/code_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
""" simple point-and-shoot camera example, with an overlay frame image. """

import time
import traceback
import adafruit_pycamera # pylint: disable=import-error

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

# User settings - try changing these:
pycam.resolution = 1 # 0-12 preset resolutions:
# 0: 240x240, 1: 320x240, 2: 640x480

pycam.led_level = 1 # 0-4 preset brightness levels
pycam.led_color = 0 # 0-7 preset colors: 0: white, 1: green, 2: yellow, 3: red,
# 4: pink, 5: blue, 6: teal, 7: rainbow
pycam.effect = 0 # 0-7 preset FX: 0: normal, 1: invert, 2: b&w, 3: red,
# 4: green, 5: blue, 6: sepia, 7: solarize

print("Overlay example camera ready.")
pycam.tone(800, 0.1)
pycam.tone(1200, 0.05)

pycam.overlay = "/heart_frame_rgb888.bmp"
pycam.overlay_transparency_color = 0xE007

while True:
pycam.blit(pycam.continuous_capture())
pycam.keys_debounce()

if pycam.shutter.short_count:
print("Shutter released")
pycam.tone(1200, 0.05)
pycam.tone(1600, 0.05)
try:
pycam.display_message("snap", color=0x00DD00)
pycam.capture_jpeg()
pycam.display_message("overlay", color=0x00DD00)
pycam.blit_overlay_into_last_capture()
pycam.live_preview_mode()
except TypeError as exception:
traceback.print_exception(exception)
pycam.display_message("Failed", color=0xFF0000)
time.sleep(0.5)
pycam.live_preview_mode()
except RuntimeError as exception:
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
time.sleep(0.5)

if pycam.card_detect.fell:
print("SD card removed")
pycam.unmount_sd_card()
pycam.display.refresh()

if pycam.card_detect.rose:
print("SD card inserted")
pycam.display_message("Mounting\nSD Card", color=0xFFFFFF)
for _ in range(3):
try:
print("Mounting card")
pycam.mount_sd_card()
print("Success!")
break
except OSError as exception:
print("Retrying!", exception)
time.sleep(0.5)
else:
pycam.display_message("SD Card\nFailed!", color=0xFF0000)
time.sleep(0.5)
pycam.display.refresh()
Binary file added examples/overlay/heart_frame_rgb888.bmp
Binary file not shown.
2 changes: 2 additions & 0 deletions examples/overlay/heart_frame_rgb888.bmp.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
# SPDX-License-Identifier: MIT
Binary file added examples/overlay/pencil_frame_rgb888.bmp
Binary file not shown.
2 changes: 2 additions & 0 deletions examples/overlay/pencil_frame_rgb888.bmp.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
# SPDX-License-Identifier: MIT
3 changes: 3 additions & 0 deletions optional_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

adafruit-circuitpython-bitmapsaver
adafruit-circuitpython-imageload

0 comments on commit b06ccf5

Please sign in to comment.