Understanding the code
There is a lot going on to bring these images to life on the LED matrix, so let's break things down a little bit.
Begin by importing the necessary modules and ensuring that we can create our display:
import math import time import random import adafruit_imageload.bmp import board import displayio import framebufferio import rgbmatrix import ulab displayio.release_displays()
RGBMatrix doesn't have a brightness control, so the Reshader
class uses ulab to quickly apply a brightness value from 0.0 to 1.0 to an original palette, updating the palette accordingly. This code works pretty quickly, especially when you make sure your images have a pallette of just a few dozen colors.
class Reshader: '''reshader fades the image to mimic brightness control''' def __init__(self, palette): self.palette = palette ulab_palette = ulab.numpy.zeros((len(palette), 3)) for i in range(len(palette)): rgb = int(palette[i]) ulab_palette[i, 2] = rgb & 0xff ulab_palette[i, 1] = (rgb >> 8) & 0xff ulab_palette[i, 0] = rgb >> 16 self.ulab_palette = ulab_palette def reshade(self, brightness): '''reshader''' palette = self.palette shaded = ulab.numpy.array(self.ulab_palette * brightness, dtype=ulab.numpy.uint8) for i in range(len(palette)): palette[i] = tuple(shaded[i])
do_crawl_down
makes an image move from the top of the screen to the bottom. Here are what all the parameters mean: image_file
is the filename of an image. Images up to 64x64 pixels work best. speed
is the down speed in pixels per second. weave
gives the amount the image weaves left and right, 0 for no weave. weave_speed
controls how quickly the weave goes. pulse
controls how much the brightness changes, values from 0 (no brightness change) to 0.5 work well.
By working with time in integer nanoseconds, we avoid rounding errors that occur when working with time in floating-point seconds. It makes the code a bit more complicated, but avoids the animation becoming "jerky" after running continuously for more than a day or so.
def do_crawl_down(image_file, *, speed=12, weave=4, pulse=.5, weave_speed=1/6, pulse_speed=1/7): '''function to scroll the image''' the_bitmap, the_palette = adafruit_imageload.load( image_file, bitmap=displayio.Bitmap, palette=displayio.Palette) shader = Reshader(the_palette) group = displayio.Group() tile_grid = displayio.TileGrid(bitmap=the_bitmap, pixel_shader=the_palette) group.append(tile_grid) display.root_group = group start_time = time.monotonic_ns() start_y = display.height # High enough to be "off the top" end_y = -the_bitmap.height # Low enough to be "off the bottom" # Mix up how the bobs and brightness change on each run r1 = random.random() * math.pi r2 = random.random() * math.pi y = start_y while y > end_y: now = time.monotonic_ns() y = start_y - speed * ((now - start_time) / 1e9) group.y = round(y) # wave from side to side group.x = round(weave * math.cos(y * weave_speed + r1)) # Change the brightness if pulse > 0: shader.reshade((1 - pulse) + pulse * math.sin(y * pulse_speed + r2)) display.refresh(minimum_frames_per_second=0, target_frames_per_second=60)
do_pulse
omits the movement part of do_crawl_down, so it's basically a simplified version of do_crawl_down
.
def do_pulse(image_file, *, duration=4, pulse_speed=1/8, pulse=.5): '''pulse animation''' the_bitmap, the_palette = adafruit_imageload.load( image_file, bitmap=displayio.Bitmap, palette=displayio.Palette) shader = Reshader(the_palette) group = displayio.Group() tile_grid = displayio.TileGrid(bitmap=the_bitmap, pixel_shader=the_palette) group.append(tile_grid) group.x = (display.width - the_bitmap.width) // 2 group.y = (display.height - the_bitmap.height) // 2 display.root_group = group start_time = time.monotonic_ns() end_time = start_time + int(duration * 1e9) now_ns = time.monotonic_ns() while now_ns < end_time: now_ns = time.monotonic_ns() current_time = (now_ns - start_time) / 1e9 shader.reshade((1 - pulse) - pulse * math.cos(2*math.pi*current_time*pulse_speed)**2) display.refresh(minimum_frames_per_second=0, target_frames_per_second=60)
Create the display object. Check out our dedicated guide for rgbmatrix to find pinouts for other boards besides the Feather M4 Express!
matrix = rgbmatrix.RGBMatrix( width=64, height=32, bit_depth=5, rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12], addr_pins=[board.A5, board.A4, board.A3, board.A2], clock_pin=board.D13, latch_pin=board.D0, output_enable_pin=board.D1) display = framebufferio.FramebufferDisplay(matrix, auto_refresh=False)
Choose randomly from 5 different effects. By using different images and different combinations of parameters, there's more variety. Why not try picking your own combinations, and see what you like best! Head to the next page for more information about customizing this with your own images.
# Image playlist - set to run randomly. Set pulse from 0 to .5 while True: show_next = random.randint(1, 5) #change to reflect how many images you add if show_next == 1: do_crawl_down("/ray.bmp") elif show_next == 2: do_crawl_down("/waves1.bmp", speed=7, weave=0, pulse=.35) elif show_next == 3: do_crawl_down("/waves2.bmp", speed=9, weave=0, pulse=.35) elif show_next == 4: do_pulse("/heart.bmp", duration=4, pulse=.45) elif show_next == 5: do_crawl_down("/dark.bmp")
Page last edited March 08, 2024
Text editor powered by tinymce.