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