Commit TextImage node

This commit is contained in:
chflame163
2024-02-07 23:41:05 +08:00
parent ccbcae4863
commit bdc6c9fdfc
11 changed files with 686 additions and 213 deletions
+122 -93
View File
@@ -13,6 +13,7 @@ Nodes are divided into 5 groups according to their functions: LayerStyle, LayerC
[中文说明点这里](./README_CN.MD)
## Update
* Commit [TextImage](#TextImage) node, it generate text images and masks.
* Add new types of [blend mode](#blend) between images. now supports up to 19 blend modes. add **color_burn, color_dodge, linear_burn, linear_dodge, overlay, soft_light, hard_light, vivid_light, pin_light, linear_light** and **hard_mix**.
The newly added blend mode is applicable to all nodes that support blend mode.
* Commit [ColorMap](#ColorMap) filter node to create a pseudo color heatmap effect.
@@ -205,7 +206,7 @@ Node options:
* LUT<sup>*</sup>: Here is a list of available. cube files in the LUT folder, and the selected LUT files will be applied to the image.
<sup>*</sup>LUT 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 "LUT dir=", after "=", enter the custom folder path name. all .cube files in this folder will be collected and displayed in the node list during ComfyUI initialization.
Open the text editing software and find the line starting with "LUT_dir=", after "=", enter the custom folder path name. all .cube 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 LUT folder that comes with the plugin will be enabled.
### <a id="table1">ColorAdapter</a>
@@ -269,98 +270,6 @@ Node options:
* V: V channel.
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">MaskBoxDetect</a>
Detect the area where the mask is located and output its position and size.
![image](image/mask_box_detect_example.png)
Node options:
![image](image/mask_box_detect_node.png)
* detect: Detection method, min_bounding_rect is the minimum bounding rectangle, max_inscribed_rect is the maximum inscribed rectangle.
* x_adjust: Adjust of horizontal deviation after detection.
* y_adjust: Adjust of vertical offset after detection.
* scale_adjust: Adjust the scaling offset after detection.
Output:
* box_preview: Preview image of detection results. Red represents the detected result, and green represents the adjust output result.
* x_percent: Horizontal position output in percentage.
* y_percent: Vertical position output in percentage.
* width: Width.
* height: Height.
* x: The x-coordinate of the top left corner position.
* y: The y-coordinate of the top left corner position.
### <a id="table1">MaskGrow</a>
Grow and shrink edges and blur the mask
![image](image/mask_grow_example.png)
Node options:
![image](image/mask_grow_node.png)
* invert_mask: Whether to reverse the mask.
* grow: Positive values expand outward, while negative values contract inward.
* blur: Blur the edge.
### <a id="table1">MaskEdgeShrink</a>
Smooth transition and shrink the mask edges while preserving edge details.
![image](image/mask_edge_shrink_example.png)
Node options:
![image](image/mask_edge_shrink_node.png)
* invert_mask: Whether to reverse the mask.
* shrink_level: Shrink the smoothness level.
* soft: Smooth amplitude.
* edge_shrink: Edge shrinkage amplitude.
* edge_reserve: Preserve the amplitude of edge details, 100 represents complete preservation, and 0 represents no preservation at all.
Comparison of MaskGrow and MaskEdgeShrink
![image](image/mask_edge_compare.png)
### <a id="table1">MaskMotionBlur</a>
Create motion blur on the mask.
![image](image/mask_motion_blur_example.png)
Node options:
![image](image/mask_motion_blur_node.png)
* invert_mask: Whether to reverse the mask.
* blur: The size of blur.
* angle: The angle of blur.
### <a id="table1">MaskGradient</a>
Create a gradient for the mask from one side.
![image](image/mask_gradient_example.png)
Node options:
![image](image/mask_gradient_node.png)
* invert_mask: Whether to reverse the mask.
* gradient_side: Generate gradient from which edge. There are four directions: top, bottom, left and right.
* gradient_scale: Gradient distance. The default value of 100 indicates that one side of the gradient is completely transparent and the other side is completely opaque. The smaller the value, the shorter the distance from transparent to opaque.
* gradient_offset: Gradient position offset.
* opacity: The opacity of the gradient.
### <a id="table1">MaskStroke</a>
Generate mask contour strokes.
![image](image/mask_stroke_example.png)
Node options:
![image](image/mask_stroke_node.png)
* invert_mask: Whether to reverse the mask.
* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction.
* stroke_width: Stroke width.
* blur: Blur of stroke.
### <a id="table1">MaskPreview</a>
Preview the input mask
![image](image/mask_invert.png)
### <a id="table1">MaskInvert</a>
Invert the mask
![image](image/mask_invert_node.png)
# <a id="table1">LayerUtility</a>
![image](image/layerutility_nodes.png)
@@ -573,6 +482,33 @@ Node options:
* start_color<sup>4</sup>: Color of the begging.
* end_color<sup>4</sup>: Color of the ending.
### <a id="table1">TextImage</a>
![image](image/text_image_example.png)
Generate images and masks from text. support for adjusting the spacing between words and lines, horizontal and vertical adjustments, it can set random changes in each character, including size and position.
![image](image/text_image_node.png)
Node options:
* size_as<sup>*</sup>: The input image or mask here will generate the output image and mask according to their size.
* font_file<sup>**</sup>: Here is a list of available font files in the font folder, and the selected font files will be used to generate images.
* spacing: Word spacing.
* leading: Row leading.
* horizontal_border: Side margin. If the text is horizontal, it is the left margin, and if it is vertical, it is the right margin.
* vertical_border: Top margin.
* scale: The overall size of the text. the initial size of text is automatically calculated based on the screen size and text content, with the longest row or column by default adapting to the image width or height. adjusting the value here will scale the text as a whole.
* variation_range: The range of random changes in characters. when this value is greater than 0, the character will undergo random changes in size and position, and the larger the value, the greater the magnitude of the change.
* variation_seed: The seed for randomly. fix this value to individual characters changes generated each time will not change.
* layout: Text layout. there are horizontal and vertical options to choose from.
* 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.
* text_color: The color of text.
* background_color: The color of background.
<sup>*</sup>Only limited to input image and mask. forcing the integration of other types of inputs will result in node errors.
<sup>**</sup>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.
### <a id="table1">ImageChannelSplit</a>
![image](image/image_channel_split_example.png)
Split the image channel into individual images.
@@ -601,6 +537,99 @@ Used to provide assistance for workflow debugging. When running, the properties
This node allows any type of input.
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">MaskBoxDetect</a>
Detect the area where the mask is located and output its position and size.
![image](image/mask_box_detect_example.png)
Node options:
![image](image/mask_box_detect_node.png)
* detect: Detection method, min_bounding_rect is the minimum bounding rectangle, max_inscribed_rect is the maximum inscribed rectangle.
* x_adjust: Adjust of horizontal deviation after detection.
* y_adjust: Adjust of vertical offset after detection.
* scale_adjust: Adjust the scaling offset after detection.
Output:
* box_preview: Preview image of detection results. Red represents the detected result, and green represents the adjust output result.
* x_percent: Horizontal position output in percentage.
* y_percent: Vertical position output in percentage.
* width: Width.
* height: Height.
* x: The x-coordinate of the top left corner position.
* y: The y-coordinate of the top left corner position.
### <a id="table1">MaskGrow</a>
Grow and shrink edges and blur the mask
![image](image/mask_grow_example.png)
Node options:
![image](image/mask_grow_node.png)
* invert_mask: Whether to reverse the mask.
* grow: Positive values expand outward, while negative values contract inward.
* blur: Blur the edge.
### <a id="table1">MaskEdgeShrink</a>
Smooth transition and shrink the mask edges while preserving edge details.
![image](image/mask_edge_shrink_example.png)
Node options:
![image](image/mask_edge_shrink_node.png)
* invert_mask: Whether to reverse the mask.
* shrink_level: Shrink the smoothness level.
* soft: Smooth amplitude.
* edge_shrink: Edge shrinkage amplitude.
* edge_reserve: Preserve the amplitude of edge details, 100 represents complete preservation, and 0 represents no preservation at all.
Comparison of MaskGrow and MaskEdgeShrink
![image](image/mask_edge_compare.png)
### <a id="table1">MaskMotionBlur</a>
Create motion blur on the mask.
![image](image/mask_motion_blur_example.png)
Node options:
![image](image/mask_motion_blur_node.png)
* invert_mask: Whether to reverse the mask.
* blur: The size of blur.
* angle: The angle of blur.
### <a id="table1">MaskGradient</a>
Create a gradient for the mask from one side.
![image](image/mask_gradient_example.png)
Node options:
![image](image/mask_gradient_node.png)
* invert_mask: Whether to reverse the mask.
* gradient_side: Generate gradient from which edge. There are four directions: top, bottom, left and right.
* gradient_scale: Gradient distance. The default value of 100 indicates that one side of the gradient is completely transparent and the other side is completely opaque. The smaller the value, the shorter the distance from transparent to opaque.
* gradient_offset: Gradient position offset.
* opacity: The opacity of the gradient.
### <a id="table1">MaskStroke</a>
Generate mask contour strokes.
![image](image/mask_stroke_example.png)
Node options:
![image](image/mask_stroke_node.png)
* invert_mask: Whether to reverse the mask.
* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction.
* stroke_width: Stroke width.
* blur: Blur of stroke.
### <a id="table1">MaskPreview</a>
Preview the input mask
![image](image/mask_invert.png)
### <a id="table1">MaskInvert</a>
Invert the mask
![image](image/mask_invert_node.png)
# <a id="table1">LayerFilter</a>
![image](image/layerfilter_nodes.png)
+119 -91
View File
@@ -10,6 +10,7 @@
* [LayerFilter](#LayerFilter)节点组提供图像效果滤镜。
## 更新说明
* 添加[TextImage](#TextImage) 节点,生成文字图像和遮罩。
* 图像之间的[混合模式](#混合模式)增加新类型,现在支持多达19种混合模式。新增color_burn颜色加深, color_dodge颜色减淡, linear_burn线性加深, linear_dodge线性减淡, overlay叠加, soft_light柔光, hard_light强光, vivid_light亮光, pin_light点光, linear_light线性光, hard_mix实色混合。新增的混合模式适用于所有支持混合模式的节点。
* 添加[ColorMap](#ColorMap) 滤镜节点,用于制作伪彩色热力图效果。
* 添加[WaterColor](#WaterColor) 和 [SkinBeauty](#SkinBeauty) 节点。这是两个图像滤镜,生成水彩画和磨皮效果。
@@ -259,97 +260,6 @@
* S: 图像的S通道。
* V: 图像的V通道。
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">MaskBoxDetect</a>
探测mask所在区域,并输出位置和大小。
![image](image/mask_box_detect_example.png)
节点选项说明:
![image](image/mask_box_detect_node.png)
* detect: 探测方法,min_bounding_rect是最小外接矩形, max_inscribed_rect是最大内接矩形。
* x_adjust: 修正探测之后的水平偏移。
* y_adjust: 修正探测之后的垂直偏移。
* scale_adjust: 修正探测之后的缩放偏移。
输出:
* box_preview: 探测结果预览图。红色表示探测到的结果,绿色表示加上修正后的输出结果。
* x_percent: 水平位置以百分比输出。
* y_percent: 垂直位置以百分比输出。
* width: 宽度输出。
* height: 高度输出。
* x: 左上角位置x坐标输出。
* y: 左上角位置y坐标输出。
### <a id="table1">MaskGrow</a>
对mask进行扩张收缩边缘和模糊处理
![image](image/mask_grow_example.png)
节点选项说明:
![image](image/mask_grow_node.png)
* invert_mask: 是否反转遮罩。
* grow: 扩张幅度。正值是向外扩张,负值是向内收缩。
* blur: 模糊。
### <a id="table1">MaskEdgeShrink</a>
使mask边缘平滑地过渡收缩,并保留边缘细节。
![image](image/mask_edge_shrink_example.png)
节点选项说明:
![image](image/mask_edge_shrink_node.png)
* invert_mask: 是否反转遮罩。
* shrink_level: 收缩平滑级别。
* soft: 平滑幅度。
* edge_shrink: 边缘收缩幅度。
* edge_reserve: 保留边缘细节幅度, 100为完全保留,0为完全不保留。
MaskGrow与MaskEdgeShrink效果对比
![image](image/mask_edge_compare.png)
### <a id="table1">MaskMotionBlur</a>
使mask产生运动模糊。
![image](image/mask_motion_blur_example.png)
节点选项说明:
![image](image/mask_motion_blur_node.png)
* invert_mask: 是否反转遮罩。
* blur: 模糊大小。
* angle: 模糊角度。
### <a id="table1">MaskGradient</a>
使mask从一侧产生渐变。
![image](image/mask_gradient_example.png)
节点选项说明:
![image](image/mask_gradient_node.png)
* invert_mask: 是否反转遮罩。
* gradient_side: 从哪个边产生渐变。有四个方向:顶侧top、底侧bottom、左侧left、右侧right。
* gradient_scale: 渐变距离。默认值100表示渐变产生一侧完全透明,另一侧完全不透明。数值越小,从透明到不透明的距离越短。
* gradient_offset: 渐变位置偏移。
* opacity: 渐变的不透明度。
### <a id="table1">MaskStroke</a>
产生mask轮廓描边。
![image](image/mask_stroke_example.png)
节点选项说明:
![image](image/mask_stroke_node.png)
* invert_mask: 是否反转遮罩。
* stroke_grow: 描边扩张/收缩幅度,正值是扩张,负值是收缩。
* stroke_width: 描边宽度。
* blur: 描边模糊。
### <a id="table1">MaskPreview</a>
预览mask
![image](image/mask_invert.png)
### <a id="table1">MaskInvert</a>
mask反转
![image](image/mask_invert_node.png)
# <a id="table1">LayerUtility</a>
![image](image/layerutility_nodes.png)
@@ -560,6 +470,31 @@ mask
* start_color<sup>4</sup>: 开始端颜色。
* end_color<sup>4</sup>: 结束端颜色。
### <a id="table1">TextImage</a>
![image](image/text_image_example.png)
从文字生成图片以及遮罩。支持字间距行间距调整,横排竖排调整,可设置文字的随机变化,包括大小和位置的随机变化。
![image](image/text_image_node.png)
节点选项说明:
* size_as<sup>*</sup>: 此处输入图像或遮罩,将按照其尺寸生成输出图像和遮罩。
* font_file<sup>**</sup>: 这里列出了font文件夹中可用的字体文件列表,选中的字体文件将被用来生成图像。
* spacing: 字间距。
* leading: 行间距。
* horizontal_border: 侧边边距。如果文字是横排,是左侧边距,竖排则是右侧边距。
* vertical_border: 顶部边距。
* scale: 文字总体大小。文字的初始大小是根据画面尺寸和文字内容自动计算,默认以最长的行或者列适配画面宽或者高。调整此处数值将整体放大和缩小文字。
* variation_range: 字符随机变化范围。此数值大于0时,字符将产生大小和位置的随机变化,数值越大,变化幅度越大。
* variation_seed: 随机变化的种子。固定此数值,每次产生的单个文字的变化不会改变。
* layout: 文字排版。有横排和竖排可选择。
* width; 画面的宽度。如果有size_as输入,此设置将被忽略。
* height; 画面的高度。如果有size_as输入,此设置将被忽略。
* text_color: 文字颜色。
* background_color: 背景颜色。
<sup>*</sup>仅限输入image和mask, 如果强制接入其他类型输入,将导致节点错误。
<sup>**</sup>font文件夹在resource_dir.ini中定义,这个文件位于插件根目录下。用文本编辑软件打开,找到“FONT_dir=”开头的这一行,编辑“=”之后为自定义文件夹路径名。这个文件夹里面所有的.ttf和.otf文件将在ComfyUI初始化时被收集并显示在节点的列表中。如果ini中设定的文件夹无效,将启用插件自带的font文件夹。
### <a id="table1">ImageChannelSplit</a>
![image](image/image_channel_split_example.png)
将图像通道拆分为单独的图片。
@@ -587,6 +522,99 @@ mask
这个节点允许任意类型的输入。
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">MaskBoxDetect</a>
探测mask所在区域,并输出位置和大小。
![image](image/mask_box_detect_example.png)
节点选项说明:
![image](image/mask_box_detect_node.png)
* detect: 探测方法,min_bounding_rect是最小外接矩形, max_inscribed_rect是最大内接矩形。
* x_adjust: 修正探测之后的水平偏移。
* y_adjust: 修正探测之后的垂直偏移。
* scale_adjust: 修正探测之后的缩放偏移。
输出:
* box_preview: 探测结果预览图。红色表示探测到的结果,绿色表示加上修正后的输出结果。
* x_percent: 水平位置以百分比输出。
* y_percent: 垂直位置以百分比输出。
* width: 宽度输出。
* height: 高度输出。
* x: 左上角位置x坐标输出。
* y: 左上角位置y坐标输出。
### <a id="table1">MaskGrow</a>
对mask进行扩张收缩边缘和模糊处理
![image](image/mask_grow_example.png)
节点选项说明:
![image](image/mask_grow_node.png)
* invert_mask: 是否反转遮罩。
* grow: 扩张幅度。正值是向外扩张,负值是向内收缩。
* blur: 模糊。
### <a id="table1">MaskEdgeShrink</a>
使mask边缘平滑地过渡收缩,并保留边缘细节。
![image](image/mask_edge_shrink_example.png)
节点选项说明:
![image](image/mask_edge_shrink_node.png)
* invert_mask: 是否反转遮罩。
* shrink_level: 收缩平滑级别。
* soft: 平滑幅度。
* edge_shrink: 边缘收缩幅度。
* edge_reserve: 保留边缘细节幅度, 100为完全保留,0为完全不保留。
MaskGrow与MaskEdgeShrink效果对比
![image](image/mask_edge_compare.png)
### <a id="table1">MaskMotionBlur</a>
使mask产生运动模糊。
![image](image/mask_motion_blur_example.png)
节点选项说明:
![image](image/mask_motion_blur_node.png)
* invert_mask: 是否反转遮罩。
* blur: 模糊大小。
* angle: 模糊角度。
### <a id="table1">MaskGradient</a>
使mask从一侧产生渐变。
![image](image/mask_gradient_example.png)
节点选项说明:
![image](image/mask_gradient_node.png)
* invert_mask: 是否反转遮罩。
* gradient_side: 从哪个边产生渐变。有四个方向:顶侧top、底侧bottom、左侧left、右侧right。
* gradient_scale: 渐变距离。默认值100表示渐变产生一侧完全透明,另一侧完全不透明。数值越小,从透明到不透明的距离越短。
* gradient_offset: 渐变位置偏移。
* opacity: 渐变的不透明度。
### <a id="table1">MaskStroke</a>
产生mask轮廓描边。
![image](image/mask_stroke_example.png)
节点选项说明:
![image](image/mask_stroke_node.png)
* invert_mask: 是否反转遮罩。
* stroke_grow: 描边扩张/收缩幅度,正值是扩张,负值是收缩。
* stroke_width: 描边宽度。
* blur: 描边模糊。
### <a id="table1">MaskPreview</a>
预览mask
![image](image/mask_invert.png)
### <a id="table1">MaskInvert</a>
mask反转
![image](image/mask_invert_node.png)
# <a id="table1">LayerFilter</a>
![image](image/layerfilter_nodes.png)
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

+26 -26
View File
@@ -2,30 +2,30 @@ import os
import glob
from .imagefunc import *
lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut')
ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini")
try:
with open(ini_file, 'r') as f:
ini = f.readlines()
for line in ini:
if line.startswith('LUT_dir='):
d = line[line.find('=') + 1:].rstrip().lstrip()
break
if os.path.exists(d):
lut_dir = d
else:
log(f'ERROR: invalid LUT dir, default to be used. check {ini_file}')
except Exception as e:
log(f'ERROR: {ini_file} ' + repr(e))
file_list = glob.glob(lut_dir + '/*.cube')
lut_dict = {}
for i in range(len(file_list)):
_, filename = os.path.split(file_list[i])
lut_dict[filename] = file_list[i]
lut_list = list(lut_dict.keys())
log(f'find {len(lut_list)} LUTs in {lut_dir}')
# lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut')
# ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini")
#
# try:
# with open(ini_file, 'r') as f:
# ini = f.readlines()
# for line in ini:
# if line.startswith('LUT_dir='):
# d = line[line.find('=') + 1:].rstrip().lstrip()
# break
# if os.path.exists(d):
# lut_dir = d
# else:
# log(f'ERROR: invalid LUT dir, default to be used. check {ini_file}')
# except Exception as e:
# log(f'ERROR: {ini_file} ' + repr(e))
#
# file_list = glob.glob(lut_dir + '/*.cube')
# lut_dict = {}
# for i in range(len(file_list)):
# _, filename = os.path.split(file_list[i])
# lut_dict[filename] = file_list[i]
# lut_list = list(lut_dict.keys())
# log(f'find {len(lut_list)} LUTs in {lut_dir}')
class ColorCorrectLUTapply:
@@ -38,7 +38,7 @@ class ColorCorrectLUTapply:
return {
"required": {
"image": ("IMAGE", ), #
"LUT": (lut_list,), # LUT文件
"LUT": (LUT_LIST,), # LUT文件
},
"optional": {
}
@@ -53,7 +53,7 @@ class ColorCorrectLUTapply:
def color_correct_LUTapply(self, image, LUT):
_image = tensor2pil(image)
lut_file = lut_dict[LUT]
lut_file = LUT_DICT[LUT]
ret_image = lut_apply(_image, lut_file)
return (pil2tensor(ret_image),)
+56 -2
View File
@@ -1,17 +1,20 @@
'''Image process functions for ComfyUI nodes
by chflame https://github.com/chflame163
'''
import os
import re
import glob
import numpy as np
import torch
import scipy.ndimage
import cv2
import random
from typing import Union, List
from PIL import Image, ImageFilter, ImageChops, ImageDraw, ImageOps
from skimage import img_as_float, img_as_ubyte
import colorsys
def log(message):
name = 'LayerStyle'
print(f"# 😺dzNodes: {name} -> {message}")
@@ -773,6 +776,15 @@ def has_letters(string:str) -> bool:
else:
return False
def random_numbers(total:int, random_range:int, seed:int=0, sum_of_numbers:int=0) -> list:
random.seed(seed)
numbers = [random.randint(-random_range//2, random_range//2) for _ in range(total - 1)]
avg = sum(numbers) // total
ret_list = []
for i in numbers:
ret_list.append(i - avg)
ret_list.append(sum_of_numbers - sum(ret_list))
return ret_list
'''CLASS'''
@@ -785,4 +797,46 @@ class AnyType(str):
chop_mode = ['normal', 'multply', 'screen', 'add', 'subtract', 'difference', 'darker', 'lighter',
'color_burn', 'color_dodge', 'linear_burn', 'linear_dodge', 'overlay',
'soft_light', 'hard_light', 'vivid_light', 'pin_light', 'linear_light', 'hard_mix']
'soft_light', 'hard_light', 'vivid_light', 'pin_light', 'linear_light', 'hard_mix']
'''Load INI File'''
default_lut_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'lut')
default_font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'font')
resource_dir_ini_file = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), "resource_dir.ini")
try:
with open(resource_dir_ini_file, 'r') as f:
ini = f.readlines()
for line in ini:
if line.startswith('LUT_dir='):
_ldir = line[line.find('=') + 1:].rstrip().lstrip()
if os.path.exists(_ldir):
default_lut_dir = _ldir
else:
log(f'ERROR: invalid LUT directory, default to be used. check {resource_dir_ini_file}')
elif line.startswith('FONT_dir='):
_fdir = line[line.find('=') + 1:].rstrip().lstrip()
if os.path.exists(_fdir):
default_font_dir = _fdir
else:
log(f'ERROR: invalid FONT directory, default to be used. check {resource_dir_ini_file}')
except Exception as e:
log(f'ERROR: {resource_dir_ini_file} ' + repr(e))
__lut_file_list = glob.glob(default_lut_dir + '/*.cube')
LUT_DICT = {}
for i in range(len(__lut_file_list)):
_, __filename = os.path.split(__lut_file_list[i])
LUT_DICT[__filename] = __lut_file_list[i]
LUT_LIST = list(LUT_DICT.keys())
log(f'find {len(LUT_LIST)} LUTs in {default_lut_dir}')
__font_file_list = glob.glob(default_font_dir + '/*.ttf')
__font_file_list.extend(glob.glob(default_font_dir + '/*.otf'))
FONT_DICT = {}
for i in range(len(__font_file_list)):
_, __filename = os.path.split(__font_file_list[i])
FONT_DICT[__filename] = __font_file_list[i]
FONT_LIST = list(FONT_DICT.keys())
log(f'find {len(FONT_LIST)} Fonts in {default_font_dir}')
+138
View File
@@ -0,0 +1,138 @@
import time
import random
from PIL import ImageFont
from .imagefunc import *
any = AnyType("*")
class TextImage:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(self):
layout_list = ['horizontal', 'vertical']
random_seed = int(time.time())
return {
"required": {
"text": ("STRING", {"multiline": True, "default": "Text"}),
"font_file": (FONT_LIST,),
"spacing": ("INT", {"default": 0, "min": -9999, "max": 9999, "step": 1}),
"leading": ("INT", {"default": 0, "min": -9999, "max": 9999, "step": 1}),
"horizontal_border": ("FLOAT", {"default": 5, "min": -100, "max": 100, "step": 0.1}), # 左右距离百分比,横排为距左侧,竖排为距右侧
"vertical_border": ("FLOAT", {"default": 5, "min": -100, "max": 100, "step": 0.1}), # 上距离百分比
"scale": ("FLOAT", {"default": 80, "min": 0.1, "max": 999, "step": 0.01}), # 整体大小与画面长宽比,横排与宽比,竖排与高比
"variation_range": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}), # 随机大小和位置范围
"variation_seed": ("INT", {"default": random_seed, "min": 0, "max": 999999999999, "step": 1}), # 随机种子
"layout": (layout_list,), # 横排or竖排
"width": ("INT", {"default": 512, "min": 4, "max": 999999, "step": 1}),
"height": ("INT", {"default": 512, "min": 4, "max": 999999, "step": 1}),
"text_color": ("STRING", {"default": "#FFA000"}), # 文字颜色
"background_color": ("STRING", {"default": "#FFFFFF"}), # 背景颜色
},
"optional": {
"size_as": (any, {}),
}
}
RETURN_TYPES = ("IMAGE", "MASK",)
RETURN_NAMES = ("image", "mask",)
FUNCTION = 'text_image'
CATEGORY = '😺dzNodes/LayerUtility'
OUTPUT_NODE = True
def text_image(self, text, font_file, spacing, leading, horizontal_border, vertical_border, scale,
variation_range, variation_seed, layout, width, height, text_color, background_color,
size_as=None
):
spacing -= 20
leading += 20
scale *= 0.7
if size_as is not None:
width, height = tensor2pil(size_as).size
text_table = []
max_char_in_line = 0
total_char = 0
spacing = int(spacing * scale / 100)
leading = int(leading * scale / 100)
lines = []
text_lines = text.split("\n")
for l in text_lines:
if len(l) > 0:
lines.append(l)
total_char += len(l)
if len(l) > max_char_in_line:
max_char_in_line = len(l)
else:
lines.append(" ")
if layout == 'vertical':
char_horizontal_size = width // len(lines)
char_vertical_size = height // max_char_in_line
char_size = min(char_horizontal_size, char_vertical_size)
if char_size < 1:
char_size = 1
start_x = width - int(width * horizontal_border/100) - char_size
else:
char_horizontal_size = width // max_char_in_line
char_vertical_size = height // len(lines)
char_size = min(char_horizontal_size, char_vertical_size)
if char_size < 1:
char_size = 1
start_x = int(width * horizontal_border/100)
start_y = int(height * vertical_border/100)
# calculate every char position and size to a table list
for i in range(len(lines)):
_x = start_x
_y = start_y
line_table = []
line_random = random_numbers(total=len(lines[i]),
random_range=int(char_size * variation_range / 25),
seed=variation_seed, sum_of_numbers=0)
for j in range(0, len(lines[i])):
offset = int((char_size + line_random[j]) * variation_range / 250)
offset = int(offset * scale / 100)
font_size = char_size - spacing + line_random[j]
font_size = int(font_size * scale / 100)
if font_size < 4:
font_size = 4
axis_x = _x + offset if random.random() > 0.5 else _x - offset
axis_y = _y + offset if random.random() > 0.5 else _y - offset
char_dict = {'char':lines[i][j],
'axis':(axis_x, axis_y),
'size':font_size}
line_table.append(char_dict)
if layout == 'vertical':
_y += char_size + line_random[j] + spacing
else:
_x += char_size + line_random[j] + spacing
if layout == 'vertical':
start_x -= leading * (i+1) + char_size
else:
start_y += leading * (i+1) + char_size
text_table.append(line_table)
# draw char
_mask = Image.new('RGB', size=(width, height), color='black')
draw = ImageDraw.Draw(_mask)
for l in range(len(lines)):
for c in range(len(lines[l])):
font_path = FONT_DICT.get(font_file)
font_size = text_table[l][c].get('size')
font = ImageFont.truetype(font_path, font_size)
draw.text(text_table[l][c].get('axis'), text_table[l][c].get('char'), font=font, fill='white')
_canvas = Image.new('RGB', size=(width, height), color=background_color)
_color = Image.new('RGB', size=(width, height), color=text_color)
_canvas.paste(_color, mask=_mask.convert('L'))
_canvas = RGB2RGBA(_canvas, _mask)
return (pil2tensor(_canvas), image2mask(_mask),)
NODE_CLASS_MAPPINGS = {
"LayerUtility: TextImage": TextImage
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: TextImage": "LayerUtility: TextImage"
}
+1 -1
View File
@@ -1,2 +1,2 @@
font_dir=C:\Windows\fonts
FONT_dir=C:\font
LUT_dir=C:\lut
+224
View File
@@ -0,0 +1,224 @@
{
"last_node_id": 28,
"last_link_id": 57,
"nodes": [
{
"id": 11,
"type": "LayerStyle: DropShadow",
"pos": [
1250,
300
],
"size": {
"0": 315,
"1": 266
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "background_image",
"type": "IMAGE",
"link": 52
},
{
"name": "layer_image",
"type": "IMAGE",
"link": 57
},
{
"name": "layer_mask",
"type": "MASK",
"link": null
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
46
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerStyle: DropShadow"
},
"widgets_values": [
false,
"normal",
46,
3,
3,
4,
12,
"#000000"
]
},
{
"id": 12,
"type": "PreviewImage",
"pos": [
1620,
310
],
"size": {
"0": 477.99102783203125,
"1": 481.061767578125
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 46
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 28,
"type": "LayerUtility: TextImage",
"pos": [
800,
470
],
"size": {
"0": 400,
"1": 408
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "size_as",
"type": "*",
"link": 56
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
57
],
"shape": 3,
"slot_index": 0
},
{
"name": "mask",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LayerUtility: TextImage"
},
"widgets_values": [
"鸿雁长飞光不度,\n 鱼龙潜跃水成文。",
"演示秋鸿楷YanShiQiuHongKai.ttf",
-20,
0,
36,
5,
80,
24,
1707317113,
"vertical",
512,
512,
"#000020",
"#FFFFFF"
]
},
{
"id": 10,
"type": "LoadImage",
"pos": [
460,
300
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
52,
56
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": [],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"1024x1024bkgd.png",
"image"
]
}
],
"links": [
[
46,
11,
0,
12,
0,
"IMAGE"
],
[
52,
10,
0,
11,
0,
"IMAGE"
],
[
56,
10,
0,
28,
0,
"*"
],
[
57,
28,
0,
11,
1,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}