diff --git a/README.MD b/README.MD index b02c434..05b6583 100644 --- a/README.MD +++ b/README.MD @@ -37,6 +37,7 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git ## Update **If the dependency package error after updating, please reinstall the relevant dependency packages. for details, please refer to [here](https://github.com/chflame163/ComfyUI_LayerStyle/issues/5).
+* Commit [SimpleTextImage](#SimpleTextImage) node, it generate simple typesetting images and masks from text. This node references some of the functionalities and code of [ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite](https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite). * Commit [PromptTagger](#PromptTagger) node,Inference the prompts based on the image. and it can replace key word for the prompt(need apply for Google Studio API key). Upgrade [ColorImageV2](#ColorImageV2) and [GradientImageV2](#GradientImageV2),support user customize preset sizes and size_as input. * Commit [LaMa](#LaMa) node, it can erase objects from the image based on the mask. this node is repackage of [IOPaint](https://www.iopaint.com). * Commit [ImageRemoveAlpha](#ImageRemoveAlpha) and [ImageCombineAlpha](#ImageCombineAlpha) nodes, alpha channel of the image can be removed or merged. @@ -652,6 +653,33 @@ The following changes have been made based on GradientImage: *Only limited to input images and masks. forcing the integration of other types of inputs will result in node errors. **The preset size is defined in ```custom_size.ini```, which is located in the root directory of the plugin. Open with text editing software. Each row represents a size, with the first value being width and the second being height, separated by a lowercase "x" in the middle. To avoid errors, please do not enter extra characters. +### SimpleTextImage +![image](image/simple_text_image_example.png) +Generate simple typesetting images and masks from text. This node references some of the functionalities and code of [ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite](https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite). + +![image](image/simple_text_image_node.png) +Node options: +* size_as*: The input image or mask here will generate the output image and mask according to their size. this input takes priority over the width and height below. +* font_file**: Here is a list of available font files in the font folder, and the selected font files will be used to generate images. +* align: Alignment options. There are three options: center, left, and right. +* char_per_line: The number of characters per line, any excess will be automatically wrapped. +* leading: The leading space. +* font_size: The size of font. +* text_color: The color of text. +* stroke_width: The width of stroke. +* stroke_color: The color of stroke. +* x_offset: The horizontal offset of the text position. +* y_offset: The vertical offset of the text position. +* width: Width of the image. If there is a size_as input, this setting will be ignored. +* height: Height of the image. If there is a size_as input, this setting will be ignored. + + +*Only limited to input image and mask. forcing the integration of other types of inputs will result in node errors. + +**The font folder is defined in ```resource_dir.ini```, this file is located in the root directory of the plugin. +Open the text editing software and find the line starting with "FONT_dir=", after "=", enter the custom folder path name. all font files in this folder will be collected and displayed in the node list during ComfyUI initialization. +If the folder set in ini is invalid, the font folder that comes with the plugin will be enabled. + ### TextImage diff --git a/README_CN.MD b/README_CN.MD index 0d9ff14..11f84a3 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -38,6 +38,7 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git ## 更新说明 **如果本插件更新后出现依赖包错误,请重新安装相关依赖包。详情见[这里](https://github.com/chflame163/ComfyUI_LayerStyle/issues/5)。
+* 添加 [SimpleTextImage](#SimpleTextImage) 节点。从文字生成简单排版的图片以及遮罩。这个节点参考了[ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite](https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite)的部分功能和代码。 * 添加 [PromptTagger](#PromptTagger) 节点,根据图片反推提示词,可以替换关键词。需要申请Google Studio API使用。升级节点[ColorImageV2](#ColorImageV2)和[GradientImageV2](#GradientImageV2),支持用户自定义预设尺寸和size_as输入。 * 添加 [Lama](#Lama) 节点,根据图像遮罩擦除物体。本节点是对[IOPaint](https://www.iopaint.com)的封装。 * 添加 [ImageRemoveAlpha](#ImageRemoveAlpha) 和 [ImageCombineAlpha](#ImageCombineAlpha) 节点,可移除或合并图片的alpha通道。 @@ -648,6 +649,36 @@ GradientImage的V2升级版。 **预设尺寸在```custom_size.ini```中定义,这个文件位于插件根目录下。用文本编辑软件打开,编辑自定义尺寸。每行表示一个尺寸,第一个数值是宽度,第二个是高度,中间用小写的"x"分隔。为避免错误请不要输入多余的字符。 + + +### SimpleTextImage +![image](image/simple_text_image_example.png) +从文字生成简单排版的图片以及遮罩。这个节点参考了[ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite](https://github.com/ZHO-ZHO-ZHO/ComfyUI-Text_Image-Composite)的部分功能和代码。 + +![image](image/simple_text_image_node.png) +节点选项说明: +* size_as*: 此处输入图像或遮罩,将按照其尺寸生成输出图像和遮罩。注意,此输入优先级高于下面的width和height。 +* text: 文字输入。 +* font_file**: 这里列出了font文件夹中可用的字体文件列表,选中的字体文件将被用来生成图像。 +* align: 对齐选项。有居中,靠左和靠右三个选项。 +* char_per_line: 每行字符数量,超过的部分将自动换行。 +* leading: 行间距。 +* font_size: 字体大小。 +* text_color: 文字颜色。 +* stroke_width: 描边宽度。 +* stroke_color: 描边颜色。 +* x_offset: 文字位置的水平偏移量。 +* y_offset: 文字位置的垂直偏移量。 +* width: 画面的宽度。如果有size_as输入,此设置将被忽略。 +* height: 画面的高度。如果有size_as输入,此设置将被忽略。 + + +*仅限输入image和mask, 如果强制接入其他类型输入,将导致节点错误。 +**font文件夹在```resource_dir.ini```中定义,这个文件位于插件根目录下。用文本编辑软件打开,找到“FONT_dir=”开头的这一行,编辑“=”之后为自定义文件夹路径名。这个文件夹里面所有的.ttf和.otf文件将在ComfyUI初始化时被收集并显示在节点的列表中。如果ini中设定的文件夹无效,将启用插件自带的font文件夹。 + + + + ### TextImage ![image](image/text_image_example.png) 从文字生成图片以及遮罩。支持字间距行间距调整,横排竖排调整,可设置文字的随机变化,包括大小和位置的随机变化。 @@ -670,7 +701,7 @@ GradientImage的V2升级版。 * background_color: 背景颜色。 - +*仅限输入image和mask, 如果强制接入其他类型输入,将导致节点错误。 **font文件夹在```resource_dir.ini```中定义,这个文件位于插件根目录下。用文本编辑软件打开,找到“FONT_dir=”开头的这一行,编辑“=”之后为自定义文件夹路径名。这个文件夹里面所有的.ttf和.otf文件将在ComfyUI初始化时被收集并显示在节点的列表中。如果ini中设定的文件夹无效,将启用插件自带的font文件夹。 diff --git a/image/simple_text_image_example.png b/image/simple_text_image_example.png new file mode 100644 index 0000000..e253c6d Binary files /dev/null and b/image/simple_text_image_example.png differ diff --git a/image/simple_text_image_node.png b/image/simple_text_image_node.png new file mode 100644 index 0000000..7935bf3 Binary files /dev/null and b/image/simple_text_image_node.png differ diff --git a/py/imagefunc.py b/py/imagefunc.py index 44c7e13..b82b34a 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -16,6 +16,7 @@ import scipy.ndimage import cv2 import random import time +from functools import lru_cache from typing import Union, List from PIL import Image, ImageFilter, ImageChops, ImageDraw, ImageOps, ImageEnhance, ImageFont from skimage import img_as_float, img_as_ubyte @@ -894,7 +895,7 @@ def generate_text_image(text:str, font_path:str, font_size:int, text_color:str=" '''Mask Functions''' - +@lru_cache(maxsize=1, typed=False) def load_RMBG_model(): current_directory = os.path.dirname(os.path.abspath(__file__)) device = "cuda" if torch.cuda.is_available() else "cpu" diff --git a/py/simple_text_image.py b/py/simple_text_image.py new file mode 100644 index 0000000..3dccff9 --- /dev/null +++ b/py/simple_text_image.py @@ -0,0 +1,103 @@ +from .imagefunc import * +import textwrap +from typing import cast + +NODE_NAME = 'SimpleTextImage' +any = AnyType("*") + +class SimpleTextImage: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + + return { + "required": { + "text": ("STRING",{"default": "text", "multiline": True}, + ), + "font_file": (FONT_LIST,), + "align": (["center", "left", "right"],), + "char_per_line": ("INT", {"default": 80, "min": 1, "max": 8096, "step": 1},), + "leading": ("INT",{"default": 8, "min": 0, "max": 8096, "step": 1},), + "font_size": ("INT",{"default": 72, "min": 1, "max": 2500, "step": 1},), + "text_color": ("STRING", {"default": "#FFFFFF"},), + "stroke_width": ("INT",{"default": 0, "min": 0, "max": 8096, "step": 1},), + "stroke_color": ("STRING",{"default": "#FF8000"},), + "x_offset": ("INT", {"default": 0, "min": 0, "max": 8096, "step": 1},), + "y_offset": ("INT", {"default": 0, "min": 0, "max": 8096, "step": 1},), + "width": ("INT", {"default": 512, "min": 1, "max": 8096, "step": 1},), + "height": ("INT", {"default": 512, "min": 1, "max": 8096, "step": 1},), + }, + "optional": { + "size_as": (any, {}), + } + } + + RETURN_TYPES = ("IMAGE", "MASK",) + RETURN_NAMES = ("image", "mask",) + FUNCTION = 'simple_text_image' + CATEGORY = '😺dzNodes/LayerUtility' + OUTPUT_NODE = True + + def simple_text_image(self, text, font_file, align, char_per_line, + leading, font_size, text_color, + stroke_width, stroke_color, x_offset, y_offset, + width, height, size_as=None + ): + + if size_as is not None: + width, height = tensor2pil(size_as).size + font_path = FONT_DICT.get(font_file) + (_, top, _, _) = ImageFont.truetype(font=font_path, size=font_size, encoding='unic').getbbox(text) + font = cast(ImageFont.FreeTypeFont, ImageFont.truetype(font_path, font_size)) + if char_per_line == 0: + char_per_line = int(width / font_size) + paragraphs = text.split('\n') + + img_height = height # line_height * len(lines) + img_width = width # max(font.getsize(line)[0] for line in lines) + + img = Image.new("RGBA", size=(img_width, img_height), color=(0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + y_text = y_offset + stroke_width + + for paragraph in paragraphs: + lines = textwrap.wrap(paragraph, width=char_per_line, expand_tabs=False, replace_whitespace=False) + for line in lines: + width = font.getbbox(line)[2] - font.getbbox(line)[0] + height = font.getbbox(line)[3] - font.getbbox(line)[1] + + # 根据 align 参数重新计算 x 坐标 + if align == "left": + x_text = x_offset + elif align == "center": + x_text = (img_width - width) // 2 + elif align == "right": + x_text = img_width - width - margin_x + else: + x_text = x_offset # 默认为左对齐 + + draw.text( + xy=(x_text, y_text), + text=line, + fill=text_color, + font=font, + stroke_width=stroke_width, + stroke_fill=stroke_color, + ) + y_text += height + leading + + y_text += leading * 2 + + return (pil2tensor(img), image2mask(img.split()[3])) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: SimpleTextImage": SimpleTextImage +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: SimpleTextImage": "LayerUtility: SimpleTextImage" +} \ No newline at end of file diff --git a/workflow/simple_text_example.json b/workflow/simple_text_example.json new file mode 100644 index 0000000..b662baa --- /dev/null +++ b/workflow/simple_text_example.json @@ -0,0 +1,360 @@ +{ + "last_node_id": 11, + "last_link_id": 23, + "nodes": [ + { + "id": 7, + "type": "LayerStyle: DropShadow", + "pos": [ + 1056, + 302 + ], + "size": { + "0": 315, + "1": 266 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 10 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 18 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 19, + 23 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: DropShadow" + }, + "widgets_values": [ + true, + "normal", + 75, + 8, + 8, + 2, + 8, + "#000000" + ] + }, + { + "id": 4, + "type": "LoadImage", + "pos": [ + 523, + 289 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 10, + 17 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "1344x768_beach (26).png", + "image" + ] + }, + { + "id": 9, + "type": "LayerUtility: SimpleTextImage", + "pos": [ + 741, + 667 + ], + "size": [ + 392.5400561523438, + 406.28336669921873 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "size_as", + "type": "*", + "link": 17 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 18 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "mask", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: SimpleTextImage" + }, + "widgets_values": [ + "Are you going to Scarborough Fair?\nParsley, sage, rosemary, and thyme.\nRemember me to one who lives there,\nShe once was the true love of mine.", + "Alibaba-PuHuiTi-Bold.ttf", + "center", + 80, + 8, + 52, + "#FFFFFF", + 0, + "#FF8000", + 0, + 300, + 512, + 512 + ] + }, + { + "id": 10, + "type": "LayerUtility: SimpleTextImage", + "pos": [ + 1310, + 666 + ], + "size": { + "0": 392.5400695800781, + "1": 406.2833557128906 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "size_as", + "type": "*", + "link": 23 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 21 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "mask", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: SimpleTextImage" + }, + "widgets_values": [ + "Scarborough Fair", + "Alibaba-PuHuiTi-Bold.ttf", + "center", + 80, + 8, + 108, + "#FFE080", + 0, + "#FF8000", + 0, + 120, + 512, + 512 + ] + }, + { + "id": 11, + "type": "LayerStyle: DropShadow", + "pos": [ + 1678, + 302 + ], + "size": { + "0": 315, + "1": 266 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 19 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 21 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": null + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 20 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: DropShadow" + }, + "widgets_values": [ + true, + "normal", + 75, + 8, + 8, + 2, + 8, + "#000000" + ] + }, + { + "id": 2, + "type": "PreviewImage", + "pos": [ + 2056, + 355 + ], + "size": [ + 714.4351806640625, + 444.5991973876953 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 20 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "links": [ + [ + 10, + 4, + 0, + 7, + 0, + "IMAGE" + ], + [ + 17, + 4, + 0, + 9, + 0, + "*" + ], + [ + 18, + 9, + 0, + 7, + 1, + "IMAGE" + ], + [ + 19, + 7, + 0, + 11, + 0, + "IMAGE" + ], + [ + 20, + 11, + 0, + 2, + 0, + "IMAGE" + ], + [ + 21, + 10, + 0, + 11, + 1, + "IMAGE" + ], + [ + 23, + 7, + 0, + 10, + 0, + "*" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file