commit 7e777fd152a49eb66cd0e2d4ca231066d6174e6b Author: Erik Stambaugh Date: Thu Mar 28 15:26:39 2024 -0700 initial commit diff --git a/inkybot.py b/inkybot.py new file mode 100755 index 0000000..24c4030 --- /dev/null +++ b/inkybot.py @@ -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? +