all repos — python-meme-bot @ d52807e5161e29edb61b82df8bd7e19a97521e23

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    MARGIN_H = img_height / 2
105    MARGIN_W = img_width / 2
106    
107    w = img_width - MARGIN_W
108    h = img_height - MARGIN_H
109    n = len(input_text.strip())
110    
111    FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
112    
113    lines = [x for x in input_text.split("\n") if x]
114    first_line = lines.pop(0)
115    text = "\n".join(lines)
116    
117    #f = (img_width / img_height) * 2
118    f = (img_height / img_width) * 2
119    
120    FONT_BASE /= f
121    LINE_WIDTH *= f * 2
122
123    text = textwrap.wrap(text.upper(), width=int(LINE_WIDTH))
124        
125    if text == []:
126        return
127    text.insert(0, first_line)
128
129    font_first = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE - (FONT_BASE / 2)))
130    font_base = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
131
132    d = ImageDraw.Draw(img)
133
134    _, _, first_txt_width, first_txt_height = d.textbbox((0, 0), text[0], font=font_first)
135    _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[1], font=font_base)
136
137    total_height = (txt_height + LINE_SPACING) * (len(text) - 1) + LINE_SPACING + first_txt_height
138        
139    y = (img_height - total_height) / 2
140        
141    for i in range(1, len(text)):
142        temp = int(font_base.getlength(text[i]))
143        if temp > max_txt_width:
144            max_txt_width = temp
145
146    max_txt_width = max_txt_width if max_txt_width > first_txt_width else first_txt_width
147    x_start = (img_width - max_txt_width) / 2
148        
149    for i in range(len(text)):
150        
151        font = font_base if i > 0 else font_first
152        _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)
153
154        y += (txt_height if i > 0 else first_txt_height) + LINE_SPACING
155        
156    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
157    
158    if img.mode in ("RGBA", "P"):
159        img = img.convert("RGB")
160    
161    return img
162
163def wot_effect(input_text: str, img: Image):
164    LETTER_SPACING = 1
165    LINE_SPACING = 3  
166    FILL = (255, 255, 255)
167    STROKE_WIDTH = 1
168    STROKE_FILL = (0, 0, 0)
169    
170    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
171    img = _darken_image(img)
172    
173    img_width, img_height = img.size
174    
175    MARGIN_H = img_height / 4
176    MARGIN_W = 0
177    
178    w = img_width - MARGIN_W
179    h = img_height - MARGIN_H
180    n = len(input_text.strip())
181    
182    FONT_BASE, LINE_WIDTH = _get_font_size(h, w, n, LETTER_SPACING, LINE_SPACING)
183
184    text = textwrap.wrap(input_text.strip(), width=int(LINE_WIDTH))
185
186    if text == []:
187        return
188    
189    font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=int(FONT_BASE))
190    d = ImageDraw.Draw(img)
191    
192    txt_height = (k2 * FONT_BASE - k3 + LINE_SPACING)
193    max_text_height = txt_height * len(text)
194    y = (img_height - max_text_height) / 2
195        
196    for i in range(len(text)):
197        txt_width = d.textbbox((0, 0), text[i], font=font)[2]
198        x = (img_width - txt_width - (len(text[i]) * LETTER_SPACING)) / 2
199
200        _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)
201
202        y += txt_height + LINE_SPACING
203        
204    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
205    
206    if img.mode in ("RGBA", "P"):
207        img = img.convert("RGB")
208    
209    return img
210
211def text_effect(text: str, img: Image):
212    LETTER_SPACING = 1
213    LINE_SPACING = 3
214    STROKE_WIDTH = 1
215    STROKE_FILL = (0, 0, 0)
216    FONT_BASE = 75
217    MARGIN = 10
218    LINE_WIDTH = 20
219    
220    img = img.resize((BASE_WIDTH, int(img.size[1] * float(BASE_WIDTH / img.size[0]))))
221    
222    text = textwrap.wrap(text, width=LINE_WIDTH)
223    if text == []:
224        return
225
226    font = ImageFont.truetype(font=ARIAL_FONT_FILE, size=FONT_BASE)
227    
228    img_width, img_height = img.size
229    d = ImageDraw.Draw(img)
230    
231    _, _, max_txt_width, txt_height = d.textbbox((0, 0), text[0], font=font)
232    
233    for line in text:
234        temp = int(font.getlength(line))
235        if temp > max_txt_width:
236            max_txt_width = temp
237    
238    
239    total_height = (txt_height + LINE_SPACING) * len(text)
240    
241    y_inf = 0
242    y_sup = img_height - total_height
243    x_inf = 0
244    x_sup = img_width - max_txt_width - 5
245    
246    fill = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
247    x = random.randint(x_inf, x_sup)
248    y = random.randint(y_inf, y_sup)
249    for i in range(len(text)):
250        _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)
251
252        y += txt_height + LINE_SPACING
253        
254    img = img.resize((int(BASE_WIDTH / 2), int(float(img.size[1]) * (BASE_WIDTH / 2) / img.size[0])))
255    
256    if img.mode in ("RGBA", "P"):
257        img = img.convert("RGB")
258    
259    return img
260
261def test_multiple(text, effect, modifier=""):
262        imgs = os.listdir("test")
263        for i in range(len(imgs)):
264            image = effect(text, Image.open(os.path.join("test", imgs[i])))
265            image.save(os.path.join("test_output", f'output{modifier}{i}.jpg'), optimize=True, quality=80)
266
267        print("Image test successful")
268
269def test(text, effect, modifier=""):
270        image = effect(text, Image.open("image.jpg"))
271        image.save('output.jpg', optimize=True, quality=80)
272
273        print("Image test successful")
274
275def main():
276    input_text = '''Bi-Rabittoh
277Prova wow
278'''
279    
280    test(input_text, splash_effect)
281    #test_multiple(input_text, splash_effect)
282    #test_multiple(input_text, splash_effect, "_long")
283    
284if __name__ ==  "__main__":
285    main()