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