From bb06503bc6e5e27c4bda3bb4359ec82cfb3b5ed5 Mon Sep 17 00:00:00 2001 From: Matt Singleton Date: Sat, 3 Feb 2024 14:02:03 -0600 Subject: initial checkin --- epdify.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 epdify.py (limited to 'epdify.py') diff --git a/epdify.py b/epdify.py new file mode 100644 index 0000000..3bf1326 --- /dev/null +++ b/epdify.py @@ -0,0 +1,133 @@ +import os +import sys + +from PIL import Image, ImageDraw, ImageFont, ImagePalette, ImageOps + +_palettes = { + "perceived": ( + 34, 30, 53, # black + 233, 235, 234, # white + 52, 102, 77, # green + 50, 51, 116, # blue + 205, 86, 82, # red + 236, 216, 101, # yellow + 209, 121, 97, # orange + ), + "native": ( + 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, 128, 0, # orange + ), +} + +_font_stacks = { + "nimbus": { + "bold": "/usr/share/fonts/urw-base35/NimbusSansNarrow-Bold.t1", + "regular": "/usr/share/fonts/urw-base35/NimbusSansNarrow-Regular.t1", + }, + "fira": { + "bold": "/usr/share/fonts/mozilla-fira/FiraSansCondensed-Medium.otf", + "regular": "/usr/share/fonts/mozilla-fira/FiraSansCondensed-Light.otf", + }, + "liberation": { + "bold": "/usr/share/fonts/liberation-narrow/LiberationSansNarrow-Bold.ttf", + "regular": "/usr/share/fonts/liberation-narrow/LiberationSansNarrow.ttf", + }, + "source": { + "bold": "/home/matt/Downloads/sourcesans/TTF/SourceSans3-Semibold.ttf", + "regular": "/home/matt/Downloads/sourcesans/TTF/SourceSans3-Medium.ttf", + }, + "plex": { + "bold": "/usr/share/fonts/ibm-plex-sans-fonts/IBMPlexSansCondensed-SemiBold.otf", + "regular": "/usr/share/fonts/ibm-plex-sans-fonts/IBMPlexSansCondensed-Medium.otf", + }, +} + +def _get_palette_image(palette): + img = Image.new("P", (0, 0)) + img.putpalette(palette) + return img + + +# "public" methods + + +def get_crop_box_and_orientation(width, height, ratio): + """ + returns the orientation and bounding box to crop an image with + the given height and width to the given aspect ratio. + """ + if width < height: + orientation = "portrait" + if width / height > ratio: + # trim width + px_to_trim = width - (height * ratio) + box = ( + px_to_trim / 2, + 0, + width - (px_to_trim / 2), + height + ) + else: + # trim height + px_to_trim = height - (width / ratio) + box = ( + 0, + px_to_trim / 2, + width, + height - (px_to_trim / 2) + ) + else: + orientation = "landscape" + if width / height > 1 / ratio: + # trim width + px_to_trim = width - (height * (1 / ratio)) + box = ( + px_to_trim / 2, + 0, + width - (px_to_trim / 2), + height + ) + else: + # trim height + px_to_trim = height - (width / (1 / ratio)) + box = ( + 0, + px_to_trim / 2, + width, + height - (px_to_trim / 2) + ) + return tuple(int(x) for x in box), orientation + + +def get_bounded_font(font_name, font_weight, text, target_size, min_size, max_width): + font_filename = _font_stacks[font_name.lower()][font_weight.lower()] + size = target_size + font = ImageFont.truetype(font_filename, size=size) + while size > min_size and font.getlength(text) > max_width: + size -= 1 + font = ImageFont.truetype(font_filename, size=size) + return font + + +def epdify(image, dither_palette="perceived", final_palette="native"): + """ + transforms the given input image into a scaled and dithered 7-color + image ready to be sent to the frame. expects image to be in the + correct aspect already (3x5 or 5x3), errors if not. + if 'simulate_perceived_palette' is True, returned image retains the + simulated paletted, suitable for reviewing images on a full-color + display, but not for loading on the frame. + """ + img = image.quantize( + colors=7, + dither=Image.Dither.FLOYDSTEINBERG, + palette=_get_palette_image(_palettes[dither_palette.lower()]), + method=Image.Quantize.LIBIMAGEQUANT, + ) + img.putpalette(_palettes[final_palette.lower()]) + return img -- cgit v1.2.3