initial commit
This commit is contained in:
commit
7e777fd152
1 changed files with 220 additions and 0 deletions
220
inkybot.py
Executable file
220
inkybot.py
Executable file
|
@ -0,0 +1,220 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys, os, random, time, signal
|
||||
import numpy as np
|
||||
import RPi.GPIO as GPIO
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from inky.inky_uc8159 import Inky
|
||||
|
||||
|
||||
class Inkybot:
|
||||
|
||||
# FIXME: a bunch of this needs to be parameterized
|
||||
|
||||
palette = [
|
||||
(0, 0, 0), # black
|
||||
(255, 255, 255), # white
|
||||
(0, 255, 0), # green
|
||||
(0, 0, 255), # blue
|
||||
(255, 0, 0), # red
|
||||
(255, 255, 0), # yellow
|
||||
(255, 140, 0), # orange
|
||||
(255, 255, 255) # white again???
|
||||
]
|
||||
|
||||
font_size = 60
|
||||
picpath = '/srv/inkybot/pictures'
|
||||
saturation = 0.7
|
||||
|
||||
exiting = False
|
||||
skip_img = False
|
||||
|
||||
|
||||
BUTTONS = [5,6,16,24]
|
||||
LABELS = ['A', 'B', 'C', 'D']
|
||||
|
||||
inky = Inky()
|
||||
|
||||
def __init__(self):
|
||||
self.font = ImageFont.truetype("fonts/3270NerdFontMono-Regular.ttf", size=self.font_size)
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
GPIO.setup(self.BUTTONS, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||
|
||||
for pin in self.BUTTONS:
|
||||
GPIO.add_event_detect(pin, GPIO.FALLING, self.handle_button, bouncetime=250)
|
||||
|
||||
|
||||
def color_similarity(self, color1, color2):
|
||||
return np.sqrt(np.sum((np.array(color1) - np.array(color2)) ** 2))
|
||||
|
||||
# def quantize_image(self, image, reference_palette, similarity_threshold):
|
||||
# pixels = image.getdata()
|
||||
# new_image = Image.new(image.mode, image.size)
|
||||
#
|
||||
# # Quantize the image colors only if they're within the similarity range to the reference palette
|
||||
# for i, pixel in enumerate(pixels):
|
||||
# best_match = min(reference_palette, key=lambda ref_color: color_similarity(ref_color, pixel))
|
||||
# if color_similarity(best_match, pixel) <= similarity_threshold:
|
||||
# new_image.putpixel((i % image.width, i // image.width), best_match)
|
||||
# else:
|
||||
# new_image.putpixel((i % image.width, i // image.width), pixel)
|
||||
#
|
||||
# return new_image
|
||||
|
||||
|
||||
def least_similar_color(self, color, palette):
|
||||
return max(self.palette, key=lambda ref_color: self.color_similarity(ref_color, color))
|
||||
|
||||
def average_outer_perimeter_color(self,image):
|
||||
# Get image dimensions
|
||||
width, height = image.size
|
||||
|
||||
# Extract the outer 1-pixel perimeter
|
||||
outer_perimeter_pixels = []
|
||||
for x in range(width):
|
||||
outer_perimeter_pixels.append(image.getpixel((x, 0))) # Top row
|
||||
outer_perimeter_pixels.append(image.getpixel((x, height - 1))) # Bottom row
|
||||
for y in range(1, height - 1):
|
||||
outer_perimeter_pixels.append(image.getpixel((0, y))) # Left column
|
||||
outer_perimeter_pixels.append(image.getpixel((width - 1, y))) # Right column
|
||||
|
||||
# Calculate average color
|
||||
total_pixels = len(outer_perimeter_pixels)
|
||||
total_red = sum(pixel[0] for pixel in outer_perimeter_pixels)
|
||||
total_green = sum(pixel[1] for pixel in outer_perimeter_pixels)
|
||||
total_blue = sum(pixel[2] for pixel in outer_perimeter_pixels)
|
||||
|
||||
average_red = total_red // total_pixels
|
||||
average_green = total_green // total_pixels
|
||||
average_blue = total_blue // total_pixels
|
||||
|
||||
return (average_red, average_green, average_blue)
|
||||
|
||||
def resize_with_letterbox(self, image, resolution, letterbox_color=(0, 0, 0)):
|
||||
target_width = resolution[0]
|
||||
target_height = resolution[1]
|
||||
|
||||
# Get original width and height
|
||||
original_width, original_height = image.size
|
||||
|
||||
# Calculate the aspect ratios
|
||||
original_aspect_ratio = original_width / original_height
|
||||
target_aspect_ratio = target_width / target_height
|
||||
|
||||
#print(image.size, resolution)
|
||||
#print(original_aspect_ratio, target_aspect_ratio)
|
||||
|
||||
# Calculate resizing factors
|
||||
if original_aspect_ratio < target_aspect_ratio:
|
||||
# Image is narrower than target, resize based on height
|
||||
new_width = int(target_height * original_aspect_ratio)
|
||||
new_height = target_height
|
||||
else:
|
||||
# Image is taller than target, resize based on width
|
||||
new_width = target_width
|
||||
new_height = int(target_width / original_aspect_ratio)
|
||||
|
||||
# Resize the image
|
||||
resized_image = image.resize((new_width, new_height), Image.ANTIALIAS)
|
||||
|
||||
# Create a new image with letterbox bars
|
||||
x_max = target_width - new_width
|
||||
if x_max // 2 > self.font_size:
|
||||
x = x_max // 2
|
||||
else:
|
||||
x = min(x_max, x_max // 2 + self.font_size)
|
||||
letterbox_image = Image.new(image.mode, (target_width, target_height), letterbox_color)
|
||||
#letterbox_image.paste(resized_image, ((target_width - new_width) // 2, (target_height - new_height) // 2))
|
||||
letterbox_image.paste(resized_image, (x, (target_height - new_height) // 2))
|
||||
|
||||
return letterbox_image
|
||||
|
||||
|
||||
def handle_button(self,pin):
|
||||
label = self.LABELS[self.BUTTONS.index(pin)]
|
||||
print(f"Button pressed: {pin} {label}")
|
||||
if pin == 24:
|
||||
self.skip_img = True
|
||||
print("skipping!")
|
||||
|
||||
def go(self):
|
||||
|
||||
text = "test"
|
||||
text_objects = [
|
||||
{
|
||||
"text": "",
|
||||
#"text": "",
|
||||
"left": 10,
|
||||
"top": 49
|
||||
},
|
||||
{
|
||||
"text": "",
|
||||
#"text": "",
|
||||
"left": 10,
|
||||
"top": 161
|
||||
},
|
||||
{
|
||||
"text": "",
|
||||
#"text": "",
|
||||
"left": 10,
|
||||
"top": 273
|
||||
},
|
||||
{
|
||||
#"text": "",
|
||||
"text": "",
|
||||
"left": 10,
|
||||
"top": 385
|
||||
}
|
||||
]
|
||||
imagelist = []
|
||||
|
||||
while self.exiting is not True:
|
||||
self.skip_img = False
|
||||
target = time.time() + 60.0
|
||||
|
||||
if len(imagelist) == 0:
|
||||
imagelist = os.listdir(self.picpath)
|
||||
random.shuffle(imagelist)
|
||||
|
||||
fn = imagelist.pop()
|
||||
print(f"Displaying {fn}")
|
||||
|
||||
image = Image.open(f"{self.picpath}/{fn}") # XXX FIXME: os join function instead
|
||||
|
||||
resizedimage = self.resize_with_letterbox(
|
||||
image,
|
||||
self.inky.resolution,
|
||||
self.average_outer_perimeter_color(image)
|
||||
)
|
||||
|
||||
draw = ImageDraw.Draw(resizedimage)
|
||||
c = self.least_similar_color(self.average_outer_perimeter_color(image), self.palette)
|
||||
c2 = self.least_similar_color(c, self.palette) # hahahahaha what am i thinking
|
||||
|
||||
for t in text_objects:
|
||||
text_width, text_height = draw.textsize(t["text"], font=self.font)
|
||||
dy = int(text_height / 2)
|
||||
for x in range(t["left"] - 1, t["left"] + 2, 1):
|
||||
for y in range(t["top"] - 1 - dy, t["top"] + 2 - dy, 1):
|
||||
draw.text((x,y), t["text"], fill=c, font=self.font)
|
||||
|
||||
draw.text((t["left"],t["top"] - dy), t["text"], fill=c2, font=self.font)
|
||||
|
||||
self.inky.set_image(resizedimage, saturation=self.saturation)
|
||||
self.inky.show()
|
||||
|
||||
while time.time() < target and self.skip_img is False:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
inkybot = Inkybot()
|
||||
inkybot.go()
|
||||
|
||||
#inky = auto(ask_user=True, verbose=True)
|
||||
|
||||
# buttons:
|
||||
# - hostap vs wpa_supplicant
|
||||
# - next image
|
||||
# - ??? mode picture vs. status?
|
||||
# - ??? music play/pause/skip?
|
||||
|
Loading…
Reference in a new issue