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 MARGIN_H = img_height / 2
105 MARGIN_W = img_width / 2
106
107 w = img_width - MARGIN_W
108 h = img_height - MARGIN_H
109 n = len(input_text.strip())
110
111 FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
112
113 lines = [x for x in input_text.split("\n") if x]
114 first_line = lines.pop(0)
115 text = "\n".join(lines)
116
117 #f = (img_width / img_height) * 2
118 f = (img_height / img_width) * 2
119
120 FONT_BASE /= f
121 LINE_WIDTH *= f * 2
122
123 text = textwrap.wrap(text.upper(), width=int(LINE_WIDTH))
124
125 if text == []:
126 return
127 text.insert(0, first_line)
128
129 font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE - (FONT_BASE / 2)))
130 font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
131
132 d = ImageDraw.Draw(img)
133
134 _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first)
135 _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base)
136
137 total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height
138
139 y = (img_height - total_height) / 2
140
141 for i in range(1, len(text)):
142 temp = int(font_base.getlength(text[i]))
143 if temp > max_txt_width:
144 max_txt_width = temp
145
146 max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width
147 x_start = (img_width - max_txt_width) / 2
148
149 for i in range(len(text)):
150
151 font = font_base if i > 0 else font_first
152 _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)
153
154 y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING
155
156 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
157
158 if img.mode in ("RGBA", "P"):
159 img = img.convert("RGB")
160
161 return img
162
163def wot_effect(input_text: str, img: Image):
164 LETTER_SPACING = 1
165 LINE_SPACING = 3
166 FILL = (255, 255, 255)
167 STROKE_WIDTH = 1
168 STROKE_FILL = (0, 0, 0)
169
170 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
171 img = _darken_image(img)
172
173 img_width, img_height = img.size
174
175 MARGIN_H = img_height / 4
176 MARGIN_W = 0
177
178 w = img_width - MARGIN_W
179 h = img_height - MARGIN_H
180 n = len(input_text.strip())
181
182 FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
183
184 text = textwrap.wrap(input_text.strip(), width=int(LINE_WIDTH))
185
186 if text == []:
187 return
188
189 font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
190 d = ImageDraw.Draw(img)
191
192 txt_height = (k2 * FONT_BASE - k3 + LINE_SPACING)
193 max_text_height = txt_height * len(text)
194 y = (img_height - max_text_height) / 2
195
196 for i in range(len(text)):
197 txt_width = d.textbbox((0, 0), text[i], font=font)[2]
198 x = (img_width - txt_width - (len(text[i]) * LETTER_SPACING)) / 2
199
200 _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)
201
202 y += txt_height + LINE_SPACING
203
204 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
205
206 if img.mode in ("RGBA", "P"):
207 img = img.convert("RGB")
208
209 return img
210
211def text_effect(text: str, img: Image):
212 LETTER_SPACING = 1
213 LINE_SPACING = 3
214 STROKE_WIDTH = 1
215 STROKE_FILL = (0, 0, 0)
216 FONT_BASE = 75
217 MARGIN = 10
218 LINE_WIDTH = 20
219
220 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
221
222 text = textwrap.wrap(text, width=LINE_WIDTH)
223 if text == []:
224 return
225
226 font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE)
227
228 img_width, img_height = img.size
229 d = ImageDraw.Draw(img)
230
231 _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[0], font=font)
232
233 for line in text:
234 temp = int(font.getlength(line))
235 if temp > max_txt_width:
236 max_txt_width = temp
237
238
239 total_height = (txt_height + LINE_SPACING) * len(text)
240
241 y_inf = 0
242 y_sup = img_height - total_height
243 x_inf = 0
244 x_sup = img_width - max_txt_width - 5
245
246 fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
247 x = random.randint(x_inf, x_sup)
248 y = random.randint(y_inf, y_sup)
249 for i in range(len(text)):
250 _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)
251
252 y += txt_height + LINE_SPACING
253
254 img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
255
256 if img.mode in ("RGBA", "P"):
257 img = img.convert("RGB")
258
259 return img
260
261def test_multiple(text, effect, modifier=""):
262 imgs = os.listdir("test")
263 for i in range(len(imgs)):
264 image = effect(text, Image.open(os.path.join("test", imgs[i])))
265 image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80)
266
267 print("Image test successful")
268
269def test(text, effect, modifier=""):
270 image = effect(text, Image.open("image.jpg"))
271 image.save('output.jpg', optimize=True, quality=80)
272
273 print("Image test successful")
274
275def main():
276 input_text = '''Bi-Rabittoh
277Prova wow
278'''
279
280 test(input_text, splash_effect)
281 #test_multiple(input_text, splash_effect)
282 #test_multiple(input_text, splash_effect, "_long")
283
284if __name__ == "__main__":
285 main()