164 lines
5.9 KiB
Python
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"
|
|
}
|