pi_miniweather/framebuffer.py
2021-03-20 21:21:43 +01:00

125 lines
3.6 KiB
Python
Executable file

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Framebuffer helper that makes lots of simpifying assumptions
bits_per_pixel assumed memory layout
16 rgb565
24 rgb
32 argb
"""
from PIL import Image
import numpy
def _read_and_convert_to_ints(filename):
with open(filename, "r") as fp:
content = fp.read()
tokens = content.strip().split(",")
return [int(t) for t in tokens if t]
def _converter_argb(image: Image):
return bytes([x for r, g, b in image.getdata() for x in (255, r, g, b)])
def _converter_rgb565(image: Image):
return bytes([x for r, g, b in image.getdata()
for x in ((g & 0x1c) << 3 | (b >> 3), r & 0xf8 | (g >> 3))])
def _converter_1_argb(image: Image):
return bytes([x for p in image.getdata()
for x in (255, p, p, p)])
def _converter_1_rgb(image: Image):
return bytes([x for p in image.getdata()
for x in (p, p, p)])
def _converter_1_rgb565(image: Image):
return bytes([(255 if x else 0) for p in image.getdata()
for x in (p, p)])
def _converter_rgba_rgb565_numpy(image: Image):
flat = numpy.frombuffer(image.tobytes(), dtype=numpy.uint32)
# note, this is assumes little endian byteorder and results in
# the following packing of an integer:
# bits 0-7: red, 8-15: green, 16-23: blue, 24-31: alpha
flat = ((flat & 0xf8) << 8) | ((flat & 0xfc00) >> 5) | ((flat & 0xf80000) >> 19)
return flat.astype(numpy.uint16).tobytes()
def _converter_no_change(image: Image):
return image.tobytes()
# anything that does not use numpy is hopelessly slow
_CONVERTER = {
("RGBA", 16): _converter_rgba_rgb565_numpy,
("RGB", 16): _converter_rgb565,
("RGB", 24): _converter_no_change,
("RGB", 32): _converter_argb,
# note numpy does not work well with mode="1" images as
# image.tobytes() loses pixel color info
("1", 16): _converter_1_rgb565,
("1", 24): _converter_1_rgb,
("1", 32): _converter_1_argb,
}
class Framebuffer(object):
def __init__(self, device_no: int):
self.path = "/dev/fb%d" % device_no
config_dir = "/sys/class/graphics/fb%d/" % device_no
self.size = tuple(_read_and_convert_to_ints(
config_dir + "/virtual_size"))
self.stride = _read_and_convert_to_ints(config_dir + "/stride")[0]
self.bits_per_pixel = _read_and_convert_to_ints(
config_dir + "/bits_per_pixel")[0]
assert self.stride == self.bits_per_pixel // 8 * self.size[0]
def __str__(self):
args = (self.path, self.size, self.stride, self.bits_per_pixel)
return "%s size:%s stride:%s bits_per_pixel:%s" % args
# Note: performance is terrible even for medium resolutions
def show(self, image: Image):
converter = _CONVERTER[(image.mode, self.bits_per_pixel)]
assert image.size == self.size
out = converter(image)
with open(self.path, "wb") as fp:
fp.write(out)
def on(self):
pass
def off(self):
pass
if __name__ == "__main__":
import time
from PIL import ImageDraw
def TestFrameBuffer(i):
fb = Framebuffer(i)
print(fb)
image = Image.new("RGBA", fb.size)
draw = ImageDraw.Draw(image)
draw.rectangle(((0, 0), fb.size), fill="green")
draw.ellipse(((0, 0), fb.size), fill="blue", outline="red")
draw.line(((0, 0), fb.size), fill="green", width=2)
start = time.time()
for i in range(5):
fb.show(image)
stop = time.time()
print("fps: %.2f" % (10 / (stop - start)))
for i in [1]:
TestFrameBuffer(i)