Skip to content

Instantly share code, notes, and snippets.

@FoamyGuy
Created February 10, 2025 21:14
Show Gist options
  • Save FoamyGuy/3e2294b499214954d6b2f66c46537928 to your computer and use it in GitHub Desktop.
Save FoamyGuy/3e2294b499214954d6b2f66c46537928 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
"""
Mirror a scaled copy of the framebuffer to 64x32 matrices,
The upper left corner of the framebuffer is displayed until the user hits ctrl-c.
Control scale, matrix size, and orientation with command line arguments.
python fbmirror_scaled.py [scale] [width] [height] [orientation]
scale int: How many times to scale down the display framebuffer. Default is 3.
width int: Total width of matrices in pixels. Default is 64.
height int: Total height of matrices in pixels. Default is 32.
orientation int: Orientation in degrees, must be 0, 90, 180, or 270.
Default is 0 or Normal orientation.
The `/dev/fb0` special file will exist if a monitor is plugged in at boot time,
or if `/boot/firmware/cmdline.txt` specifies a resolution such as
`... video=HDMI-A-1:640x480M@60D`.
"""
import sys
import adafruit_raspberry_pi5_piomatter as piomatter
import numpy as np
import PIL.Image as Image
import click
import piomatter_click
with open("/sys/class/graphics/fb0/virtual_size") as f:
screenx, screeny = [int(word) for word in f.read().split(",")]
with open("/sys/class/graphics/fb0/bits_per_pixel") as f:
bits_per_pixel = int(f.read())
assert bits_per_pixel == 16
bytes_per_pixel = bits_per_pixel // 8
dtype = {2: np.uint16, 4: np.uint32}[bytes_per_pixel]
with open("/sys/class/graphics/fb0/stride") as f:
stride = int(f.read())
linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype)
@click.command
@click.option("--x-offset", "xoffset", type=int, help="The x offset of top left corner of the region to mirror", default=0)
@click.option("--y-offset", "yoffset", type=int, help="The y offset of top left corner of the region to mirror", default=0)
@click.option("--scale", "scale", type=int, help="The scale factor to reduce the display down by.", default=3)
@piomatter_click.standard_options
def main(xoffset, yoffset, scale, width, height, serpentine, rotation, colorspace, pinout, n_planes, n_addr_lines):
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, rotation=rotation)
matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width, 3), dtype=dtype)
matrix = piomatter.PioMatter(colorspace=colorspace, pinout=pinout, framebuffer=matrix_framebuffer, geometry=geometry)
while True:
tmp = linux_framebuffer[yoffset:yoffset + height * scale, xoffset:xoffset + width * scale]
# Convert the RGB565 framebuffer into RGB888Packed (so that we can use PIL image operations to rescale it)
r = (tmp & 0xf800) >> 8
r = r | (r >> 5)
r = r.astype(np.uint8)
g = (tmp & 0x07e0) >> 3
g = g | (g >> 6)
g = g.astype(np.uint8)
b = (tmp & 0x001f) << 3
b = b | (b >> 5)
b = b.astype(np.uint8)
img = Image.fromarray(np.stack([r, g, b], -1))
img = img.resize((width, height))
print(img.mode)
#np_arr = np.asarray(img)
np_arr = np.array(img)
print(f"matrix fb: {matrix_framebuffer.shape} np_arr: {np_arr.shape}")
matrix_framebuffer[:, :] = np_arr
matrix.show()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment