initial commit

This commit is contained in:
Erik Stambaugh 2024-03-28 15:26:39 -07:00
commit 7e777fd152

220
inkybot.py Executable file
View 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?