Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overlay feature and example #28

Merged
merged 9 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 109 additions & 2 deletions adafruit_pycamera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
#
# SPDX-License-Identifier: MIT
"""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 @@ -147,7 +148,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 @@ -230,6 +231,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 @@ -797,6 +804,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 @@ -827,6 +835,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 @@ -871,6 +962,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
6 changes: 3 additions & 3 deletions examples/basic_camera/code.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# SPDX-FileCopyrightText: Copyright (c) 2023 john park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
''' simple point-and-shoot camera example. No bells! Zero whistles! '''
""" simple point-and-shoot camera example. No bells! Zero whistles! """

import time
import adafruit_pycamera # pylint: disable=import-error
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 = 8 # 0-12 preset resolutions:
pycam.resolution = 8 # 0-12 preset resolutions:
# 0: 240x240, 1: 320x240, 2: 640x480, 3: 800x600, 4: 1024x768,
# 5: 1280x720, 6: 1280x1024, 7: 1600x1200, 8: 1920x1080, 9: 2048x1536,
# 10: 2560x1440, 11: 2560x1600, 12: 2560x1920
Expand Down
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
Loading