diff --git a/README.MD b/README.MD index 43d8140..21c76fd 100644 --- a/README.MD +++ b/README.MD @@ -102,6 +102,8 @@ When this error has occurred, please check the network environment. ## Update **If the dependency package error after updating, please reinstall the relevant dependency packages.
+* Commit [CheckMaskV2](#CheckMaskV2) node, Added the ```simple``` method to detect masks more quickly. +* Commit [ImageReel](#ImageReel) and [ImageReelComposite](#ImageReelComposite) nodes to composite multiple images on a canvas. * [NumberCalculatorV2](#NumberCalculatorV2) and [NumberCalculator](#NumberCalculator) add the ```min``` and ```max``` method. * Optimize node loading speed. * [Florence2Image2Prompt](#Florence2Image2Prompt) add support for ```thwri/CogFlorence-2-Large-Freeze``` and ```thwri/CogFlorence-2.1-Large``` models. Please download the model files from [BaiduNetdisk](https://pan.baidu.com/s/1hzw9-QiU1vB8pMbBgofZIA?pwd=mfl3) or [huggingface/CogFlorence-2-Large-Freeze](https://huggingface.co/thwri/CogFlorence-2-Large-Freeze/tree/main) and [huggingface/CogFlorence-2.1-Large](https://huggingface.co/thwri/CogFlorence-2.1-Large/tree/main) , then copy it to ```ComfyUI/models/florence2``` folder. @@ -809,6 +811,44 @@ Node options: * opacity: Opacity of blend. * [note](#notes) + +### ImageReel +Display multiple images in one reel. Text annotations can be added to each image in the reel. By using the [ImageReelComposite](#ImageReelComposite) node, multiple reel can be combined into one image. +![image](image/image_reel_example.jpg) + +Node Options: +![image](image/image_reel_node.jpg) +* image1: The first image. it must be input. +* image2: The second image. optional input. +* image3: The third image. optional input. +* image4: The fourth image. optional input. +* image1_text: Text annotation for the first image. +* image2_text: Text annotation for the second image. +* image3_text: Text annotation for the third image. +* image4_text: Text annotation for the fourth image. +* reel_height: The height of reel. +* border: The border width of the image in the reel. + +Output: +* reel: The reel of [ImageReelComposite](#ImageReelComposit) node input. + +### ImageReelComposite +Combine multiple reel into one image. + +Node Options: +![image](image/image_reel_composit_node.jpg) +* reel_1: The first reel. it must be input. +* reel_2: The second reel. optional input. +* reel_3: The third reel. optional input. +* reel_4: The fourth reel. optional input. +* 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. +* border: The border width of the reel. +* color_theme: Theme color for the output image. +*The font folder is defined in ```resource_dir.ini```, this file is located in the root directory of the plug-in, and the default name is ```resource_dir.ini.example```. to use this file for the first time, you need to change the file suffix to ```.ini```. +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. + + ### ImageOpacity Adjust image opacity ![image](image/image_opacity_example.jpg) @@ -1255,6 +1295,12 @@ Node Options: * white_point: The white point threshold used to determine whether the mask is valid is considered valid if it exceeds this value. * area_percent: The percentage of effective areas. If the proportion of effective areas exceeds this value, output True. +### CheckMaskV2 +On the basis of CheckMask, the ```method``` option has been added, which allows for the selection of different detection methods. The ```area_percent``` is changed to a floating point number with an accuracy of 2 decimal places, which can detect smaller effective areas. + +Node Options: +![image](image/check_mask_v2_node.jpg) +* method: There are two detection methods, which are ```simple``` and ```detectability```. The simple method only detects whether the mask is completely black, while the detect_percent method detects the proportion of effective areas. ### If ![image](image/if_example.jpg) diff --git a/README_CN.MD b/README_CN.MD index 5ef9aa7..4151ee3 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -103,6 +103,8 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git ## 更新说明 **如果本插件更新后出现依赖包错误,请重新安装相关依赖包。 +* 添加 [CheckMaskV2](#CheckMaskV2) 节点,增加了```simple```方法以更快速检测遮罩。 +* 添加 [ImageReel ](#ImageReel) 和 [ImageReelComposit](#ImageReelComposit) 节点,可将多张图片显示在一起。 * [NumberCalculatorV2](#NumberCalculatorV2) 和 [NumberCalculator](#NumberCalculator) 节点增加 ```min``` 和 ```max``` 方法。 * 优化节点加载速度。 * [Florence2Image2Prompt](#Florence2Image2Prompt) 增加thwri/CogFlorence-2-Large-Freeze 和 thwri/CogFlorence-2.1-Large 模型支持。请从[百度网盘](https://pan.baidu.com/s/1hzw9-QiU1vB8pMbBgofZIA?pwd=mfl3) 或 [huggingface/CogFlorence-2-Large-Freeze](https://huggingface.co/thwri/CogFlorence-2-Large-Freeze/tree/main) 和 [huggingface/CogFlorence-2.1-Large](https://huggingface.co/thwri/CogFlorence-2.1-Large/tree/main) 下载模型文件并复制到```ComfyUI/models/florence2```文件夹。 @@ -799,6 +801,40 @@ ImageScaleByAspectRatio的V2升级版 * opacity: 不透明度。 * [节点注解](#节点注解) +### ImageReel +将多张图片显示在一个卷轴中。可为卷轴中的每张图片添加文字注解。配合[ImageReelComposite](#ImageReelComposit)节点可将多个卷轴拼合为一张图片。 +![image](image/image_reel_example.jpg) + +节点选项说明: +![image](image/image_reel_node.jpg) +* image1: 第一张图片。必须输入。 +* image2: 第二张图片。可选输入图片。 +* image3: 第三张图片。可选输入图片。 +* image4: 第四张图片。可选输入图片。 +* image1_text: 第一张图片的文字注解。 +* image2_text: 第二张图片的文字注解。 +* image3_text: 第三张图片的文字注解。 +* image4_text: 第四张图片的文字注解。 +* reel_height: 卷轴高度。 +* border: 卷轴中图片的边框宽度。 + +输出: +* reel:卷轴,用于输入[ImageReelComposite](#ImageReelComposit)节点。 + +### ImageReelComposite +将多个卷轴拼合为一张图片。 + +节点选项说明: +![image](image/image_reel_composit_node.jpg) +* reel_1: 第一个卷轴。必须输入。 +* reel_2: 第二个卷轴。可选输入。 +* reel_3: 第三个卷轴。可选输入。 +* reel_4: 第四个卷轴。可选输入。 +* font_file*: 字体文件。 +* border: 卷轴的边框宽度。 +* color_theme: 主题色。 +*font文件夹在```resource_dir.ini```中定义,这个文件位于插件根目录下, 默认名字是```resource_dir.ini.example```, 初次使用这个文件需将文件后缀改为.ini。用文本编辑软件打开,找到“FONT_dir=”开头的这一行,编辑“=”之后为自定义文件夹路径名。这个文件夹里面所有的.ttf和.otf文件将在ComfyUI初始化时被收集并显示在节点的列表中。如果ini中设定的文件夹无效,将启用插件自带的font文件夹。 + ### ImageOpacity 调整图像不透明度。 ![image](image/image_opacity_example.jpg) @@ -1242,6 +1278,13 @@ BooleanOperator的升级版,增加了节点内数值输入,增加了大于 * white_point: 判断遮罩是否有效的白点值,高于此值被计入有效。 * area_percent: 有效区域所占百分比。检测有效区域占比超过此值则输出True。 +### CheckMaskV2 +在CheckMask基础上增加了```method```选项,可以选择不同的检测方法。area_percent改为浮点数,精度为小数点后2位,可检测更小的有效区域。 + +节点选项说明: +![image](image/check_mask_v2_node.jpg) +* method: 检测方法,有```simple``` 和 ```detect_percent``` 两种。simple方法仅检测mask是否全黑,detect_percent方法检测有效区域占比。 + ### If ![image](image/if_example.jpg) 根据布尔值条件输入切换输出。可用于任意类型的数据切换,包括且不限于数值、字符串、图片、遮罩、模型、latent、pipe管线等。 diff --git a/image/check_mask_v2_node.jpg b/image/check_mask_v2_node.jpg new file mode 100644 index 0000000..be3a091 Binary files /dev/null and b/image/check_mask_v2_node.jpg differ diff --git a/image/image_reel_composit_node.jpg b/image/image_reel_composit_node.jpg new file mode 100644 index 0000000..78f37ee Binary files /dev/null and b/image/image_reel_composit_node.jpg differ diff --git a/image/image_reel_example.jpg b/image/image_reel_example.jpg new file mode 100644 index 0000000..a27162f Binary files /dev/null and b/image/image_reel_example.jpg differ diff --git a/image/image_reel_node.jpg b/image/image_reel_node.jpg new file mode 100644 index 0000000..c96c12d Binary files /dev/null and b/image/image_reel_node.jpg differ diff --git a/py/check_mask_v2.py b/py/check_mask_v2.py new file mode 100644 index 0000000..cbd0e29 --- /dev/null +++ b/py/check_mask_v2.py @@ -0,0 +1,56 @@ +from .imagefunc import * + +NODE_NAME = 'CheckMaskV2' + +# 检查mask是否有效,如果mask面积少于指定比例则判为无效mask +class CheckMaskV2: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + method_list = ['simple', 'detect_percent'] + blank_mask_list = ['white', 'black'] + return { + "required": { + "mask": ("MASK",), # + "method": (method_list,), # + "white_point": ("INT", {"default": 1, "min": 1, "max": 254, "step": 1}), # 用于判断mask是否有效的白点值,高于此值被计入有效 + "area_percent": ("FLOAT", {"default": 0.01, "min": 0, "max": 100, "step": 0.01}), # 区域百分比,低于此则mask判定无效 + }, + "optional": { # + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ('bool',) + FUNCTION = 'check_mask_v2' + CATEGORY = '😺dzNodes/LayerUtility' + + def check_mask_v2(self, mask, method, white_point, area_percent,): + + if mask.dim() == 2: + mask = torch.unsqueeze(mask, 0) + tensor_mask = mask[0] + print(f"tensor_mask={tensor_mask},shape is {tensor_mask.shape}") + pil_mask = tensor2pil(tensor_mask) + if pil_mask.width * pil_mask.height > 262144: + target_width = 512 + target_height = int(target_width * pil_mask.height / pil_mask.width) + pil_mask = pil_mask.resize((target_width, target_height), Image.LANCZOS) + ret_bool = False + if method == 'simple': + ret_bool = is_valid_mask(tensor_mask) + else: + ret_bool = mask_white_area(pil_mask, white_point) * 100 > area_percent + + return (ret_bool,) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: CheckMaskV2": CheckMaskV2 +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: CheckMaskV2": "LayerUtility: Check Mask V2" +} \ No newline at end of file diff --git a/py/image_reel.py b/py/image_reel.py new file mode 100644 index 0000000..708b1e5 --- /dev/null +++ b/py/image_reel.py @@ -0,0 +1,214 @@ +from .imagefunc import * + +class ImageReelPipeline: + def __init__(self): + self.image = None + self.texts = {} + self.reel_height = 0 + self.reel_border = 0 + +Reel = ImageReelPipeline() +class ImageReel: + + def __init__(self): + self.NODE_NAME = 'ImageReel' + + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "image1": ("IMAGE",), + "image1_text": ("STRING", {"multiline": False, "default": "image1"}), + "image2_text": ("STRING", {"multiline": False, "default": "image2"}), + "image3_text": ("STRING", {"multiline": False, "default": "image3"}), + "image4_text": ("STRING", {"multiline": False, "default": "image4"}), + "reel_height": ("INT", {"default": 512, "min": 64, "max": 2048}), + "border": ("INT", {"default": 32, "min": 8, "max": 512}), + }, + "optional": { + "image2": ("IMAGE",), + "image3": ("IMAGE",), + "image4": ("IMAGE",), + } + } + + RETURN_TYPES = ("Reel",) + RETURN_NAMES = ("reel",) + FUNCTION = 'image_reel' + CATEGORY = '😺dzNodes/LayerUtility' + + def image_reel(self, image1, image1_text, image2_text, image3_text, image4_text, + reel_height, border, + image2=None, image3=None, image4=None,): + + image_list = [] + texts = [] + for img in image1: + i = self.resize_image_to_height(tensor2pil(img.unsqueeze(0)),reel_height) + image_list.append(i) + texts.append([image1_text,i.width]) + if image2 is not None: + for img in image2: + i = self.resize_image_to_height(tensor2pil(img.unsqueeze(0)),reel_height) + image_list.append(i) + texts.append([image2_text,i.width]) + if image3 is not None: + for img in image3: + i = self.resize_image_to_height(tensor2pil(img.unsqueeze(0)),reel_height) + image_list.append(i) + texts.append([image3_text,i.width]) + if image4 is not None: + for img in image4: + i = self.resize_image_to_height(tensor2pil(img.unsqueeze(0)),reel_height) + image_list.append(i) + texts.append([image4_text,i.width]) + + reel = ImageReel() + reel.image = self.draw_reel_image(image_list, border, reel_height) + reel.texts = texts + reel.reel_height = reel_height + reel.reel_border = border + return (reel,) + + def resize_image_to_height(self, image, target_height) -> Image: + w = int(target_height / image.height * image.width) + return image.resize((w, target_height), Image.LANCZOS) + + def draw_reel_image(self, image_list, border, reel_height) -> Image: + reel_width = 0 + for img in image_list: + reel_width += img.width + border + reel_img = Image.new('RGBA', (reel_width, reel_height + border), color=(0, 0, 0, 0)) + #paste images + w = border // 2 + for img in image_list: + reel_img.paste(img, (w, border // 2)) + w += img.width + border + return reel_img + + +class ImageReelComposit: + + def __init__(self): + self.NODE_NAME = 'ImageReelComposit' + + @classmethod + def INPUT_TYPES(self): + color_theme_list = ['light', 'dark'] + return { + "required": { + "reel_1": ("Reel",), + "font_file": (FONT_LIST,), + "font_size": ("INT", {"default": 40, "min": 4, "max": 1024}), + "border": ("INT", {"default": 32, "min": 8, "max": 512}), + "color_theme": (color_theme_list,), + }, + "optional": { + "reel_2": ("Reel",), + "reel_3": ("Reel",), + "reel_4": ("Reel",), + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image1",) + FUNCTION = 'image_reel_composit' + CATEGORY = '😺dzNodes/LayerUtility' + + def image_reel_composit(self, reel_1, font_file, font_size, border, color_theme, reel_2=None, reel_3=None, reel_4=None,): + + ret_images = [] + + if color_theme == 'light': + bg_color = "#E5E5E5" + text_color = "#121212" + else: + bg_color = "#121212" + text_color = "#E5E5E5" + + + font_space = int(font_size * 1.5) + width = reel_1.image.width + height = reel_1.image.height + font_space + border + if reel_2 is not None: + width = max(width, reel_2.image.width) + height += reel_2.image.height + font_space + border + if reel_3 is not None: + width = max(width, reel_3.image.width) + height += reel_3.image.height + font_space + border + if reel_4 is not None: + width = max(width, reel_4.image.width) + height += reel_4.image.height + font_space + border + + ret_image = Image.new('RGB', (width, height), color=bg_color) + paste_y = 0 + reel1_text_image = self.draw_reel_text(reel_1, font_file, font_size, text_color) + shadow_size = reel_1.image.height // 80 + ret_image = self.paste_drop_shadow(ret_image, reel_1.image, reel1_text_image, ((width - reel_1.image.width) // 2, paste_y), + shadow_size, text_color) + + paste_y += reel_1.image.height + font_space + border + if reel_2 is not None: + reel2_text_image = self.draw_reel_text(reel_2, font_file, font_size, text_color) + shadow_size = reel_2.image.height // 80 + ret_image = self.paste_drop_shadow(ret_image, reel_2.image, reel2_text_image, ((width - reel_2.image.width) // 2, paste_y), + shadow_size, text_color) + paste_y += reel_2.image.height + font_space + border + if reel_3 is not None: + reel3_text_image = self.draw_reel_text(reel_3, font_file, font_size, text_color) + shadow_size = reel_3.image.height // 80 + ret_image = self.paste_drop_shadow(ret_image, reel_3.image, reel3_text_image,((width - reel_3.image.width) // 2, paste_y), + shadow_size, text_color) + paste_y += reel_3.image.height + font_space + border + if reel_4 is not None: + reel4_text_image = self.draw_reel_text(reel_4, font_file, font_size, text_color) + shadow_size = reel_4.image.height // 80 + ret_image = self.paste_drop_shadow(ret_image, reel_4.image, reel4_text_image,((width - reel_4.image.width) // 2, paste_y), + shadow_size, text_color) + + ret_images.append(pil2tensor(ret_image)) + + log(f"{self.NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish') + return (torch.cat(ret_images, dim=0),) + + def paste_drop_shadow(self, background_image, image, text_image, box, shadow_size, text_color) -> Image: + # drop shadow + _mask = image.split()[3] + _blured_mask = gaussian_blur(_mask, shadow_size//1.3) + _blured_mask = adjust_levels(_blured_mask, 0, 255, 0.5, 0, output_white=54).convert('L') + background_image.paste(Image.new('RGBA', image.size, color="black"), (box[0]+shadow_size, box[1]+shadow_size), mask=_blured_mask) + background_image.paste(image, box, mask=_mask) + background_image.paste(Image.new('RGB', text_image.size, color=text_color), (box[0], box[1] + image.height), mask=text_image.split()[3]) + return background_image + + def draw_reel_text(self, reel, font_file, font_size, text_color) -> Image: + font_path = FONT_DICT.get(font_file) + font = ImageFont.truetype(font_path, font_size) + texts = reel.texts + text_image = Image.new('RGBA', (reel.image.width, reel.reel_border + int(font_size * 1.5)), color=(0, 0, 0, 0)) + draw = ImageDraw.Draw(text_image) + x = reel.reel_border + for t in texts: + text = t[0] + width = t[1] + text_width = font.getbbox(text)[2] + draw.text( + xy=(x + width // 2 - text_width//2, reel.reel_border//4), + text=text, + fill=text_color, + font=font, + ) + x += width + reel.reel_border + return text_image + + + +NODE_CLASS_MAPPINGS = { + "LayerUtility: ImageReel": ImageReel, + "LayerUtility: ImageReelComposit": ImageReelComposit +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: ImageReel": "LayerUtility: Image Reel", + "LayerUtility: ImageReelComposit": "LayerUtility: Image Reel Composit" +} \ No newline at end of file diff --git a/py/image_scale_by_aspect_ratio_v2.py b/py/image_scale_by_aspect_ratio_v2.py index 9da5751..0b2b5bf 100644 --- a/py/image_scale_by_aspect_ratio_v2.py +++ b/py/image_scale_by_aspect_ratio_v2.py @@ -63,17 +63,23 @@ class ImageScaleByAspectRatioV2: mask = torch.unsqueeze(mask, 0) for m in mask: m = torch.unsqueeze(m, 0) - orig_masks.append(m) - _width, _height = tensor2pil(orig_masks[0]).size - if (orig_width > 0 and orig_width != _width) or (orig_height > 0 and orig_height != _height): - log(f"Error: {NODE_NAME} skipped, because the mask is does'nt match image.", message_type='error') - return (None, None, None, 0, 0,) - elif orig_width + orig_height == 0: - orig_width = _width - orig_height = _height + print(f"m.shape={m.shape}") + if not is_valid_mask(m) and m.shape==torch.Size([1,64,64]): + log(f"Warning: {NODE_NAME} input mask is empty, ignore it.", message_type='warning') + else: + orig_masks.append(m) + + if len(orig_masks) > 0: + _width, _height = tensor2pil(orig_masks[0]).size + if (orig_width > 0 and orig_width != _width) or (orig_height > 0 and orig_height != _height): + log(f"Error: {NODE_NAME} execute failed, because the mask is does'nt match image.", message_type='error') + return (None, None, None, 0, 0,) + elif orig_width + orig_height == 0: + orig_width = _width + orig_height = _height if orig_width + orig_height == 0: - log(f"Error: {NODE_NAME} skipped, because the image or mask at least one must be input.", message_type='error') + log(f"Error: {NODE_NAME} execute failed, because the image or mask at least one must be input.", message_type='error') return (None, None, None, 0, 0,) if aspect_ratio == 'original': diff --git a/py/imagefunc.py b/py/imagefunc.py index d3affc6..6c1f0cc 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -1813,6 +1813,8 @@ def HSV_255level_to_Hex(HSV: list) -> str: return '#' + hex_r + hex_g + hex_b '''Value Functions''' +def is_valid_mask(tensor:torch.Tensor) -> bool: + return not bool(torch.all(tensor == 0).item()) def step_value(start_value, end_value, total_step, step) -> float: # 按当前步数在总步数中的位置返回比例值 factor = step / total_step diff --git a/pyproject.toml b/pyproject.toml index 4087a73..4a776ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_layerstyle" description = "A set of nodes for ComfyUI it generate image like Adobe Photoshop's Layer Style. the Drop Shadow is first completed node, and follow-up work is in progress." -version = "1.0.27" +version = "1.0.28" license = "MIT" dependencies = ["numpy", "pillow", "torch", "matplotlib", "Scipy", "scikit_image", "opencv-contrib-python", "pymatting", "segment_anything", "timm", "addict", "yapf", "colour-science", "wget", "mediapipe", "loguru", "typer_config", "fastapi", "rich", "google-generativeai", "diffusers", "omegaconf", "tqdm", "transformers", "kornia", "image-reward", "ultralytics", "blend_modes", "blind-watermark", "qrcode", "pyzbar", "transparent-background", "huggingface_hub", "psd-tools"] diff --git a/workflow/image_reel_example.json b/workflow/image_reel_example.json new file mode 100644 index 0000000..e8dcb62 --- /dev/null +++ b/workflow/image_reel_example.json @@ -0,0 +1,477 @@ +{ + "last_node_id": 9, + "last_link_id": 8, + "nodes": [ + { + "id": 3, + "type": "LoadImage", + "pos": [ + 290, + 340 + ], + "size": [ + 315, + 314 + ], + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "1280x720car.jpg", + "image" + ] + }, + { + "id": 4, + "type": "LoadImage", + "pos": [ + 290, + 710 + ], + "size": [ + 315, + 314.0000114440918 + ], + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "1344x768_redcar.png", + "image" + ] + }, + { + "id": 7, + "type": "LoadImage", + "pos": [ + 990, + 650 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 5 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "1344x768_hair.png", + "image" + ] + }, + { + "id": 8, + "type": "LoadImage", + "pos": [ + 980, + 1020 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 3, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 8 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "768x1344_dress.png", + "image" + ] + }, + { + "id": 1, + "type": "LayerUtility: ImageReel", + "pos": [ + 650, + 590 + ], + "size": { + "0": 315, + "1": 238 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "image1", + "type": "IMAGE", + "link": 2 + }, + { + "name": "image2", + "type": "IMAGE", + "link": 3 + }, + { + "name": "image3", + "type": "IMAGE", + "link": null + }, + { + "name": "image4", + "type": "IMAGE", + "link": null + } + ], + "outputs": [ + { + "name": "reel", + "type": "Reel", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: ImageReel" + }, + "widgets_values": [ + "image1 on reel1", + "image2 on reel1", + "image3", + "image4", + 768, + 32 + ] + }, + { + "id": 5, + "type": "LayerUtility: ImageReel", + "pos": [ + 1350, + 750 + ], + "size": { + "0": 315, + "1": 238 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "image1", + "type": "IMAGE", + "link": 4 + }, + { + "name": "image2", + "type": "IMAGE", + "link": 5 + }, + { + "name": "image3", + "type": "IMAGE", + "link": 8 + }, + { + "name": "image4", + "type": "IMAGE", + "link": null + } + ], + "outputs": [ + { + "name": "reel", + "type": "Reel", + "links": [ + 6 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: ImageReel" + }, + "widgets_values": [ + "image1 on reel2", + "image2 on reel2", + "image3 on reel2", + "image4", + 1024, + 32 + ] + }, + { + "id": 2, + "type": "LayerUtility: ImageReelComposit", + "pos": [ + 1690, + 590 + ], + "size": { + "0": 315, + "1": 190 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "reel_1", + "type": "Reel", + "link": 1 + }, + { + "name": "reel_2", + "type": "Reel", + "link": 6 + }, + { + "name": "reel_3", + "type": "Reel", + "link": null + }, + { + "name": "reel_4", + "type": "Reel", + "link": null + } + ], + "outputs": [ + { + "name": "image1", + "type": "IMAGE", + "links": [ + 7 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerUtility: ImageReelComposit" + }, + "widgets_values": [ + "Alibaba-PuHuiTi-Heavy.ttf", + 40, + 32, + "light" + ] + }, + { + "id": 9, + "type": "PreviewImage", + "pos": [ + 2060, + 380 + ], + "size": [ + 1023.9856266119964, + 713.8515867095819 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 7 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 6, + "type": "LoadImage", + "pos": [ + 990, + 280 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 4, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 4 + ], + "shape": 3 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "768x1344_beach.png", + "image" + ] + } + ], + "links": [ + [ + 1, + 1, + 0, + 2, + 0, + "Reel" + ], + [ + 2, + 3, + 0, + 1, + 0, + "IMAGE" + ], + [ + 3, + 4, + 0, + 1, + 1, + "IMAGE" + ], + [ + 4, + 6, + 0, + 5, + 0, + "IMAGE" + ], + [ + 5, + 7, + 0, + 5, + 1, + "IMAGE" + ], + [ + 6, + 5, + 0, + 2, + 1, + "Reel" + ], + [ + 7, + 2, + 0, + 9, + 0, + "IMAGE" + ], + [ + 8, + 8, + 0, + 5, + 2, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.6209213230591553, + "offset": [ + 304.4138606804165, + 388.12270422110066 + ] + } + }, + "version": 0.4 +} \ No newline at end of file