Effects.py (view raw)
1from PIL import Image, ImageDraw, ImageFont, ImageEnhance
2import textwrap, os
3
4BASE_WIDTH = 1200
5IMPACT_FONT_FILE = os.path.join("fonts", "impact.ttf")
6ARIAL_FONT_FILE = os.path.join("fonts", "opensans.ttf")
7
8def _darken_image(image: Image, amount=0.5):
9 return ImageEnhance.Brightness(image).enhance(amount)
10
11def _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)):
12
13 for i in range(len(line)):
14 d.text((x, y), line[i], fill=fill, stroke_width=stroke_width, font=font, stroke_fill=stroke_fill)
15 x += font.getlength(line[i]) + letter_spacing
16
17def tt_bt_effect(text: str, img: Image):
18 LETTER_SPACING = 9
19 LINE_SPACING = 10
20 FILL = (255, 255, 255)
21 STROKE_WIDTH = 9
22 STROKE_FILL = (0, 0, 0)
23 FONT_BASE = 100
24 MARGIN = 10
25
26 def _draw_tt_bt(text, img, bottom=False):
27 split_caption = textwrap.wrap(text.upper(), width=20)
28 if split_caption == []:
29 return
30 font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE
31 font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size)
32 img_width, img_height = img.size
33
34 d = ImageDraw.Draw(img)
35 txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3]
36
37 if bottom:
38 factor = -1
39 split_caption.reverse()
40 y = (img_height - (img_height / MARGIN)) - (txt_height / 2)
41 else:
42 factor = 1
43 y = (img_height / MARGIN) - (txt_height / 1.5)
44
45 for line in split_caption:
46 txt_width = d.textbbox((0, 0), line, font=font)[2]
47
48 x = (img_width - txt_width - (len(line) * LETTER_SPACING))/2
49
50 _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL)
51
52 y += (txt_height + LINE_SPACING) * factor
53
54 lines = [x for x in text.split("\n") if x]
55
56 tt = lines[0] if len(lines) > 0 else None
57 bt = lines[1] if len(lines) > 1 else None
58
59 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
60
61 if tt is None and bt is None:
62 return img
63
64 if (tt is not None):
65 _draw_tt_bt(tt, img)
66 if (bt is not None):
67 _draw_tt_bt(bt, img, bottom=True)
68
69 img = img.resize((int(BASE_WIDTH/2), int(float(img.size[1]) * (BASE_WIDTH/2) / img.size[0])))
70
71 if img.mode in ("RGBA", "P"):
72 img = img.convert("RGB")
73
74 return img
75
76def splash_effect(text: str, img: Image):
77 LETTER_SPACING = 1
78 LINE_SPACING = 3
79 FILL = (255, 255, 255)
80 STROKE_WIDTH = 1
81 STROKE_FILL = (0, 0, 0)
82 FONT_FIRST = 50
83 FONT_BASE = 75
84 MARGIN = 10
85 LINE_WIDTH = 20
86
87 def _draw_splash(text, img):
88 font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_FIRST)
89 font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE)
90
91 img_width, img_height = img.size
92
93 d = ImageDraw.Draw(img)
94
95 _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first)
96 _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base)
97
98 total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height
99 y = (img_height - total_height) / 2
100
101 for i in range(1, len(text)):
102 #temp = d.textbbox((0, 0), text[i], font=font_base)[2]
103 temp = int(font_base.getlength(text[i]))
104 if temp > max_txt_width:
105 max_txt_width = temp
106
107 max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width
108 x_start = (img_width - max_txt_width) / 2
109
110 for i in range(len(text)):
111 line = text[i]
112 x = x_start
113 '''
114 if align == "center":
115 txt_width = d.textbbox((0, 0), line, font=font)[2]
116 x = (img_width - txt_width - (len(line) * LETTER_SPACING))/2
117 '''
118 font = font_base if i > 0 else font_first
119 _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL)
120
121 y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING
122
123 lines = [x for x in text.split("\n") if x]
124 if len(lines) < 2:
125 return img
126
127 img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
128
129 img = _darken_image(img)
130
131 split_text = textwrap.wrap(lines[1].upper(), width=LINE_WIDTH)
132 if split_text == []:
133 return
134 split_text.insert(0, lines[0])
135 _draw_splash(split_text, img)
136
137 img = img.resize((int(BASE_WIDTH/2), int(float(img.size[1]) * (BASE_WIDTH/2) / img.size[0])))
138
139 if img.mode in ("RGBA", "P"):
140 img = img.convert("RGB")
141
142 return img
143
144def test_multiple(text, effect, modifier=""):
145 imgs = os.listdir("test")
146 for i in range(len(imgs)):
147 image = effect(text, Image.open(os.path.join("test", imgs[i])))
148 image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80)
149
150 print("Image test successful")
151
152def test(text, effect, modifier=""):
153 image = effect(text, Image.open("image.jpg"))
154 image.save('output.jpg', optimize=True, quality=80)
155
156 print("Image test successful")
157
158def main():
159 #test("Autore\ntesto un po' più lungo ma non troppo eh\nquesto verrà scartato\npure questo", splash_effect)
160 test_multiple("top text\nbottom text", splash_effect)
161 test_multiple("top text top text top text top text top text\nbottom text bottom text bottom text bottom text bottom text", splash_effect, "_long")
162
163if __name__ == "__main__":
164 main()