|
|
|
|
|
|
|
|
|
|
|
import numpy as np |
|
import torch |
|
import os |
|
from PIL import Image, ImageDraw, ImageOps, ImageFont |
|
from ..categories import icons |
|
from ..config import color_mapping, COLORS |
|
from ..nodes.graphics_functions import (hex_to_rgb, |
|
get_color_values, |
|
text_panel, |
|
combine_images, |
|
apply_outline_and_border, |
|
get_font_size, |
|
draw_text_on_image, |
|
crop_and_resize_image) |
|
|
|
font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts") |
|
file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ALIGN_OPTIONS = ["top", "center", "bottom"] |
|
ROTATE_OPTIONS = ["text center", "image center"] |
|
JUSTIFY_OPTIONS = ["left", "center", "right"] |
|
PERSPECTIVE_OPTIONS = ["top", "bottom", "left", "right"] |
|
|
|
|
|
|
|
def tensor2pil(image): |
|
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) |
|
|
|
def pil2tensor(image): |
|
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) |
|
|
|
|
|
class CR_MultiPanelMemeTemplate: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts") |
|
file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] |
|
templates = ["vertical - 2 image + 2 text", |
|
"vertical - 3 image + 3 text", |
|
"vertical - 4 image + 4 text", |
|
"horizontal - 2 image + 2 text", |
|
"horizontal - text bar + 2 image", |
|
"text bar + 1 image with overlay text", |
|
"text bar + 4 image", |
|
"text bar + 4 image with overlay text"] |
|
colors = COLORS[1:] |
|
|
|
return {"required": { |
|
"template": (templates,), |
|
"image_1": ("IMAGE",), |
|
"text_1": ("STRING", {"multiline": True, "default": "text_1"}), |
|
"text_2": ("STRING", {"multiline": True, "default": "text_2"}), |
|
"text_3": ("STRING", {"multiline": True, "default": "text_3"}), |
|
"text_4": ("STRING", {"multiline": True, "default": "text_4"}), |
|
"font_name": (file_list,), |
|
"font_size": ("INT", {"default": 50, "min": 1, "max": 1024}), |
|
"font_color": (colors,), |
|
"bar_color": (colors,), |
|
"reverse_panels": (["No", "Yes"],), |
|
}, |
|
"optional": { |
|
"image_2": ("IMAGE",), |
|
"image_3": ("IMAGE",), |
|
"image_4": ("IMAGE",), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "draw_text" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Template") |
|
|
|
def draw_text(self, template, image_1, text_1, text_2, text_3, text_4, |
|
font_name, font_size, font_color, bar_color, reverse_panels, image_2 = None, image_3 = None, image_4 = None): |
|
|
|
show_help = "example help text" |
|
|
|
|
|
return image_1, show_help, |
|
|
|
|
|
class CR_PopularMemeTemplates: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
templates = ["Expanding brain", |
|
"My honest reaction", |
|
"The GF I want", |
|
"Who would win?", |
|
"I have 4 sides", |
|
"This is Fine", |
|
"Is This a Pigeon?", |
|
"Drake hotline bling"] |
|
colors = COLORS[1:] |
|
|
|
return {"required": { |
|
"meme": (templates,), |
|
"image_1": ("IMAGE",), |
|
|
|
"text_1": ("STRING", {"multiline": True, "default": "text_1"}), |
|
"text_2": ("STRING", {"multiline": True, "default": "text_2"}), |
|
"text_3": ("STRING", {"multiline": True, "default": "text_3"}), |
|
"text_4": ("STRING", {"multiline": True, "default": "text_4"}), |
|
"font_name": (file_list,), |
|
"font_size": ("INT", {"default": 50, "min": 1, "max": 1024}), |
|
"font_color": (colors,), |
|
}, |
|
"optional": { |
|
"image_2": ("IMAGE",), |
|
"image_3": ("IMAGE",), |
|
"image_4": ("IMAGE",), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "draw_text" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Template") |
|
|
|
def draw_text(self, meme, image_1, text_1, text_2, text_3, text_4, |
|
font_name, font_size, font_color, image_2 = None, image_3 = None, image_4 = None): |
|
|
|
show_help = "example help text" |
|
|
|
|
|
return image_1, show_help, |
|
|
|
|
|
class CR_DrawPerspectiveText: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
return {"required": { |
|
"image_width": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"image_height": ("INT", {"default": 512, "min": 64, "max": 2048}), |
|
"text": ("STRING", {"multiline": True, "default": "text"}), |
|
"font_name": (file_list,), |
|
"font_size": ("INT", {"default": 50, "min": 1, "max": 1024}), |
|
"font_color": (COLORS,), |
|
"background_color": (COLORS,), |
|
"align": (ALIGN_OPTIONS,), |
|
"justify": (JUSTIFY_OPTIONS,), |
|
"margins": ("INT", {"default": 0, "min": -1024, "max": 1024}), |
|
"line_spacing": ("INT", {"default": 0, "min": -1024, "max": 1024}), |
|
"position_x": ("INT", {"default": 0, "min": -4096, "max": 4096}), |
|
"position_y": ("INT", {"default": 0, "min": -4096, "max": 4096}), |
|
"perspective_factor": ("FLOAT", {"default": 0.00, "min": 0.00, "max": 1.00, "step": 0.01}), |
|
"perspective_direction": (PERSPECTIVE_OPTIONS,), |
|
}, |
|
"optional": { |
|
"font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
"bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}) |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "draw_text" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Text") |
|
|
|
def draw_text(self, image_width, image_height, text, |
|
font_name, font_size, font_color, background_color, |
|
margins, line_spacing, |
|
position_x, position_y, |
|
align, justify, |
|
perspective_factor, perspective_direction, |
|
font_color_hex='#000000', bg_color_hex='#000000'): |
|
|
|
|
|
text_color = get_color_values(font_color, font_color_hex, color_mapping) |
|
bg_color = get_color_values(background_color, bg_color_hex, color_mapping) |
|
|
|
|
|
size = (image_width, image_height) |
|
text_image = Image.new('RGB', size, text_color) |
|
back_image = Image.new('RGB', size, bg_color) |
|
text_mask = Image.new('L', back_image.size) |
|
|
|
|
|
text_mask = draw_masked_text_v2(text_mask, text, font_name, font_size, |
|
margins, line_spacing, |
|
position_x, position_y, |
|
align, justify, |
|
perspective_factor, perspective_direction) |
|
|
|
|
|
image_out = Image.composite(text_image, back_image, text_mask) |
|
preview_out = text_mask |
|
|
|
show_help = "example help text" |
|
|
|
|
|
return pil2tensor(image_out), pil2tensor(preview_out), show_help, |
|
|
|
|
|
class CR_SimpleAnnotations: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
bar_opts = ["top", "bottom", "top and bottom", "no bars"] |
|
|
|
return {"required": { |
|
"image": ("IMAGE",), |
|
"text_top": ("STRING", {"multiline": True, "default": "text_top"}), |
|
"text_bottom": ("STRING", {"multiline": True, "default": "text_bottom"}), |
|
"font_name": (file_list,), |
|
"max_font_size": ("INT", {"default": 100, "min": 50, "max": 150}), |
|
"font_color": (COLORS,), |
|
"bar_color": (COLORS,), |
|
"bar_options": (bar_opts,), |
|
"bar_scaling_factor": ("FLOAT", {"default": 0.2, "min": 0.1, "max": 2, "step": 0.1}), |
|
}, |
|
"optional": { |
|
"font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
"bar_color_hex": ("STRING", {"multiline": False, "default": "#000000"}) |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "make_meme" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Text") |
|
|
|
def make_meme(self, image, |
|
text_top, text_bottom, |
|
font_name, max_font_size, |
|
font_color, bar_color, bar_options, bar_scaling_factor, |
|
font_color_hex='#000000', |
|
bar_color_hex='#000000'): |
|
|
|
text_color = get_color_values(font_color, font_color_hex, color_mapping) |
|
bar_color = get_color_values(bar_color, bar_color_hex, color_mapping) |
|
|
|
|
|
image_3d = image[0, :, :, :] |
|
|
|
|
|
if bar_options == "top": |
|
height_factor = 1 + bar_scaling_factor |
|
elif bar_options == "bottom": |
|
height_factor = 1 + bar_scaling_factor |
|
elif bar_options == "top and bottom": |
|
height_factor = 1 + 2 * bar_scaling_factor |
|
else: |
|
height_factor = 1.0 |
|
|
|
|
|
back_image = tensor2pil(image_3d) |
|
size = back_image.width, int(back_image.height * height_factor) |
|
result_image = Image.new("RGB", size) |
|
|
|
|
|
font_file = "fonts\\" + str(font_name) |
|
resolved_font_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), font_file) |
|
|
|
|
|
draw = ImageDraw.Draw(result_image) |
|
|
|
|
|
bar_width = back_image.width |
|
bar_height = back_image.height // 5 |
|
top_bar = Image.new("RGB", (bar_width, bar_height), bar_color) |
|
bottom_bar = Image.new("RGB", (bar_width, bar_height), bar_color) |
|
|
|
|
|
if bar_options == "top" or bar_options == "top and bottom": |
|
image_out = result_image.paste(back_image, (0, bar_height)) |
|
else: |
|
image_out = result_image.paste(back_image, (0, 0)) |
|
|
|
|
|
if bar_options == "top" or bar_options == "top and bottom": |
|
result_image.paste(top_bar, (0, 0)) |
|
font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size) |
|
draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, "No") |
|
|
|
if bar_options == "bottom" or bar_options == "top and bottom": |
|
result_image.paste(bottom_bar, (0, (result_image.height - bar_height))) |
|
font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size) |
|
if bar_options == "bottom": |
|
y_position = back_image.height |
|
else: |
|
y_position = bar_height + back_image.height |
|
draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No") |
|
|
|
|
|
if bar_options == "bottom" and text_top > "": |
|
font_top = get_font_size(draw, text_top, bar_width, bar_height, resolved_font_path, max_font_size) |
|
draw_text_on_image(draw, 0, bar_width, bar_height, text_top, font_top, text_color, "No") |
|
|
|
if (bar_options == "top" or bar_options == "none") and text_bottom > "": |
|
font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size) |
|
y_position = back_image.height |
|
draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No") |
|
|
|
if bar_options == "none" and text_bottom > "": |
|
font_bottom = get_font_size(draw, text_bottom, bar_width, bar_height, resolved_font_path, max_font_size) |
|
y_position = back_image.height - bar_height |
|
draw_text_on_image(draw, y_position, bar_width, bar_height, text_bottom, font_bottom, text_color, "No") |
|
|
|
show_help = "example help text" |
|
|
|
image_out = np.array(result_image).astype(np.float32) / 255.0 |
|
image_out = torch.from_numpy(image_out).unsqueeze(0) |
|
|
|
|
|
|
|
return (image_out, show_help, ) |
|
|
|
|
|
class CR_ApplyAnnotations: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts") |
|
file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] |
|
bar_opts = ["no bars", "top", "bottom", "top and bottom"] |
|
|
|
return {"required": { |
|
"image": ("IMAGE", ), |
|
"annotation_stack": ("ANNOTATION_STACK", ), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "apply_annotations" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Text") |
|
|
|
def apply_annotations(self, image, annotation_stack): |
|
|
|
show_help = "example help text" |
|
|
|
return (image_out, show_help, ) |
|
|
|
|
|
class CR_AddAnnotation: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "fonts") |
|
file_list = [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] |
|
bar_opts = ["no bars", "top", "bottom", "top and bottom"] |
|
|
|
return {"required": { |
|
"text": ("STRING", {"multiline": True, "default": "text_top"}), |
|
"font_name": (file_list,), |
|
"font_size": ("INT", {"default": 100, "min": 20, "max": 150}), |
|
"font_color": (COLORS,), |
|
"position_x": ("INT", {"default": 0, "min": 0, "max": 4096}), |
|
"position_y": ("INT", {"default": 0, "min": 0, "max": 4096}), |
|
"justify": (JUSTIFY_OPTIONS,), |
|
}, |
|
"optional": { |
|
"annotation_stack": ("ANNOTATION_STACK",), |
|
"font_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("ANNOTATION_STACK", "STRING", ) |
|
RETURN_NAMES = ("ANNOTATION_STACK", "show_help", ) |
|
FUNCTION = "add_annotation" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Text") |
|
|
|
def add_annotation(self, image, |
|
font_name, font_size, font_color, |
|
position_x, position_y, justify, |
|
annotation_stack=None, font_color_hex='#000000'): |
|
|
|
show_help = "example help text" |
|
|
|
return (annotation_stack, show_help, ) |
|
|
|
|
|
class CR_SimpleImageWatermark: |
|
|
|
@classmethod |
|
def INPUT_TYPES(cls): |
|
|
|
ALIGN_OPTIONS = ["center", "top left", "top center", "top right", "bottom left", "bottom center", "bottom right"] |
|
|
|
return {"required": { |
|
"image": ("IMAGE",), |
|
"watermark_image": ("IMAGE",), |
|
"watermark_scale": ("FLOAT", {"default": 1, "min": 0.1, "max": 5.00, "step": 0.01}), |
|
"opacity": ("FLOAT", {"default": 0.30, "min": 0.00, "max": 1.00, "step": 0.01}), |
|
"align": (ALIGN_OPTIONS,), |
|
"x_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}), |
|
"y_margin": ("INT", {"default": 20, "min": -1024, "max": 1024}), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", ) |
|
FUNCTION = "overlay_image" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Layout") |
|
|
|
def overlay_image(self, image, watermark_image, watermark_scale, opacity, align, x_margin, y_margin): |
|
|
|
|
|
image = tensor2pil(image) |
|
watermark_image = tensor2pil(watermark_image) |
|
|
|
|
|
image = image.convert("RGBA") |
|
watermark = watermark_image.convert("RGBA") |
|
|
|
|
|
watermark = watermark.resize(image.size) |
|
|
|
|
|
watermark_layer = Image.new("RGBA", image.size, (0, 0, 0, 0)) |
|
draw = ImageDraw.Draw(watermark_layer) |
|
|
|
|
|
if align == 'center': |
|
watermark_pos = ((image.width - watermark.width) // 2, (image.height - watermark.height) // 2) |
|
elif align == 'top left': |
|
watermark_pos = (x_margin, y_margin) |
|
elif align == 'top center': |
|
watermark_pos = ((image.width - watermark.width) // 2, y_margin) |
|
elif align == 'top right': |
|
watermark_pos = (image.width - watermark.width - x_margin, y_margin) |
|
elif align == 'bottom left': |
|
watermark_pos = (x_margin, image.height - watermark.height - y_margin) |
|
elif align == 'bottom center': |
|
watermark_pos = ((image.width - watermark.width) // 2, image.height - watermark.height - y_margin) |
|
elif align == 'bottom right': |
|
watermark_pos = (image.width - watermark.width - x_margin, image.height - watermark.height - y_margin) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if opacity != 1: |
|
watermark_layer = reduce_opacity(watermark_layer, opacity) |
|
|
|
|
|
image_out = Image.composite(watermark_layer, image, watermark_layer) |
|
|
|
|
|
return pil2tensor(image_out) |
|
|
|
|
|
class CR_ComicPanelTemplatesAdvanced: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
directions = ["left to right", "right to left"] |
|
|
|
templates = ["custom", |
|
"G22", "G33", |
|
"H2", "H3", |
|
"H12", "H13", |
|
"H21", "H23", |
|
"H31", "H32", |
|
"V2", "V3", |
|
"V12", "V13", |
|
"V21", "V23", |
|
"V31", "V32"] |
|
|
|
return {"required": { |
|
"page_width": ("INT", {"default": 512, "min": 8, "max": 4096}), |
|
"page_height": ("INT", {"default": 512, "min": 8, "max": 4096}), |
|
"template": (templates,), |
|
"reading_direction": (directions,), |
|
"border_thickness": ("INT", {"default": 5, "min": 0, "max": 1024}), |
|
"outline_thickness": ("INT", {"default": 2, "min": 0, "max": 1024}), |
|
"outline_color": (COLORS,), |
|
"panel_color": (COLORS,), |
|
"background_color": (COLORS,), |
|
}, |
|
"optional": { |
|
"images1": ("IMAGE",), |
|
"images2": ("IMAGE",), |
|
"images3": ("IMAGE",), |
|
"images4": ("IMAGE",), |
|
"custom_panel_layout": ("STRING", {"multiline": False, "default": "H123"}), |
|
"outline_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
"panel_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
"bg_color_hex": ("STRING", {"multiline": False, "default": "#000000"}), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("IMAGE", "STRING", ) |
|
RETURN_NAMES = ("image", "show_help", ) |
|
FUNCTION = "layout" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Template") |
|
|
|
def layout(self, page_width, page_height, template, reading_direction, |
|
border_thickness, outline_thickness, |
|
outline_color, panel_color, background_color, |
|
images1=None, images2=None, images3=None, images4=None, custom_panel_layout='G44', |
|
outline_color_hex='#000000', panel_color_hex='#000000', bg_color_hex='#000000'): |
|
|
|
|
|
|
|
panels = [] |
|
k = 0 |
|
batches = 0 |
|
|
|
|
|
if images1 is not None: |
|
images1 = [tensor2pil(image) for image in images1] |
|
len_images1 = len(images1) |
|
batches+=1 |
|
|
|
if images2 is not None: |
|
images2 = [tensor2pil(image) for image in images2] |
|
len_images2 = len(images2) |
|
batches+=1 |
|
|
|
if images3 is not None: |
|
images3 = [tensor2pil(image) for image in images3] |
|
len_images3 = len(images3) |
|
batches+=1 |
|
|
|
if images4 is not None: |
|
images4 = [tensor2pil(image) for image in images4] |
|
len_images4 = len(images4) |
|
batches+=1 |
|
|
|
|
|
outline_color = get_color_values(outline_color, outline_color_hex, color_mapping) |
|
panel_color = get_color_values(panel_color, panel_color_hex, color_mapping) |
|
bg_color = get_color_values(background_color, bg_color_hex, color_mapping) |
|
|
|
|
|
size = (page_width - (2 * border_thickness), page_height - (2 * border_thickness)) |
|
page = Image.new('RGB', size, bg_color) |
|
draw = ImageDraw.Draw(page) |
|
|
|
if template == "custom": |
|
template = custom_panel_layout |
|
|
|
|
|
first_char = template[0] |
|
if first_char == "G": |
|
rows = int(template[1]) |
|
columns = int(template[2]) |
|
panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns |
|
panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows |
|
|
|
|
|
|
|
for i in range(rows): |
|
|
|
for j in range(columns): |
|
|
|
create_and_paste_panel(page, border_thickness, outline_thickness, |
|
panel_width, panel_height, page.width, |
|
panel_color, bg_color, outline_color, |
|
images1, i, j, k, len_images1, reading_direction) |
|
k += 1 |
|
|
|
elif first_char == "H": |
|
rows = len(template) - 1 |
|
panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows |
|
|
|
|
|
|
|
for i in range(rows): |
|
columns = int(template[i+1]) |
|
panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns |
|
|
|
for j in range(columns): |
|
|
|
create_and_paste_panel(page, border_thickness, outline_thickness, |
|
panel_width, panel_height, page.width, |
|
panel_color, bg_color, outline_color, |
|
images1, i, j, k, len_images1, reading_direction) |
|
k += 1 |
|
|
|
elif first_char == "V": |
|
columns = len(template) - 1 |
|
panel_width = (page.width - (2 * columns * (border_thickness + outline_thickness))) // columns |
|
|
|
|
|
|
|
for j in range(columns): |
|
rows = int(template[j+1]) |
|
panel_height = (page.height - (2 * rows * (border_thickness + outline_thickness))) // rows |
|
|
|
for i in range(rows): |
|
|
|
create_and_paste_panel(page, border_thickness, outline_thickness, |
|
panel_width, panel_height, page.width, |
|
panel_color, bg_color, outline_color, |
|
images1, i, j, k, len_images1, reading_direction) |
|
k += 1 |
|
|
|
|
|
if border_thickness > 0: |
|
page = ImageOps.expand(page, border_thickness, bg_color) |
|
|
|
show_help = "example help text" |
|
|
|
return (pil2tensor(page), show_help, ) |
|
|
|
|
|
''' |
|
class CR_ASCIIPattern: |
|
|
|
@classmethod |
|
def INPUT_TYPES(s): |
|
|
|
return {"required": { |
|
"image": ("IMAGE",), |
|
} |
|
} |
|
|
|
RETURN_TYPES = ("STRING", "STRING", ) |
|
RETURN_NAMES = ("multiline_text", "show_help", ) |
|
FUNCTION = "draw_pattern" |
|
CATEGORY = icons.get("Comfyroll/Graphics/Pattern") |
|
|
|
def draw_pattern(self, image): |
|
|
|
pixel_ascii_map = "`^\",:;Il!i~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$" |
|
|
|
im = image |
|
x = list(im.getdata()) |
|
for pixel in iter(x): |
|
x = sum(pixel) // 3 # integer division |
|
x = (x * len(pixel_ascii_map)) // 255 # rescaling |
|
ascii_val = pixel_ascii_map[x] |
|
|
|
text_out = ascii_val |
|
|
|
show_help = "https://github.com/Suzie1/ComfyUI_Comfyroll_CustomNodes/wiki/Text-Nodes#cr-ascii-pattern" |
|
|
|
# Convert the PIL image back to a torch tensor |
|
return (text_out, show_help, ) |
|
''' |
|
|
|
|
|
|
|
|
|
''' |
|
NODE_CLASS_MAPPINGS = { |
|
"CR Multi-Panel Meme Template": CR_MultiPanelMemeTemplate, |
|
"CR Popular Meme Templates": CR_PopularMemeTemplates, |
|
"CR Draw Perspective Text": CR_DrawPerspectiveText, |
|
"CR Simple Annotations": CR_SimpleAnnotations, |
|
"CR Apply Annotations": CR_ApplyAnnotations, |
|
"CR Add Annotation": CR_AddAnnotation, |
|
"CR Simple Image Watermark": CR_SimpleImageWatermark, |
|
"CR Comic Panel Templates Advanced": CR_ComicPanelTemplatesAdvanced, |
|
"CR ASCII Pattern": CR_ASCIIPattern, |
|
} |
|
''' |
|
|
|
|