Files
CavinHuang-comfyui-nodes-docs/docs/LayerUtility%3A SaveImagePlus.md
2024-06-14 19:23:49 +08:00

251 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Documentation
- Class name: SaveImagePlus
- Category: 😺dzNodes/LayerUtility/SystemIO
- Output node: True
- Repo Ref: https://github.com/chflame163/ComfyUI_LayerStyle
增强版的保存图片节点。可自定义保存图片的目录,文件名增加时间戳,选择保存格式,设置图片压缩率,设置是否保存工作流,以及可选给图片添加隐形水印(以肉眼无法觉察的方式添加信息,使用配套的ShowBlindWaterMark节点可以解码水印)。可选择是否同时输出工作流的json文件。
*输入%date表示当前日期(YY-mm-dd),%time表示当前时间(HH-MM-SS)。可以输入/表示子目录。例如%date/name_%time 将输出图片到YY-mm-dd文件夹下,以name_HH-MM-SS为文件名前缀。
# Input types
## Required
- images
- 图片。
- Comfy dtype: IMAGE
- Python dtype: torch.Tensor
- custom_path
- 用户自定义目录,请按正确的格式输入目录名。如果为空则保存在ComfyUI默认的output目录。
- Comfy dtype: STRING
- Python dtype: str
- filename_prefix
- 文件名前缀。
- Comfy dtype: STRING
- Python dtype: str
- timestamp
- 为文件名加上时间戳,可选择日期、时间到秒和时间到毫秒。
- Comfy dtype: ["None", "second", "millisecond"]
- Python dtype: str
- format
- 图片保存格式。目前提供png和jpg两种。注意RGBA模式的图片仅支持png格式。
- Comfy dtype: ["png", "jpg"]
- Python dtype: str
- quality
- 图片质量,数值范围10-100,数值越高,图片质量越好,文件的体积也对应增大。
- Comfy dtype: INT
- Python dtype: int
- meta_data
- 是否保存元数据即工作流信息到png文件。如果不希望泄露工作流,请把这里设置为false。
- Comfy dtype: BOOLEAN
- Python dtype: bool
- blind_watermark
- 这里输入的文字(不支持多语言)将被转换为二维码作为隐形水印保存,使用ShowBlindWaterMark节点可以解码水印。注意有水印的图片建议保存为png格式,质量较低的jpg格式将导致水印信息丢失。
- Comfy dtype: STRING
- Python dtype: str
- save_workflow_as_json
- 是否同时输出工作流为json文件(输出的json与图片在同一目录)。
- Comfy dtype: BOOLEAN
- Python dtype: bool
- preview
- 预览。
- Comfy dtype: BOOLEAN
- Python dtype: bool
## Hidden
- prompt
- 提示。
- Comfy dtype: PROMPT
- Python dtype: str
- extra_pnginfo
- 额外PNG信息。
- Comfy dtype: EXTRA_PNGINFO
- Python dtype: dict
# Output types
- 无
# Usage tips
- Infra type: CPU
# Source code
```python
class SaveImagePlus:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {"required":
{"images": ("IMAGE", ),
"custom_path": ("STRING", {"default": ""}),
"filename_prefix": ("STRING", {"default": "comfyui"}),
"timestamp": (["None", "second", "millisecond"],),
"format": (["png", "jpg"],),
"quality": ("INT", {"default": 80, "min": 10, "max": 100, "step": 1}),
"meta_data": ("BOOLEAN", {"default": False}),
"blind_watermark": ("STRING", {"default": ""}),
"save_workflow_as_json": ("BOOLEAN", {"default": False}),
"preview": ("BOOLEAN", {"default": True}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "save_image_plus"
OUTPUT_NODE = True
CATEGORY = '😺dzNodes/LayerUtility/SystemIO'
def save_image_plus(self, images, custom_path, filename_prefix, timestamp, format, quality,
meta_data, blind_watermark, preview, save_workflow_as_json,
prompt=None, extra_pnginfo=None):
now = datetime.datetime.now()
custom_path = custom_path.replace("%date", now.strftime("%Y-%m-%d"))
custom_path = custom_path.replace("%time", now.strftime("%H-%M-%S"))
filename_prefix = filename_prefix.replace("%date", now.strftime("%Y-%m-%d"))
filename_prefix = filename_prefix.replace("%time", now.strftime("%H-%M-%S"))
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
temp_sub_dir = generate_random_name('_savepreview_', '_temp', 16)
temp_dir = os.path.join(folder_paths.get_temp_directory(), temp_sub_dir)
for image in images:
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
if blind_watermark != "":
img_mode = img.mode
wm_size = watermark_image_size(img)
import qrcode
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=20,
border=1,
)
qr.add_data(blind_watermark.encode('utf-8'))
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
qr_image = qr_image.resize((wm_size, wm_size), Image.BICUBIC).convert("L")
y, u, v, _ = image_channel_split(img, mode='YCbCr')
_u = add_invisibal_watermark(u, qr_image)
wm_img = image_channel_merge((y, _u, v), mode='YCbCr')
if img.mode == "RGBA":
img = RGB2RGBA(wm_img, img.split()[-1])
else:
img = wm_img.convert(img_mode)
metadata = None
if meta_data:
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
if timestamp == "millisecond":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]}'
elif timestamp == "second":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S")}'
else:
file = f'{filename}_{counter:05}'
preview_filename = ""
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):
shutil.rmtree(temp_dir)
try:
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')
try:
preview_filename = os.path.join(generate_random_name('saveimage_preview_', '_temp', 16) + '.png')
img.save(os.path.join(temp_dir, preview_filename))
except Exception as e:
print(e)
log(f"Error: {NODE_NAME} skipped, because unable to create temporary file.", message_type='warning')
# check if file exists, change filename
while os.path.isfile(os.path.join(full_output_folder, f"{file}.{format}")):
counter += 1
if timestamp == "millisecond":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S-%f")[:-3]}_{counter:05}'
elif timestamp == "second":
file = f'{filename}_{now.strftime("%Y-%m-%d_%H-%M-%S")}_{counter:05}'
else:
file = f"{filename}_{counter:05}"
image_file_name = os.path.join(full_output_folder, f"{file}.{format}")
json_file_name = os.path.join(full_output_folder, f"{file}.json")
if format == "png":
img.save(image_file_name, pnginfo=metadata, compress_level= (100 - quality) // 10)
else:
if img.mode == "RGBA":
img = img.convert("RGB")
img.save(image_file_name, quality=quality)
log(f"{NODE_NAME} -> Saving image to {image_file_name}")
if save_workflow_as_json:
try:
workflow = (extra_pnginfo or {}).get('workflow')
if workflow is None:
log('No workflow found, skipping saving of JSON')
with open(f'{json_file_name}', 'w') as workflow_file:
json.dump(workflow, workflow_file)
log(f'Saved workflow to {json_file_name}')
except Exception as e:
log(
f'Failed to save workflow as json due to: {e}, proceeding with the remainder of saving execution', message_type="warning")
if preview:
if custom_path == "":
results.append({
"filename": f"{file}.{format}",
"subfolder": subfolder,
"type": self.type
})
else:
results.append({
"filename": preview_filename,
"subfolder": temp_sub_dir,
"type": "temp"
})
counter += 1
return { "ui": { "images": results } }
```