Files
2024-07-05 16:33:01 -05:00

164 lines
5.9 KiB
Python

import os
import sys
import torch
import textwrap
import torchvision.transforms.v2 as T
from PIL import Image, ImageDraw, ImageFont, ImageColor
import folder_paths
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "comfy"))
FONTS_DIR = folder_paths.get_input_directory()
class MemeMaker:
"""
A node to add meme-style text to an image
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"meme_text": ("STRING", {
"multiline": True,
"default": "Your Meme Text Here"
}),
"font": ([f for f in os.listdir(FONTS_DIR) if f.endswith('.ttf') or f.endswith('.otf')], ),
"max_font_size": ("INT", {
"default": 128,
"min": 1,
"max": 9999,
"step": 1
}),
"font_color": ("STRING", {
"multiline": False,
"default": "#FFFFFF"
}),
"outline_color": ("STRING", {
"multiline": False,
"default": "#000000"
}),
"outline_width": ("INT", {
"default": 2,
"min": 0,
"max": 10,
"step": 1
}),
"horizontal_align": (["left", "center", "right"], {
"default": "center"
}),
"vertical_align": (["top", "center", "bottom"], {
"default": "bottom"
}),
"padding_left": ("INT", {
"default": 20,
"min": 0,
"max": 9999,
"step": 1
}),
"padding_right": ("INT", {
"default": 20,
"min": 0,
"max": 9999,
"step": 1
}),
"padding_top": ("INT", { # New input for padding_top
"default": 20,
"min": 0,
"max": 9999,
"step": 1
}),
"padding_bottom": ("INT", {
"default": 20,
"min": 0,
"max": 9999,
"step": 1
}),
"meme_height": ("INT", {
"default": 128,
"min": 1,
"max": 9999,
"step": 1
}),
},
"optional": {
"image_input": ("IMAGE",),
},
}
RETURN_TYPES = ("IMAGE", "MASK",)
FUNCTION = "execute"
CATEGORY = "Image Processing"
def execute(self, meme_text, font, max_font_size, font_color, outline_color, outline_width, horizontal_align, vertical_align, padding_left, padding_right, padding_top, padding_bottom, meme_height, image_input=None):
meme_text = meme_text.upper()
font_path = os.path.join(FONTS_DIR, font)
font = ImageFont.truetype(font_path, max_font_size)
if image_input is not None:
image_input = T.ToPILImage()(image_input.permute([0, 3, 1, 2])[0]).convert('RGBA')
width = image_input.width
height = image_input.height
image = Image.new('RGBA', (width, height), color=(0, 0, 0, 0))
else:
raise ValueError("image_input is required")
draw = ImageDraw.Draw(image)
max_width = width - padding_left - padding_right
max_height = meme_height
def get_text_size(text, font):
lines = textwrap.wrap(text, width=max_width // (font_size // 2))
return draw.multiline_textbbox((0, 0), "\n".join(lines), font=font)
font_size = max_font_size
bbox = get_text_size(meme_text, font)
while (bbox[2] - bbox[0] > max_width or bbox[3] - bbox[1] > max_height) and font_size > 1:
font_size -= 1
font = ImageFont.truetype(font_path, font_size)
bbox = get_text_size(meme_text, font)
lines = textwrap.wrap(meme_text, width=max_width // (font_size // 2))
total_text_height = sum(draw.textbbox((0, 0), line, font=font)[3] for line in lines)
if vertical_align == "top":
text_y = padding_top # Use padding_top here
elif vertical_align == "center":
text_y = (height - total_text_height) // 2
elif vertical_align == "bottom":
text_y = height - total_text_height - padding_bottom
for line in lines:
line_width = draw.textbbox((0, 0), line, font=font)[2]
if horizontal_align == "left":
text_x = padding_left
elif horizontal_align == "center":
text_x = (width - line_width) // 2
elif horizontal_align == "right":
text_x = width - line_width - padding_right
# Draw outline
for adj in range(-outline_width, outline_width + 1):
for adj2 in range(-outline_width, outline_width + 1):
if adj != 0 or adj2 != 0:
draw.text((text_x + adj, text_y + adj2), line, font=font, fill=outline_color)
# Draw text
draw.text((text_x, text_y), line, font=font, fill=font_color)
text_y += draw.textbbox((0, 0), line, font=font)[3]
if image_input is not None:
image = Image.alpha_composite(image_input, image)
image_tensor_out = T.ToTensor()(image).unsqueeze(0).permute([0, 2, 3, 1])
mask = image_tensor_out[:, :, :, 3] if image_tensor_out.shape[3] == 4 else torch.ones_like(image_tensor_out[:, :, :, 0])
return (image_tensor_out[:, :, :, :3], mask,)
NODE_CLASS_MAPPINGS = {
"MemeMaker": MemeMaker
}
NODE_DISPLAY_NAME_MAPPINGS = {
"MemeMaker": "Meme Maker - fonts autoread in inputs folder"
}