all repos — python-meme-bot @ 23056f54bf43bea42b9437056f1152e31f526381

Telegram Bot that uses PIL to compute light image processing.

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()