bitmapfilter – Convolve an image with a kernel

Available on these boards
  • 0xCB Helios
  • 42. Keebs Frood
  • ARAMCON Badge 2019
  • ARAMCON2 Badge
  • ATMegaZero ESP32-S2
  • Adafruit CLUE nRF52840 Express
  • Adafruit Circuit Playground Bluefruit
  • Adafruit Feather Bluefruit Sense
  • Adafruit Feather ESP32 V2
  • Adafruit Feather ESP32S3 No PSRAM
  • Adafruit Feather MIMXRT1011
  • Adafruit Feather RP2040
  • Adafruit Feather RP2040 CAN
  • Adafruit Feather RP2040 DVI
  • Adafruit Feather RP2040 Prop-Maker
  • Adafruit Feather RP2040 RFM
  • Adafruit Feather RP2040 Scorpio
  • Adafruit Feather RP2040 ThinkInk
  • Adafruit Feather RP2040 USB Host
  • Adafruit Feather STM32F405 Express
  • Adafruit Feather nRF52840 Express
  • Adafruit Floppsy RP2040
  • Adafruit ItsyBitsy ESP32
  • Adafruit ItsyBitsy RP2040
  • Adafruit ItsyBitsy nRF52840 Express
  • Adafruit KB2040
  • Adafruit LED Glasses Driver nRF52840
  • Adafruit Macropad RP2040
  • Adafruit MatrixPortal S3
  • Adafruit Metro ESP32S3
  • Adafruit Metro RP2040
  • Adafruit Metro nRF52840 Express
  • Adafruit QT Py ESP32 PICO
  • Adafruit QT Py ESP32-S3 no psram
  • Adafruit QT Py RP2040
  • Adafruit QT2040 Trinkey
  • Adafruit-Qualia-S3-RGB666
  • Arduino Nano 33 BLE
  • Arduino Nano ESP32
  • Arduino Nano RP2040 Connect
  • AtelierDuMaker nRF52840 Breakout
  • BBQ20KBD
  • BLE-SS dev board Multi Sensor
  • BLING!
  • BLOK
  • BPI-Leaf-S3
  • BPI-PicoW-S3
  • BastBLE
  • Bee-Data-Logger
  • Bee-Motion-S3
  • Bee-S3
  • BlizzardS3
  • BlueMicro840
  • COSMO-Pico
  • CRCibernetica IdeaBoard
  • Challenger NB RP2040 WiFi
  • Challenger RP2040 LTE
  • Challenger RP2040 LoRa
  • Challenger RP2040 SD/RTC
  • Challenger RP2040 SubGHz
  • Challenger RP2040 WiFi
  • Challenger RP2040 WiFi/BLE
  • ColumbiaDSL-Sensor-Board-V1
  • Cytron EDU PICO W
  • Cytron Maker Feather AIoT S3
  • Cytron Maker Nano RP2040
  • Cytron Maker Pi RP2040
  • Cytron Maker Uno RP2040
  • DFRobot FireBeetle 2 ESP32-S3
  • Datanoise PicoADK
  • Diodes Delight Piunora
  • E-Fidget
  • ELECFREAKS PICO:ED
  • ES3ink
  • ESP32-C6-DevKitC-1-N8
  • ESP32-S2-DevKitC-1-N8R2
  • ESP32-S3-Box-2.5
  • ESP32-S3-Box-Lite
  • ESP32-S3-DevKitC-1-N32R8
  • ESP32-S3-DevKitC-1-N8
  • ESP32-S3-DevKitC-1-N8R2
  • ESP32-S3-DevKitC-1-N8R8
  • ESP32-S3-DevKitC-1-N8R8-with-HACKTABLET
  • ESP32-S3-DevKitM-1-N8
  • ESP32-S3-EYE
  • ESP32-S3-USB-OTG-N8
  • Electrolama minik
  • Electronut Labs Blip
  • Electronut Labs Papyr
  • EncoderPad RP2040
  • Espressif ESP32 DevKitc V4 WROVER
  • Espressif-ESP32-S3-LCD-EV-Board
  • Espruino Bangle.js 2
  • Espruino Wifi
  • Feather MIMXRT1011
  • Feather MIMXRT1062
  • FeatherS2
  • FeatherS2 PreRelease
  • FeatherS3
  • Fig Pi
  • HEIA-FR Picomo V2
  • Hack Club Sprig
  • Hardkernel Odroid Go
  • Heltec ESP32-S3-WIFI-LoRa-V3
  • HiiBot BlueFi
  • IMXRT1010-EVK
  • IMXRT1015-EVK
  • IkigaiSense Vita nRF52840
  • IoTs2
  • LILYGO T-DECK
  • LILYGO T-DISPLAY
  • LILYGO T-DISPLAY S3 v1.2
  • LILYGO TEMBED ESP32S3
  • LILYGO TTGO T-DISPLAY v1.1
  • LOLIN S3 16MB Flash 8MB PSRAM
  • LOLIN S3 PRO 16MB Flash 8MB PSRAM
  • Lilygo T-watch 2020 V3
  • M5 Stack Cardputer
  • M5Stack AtomS3
  • M5Stack AtomS3 Lite
  • M5Stack AtomS3U
  • M5Stack Core Basic
  • M5Stack Core Fire
  • M5Stack Core2
  • M5Stack CoreS3
  • M5Stack Dial
  • M5Stack M5Paper
  • MDBT50Q-DB-40
  • MDBT50Q-RX Dongle
  • MakerDiary nRF52840 MDK
  • MakerDiary nRF52840 MDK USB Dongle
  • MakerFabs-ESP32-S3-Parallel-TFT-With-Touch-7inch
  • Makerdiary M60 Keyboard
  • Makerdiary Pitaya Go
  • Makerdiary nRF52840 Connect Kit
  • Makerdiary nRF52840 M.2 Developer Kit
  • Maple Computing Elite-Pi
  • Melopero Shake RP2040
  • Metro MIMXRT1011
  • MicroDev microS2
  • NUCLEO STM32F746
  • NUCLEO STM32F767
  • NUCLEO STM32H743
  • NanoS3
  • Neuron
  • OPENMV-H7 R1
  • Oak Dev Tech BREAD2040
  • Oak Dev Tech Cast-Away RP2040
  • Open Hardware Summit 2020 Badge
  • Oxocard Artwork
  • Oxocard Connect
  • Oxocard Galaxy
  • Oxocard Science
  • PCA10056 nRF52840-DK
  • PCA10059 nRF52840 Dongle
  • PYB LR Nano V2
  • Pajenicko PicoPad
  • Particle Argon
  • Particle Boron
  • Particle Xenon
  • PillBug
  • Pimoroni Badger 2040
  • Pimoroni Badger 2040 W
  • Pimoroni Inky Frame 5.7
  • Pimoroni Inky Frame 7.3
  • Pimoroni Interstate 75
  • Pimoroni Keybow 2040
  • Pimoroni Motor 2040
  • Pimoroni PGA2040
  • Pimoroni Pico DV Base W
  • Pimoroni Pico LiPo (16MB)
  • Pimoroni Pico LiPo (4MB)
  • Pimoroni Pico dv Base
  • Pimoroni PicoSystem
  • Pimoroni Plasma 2040
  • Pimoroni Plasma 2040W
  • Pimoroni Servo 2040
  • Pimoroni Tiny 2040 (2MB)
  • Pimoroni Tiny 2040 (8MB)
  • ProS3
  • PyKey 18 Numpad
  • PyKey 44 Ergo
  • PyKey 60
  • PyKey 87 TKL
  • PyboardV1_1
  • RF.Guru RP2040
  • RP2.65-F
  • RP2040 Stamp
  • Raspberry Breadstick
  • Raspberry Pi 4B
  • Raspberry Pi Compute Module 4
  • Raspberry Pi Compute Module 4 IO Board
  • Raspberry Pi Pico
  • Raspberry Pi Pico W
  • Raspberry Pi Zero
  • Raspberry Pi Zero 2W
  • Raspberry Pi Zero W
  • SAM E54 Xplained Pro
  • SSCI ISP1807 Dev Board
  • SSCI ISP1807 Micro Board
  • ST STM32F746G Discovery
  • STM32F412G_DISCO
  • STM32F4_DISCO
  • Seeed XIAO nRF52840 Sense
  • Seeeduino XIAO RP2040
  • SiLabs xG24 Dev Kit
  • SiLabs xG24 Explorer Kit
  • Silicognition LLC RP2040-Shim
  • SparkFun MicroMod RP2040 Processor
  • SparkFun MicroMod nRF52840 Processor
  • SparkFun Pro Micro RP2040
  • SparkFun Pro nRF52840 Mini
  • SparkFun STM32 MicroMod Processor
  • SparkFun Teensy MicroMod Processor
  • SparkFun Thing Plus - RP2040
  • SparkFun Thing Plus - STM32
  • Sparkfun Thing Plus MGM240P
  • Sunton-ESP32-8048S070
  • SuperMini NRF52840
  • Swan R5
  • TG-Watch
  • Teensy 4.0
  • Teensy 4.1
  • Teknikio Bluebird
  • TinkeringTech ScoutMakes Azul
  • TinyC6
  • TinyS3
  • TinyWATCH S3
  • VCC-GND Studio YD RP2040
  • VCC-GND YD-ESP32-S3 (N16R8)
  • VCC-GND YD-ESP32-S3 (N8R8)
  • W5100S-EVB-Pico
  • W5500-EVB-Pico
  • WSC-1450
  • WarmBit BluePixel nRF52840
  • Waveshare ESP32-S3-GEEK
  • Waveshare ESP32-S3-Pico
  • Waveshare ESP32S3 LCD 1.28
  • Waveshare RP2040-GEEK
  • Waveshare RP2040-LCD-0.96
  • Waveshare RP2040-LCD-1.28
  • Waveshare RP2040-Plus (16MB)
  • Waveshare RP2040-Plus (4MB)
  • Waveshare RP2040-TOUCH-LCD-1.28
  • Waveshare RP2040-Tiny
  • Waveshare RP2040-Zero
  • WeAct ESP32-C6 (8MB)
  • WeAct Studio Pico
  • WeAct Studio Pico 16MB
  • WisdPi Ardu2040M
  • WisdPi Tiny RP2040
  • iLabs Challenger 840
  • iMX RT 1020 EVK
  • iMX RT 1040 EVK
  • iMX RT 1050 EVKB
  • iMX RT 1060 EVK
  • iMX RT 1060 EVKB
  • nice!nano
  • nullbits Bit-C PRO
  • splitkb.com Liatris
  • stm32f411ce-blackpill
  • stm32f411ce-blackpill-with-flash
  • takayoshiotake Octave RP2040
  • uGame22

bitmapfilter.morph(bitmap: displayio.Bitmap, weights: Sequence[int], mul: float | None = None, add: float = 0, mask: displayio.Bitmap | None = None, threshold=False, offset: int = 0, invert: bool = False) displayio.Bitmap

The name of the function comes from OpenMV. ImageMagick calls this “-morphology” (“-morph” is an unrelated image blending algorithm). PIL calls this “kernel”.

For background on how this kind of image processing, including some useful weights values, see wikipedia’s article on the subject.

The bitmap, which must be in RGB565_SWAPPED format, is modified according to the weights. Then a scaling factor mul and an offset factor add are applied.

The weights must be a sequence of integers. The length of the tuple must be the square of an odd number, usually 9 and sometimes 25. Specific weights create different effects. For instance, these weights represent a 3x3 gaussian blur: [1, 2, 1, 2, 4, 2, 1, 2, 1]

mul is number to multiply the convolution pixel results by. If None (the default) is passed, the value of 1/sum(weights) is used (or 1 if sum(weights) is 0). For most weights, his default value will preserve the overall image brightness.

add is a value to add to each convolution pixel result.

mul basically allows you to do a global contrast adjustment and add allows you to do a global brightness adjustment. Pixels that go outside of the image mins and maxes for color channels will be clipped.

If you’d like to adaptive threshold the image on the output of the filter you can pass threshold=True which will enable adaptive thresholding of the image which sets pixels to one or zero based on a pixel’s brightness in relation to the brightness of the kernel of pixels around them. A negative offset value sets more pixels to 1 as you make it more negative while a positive value only sets the sharpest contrast changes to 1. Set invert to invert the binary image resulting output.

mask is another image to use as a pixel level mask for the operation. The mask should be an image the same size as the image being operated on. Only pixels set to a non-zero value in the mask are modified.

kernel_gauss_3 = [
    1, 2, 1,
    2, 4, 2,
    1, 2, 1]

def blur(bitmap):
    """Blur the bitmap with a 3x3 gaussian kernel"""
    bitmapfilter.morph(bitmap, kernel_gauss_3, 1/sum(kernel_gauss_3))
class bitmapfilter.ChannelScale(r: float, g: float, b: float)

A weight object to use with mix() that scales each channel independently

This is useful for global contrast and brightness adjustment on a per-component basis. For instance, to cut red contrast in half (while keeping the minimum value as black or 0.0),

reduce_red_contrast = bitmapfilter.ChannelScale(0.5, 1, 1)

Construct a ChannelScale object

The r parameter gives the scale factor for the red channel of pixels, and so forth.

class bitmapfilter.ChannelScaleOffset(r: float, r_add: float, g: float, g_add: float, b: float, b_add: float)

A weight object to use with mix() that scales and offsets each channel independently

The r, g, and b parameters give a scale factor for each color component, while the r_add`, ``g_add and b_add give offset values added to each component.

This is useful for global contrast and brightness adjustment on a per-component basis. For instance, to cut red contrast in half while adjusting the brightness so that the middle value is still 0.5:

reduce_red_contrast = bitmapfilter.ChannelScaleOffset(
        0.5, 0.25,
        1, 0,
        1, 0)

Construct a ChannelScaleOffset object

class bitmapfilter.ChannelMixer(rr: float, rg: float, rb: float, gr: float, gg: float, gb: float, br: float, bg: float, bb: float)

A weight object to use with mix() that mixes different channels together

The parameters with names like rb give the fraction of each channel to mix into every other channel. For instance, rb gives the fraction of blue to mix into red, and gg gives the fraction of green to mix into green.

Conversion to sepia is an example where a ChannelMixer is appropriate, because the sepia conversion is defined as mixing a certain fraction of R, G, and B input values into each output value:

sepia_weights = bitmapfilter.ChannelMixer(
    .393,  .769,   .189,
    .349,  .686,   .168,
    .272,  .534,   .131)

def sepia(bitmap):
    """Convert the bitmap to sepia"""
    bitmapfilter.mix(bitmap, sepia_weights)
mix_into_red = ChannelMixer(
        0.5, 0.25, 0.25,
        0,   1,    0,
        0,   1,    0)

Construct a ChannelMixer object

class bitmapfilter.ChannelMixerOffset(rr: float, rg: float, rb: float, r_add: float, gr: float, gg: float, gb: float, g_add: float, br: float, bg: float, bb: float, b_add: float)

A weight object to use with mix() that mixes different channels together, plus an offset value

The parameters with names like rb give the fraction of each channel to mix into every other channel. For instance, rb gives the fraction of blue to mix into red, and gg gives the fraction of green to mix into green. The r_add, g_add and b_add parameters give offsets applied to each component.

For instance, to perform sepia conversion but also increase the overall brightness by 10%:

sepia_weights_brighten = bitmapfilter.ChannelMixerOffset(
    .393,  .769,   .189, .1
    .349,  .686,   .168, .1
    .272,  .534,   .131, .1)

Construct a ChannelMixerOffset object

bitmapfilter.mix(bitmap: displayio.Bitmap, weights: ChannelScale | ChannelScaleOffset | ChannelMixer | ChannelMixerOffset, mask: displayio.Bitmap | None = None) displayio.Bitmap

Perform a channel mixing operation on the bitmap

This is similar to the “channel mixer” tool in popular photo editing software. Imagemagick calls this “-color-matrix”. In PIL, this is accomplished with the convert method’s matrix argument.

The bitmap, which must be in RGB565_SWAPPED format, is modified according to the weights.

The weights must be one of the above types: ChannelScale, ChannelScaleOffset, ChannelMixer, or ChannelMixerOffset. For the effect of each different kind of weights object, see the type documentation.

After computation, any out of range values are clamped to the greatest or smallest valid value.

mask is another image to use as a pixel level mask for the operation. The mask should be an image the same size as the image being operated on. Only pixels set to a non-zero value in the mask are modified.

bitmapfilter.solarize(bitmap, threshold: float = 0.5, mask: displayio.Bitmap | None = None)

Create a “solarization” effect on an image

This filter inverts pixels with brightness values above threshold, while leaving lower brightness pixels alone.

This effect is similar to an effect observed in real life film which can also be produced during the printmaking process

PIL and ImageMagic both call this “solarize”.

bitmapfilter.LookupFunction

Any function which takes a number and returns a number. The input and output values should be in the range from 0 to 1 inclusive.

bitmapfilter.ThreeLookupFunctions

Any sequenceof three LookupFunction objects

bitmapfilter.lookup(bitmap: displayio.Bitmap, lookup: LookupFunction | ThreeLookupFunctions, mask: displayio.Bitmap | None) displayio.Bitmap

Modify the channels of a bitmap according to a look-up table

This can be used to implement non-linear transformations of color values, such as gamma curves.

This is similar to, but more limiting than, PIL’s “LUT3D” facility. It is not directly available in OpenMV or ImageMagic.

The bitmap, which must be in RGB565_SWAPPED format, is modified according to the values of the lookup function or functions.

If one lookup function is supplied, the same function is used for all 3 image channels. Otherwise, it must be a tuple of 3 functions. The first function is used for R, the second function for G, and the third for B.

Each lookup function is called for each possible channel value from 0 to 1 inclusive (64 times for green, 32 times for red or blue), and the return value (also from 0 to 1) is used whenever that color value is returned.

mask is another image to use as a pixel level mask for the operation. The mask should be an image the same size as the image being operated on. Only pixels set to a non-zero value in the mask are modified.

bitmapfilter.false_color(bitmap: displayio.Bitmap, palette: displayio.Palette, mask: displayio.Bitmap | None) displayio.Bitmap

Convert the image to false color using the given palette

In OpenMV this is accomplished via the ironbow function, which uses a default palette known as “ironbow”. Imagemagic produces a similar effect with -clut. PIL can accomplish this by converting an image to “L” format, then applying a palette to convert it into “P” mode.

The bitmap, which must be in RGB565_SWAPPED format, is converted into false color.

The palette, which must be of length 256, is used as a look-up table.

Each pixel is converted to a luminance (brightness/greyscale) value in the range 0..255, then the corresponding palette entry is looked up and stored in the bitmap.

mask is another image to use as a pixel level mask for the operation. The mask should be an image the same size as the image being operated on. Only pixels set to a non-zero value in the mask are modified.

bitmapfilter.BlendFunction

A function used to blend two images

bitmapfilter.BlendTable

A precomputed blend table

There is not actually a BlendTable type. The real type is actually any buffer 4096 bytes in length.

bitmapfilter.blend_precompute(lookup: BlendFunction, table: BlendTable | None = None) BlendTable

Precompute a BlendTable from a BlendFunction

If the optional table argument is provided, an existing BlendTable is updated with the new function values.

The function’s two arguments will range from 0 to 1. The returned value should also range from 0 to 1.

A function to do a 33% blend of each source image could look like this:

def blend_one_third(a, b):
    return a * .33 + b * .67
bitmapfilter.blend(dest: displayio.Bitmap, src1: displayio.Bitmap, src2: displayio.Bitmap, lookup: BlendFunction | BlendTable, mask: displayio.Bitmap | None = None) displayio.Bitmap

Blend the ‘src1’ and ‘src2’ images according to lookup function or table ‘lookup’

If lookup is a function, it is converted to a BlendTable by internally calling blend_precompute. If a blend function is used repeatedly it can be more efficient to compute it once with blend_precompute.

If the mask is supplied, pixels from src1 are taken unchanged in masked areas.

The source and destination bitmaps may be the same bitmap.

The destination bitmap is returned.