Skip to content

Commit

Permalink
commit LoadPSD node
Browse files Browse the repository at this point in the history
  • Loading branch information
chflame163 committed May 28, 2024
1 parent 8cf4339 commit 17db0ff
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 11 deletions.
18 changes: 18 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ When this error has occurred, please check the network environment.
## Update
<font size="4">**If the dependency package error after updating, please reinstall the relevant dependency packages. </font><br />

* Commit [LoadPSD](#LoadPSD) node, It read the psd format, and output layer images.
* Commit [SegformerB2ClothesUltra](#SegformerB2ClothesUltra) node, it used to segment character clothing. The model segmentation code is from[StartHua](https://github.com/StartHua/Comfyui_segformer_b2_clothes), thanks to the original author.
* [SaveImagePlus](#SaveImagePlus) node adds the output workflow to the json function, supports ```%date``` and ```%time``` to embeddint date or time to path and filename, and adds the preview switch.
* Commit [SaveImagePlus](#SaveImagePlus) node,It can customize the directory where the picture is saved, add a timestamp to the file name, select the save format, set the image compression rate, set whether to save the workflow, and optionally add invisible watermarks to the picture.
Expand Down Expand Up @@ -1152,6 +1153,23 @@ Node Options:
* image: The input QR code image.
* pre_blur: Pre-blurring, you can try to adjust this value for QR codes that are difficult to identify.

### <a id="table1">LoadPSD</a>
![image](image/load_image_example.jpg)
Load the PSD format file and export the layers.

Node Options:
![image](image/load_image_node.jpg)
* image: Here is a list of *.psd files under ```ComfyUI/input```, where previously loaded psd images can be selected.
* file_path: The complete path and file name of the psd file.
* include_hidden_layer: whether include hidden layers.
* find_layer_by: The method for finding layers can be selected by layer key number or layer name. Layer groups are treated as one layer.
* layer_index: The layer key number, where 0 is the bottom layer, is incremented sequentially. If include_hiddenlayer is set to false, hidden layers are not counted. Set to -1 to output the top layer.
* layer_name: Layer name. Note that capitalization and punctuation must match exactly.

Outputs:
flat_image: PSD preview image.
layer_iamge: Find the layer output.
all_layers: Batch images containing all layers.


# <a id="table1">LayerMask</a>
Expand Down
18 changes: 18 additions & 0 deletions README_CN.MD
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git
## 更新说明
<font size="4">**如果本插件更新后出现依赖包错误,请重新安装相关依赖包。

* 添加 [LoadPSD](#LoadPSD) 节点, 读取psd格式并输出图层图片。
* 添加 [SegformerB2ClothesUltra](#SegformerB2ClothesUltra)节点,用于分割人物服装。模型分割代码来自[StartHua](https://github.com/StartHua/Comfyui_segformer_b2_clothes),感谢原作者。
* [SaveImagePlus](#SaveImagePlus)节点增加输出工作流为json功能,支持使用```%date``````%time```在路径和文件名嵌入时间,增加预览开关。
* 添加 [SaveImagePlus](#SaveImagePlus)节点,可自定义保存图片的目录,文件名增加时间戳,选择保存格式,设置图片压缩率,设置是否保存工作流,以及可选给图片添加隐形水印。
Expand Down Expand Up @@ -1138,6 +1139,23 @@ cropped_mask: 裁切后的遮罩。
* image: 输入二维码图片。
* pre_blur: 预模糊,对难以识别的二维码可以尝试调整此数值。

### <a id="table1">LoadPSD</a>
![image](image/load_image_example.jpg)
加载PSD格式文件,并导出图层。

节点选项说明:
![image](image/load_image_node.jpg)
* image: 这里列出了```ComfyUI/input```下的*.psd文件,之前加载过的psd图片可以从这里选择。
* file_path: psd文件的完整路径以及文件名。
* include_hidden_layer: 是否包括隐藏图层。
* find_layer_by: 查找图层的方法,可选择按图层索引编号或者图层名称查找。图层组被作为一个图层对待。
* layer_index: 图层索引编号,0是最下面的图层,依次递增。如果include_hidden_layer设置为false,隐藏的图层不计入。设为-1则输出最上层的图层。
* layer_name: 图层名称。注意大小写和标点符号必须完全匹配。

输出:
flat_image: psd预览图。
layer_iamge: 查找的图层输出。
all_layers: 包含全部图层的批量图片。


# <a id="table1">LayerMask</a>
Expand Down
Binary file added image/load_image_example.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image/load_image_node.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 26 additions & 2 deletions py/imagefunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1246,11 +1246,20 @@ def decode_watermark(image:Image, watermark_image_size:int=94) -> Image:
except Exception as e:
log(f"blind watermark extract fail, {e}")
ret_image = Image.new("RGB", (64, 64), color="black")

ret_image = normalize_gray(ret_image)

return ret_image

def generate_text_image(width:int, height:int, text:str, font_file:str, text_scale:float=1, font_color:str="#FFFFFF",) -> Image:
image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(image)
font_size = int(width / len(text) * text_scale)
font = ImageFont.truetype(font_file, font_size)
bbox = draw.textbbox((0, 0), text, font=font)
text_width, text_height = bbox[2] - bbox[0], bbox[3] - bbox[1]
x = int((width - text_width) / 2)
y = int((height - text_height) / 2) - int(font_size / 2)
draw.text((x, y), text, font=font, fill=font_color)
return image

'''Mask Functions'''
@lru_cache(maxsize=1, typed=False)
Expand Down Expand Up @@ -1616,6 +1625,21 @@ def Hex_to_HSV_255level(inhex:str) -> list:
HSV = colorsys.rgb_to_hsv(RGB[0] / 255.0, RGB[1] / 255.0, RGB[2] / 255.0)
return [int(x * 255) for x in HSV]


def HSV_255level_to_Hex(HSV: list) -> str:
if len(HSV) != 3 or any((not isinstance(v, int) or v < 0 or v > 255) for v in HSV):
raise ValueError('Invalid HSV values, each value should be an integer between 0 and 255')

H, S, V = HSV
RGB = tuple(int(x * 255) for x in hsv_to_rgb(H / 255.0, S / 255.0, V / 255.0))

# Convert RGB values to hexadecimal format
hex_r = format(RGB[0], '02x')
hex_g = format(RGB[1], '02x')
hex_b = format(RGB[2], '02x')

return '#' + hex_r + hex_g + hex_b

'''Value Functions'''

def step_value(start_value, end_value, total_step, step) -> float: # 按当前步数在总步数中的位置返回比例值
Expand Down
101 changes: 101 additions & 0 deletions py/loadpsd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import os
import numpy as np
import torch
import folder_paths
import node_helpers
from nodes import LoadImage
from PIL import Image,ImageOps,ImageSequence,ImageDraw,ImageFont
from psd_tools import PSDImage
from psd_tools.api.layers import Layer
from .imagefunc import pil2tensor, log, FONT_DICT, generate_text_image


class LoadPSD(LoadImage):
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_dir) if os.path.isfile(
os.path.join(input_dir, f)) and f.endswith(".psd")]
fine_layer_method = ["layer_index", "layer_name"]
return {"required":{
"image": (sorted(files), {"image_upload": True}),
"file_path": ("STRING", {"default": ""}),
"include_hidden_layer": ("BOOLEAN", {"default": False}),
"find_layer_by": (fine_layer_method,),
"layer_index": ("INT", {"default": 0, "min": -1, "max": 999, "step": 1}),
"layer_name": ("STRING", {"default": ""}),
},
"optional": {
}
}


RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE",)
RETURN_NAMES = ("flat_image", "layer_image", "all_layers",)
FUNCTION = "load_psd"
CATEGORY = '😺dzNodes/LayerUtility'

def load_psd(self, image, file_path, include_hidden_layer, layer_index, find_layer_by, layer_name,):

NODE_NAME = 'LoadPSD'
number_of_layers = 1
layer_image = []
all_layers = []
if file_path == "":
psd_file_path = folder_paths.get_annotated_filepath(image)
else:
psd_file_path = folder_paths.get_annotated_filepath(file_path)
flat_image = Image.open(psd_file_path).convert("RGB")
width, height = flat_image.size
if image.endswith(".psd"):
log(f"{NODE_NAME} -> Loading PSD file: {psd_file_path}")
psd_image = PSDImage.open(psd_file_path)
layers = []
for layer in psd_image:
if include_hidden_layer:
if not layer.is_visible():
layer.visible = True
layers.append(layer)
else:
if layer.is_visible():
layers.append(layer)

number_of_layers = len(layers)
for i in range(number_of_layers):
layer_canvas = Image.new("RGBA", (width, height), (0, 0, 0, 0))
layer_canvas.paste(layers[i].composite(), layers[i].bbox)
all_layers.append(pil2tensor(layer_canvas))
if find_layer_by == "layer_name":
if layers[i].name == layer_name:
layer_image.append(pil2tensor(layer_canvas))
log(f"{NODE_NAME} -> Layer {i} : {layer.name} found.")
elif find_layer_by == "layer_index":
if i == layer_index:
layer_image.append(pil2tensor(layer_canvas))
log(f"{NODE_NAME} -> Layer {i} : {layer.name} found.")

text = "Layer Not Found!"
font_file = list(FONT_DICT.values())[0]
empty_layer_image = generate_text_image(flat_image.width, flat_image.height, text, font_file, font_color="#F01000")
if layer_image == []:
if layer_index == -1:
layer_image.append(all_layers[-1])
elif find_layer_by == "layer_name":
log(f'{NODE_NAME} -> Layer "{layer_name}" not found, top layer will be output.', message_type="warning")
elif find_layer_by == "layer_index":
log(f'{NODE_NAME} -> Layer index {layer_index} not found, top layer will be output.', message_type="warning")
layer_image.append(pil2tensor(empty_layer_image))
else:
layer_image.append(pil2tensor(flat_image))
all_layers.append(pil2tensor(flat_image))

return (torch.cat([pil2tensor(flat_image),], dim=0), torch.cat(layer_image, dim=0), torch.cat(all_layers, dim=0),)


NODE_CLASS_MAPPINGS = {
"LayerUtility: LoadPSD": LoadPSD,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: LoadPSD": "LayerUtility: Load PSD",
}
21 changes: 14 additions & 7 deletions py/save_image_plus.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os.path
import shutil
from PIL.PngImagePlugin import PngInfo
import datetime
from .imagefunc import *
Expand All @@ -24,7 +25,7 @@ def INPUT_TYPES(s):
"meta_data": ("BOOLEAN", {"default": False}),
"blind_watermark": ("STRING", {"default": ""}),
"save_workflow_as_json": ("BOOLEAN", {"default": False}),
"preview": ("BOOLEAN", {"default": False}),
"preview": ("BOOLEAN", {"default": True}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
Expand Down Expand Up @@ -94,10 +95,15 @@ def save_image_plus(self, images, custom_path, filename_prefix, timestamp, forma


preview_filename = ""
if not os.path.exists(custom_path):
if custom_path != "":
raise FileNotFoundError("custom_path is not a valid path")
else:
if custom_path != "":
if not os.path.exists(custom_path):
try:
os.makedirs(custom_path)
except Exception as e:
log(f"Error: {NODE_NAME} skipped, because unable to create temporary folder.",
message_type='warning')
raise FileNotFoundError(f"cannot create custom_path {custom_path}, {e}")

full_output_folder = os.path.normpath(custom_path)
# save preview image to temp_dir
if os.path.isdir(temp_dir):
Expand All @@ -106,7 +112,8 @@ def save_image_plus(self, images, custom_path, filename_prefix, timestamp, forma
os.makedirs(temp_dir)
except Exception as e:
print(e)
log(f"Error: {NODE_NAME} skipped, because unable to create temporary folder.", message_type='warning')
log(f"Error: {NODE_NAME} skipped, because unable to create temporary folder.",
message_type='warning')
try:
preview_filename = os.path.join(generate_random_name('saveimage_preview_', '_temp', 16) + '.png')
img.save(os.path.join(temp_dir, preview_filename))
Expand All @@ -133,7 +140,7 @@ def save_image_plus(self, images, custom_path, filename_prefix, timestamp, forma
if img.mode == "RGBA":
img = img.convert("RGB")
img.save(image_file_name, quality=quality)
log(f"Saving image to {image_file_name}")
log(f"{NODE_NAME} -> Saving image to {image_file_name}")

if save_workflow_as_json:
try:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[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.0"
version = "1.0.1"
license = "LICENSE"
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"]
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", "psd-tools"]

[project.urls]
Repository = "https://github.com/chflame163/ComfyUI_LayerStyle"
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ blend_modes
blind-watermark
qrcode
pyzbar
psd-tools

0 comments on commit 17db0ff

Please sign in to comment.