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
|