Skip to content

Add image() support, big buffer on CPython and some linux demos #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions adafruit_rgb_display/rgb.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,15 @@
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_RGB_Display.git"

# This is the size of the buffer to be used for fill operations, in 16-bit
# units. We use 256, which is 512 bytes — size of the DMA buffer on SAMD21.
# units. #We use 256, which is 512 bytes — size of the DMA buffer on SAMD21.
_BUFFER_SIZE = const(256)

# If we're on CPython, we have more memory, so get a big ol chunk!
try:
import platform
if "CPython" in platform.python_implementation():
_BUFFER_SIZE = const(320*240) # blit the whole thing at once
except ImportError:
pass

def color565(r, g=0, b=0):
"""Convert red, green and blue values (0-255) into a 16-bit 565 encoding. As
Expand Down Expand Up @@ -106,8 +112,8 @@ class Display: #pylint: disable-msg=no-member
_COLUMN_SET = None
_RAM_WRITE = None
_RAM_READ = None
_X_START = 0
_Y_START = 0
_X_START = 0 # pylint: disable=invalid-name
_Y_START = 0 # pylint: disable=invalid-name
_INIT = ()
_ENCODE_PIXEL = ">H"
_ENCODE_POS = ">HH"
Expand Down Expand Up @@ -157,6 +163,30 @@ def pixel(self, x, y, color=None):
self._block(x, y, x, y, self._encode_pixel(color))
return None

def image(self, img, rotation=0):
"""Set buffer to value of Python Imaging Library image. The image should
be in 1 bit mode and a size equal to the display size."""
if not img.mode in ('RGB', 'RGBA'):
raise ValueError('Image must be in mode RGB or RGBA')
if rotation not in (0, 90, 180, 270):
raise ValueError('Rotation must be 0/90/180/270')
if rotation != 0:
img = img.rotate(rotation, expand=True)
imwidth, imheight = img.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display ({0}x{1}).' \
.format(self.width, self.height))
pixels = bytearray(self.width * self.height * 2)
# Iterate through the pixels
for x in range(self.width): # yes this double loop is slow,
for y in range(self.height): # but these displays are small!
pix = color565(img.getpixel((x, y)))
pixels[2*(y * self.width + x)] = pix >> 8
pixels[2*(y * self.width + x) + 1] = pix & 0xFF

#print([hex(x) for x in pixels])
self._block(0, 0, self.width-1, self.height - 1, pixels)

#pylint: disable-msg=too-many-arguments
def fill_rectangle(self, x, y, width, height, color):
"""Draw a rectangle at specified position with specified width and
Expand Down Expand Up @@ -192,7 +222,8 @@ class DisplaySPI(Display):
"""Base class for SPI type devices"""
#pylint: disable-msg=too-many-arguments
def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
baudrate=12000000, polarity=0, phase=0):
baudrate=12000000, polarity=0, phase=0, *,
x_offset=0, y_offset=0):
self.spi_device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
polarity=polarity, phase=phase)
self.dc_pin = dc
Expand All @@ -201,6 +232,8 @@ def __init__(self, spi, dc, cs, rst=None, width=1, height=1,
if self.rst:
self.rst.switch_to_output(value=0)
self.reset()
self._X_START = x_offset
self._Y_START = y_offset
super().__init__(width, height)
#pylint: enable-msg=too-many-arguments

Expand Down
13 changes: 7 additions & 6 deletions adafruit_rgb_display/st7789.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ class ST7789(DisplaySPI):
_PAGE_SET = _RASET
_RAM_WRITE = _RAMWR
_RAM_READ = _RAMRD
_Y_START = 80
_INIT = (
(_SWRESET, None),
(_SLPOUT, None),
Expand All @@ -113,14 +112,16 @@ class ST7789(DisplaySPI):
)

#pylint: disable-msg=useless-super-delegation, too-many-arguments
def __init__(self, spi, dc, cs, rst=None, width=240, height=240,
baudrate=16000000, polarity=0, phase=0):
def __init__(self, spi, dc, cs, rst=None, width=240, height=320,
baudrate=16000000, polarity=0, phase=0, *,
x_offset=0, y_offset=0):
super().__init__(spi, dc, cs, rst, width, height,
baudrate=baudrate, polarity=polarity, phase=phase)

baudrate=baudrate, polarity=polarity, phase=phase,
x_offset=x_offset, y_offset=y_offset)
def init(self):

super().init()
cols = struct.pack('>HH', 0, self.width)
cols = struct.pack('>HH', self._X_START, self.width + self._X_START)
rows = struct.pack('>HH', self._Y_START, self.height + self._Y_START)
for command, data in (
(_CASET, cols),
Expand Down
175 changes: 175 additions & 0 deletions examples/rgbdisplay_fbcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

# definitions from linux/fb.h
FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4



class Bitfield: # pylint: disable=too-few-public-methods
def __init__(self, offset, length, msb_right):
self.offset = offset
self.length = length
self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
# http://www.pygame.org/docs/ref/surface.html
class Framebuffer: # pylint: disable=too-many-instance-attributes

def __init__(self, dev):
self.dev = dev
self.fbfd = os.open(dev, os.O_RDWR)
vinfo = struct.unpack("8I12I16I4I",
fcntl.ioctl(self.fbfd,
FBIOGET_VSCREENINFO,
" "*((8+12+16+4)*4)))
finfo = struct.unpack("16cL4I3HI",
fcntl.ioctl(self.fbfd,
FBIOGET_FSCREENINFO, " "*48))

bytes_per_pixel = (vinfo[6] + 7) // 8
screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

fbp = mmap.mmap(self.fbfd, screensize,
flags=mmap.MAP_SHARED, prot=mmap.PROT_READ)

self.fbp = fbp
self.xres = vinfo[0]
self.yres = vinfo[1]
self.xoffset = vinfo[4]
self.yoffset = vinfo[5]
self.bits_per_pixel = vinfo[6]
self.bytes_per_pixel = bytes_per_pixel
self.grayscale = vinfo[7]
self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
self.nonstd = vinfo[20]
self.name = b''.join([x for x in finfo[0:15] if x != b'\x00'])
self.type = finfo[18]
self.visual = finfo[20]
self.line_length = finfo[24]
self.screensize = screensize

def close(self):
self.fbp.close()
os.close(self.fbfd)

def blank(self, blank):
# Blanking is not supported by all drivers
try:
if blank:
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
else:
fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
except IOError:
pass

def __str__(self):
visual_list = ['MONO01', 'MONO10', 'TRUECOLOR',
'PSEUDOCOLOR', 'DIRECTCOLOR',
'STATIC PSEUDOCOLOR', 'FOURCC']
type_list = ['PACKED_PIXELS', 'PLANES', 'INTERLEAVED_PLANES',
'TEXT', 'VGA_PLANES', 'FOURCC']
visual_name = 'unknown'
if self.visual < len(visual_list):
visual_name = visual_list[self.visual]
type_name = 'unknown'
if self.type < len(type_list):
type_name = type_list[self.type]

return \
"mode \"%sx%s\"\n" % (self.xres, self.yres) + \
" nonstd %s\n" % self.nonstd + \
" rgba %s/%s,%s/%s,%s/%s,%s/%s\n" % (self.red.length,
self.red.offset,
self.green.length,
self.green.offset,
self.blue.length,
self.blue.offset,
self.transp.length,
self.transp.offset) + \
"endmode\n" + \
"\n" + \
"Frame buffer device information:\n" + \
" Device : %s\n" % self.dev + \
" Name : %s\n" % self.name + \
" Size : (%d, %d)\n" % (self.xres, self.yres) + \
" Length : %s\n" % self.screensize + \
" BPP : %d\n" % self.bits_per_pixel + \
" Type : %s\n" % type_name + \
" Visual : %s\n" % visual_name + \
" LineLength : %s\n" % self.line_length

device = '/dev/fb0'
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin,
baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)

height = disp.width # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
t = time.monotonic()
fb.fbp.seek(0)
b = fb.fbp.read(fb.screensize)
fbimage = Image.frombytes('RGBA', (fb.xres, fb.yres), b, 'raw')
b, g, r, a = fbimage.split()
fbimage = Image.merge("RGB", (r, g, b))
fbimage = fbimage.resize((width, height))

disp.image(fbimage, rotation)
print(1.0 / (time.monotonic()-t))
fb.close()
84 changes: 84 additions & 0 deletions examples/rgbdisplay_minipitftstats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-

import time
import subprocess
import digitalio
import board
from PIL import Image, ImageDraw, ImageFont
import adafruit_rgb_display.st7789 as st7789


# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

# Config for display baudrate (default max is 24mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(spi, cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE,
width=135, height=240, x_offset=53, y_offset=40)

# Create blank image for drawing.
# Make sure to create image with mode 'RGB' for full color.
height = disp.width # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new('RGB', (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = -2
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = 0


# Alternatively load a TTF font. Make sure the .ttf font file is in the
# same directory as the python script!
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 24)

while True:
# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=0)

# Shell scripts for system monitoring from here:
# https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load
cmd = "hostname -I | cut -d\' \' -f1"
IP = "IP: "+subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "top -bn1 | grep load | awk '{printf \"CPU Load: %.2f\", $(NF-2)}'"
CPU = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%s MB %.2f%%\", $3,$2,$3*100/$2 }'"
MemUsage = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "df -h | awk '$NF==\"/\"{printf \"Disk: %d/%d GB %s\", $3,$2,$5}'"
Disk = subprocess.check_output(cmd, shell=True).decode("utf-8")
cmd = "cat /sys/class/thermal/thermal_zone0/temp | awk \'{printf \"CPU Temp: %.1f C\", $(NF-0) / 1000}\'" # pylint: disable=line-too-long
Temp = subprocess.check_output(cmd, shell=True).decode("utf-8")

# Write four lines of text.
y = top
draw.text((x, y), IP, font=font, fill="#FFFFFF")
y += font.getsize(IP)[1]
draw.text((x, y), CPU, font=font, fill="#FFFF00")
y += font.getsize(CPU)[1]
draw.text((x, y), MemUsage, font=font, fill="#00FF00")
y += font.getsize(MemUsage)[1]
draw.text((x, y), Disk, font=font, fill="#0000FF")
y += font.getsize(Disk)[1]
draw.text((x, y), Temp, font=font, fill="#FF00FF")

# Display image.
disp.image(image, rotation)
time.sleep(.1)
Loading