Итоговый код
import uuid
from dataclasses import dataclass
from typing import Optional
from PIL import Image, ImageDraw, ImageFont
@dataclass
class Point:
x: Optional[int]
y: int
@dataclass
class Meme:
filename: str
slots: list[Point]
MEMES = {
'Coding cat': Meme(
'img/coding_cat.jpg',
[
Point(None, 15),
Point(None, 250)
]
),
'Hackerman': Meme(
'img/hackerman.jpg',
[
Point(None, 20)
]
),
}
def create_font(img, label, name, default_size):
font = ImageFont.truetype(name, default_size)
label_width = font.getlength(label)
ratio = label_width / img.width
max_ratio = 0.8
if ratio > max_ratio:
new_size = int(default_size / (1 + ratio - max_ratio))
return ImageFont.truetype(name, new_size)
return font
def calc_left_padding(img, label, font):
label_width = font.getlength(label)
return int((img.width - label_width) / 2)
def make_unique_filename(source):
filename, _, extension = source.rpartition('.')
return f'{filename}_{uuid.uuid4()}.{extension}'
def fill_meme(meme, labels):
slots_count = len(meme.slots)
labels_count = len(labels)
if slots_count != labels_count:
raise ValueError(
'Неверное количество надписей: '
f'ожидалось {slots_count}, '
f'получено {labels_count}'
)
# Загружаем изображение
with Image.open(meme.filename) as img:
canvas = ImageDraw.Draw(img)
for slot, label in zip(meme.slots, labels):
label = label.upper()
default_font_size = img.height // 6
font = create_font(img, label, 'ariblk.ttf', default_font_size)
x, y = slot.x, slot.y
if x is None:
x = calc_left_padding(img, label, font)
canvas.text((x, y), label, font=font, fill='white')
filename = make_unique_filename(meme.filename)
img.save(filename)
fill_meme(MEMES['Coding cat'], ['Компилируем HTML', 'В CSS'])
fill_meme(MEMES['Coding cat'], ['2 минуты пишу код', '3 часа отлаживаю'])
fill_meme(MEMES['Hackerman'], ['from PIL import *'])
fill_meme(MEMES['Hackerman'], ['print("Hello, world")'])