diff --git a/adafruit_pycamera/imageprocessing.py b/adafruit_pycamera/imageprocessing.py index d7fa770..98932aa 100644 --- a/adafruit_pycamera/imageprocessing.py +++ b/adafruit_pycamera/imageprocessing.py @@ -82,3 +82,123 @@ def emboss_greyscale(bitmap, mask=None): def ironbow(bitmap, mask=None): """Convert an image to false color using the 'ironbow palette'""" return bitmapfilter.false_color(bitmap, ironbow_palette, mask=mask) + + +# pylint: disable=invalid-name +def alphablend_maker(frac, nfrac=None): + """Create an alpha-blending function for a specific fractional value + + The resulting function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``. + """ + if nfrac is None: + nfrac = 1 - frac + + def inner(a, b): + return frac * a + nfrac * b + + return inner + + +def screen_func(a, b): + """The 'screen' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return 1 - (1 - a) * (1 - b) + + +def overlay_func(a, b): + """The 'overlay' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return 2 * a * b if a < 0.5 else 1 - 2 * (1 - a) * (1 - b) + + +def hard_light_func(a, b): + """The 'hard light' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return 2 * a * b if b < 0.5 else 1 - 2 * (1 - a) * (1 - b) + + +# illusions.hu formula version +def soft_light_func(a, b): + """The 'soft light' blend mode. + + There are various soft light blend functions. The "illusions.hu" variant of + soft light is used. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a ** (2 ** (2 * 0.5 - b)) + + +def color_dodge_func(a, b): + """The 'color dodge' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a / (1 - b) if b != 1 else 1 + + +def linear_dodge_func(a, b): + """The 'linear dodge' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a + b + + +def divide_func(a, b): + """The 'divide' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a / b if b else 1 + + +def multiply_func(a, b): + """The 'multiply' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a * b + + +def subtract_func(a, b): + """The 'subtract' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a - b + + +def color_burn_func(a, b): + """The 'color burn' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a * (1 - b) + + +def linear_burn_func(a, b): + """The 'linear burn' blend mode. + + This function can be used with ``bitmapfilter.blend`` and + ``bitmapfilter.blend_precompute``.""" + return a + b - 1 + + +darken_only_func = min +"""The 'darken only' blend mode. + +This function can be used with ``bitmapfilter.blend`` and +``bitmapfilter.blend_precompute``.""" +lighten_only_func = max +"""The 'screen' blend mode. + +This function can be used with ``bitmapfilter.blend`` and +``bitmapfilter.blend_precompute``.""" diff --git a/examples/camera/boot.py b/examples/camera/boot.py new file mode 100644 index 0000000..83662bc --- /dev/null +++ b/examples/camera/boot.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense + +"""Automatically create the /sd mount point at boot time""" + +import os +import storage + +storage.remount("/", readonly=False) + +try: + os.mkdir("/sd") +except OSError: + pass # It's probably 'file exists', OK to ignore + +storage.remount("/", readonly=True) diff --git a/examples/filter/code.py b/examples/filter/code.py index 94d90f3..8d4598c 100644 --- a/examples/filter/code.py +++ b/examples/filter/code.py @@ -21,7 +21,95 @@ from adafruit_pycamera import imageprocessing from adafruit_pycamera import PyCameraBase + +blend_50_50 = bitmapfilter.blend_precompute(imageprocessing.alphablend_maker(0.5)) +screen = bitmapfilter.blend_precompute(imageprocessing.screen_func) +overlay = bitmapfilter.blend_precompute(imageprocessing.overlay_func) +hard_light = bitmapfilter.blend_precompute(imageprocessing.hard_light_func) +soft_light = bitmapfilter.blend_precompute(imageprocessing.soft_light_func) +color_dodge = bitmapfilter.blend_precompute(imageprocessing.color_dodge_func) +# linear_dodge = bitmapfilter.blend_precompute(imageprocessing.linear_dodge_func) +# divide = bitmapfilter.blend_precompute(imageprocessing.divide_func) +multiply = bitmapfilter.blend_precompute(imageprocessing.multiply_func) +# subtract = bitmapfilter.blend_precompute(imageprocessing.subtract_func) +# color_burn = bitmapfilter.blend_precompute(imageprocessing.color_burn_func) +# linear_burn = bitmapfilter.blend_precompute(imageprocessing.linear_burn_func) +# darken_only = bitmapfilter.blend_precompute(min) +# lighten_only = bitmapfilter.blend_precompute(max) + + +def blender(f): + def inner(b): + return bitmapfilter.blend(b, b, testpattern, f) + + return inner + + +def reverse_blender(f): + def inner(b): + return bitmapfilter.blend(b, testpattern, b, f) + + return inner + + +inverse_greyscale_weights = bitmapfilter.ChannelMixer( + 1 - 0.299, + 1 - 0.587, + 1 - 0.114, + 1 - 0.299, + 1 - 0.587, + 1 - 0.114, + 1 - 0.299, + 1 - 0.587, + 1 - 0.114, +) + +blur_more = [ + 4, + 15, + 24, + 15, + 4, + 15, + 61, + 97, + 61, + 15, + 24, + 97, + 154, + 97, + 24, + 15, + 61, + 97, + 61, + 15, + 4, + 15, + 24, + 15, + 4, +] + + +# "Sketch" filter based on +# https://www.freecodecamp.org/news/sketchify-turn-any-image-into-a-pencil-sketch-with-10-lines-of-code-cf67fa4f68ce/ +def sketch(b): + bitmapfilter.mix(b, inverse_greyscale_weights) + memoryview(auxbuffer)[:] = memoryview(b) + bitmapfilter.morph(auxbuffer, blur_more) + bitmapfilter.blend(b, auxbuffer, b, color_dodge) + bitmapfilter.mix(b, inverse_greyscale_weights) # get rid of magenta halos + return b + + effects = [ + ("sketch", sketch), + ("50/50", blender(blend_50_50)), + ("multiply", blender(multiply)), + ("soft light", blender(soft_light)), + ("hard_light", blender(hard_light)), ("blue cast", imageprocessing.blue_cast), ("blur", imageprocessing.blur), ("bright", lambda b: bitmapfilter.mix(b, bitmapfilter.ChannelScale(2.0, 2.0, 2.0))), @@ -65,6 +153,9 @@ def cycle(seq): pycam = PyCameraBase() pycam.init_display() +testpattern = displayio.Bitmap(208, 208, 65535) +auxbuffer = displayio.Bitmap(208, 208, 65535) + def main(): filename = "/cornell_box_208x208.jpg" @@ -74,6 +165,9 @@ def main(): decoder.open(filename) decoder.decode(bitmap0) + decoder.open("/testpattern_208x208.jpg") + decoder.decode(testpattern) + label = Label(font=FONT, x=0, y=8) pycam.display.root_group = label pycam.display.refresh() diff --git a/examples/filter/testpattern_208x208.jpg b/examples/filter/testpattern_208x208.jpg new file mode 100644 index 0000000..94b01de Binary files /dev/null and b/examples/filter/testpattern_208x208.jpg differ diff --git a/examples/filter/testpattern_208x208.jpg.license b/examples/filter/testpattern_208x208.jpg.license new file mode 100644 index 0000000..746796b --- /dev/null +++ b/examples/filter/testpattern_208x208.jpg.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries + +SPDX-License-Identifier: Unlicense