all repos — python-meme-bot @ 40a86f28087acdc6b2caadf357e734a89319e838

Telegram Bot that uses PIL to compute light image processing.

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