all repos — python-meme-bot @ 06f4f2d1be1acacaaf84179a7a8ad0d2b78ef739

Telegram Bot that uses PIL to compute light image processing.

Effects.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 img_to_bio(image):
 32    
 33    bio = BytesIO()
 34    
 35    if image.mode in ("RGBA", "P"):
 36        image = image.convert("RGB")
 37
 38    image.save(bio, 'JPEG')
 39    bio.seek(0)
 40    return bio
 41
 42def _darken_image(image: Image, amount=0.5):
 43    return Brightness(image).enhance(amount)
 44
 45def _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)):
 46    
 47    for i in range(len(line)):
 48                d.text((x, y), line[i], fill=fill, stroke_width=stroke_width, font=font, stroke_fill=stroke_fill)
 49                x += font.getlength(line[i]) + letter_spacing
 50
 51def _draw_tt_bt(text: str, img: Image, bottom=False):
 52        
 53    LETTER_SPACING = 9
 54    LINE_SPACING = 10  
 55    FILL = (255, 255, 255)
 56    STROKE_WIDTH = 9
 57    STROKE_FILL = (0, 0, 0)
 58    FONT_BASE = 100
 59    MARGIN = 10
 60
 61    split_caption = textwrap.wrap(text.upper(), width=20)
 62    if split_caption == []:
 63        return
 64    
 65    font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE
 66    font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size)
 67    img_width, img_height = img.size
 68
 69    d = ImageDraw.Draw(img)
 70    txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3]
 71
 72    if bottom:
 73        factor = -1
 74        split_caption.reverse()
 75        y = (img_height - (img_height / MARGIN)) - (txt_height / 2)
 76    else:
 77        factor = 1
 78        y = (img_height / MARGIN) - (txt_height / 1.5)
 79
 80    for line in split_caption:
 81        txt_width = d.textbbox((0, 0), line, font=font)[2]
 82
 83        x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2
 84
 85        _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL)
 86
 87        y += (txt_height + LINE_SPACING) * factor
 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_tt_bt(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 tt_bt_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_tt_bt(tt, img)
122    if (bt is not None):
123        _draw_tt_bt(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()