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