all repos — python-meme-bot @ d6840de4407d0edc3d794313bb3a1bde777e745d

Telegram Bot that uses PIL to compute light image processing.

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