126 lines
3.6 KiB
Python
126 lines
3.6 KiB
Python
|
#!/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)
|