通过 PIL/Python 添加元素到 OLED 显示器而不擦除其余部分

Add element to OLED display via PIL/Python without erasing rest

我有一个 SH1106 显示器连接到我的 Raspberry Pi,我正在使用 luma.oled 进行控制。

我可以用不同的字体显示各种内容,这很棒。但是,我不知道如何在不刷新整个显示的情况下向当前显示的内容添加内容。我的代码是这样的:

from os import system
import serial
from time import sleep
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont

# config display
device = sh1106(i2c(port=1, address=0x3C), rotate=0)
device.clear()
FA_solid = ImageFont.truetype('/home/pi/Desktop/tests/fa-solid-900.ttf', 16)
FA_regular = ImageFont.truetype('/home/pi/Desktop/tests/fa-regular-400.ttf', 16)
text_large = ImageFont.truetype('/home/pi/Desktop/tests/coolvetica condensed rg.ttf', 48)
text_small = ImageFont.truetype('/home/pi/Desktop/tests/coolvetica condensed rg.ttf', 16)

# display things
def show_icon(code):
    with canvas(device) as draw:
        draw.text((112, 0), text=code, font=FA_solid, fill="white")
        
def large_text(content, paddingleft =0, paddingtop =0):
    with canvas(device) as draw:
        draw.text((0, 0), text=content, font=text_large, fill="white")
        
def small_text(content, paddingleft =0, paddingtop =0):
    with canvas(device) as draw:
        draw.text((0, 0), text=content, font=text_small, fill="white")

show_icon("\uf124")
sleep(2)
large_text("Hi ;)")
sleep(10)
device.clear()

这会在右上角显示一个来自fontawesome的图标,然后清屏显示Hi。我怎样才能改变它来显示图标+嗨?理想情况下,我会在屏幕上设置“区域”,我可以在其中更改图标区域,同时保持文本显示,反之亦然。谢谢!

编辑--------------------

这是我的代码,改编自下面马克的回答。更好,但还没有。区域 1 和 3 在更新 2 时保持不变,但是当我重新绘制屏幕时,它会空白半秒钟然后更新,这是我不想要的。

def UpdateDisplay(z1,z2,z3):
    """Pass in the three zones and they will be sent to the screen"""

    device = sh1106(i2c(port=1, address=0x3C), rotate=0)

    # Make a black canvas the size of the entire screen
    whole = Image.new("1", (128,64))

    # Now paste in the 3 zones to form the whole
    whole.paste(z1, (2,2))        # zone1 at top-left
    whole.paste(z2, (66,2))       # zone2 at top-right
    whole.paste(z3, (2,34))       # zone3 across the bottom

    # I save the image here, but you would write it to the screen with "device.display()"
    device.display(whole)
    return

# Make zone1 dark grey and annotate it
z1 = Image.new("1", (60,30))
z1draw = ImageDraw.Draw(z1)
z1draw.text((10,10),"Zone1", fill="white")

# Make zone2 mid-grey and annotate it
z2 = Image.new("1", (60,30))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2", fill="white")

# Make zone3 light grey and annotate it
z3 = Image.new("1", (124,28))
z3draw = ImageDraw.Draw(z3)
z3draw.text((10,10),"Zone3", fill="white")

# Blit all zones to display
UpdateDisplay(z1,z2,z3)

sleep(5)

# Make zone2 mid-grey and annotate it
z2 = Image.new("1", (60,30))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2 changed", fill="white")

UpdateDisplay(z1,z2,z3)

我没有要测试的 SH1106,而且我从未使用过 luma 库,因此可能有一种更简单的方法来完成您想要的操作。如果是这样,也许有人会好心地联系我,我会删除这个答案。

我用了很多PIL,所以我查看了here第28行:

background = Image.new("RGB", device.size, "white")
background.paste(frame.resize(size, resample=Image.LANCZOS), posn)
device.display(background.convert(device.mode))

所以,看来您可以创建一个 PIL Image 并像那样将其发送到显示器。第一行创建一个与整个显示大小相同的空白白色 canvas,第二行将另一个 PIL Image 粘贴到指定位置的 canvas 上,最后一行将图像发送到展示。因此,您需要做的就是分别定义 N "zones" 并分别绘制它们(每个都是 PIL Image),然后在需要时要更新显示,请将 N 区域粘贴到您想要的位置,然后将完成的图片发送到显示。

抱歉,我不能更精确,但我没有什么可以测试的。这是一个小例子,其中有 3 个区域,可以单独绘制,然后在调用 device.display()

之前组装成一个整体
#!/usr/bin/env python3

from PIL import Image, ImageDraw

def UpdateDisplay(z1,z2,z3):
   """Pass in the three zones and they will be sent to the screen"""

   # Make a black canvas the size of the entire screen
   whole = Image.new("RGB", (128,64), (0,0,0))

   # Now paste in the 3 zones to form the whole
   whole.paste(z1, (2,2))        # zone1 at top-left
   whole.paste(z2, (66,2))       # zone2 at top-right
   whole.paste(z3, (2,34))       # zone3 across the bottom

   # I save the image here, but you would write it to the screen with "device.display()"
   whole.save('result.png')
   return

# Make zone1 dark grey and annotate it
z1 = Image.new("RGB", (60,30), (64,64,64))
z1draw = ImageDraw.Draw(z1)
z1draw.text((10,10),"Zone1")

# Make zone2 mid-grey and annotate it
z2 = Image.new("RGB", (60,30), (128,128,128))
z2draw = ImageDraw.Draw(z2)
z2draw.text((10,10),"Zone2")

# Make zone3 light grey and annotate it
z3 = Image.new("RGB", (124,28), (192,192,192))
z3draw = ImageDraw.Draw(z3)
z3draw.text((10,10),"Zone3")

# Blit all zones to display
UpdateDisplay(z1,z2,z3)

# Now change just zone 2 and update display
z2.paste("red", (0,0,z2.width,z2.height))
UpdateDisplay(z1,z2,z3)

这里是原图:

更新 zone2 后再次出现:

好的,所以我基本上明白了:

from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106
from PIL import ImageFont, Image, ImageDraw    

### setting up display using LUMA oled
device = sh1106(i2c(port=1, address=0x3C), rotate=0)
device.clear()

### Initialize drawing zone (aka entire screen)
output = Image.new("1", (128,64))
add_to_image = ImageDraw.Draw(output)

### I have the exterior temp and altitude I want to display. Each has an assigned zone for the icon (FontAwesome) and the data
# temp_ext
temp_zone = [(14,44), (36,64)]
temp_start = (14,44)
temp_icon_zone = [(0,48), (15,64)]
temp_icon_start = (3,48)
add_to_image.text(temp_icon_start, "\uf2c9", font=FA_solid, fill="white")

### every time I have a new reading, I basically draw a black rectangle over what I had and the rewrite the text
add_to_image.rectangle(temp_zone, fill="black", outline = "black")
add_to_image.text(temp_start, str(temp_c), font=text_medium, fill="white")
device.display(output)

这使我能够只更新我想要的屏幕部分,其余部分保持原样,而且至关重要的是,在重写信息时不会有半秒钟的空白屏幕。欢迎提出优化建议!

我仍然需要查看内存使用情况,当不同区域同时更新时感觉有点迟钝。但它有效!