aboutsummaryrefslogtreecommitdiff
path: root/epdify.py
blob: 3bf132617a126f39a2212ad2516f048e771e810d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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