Effects.py (view raw)
1from PIL import Image, ImageDraw, ImageFont, ImageEnhance
2import textwrap, os, random, time, math
3
4random.seed(time.time())
5
6BASE_WIDTH = 1200
7IMPACT_FONT_FILE = os.path.join("fonts", "impact.ttf")
8ARIAL_FONT_FILE = os.path.join("fonts", "opensans.ttf")
9
10
11
12def _get_font_size(h, w, n, letter_spacing, line_spacing):
13
14 k1 = 0.612123
15 k2 = 1.216428
16 k3 = 0.341428
17 k4 = 0.364576
18
19 font_size = (math.sqrt(4 * k1 * k2 * h * n * w + math.pow(k2, 2) * math.pow(n, 2) * math.pow(letter_spacing, 2) + ((2 * k1 * k2 * k3 - 2 * k4 * math.pow(k2, 2)) * math.pow(n, 2) - 2 * k1 * k2 * line_spacing) * letter_spacing + math.pow(k1, 2) * math.pow(n, 2) * math.pow(line_spacing, 2) + (2 * k1 * k4 * k2 - 2 * math.pow(k1, 2) * k3) * math.pow(n, 2) * line_spacing + (math.pow(k1, 2) * math.pow(k3, 2) - 2 * k1 * k4 * k2 * k3 + math.pow(k4, 2) * math.pow(k2, 2)) * math.pow(n, 2)) - k2 * n * letter_spacing - k1 * n * line_spacing + (k1 * k3 + k4 * k2) * n) / (2 * k1 * k2 * n)
20 line_width = w / (k1 * font_size - k4 + letter_spacing)
21 return font_size, line_width
22
23def _darken_image(image: Image, amount=0.5):
24 return ImageEnhance.Brightness(image).enhance(amount)
25
26def _draw_line(d: ImageDraw, x: int, y: int, line: str, font: ImageFont, letter_spacing: int = 9, fill = (255, 255, 255), stroke_width: int = 9, stroke_fill = (0, 0, 0)):
27
28 for i in range(len(line)):
29 d.text((x, y), line[i], fill=fill, stroke_width=stroke_width, font=font, stroke_fill=stroke_fill)
30 x += font.getlength(line[i]) + letter_spacing
31
32def tt_bt_effect(text: str, img: Image):
33 LETTER_SPACING = 9
34 LINE_SPACING = 10
35 FILL = (255, 255, 255)
36 STROKE_WIDTH = 9
37 STROKE_FILL = (0, 0, 0)
38 FONT_BASE = 100
39 MARGIN = 10
40
41 def _draw_tt_bt(text, img, bottom=False):
42 split_caption = textwrap.wrap(text.upper(), width=20)
43 if split_caption == []:
44 return
45 font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE
46 font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size)
47 img_width, img_height = img.size
48
49 d = ImageDraw.Draw(img)
50 txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3]
51
52 if bottom:
53 factor = -1
54 split_caption.reverse()
55 y = (img_height - (img_height / MARGIN)) - (txt_height / 2)
56 else:
57 factor = 1
58 y = (img_height / MARGIN) - (txt_height / 1.5)
59
60 for line in split_caption:
61 txt_width = d.textbbox((0, 0), line, font=font)[2]
62
63 x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2
64
65 _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL)
66
67 y += (txt_height + LINE_SPACING) * factor
68
69 lines = [x for x in text.split("\n") if x]
70
71 tt = lines[0] if len(lines) > 0 else None
72 bt = lines[1] if len(lines) > 1 else None
73
74 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
75
76 if tt is None and bt is None:
77 return img
78
79 if (tt is not None):
80 _draw_tt_bt(tt, img)
81 if (bt is not None):
82 _draw_tt_bt(bt, img, bottom=True)
83
84 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
85
86 if img.mode in ("RGBA", "P"):
87 img = img.convert("RGB")
88
89 return img
90
91def splash_effect(input_text: str, img: Image):
92 LETTER_SPACING = 1
93 LINE_SPACING = 3
94 FILL = (255, 255, 255)
95 STROKE_WIDTH = 1
96 STROKE_FILL = (0, 0, 0)
97
98 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
99
100 img = _darken_image(img)
101
102 img_width, img_height = img.size
103
104 w = img_width / 2
105 h = img_height / 2
106 n = len(input_text.strip())
107
108 FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
109
110 lines = [x for x in input_text.split("\n") if x]
111 first_line = lines.pop(0)
112 text = "\n".join(lines)
113
114 FONT_BASE /= 2
115 LINE_WIDTH *= 2
116
117 text = textwrap.wrap(text.upper(), width=int(LINE_WIDTH))
118
119 if text == []:
120 return
121 text.insert(0, first_line)
122
123 font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE - (FONT_BASE / 2)))
124 font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
125
126 d = ImageDraw.Draw(img)
127
128 _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first)
129 _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base)
130
131 total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height
132
133 y = (img_height - total_height) / 2
134
135 for i in range(1, len(text)):
136 temp = int(font_base.getlength(text[i]))
137 if temp > max_txt_width:
138 max_txt_width = temp
139
140 max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width
141 x_start = (img_width - max_txt_width) / 2
142
143 for i in range(len(text)):
144
145 font = font_base if i > 0 else font_first
146 _draw_line(d=d, x=x_start, y=y, line=text[i], font=font, letter_spacing=LETTER_SPACING, fill=FILL, stroke_width=STROKE_WIDTH, stroke_fill=STROKE_FILL)
147
148 y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING
149
150 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
151
152 if img.mode in ("RGBA", "P"):
153 img = img.convert("RGB")
154
155 return img
156
157def wot_effect(input_text: str, img: Image):
158 LETTER_SPACING = 1
159 LINE_SPACING = 3
160 FILL = (255, 255, 255)
161 STROKE_WIDTH = 1
162 STROKE_FILL = (0, 0, 0)
163
164 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
165 img = _darken_image(img)
166
167 img_width, img_height = img.size
168
169 MARGIN_H = img_height / 4
170 MARGIN_W = 0
171
172 w = img_width - MARGIN_W
173 h = img_height - MARGIN_H
174 n = len(input_text.strip())
175
176 FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
177
178 text = textwrap.wrap(input_text.strip(), width=int(LINE_WIDTH))
179
180 if text == []:
181 return
182
183 font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
184 d = ImageDraw.Draw(img)
185
186 txt_height = (k2 * FONT_BASE - k3 + LINE_SPACING)
187 max_text_height = txt_height * len(text)
188 y = (img_height - max_text_height) / 2
189
190 for i in range(len(text)):
191 txt_width = d.textbbox((0, 0), text[i], font=font)[2]
192 x = (img_width - txt_width - (len(text[i]) * LETTER_SPACING)) / 2
193
194 _draw_line(d=d, x=x, y=y, line=text[i], font=font, letter_spacing=LETTER_SPACING, fill=FILL, stroke_width=STROKE_WIDTH, stroke_fill=STROKE_FILL)
195
196 y += txt_height + LINE_SPACING
197
198 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
199
200 if img.mode in ("RGBA", "P"):
201 img = img.convert("RGB")
202
203 return img
204
205def text_effect(text: str, img: Image):
206 LETTER_SPACING = 1
207 LINE_SPACING = 3
208 STROKE_WIDTH = 1
209 STROKE_FILL = (0, 0, 0)
210 FONT_BASE = 75
211 MARGIN = 10
212 LINE_WIDTH = 20
213
214 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
215
216 text = textwrap.wrap(text, width=LINE_WIDTH)
217 if text == []:
218 return
219
220 font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE)
221
222 img_width, img_height = img.size
223 d = ImageDraw.Draw(img)
224
225 _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[0], font=font)
226
227 for line in text:
228
229 temp = int(font.getlength(line))
230 if temp > max_txt_width:
231 max_txt_width = temp
232
233
234 total_height = (txt_height + LINE_SPACING) * len(text)
235
236 y_inf = 0
237 y_sup = img_height - total_height
238 x_inf = 0
239 x_sup = img_width - max_txt_width - 5
240
241 fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
242 x = random.randint(x_inf, x_sup)
243 y = random.randint(y_inf, y_sup)
244
245 for i in range(len(text)):
246
247 _draw_line(d=d, x=x, y=y, line=text[i], font=font, letter_spacing=LETTER_SPACING, fill=fill, stroke_width=STROKE_WIDTH, stroke_fill=STROKE_FILL)
248
249 y += txt_height + LINE_SPACING
250
251 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
252
253 if img.mode in ("RGBA", "P"):
254 img = img.convert("RGB")
255
256 return img
257
258def test_multiple(text, effect, modifier=""):
259
260 imgs = os.listdir("test")
261 for i in range(len(imgs)):
262 image = effect(text, Image.open(os.path.join("test", imgs[i])))
263 image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80)
264
265 print("Image test successful")
266
267def test(text, effect, modifier=""):
268
269 image = effect(text, Image.open("image.jpg"))
270 image.save('output.jpg', optimize=True, quality=80)
271
272 print("Image test successful")
273
274def main():
275 input_text = '''Bi-Rabittoh
276Prova wow
277'''
278
279 #test(input_text, splash_effect)
280 test_multiple(input_text, splash_effect)
281 #test_multiple(input_text, splash_effect, "_long")
282
283if __name__ == "__main__":
284 main()