all repos — python-meme-bot @ fa8fccdd3e2c64ace2449eb2f36966583633c25b

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
 10def _darken_image(image: Image, amount=0.5):
 11    return ImageEnhance.Brightness(image).enhance(amount)
 12
 13def _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)):
 14    
 15    for i in range(len(line)):
 16                d.text((x, y), line[i], fill=fill, stroke_width=stroke_width, font=font, stroke_fill=stroke_fill)
 17                x += font.getlength(line[i]) + letter_spacing
 18
 19def tt_bt_effect(text: str, img: Image):
 20    LETTER_SPACING = 9
 21    LINE_SPACING = 10  
 22    FILL = (255, 255, 255)
 23    STROKE_WIDTH = 9
 24    STROKE_FILL = (0, 0, 0)
 25    FONT_BASE = 100
 26    MARGIN = 10
 27    
 28    def _draw_tt_bt(text, img, bottom=False):
 29        split_caption = textwrap.wrap(text.upper(), width=20)
 30        if split_caption == []:
 31            return
 32        font_size = FONT_BASE + 10 if len(split_caption) <= 1 else FONT_BASE
 33        font = ImageFont.truetype(font=IMPACT_FONT_FILE, size=font_size)
 34        img_width, img_height = img.size
 35
 36        d = ImageDraw.Draw(img)
 37        txt_height = d.textbbox((0, 0), split_caption[0], font=font)[3]
 38
 39        if bottom:
 40            factor = -1
 41            split_caption.reverse()
 42            y = (img_height - (img_height / MARGIN)) - (txt_height / 2)
 43        else:
 44            factor = 1
 45            y = (img_height / MARGIN) - (txt_height / 1.5)
 46
 47        for line in split_caption:
 48            txt_width = d.textbbox((0, 0), line, font=font)[2]
 49
 50            x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2
 51
 52            _draw_line(d, x, y, line, font, LETTER_SPACING, FILL, STROKE_WIDTH, STROKE_FILL)
 53
 54            y += (txt_height + LINE_SPACING) * factor
 55    
 56    lines = [x for x in text.split("\n") if x]
 57    
 58    tt = lines[0] if len(lines) > 0 else None
 59    bt = lines[1] if len(lines) > 1 else None
 60    
 61    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
 62    
 63    if tt is None and bt is None:
 64        return img
 65    
 66    if (tt is not None):
 67        _draw_tt_bt(tt, img)
 68    if (bt is not None):
 69        _draw_tt_bt(bt, img, bottom=True)
 70        
 71    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
 72    
 73    if img.mode in ("RGBA", "P"):
 74        img = img.convert("RGB")
 75    
 76    return img
 77
 78def splash_effect(input_text: str, img: Image):
 79    LETTER_SPACING = 1
 80    LINE_SPACING = 3  
 81    FILL = (255, 255, 255)
 82    STROKE_WIDTH = 1
 83    STROKE_FILL = (0, 0, 0)
 84    
 85    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
 86    
 87    img = _darken_image(img)
 88    
 89    img_width, img_height = img.size
 90    
 91    MARGIN_H = img_height / 2
 92    MARGIN_W = img_width / 2
 93    
 94    w = img_width - MARGIN_W
 95    h = img_height - MARGIN_H
 96    n = len(input_text.strip())
 97    k1 = 0.612123
 98    k2 = 1.216428
 99    k3 = 0.341428
100    k4 = 0.364576
101    
102    FONT_BASE = (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)
103    LINE_WIDTH = w / (k1 * FONT_BASE - k4 + LETTER_SPACING)
104    
105    lines = [x for x in input_text.split("\n") if x]
106    first_line = lines.pop(0)
107    text = "\n".join(lines)
108    
109    #f = (img_width / img_height) * 2
110    f = (img_height / img_width) * 2
111    
112    FONT_BASE /= f
113    LINE_WIDTH *= f * 2
114
115    text = textwrap.wrap(text.upper(), width=int(LINE_WIDTH))
116        
117    if text == []:
118        return
119    text.insert(0, first_line)
120
121    font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE - (FONT_BASE / 2)))
122    font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
123
124    d = ImageDraw.Draw(img)
125
126    _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first)
127    _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base)
128
129    total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height
130        
131    y = (img_height - total_height) / 2
132        
133    for i in range(1, len(text)):
134        temp = int(font_base.getlength(text[i]))
135        if temp > max_txt_width:
136            max_txt_width = temp
137
138    max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width
139    x_start = (img_width - max_txt_width) / 2
140        
141    for i in range(len(text)):
142        '''
143        if align == "center":
144            txt_width = d.textbbox((0, 0), line, font=font)[2]
145            x = (img_width - txt_width - (len(line) * LETTER_SPACING)) / 2
146        '''
147        font = font_base if i > 0 else font_first
148        _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)
149
150        y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING
151        
152    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
153    
154    if img.mode in ("RGBA", "P"):
155        img = img.convert("RGB")
156    
157    return img
158
159def wot_effect(input_text: str, img: Image):
160    LETTER_SPACING = 1
161    LINE_SPACING = 3  
162    FILL = (255, 255, 255)
163    STROKE_WIDTH = 1
164    STROKE_FILL = (0, 0, 0)
165    
166    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
167    img = _darken_image(img)
168    
169    img_width, img_height = img.size
170    
171    MARGIN_H = img_height / 4
172    MARGIN_W = 0
173    
174    w = img_width - MARGIN_W
175    h = img_height - MARGIN_H
176    n = len(input_text.strip())
177    k1 = 0.612123
178    k2 = 1.216428
179    k3 = 0.341428
180    k4 = 0.364576
181    
182    FONT_BASE = (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)
183    LINE_WIDTH = w / (k1 * FONT_BASE - k4 + LETTER_SPACING)
184
185    text = textwrap.wrap(input_text.strip(), width=int(LINE_WIDTH))
186
187    if text == []:
188        return
189    
190    font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
191    d = ImageDraw.Draw(img)
192    
193    txt_height = (k2 * FONT_BASE - k3 + LINE_SPACING)
194    max_text_height = txt_height * len(text)
195    y = (img_height - max_text_height) / 2
196        
197    for i in range(len(text)):
198        txt_width = d.textbbox((0, 0), text[i], font=font)[2]
199        x = (img_width - txt_width - (len(text[i]) * LETTER_SPACING)) / 2
200
201        _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)
202
203        y += txt_height + LINE_SPACING
204        
205    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
206    
207    if img.mode in ("RGBA", "P"):
208        img = img.convert("RGB")
209    
210    return img
211
212def text_effect(text: str, img: Image):
213    LETTER_SPACING = 1
214    LINE_SPACING = 3
215    STROKE_WIDTH = 1
216    STROKE_FILL = (0, 0, 0)
217    FONT_BASE = 75
218    MARGIN = 10
219    LINE_WIDTH = 20
220    
221    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
222    
223    text = textwrap.wrap(text, width=LINE_WIDTH)
224    if text == []:
225        return
226
227    font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE)
228    
229    img_width, img_height = img.size
230    d = ImageDraw.Draw(img)
231    
232    _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[0], font=font)
233    
234    for line in text:
235        temp = int(font.getlength(line))
236        if temp > max_txt_width:
237            max_txt_width = temp
238    
239    
240    total_height = (txt_height + LINE_SPACING) * len(text)
241    
242    y_inf = 0
243    y_sup = img_height - total_height
244    x_inf = 0
245    x_sup = img_width - max_txt_width - 5
246    
247    fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
248    x = random.randint(x_inf, x_sup)
249    y = random.randint(y_inf, y_sup)
250    for i in range(len(text)):
251        _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)
252
253        y += txt_height + LINE_SPACING
254        
255    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
256    
257    if img.mode in ("RGBA", "P"):
258        img = img.convert("RGB")
259    
260    return img
261
262def test_multiple(text, effect, modifier=""):
263        imgs = os.listdir("test")
264        for i in range(len(imgs)):
265            image = effect(text, Image.open(os.path.join("test", imgs[i])))
266            image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80)
267
268        print("Image test successful")
269
270def test(text, effect, modifier=""):
271        image = effect(text, Image.open("image.jpg"))
272        image.save('output.jpg', optimize=True, quality=80)
273
274        print("Image test successful")
275
276def main():
277    input_text = '''Bi-Rabittoh
278Prova wow
279'''
280    
281    test(input_text, splash_effect)
282    #test_multiple(input_text, splash_effect)
283    #test_multiple(input_text, splash_effect, "_long")
284    
285if __name__ ==  "__main__":
286    main()