commit SaveImagePlusV2 node

This commit is contained in:
chflame163
2025-05-14 20:02:35 +08:00
parent fe35b54bd2
commit 9ed67eb919
4 changed files with 215 additions and 10 deletions
+13 -1
View File
@@ -145,6 +145,7 @@ Please try downgrading the ```protobuf``` dependency package to 3.20.3, or set e
**If the dependency package error after updating, please double clicking ```repair_dependency.bat``` (for Official ComfyUI Protable) or ```repair_dependency_aki.bat``` (for ComfyUI-aki-v1.x) in the plugin folder to reinstall the dependency packages.
* Commit [SaveImagePLusV2](SaveImagePlusV2) node, add custom file names and setting up the dpi of image.
* Commit [GeminiImageEdit](#GeminiImageEdit) node, support using gemini-2.0-flash-exp-image-generation API for image editing.
* Commit [GeminiV2](#GeminiV2) and [ObjectDetectorGeminiV2](#ObjectDetectorGeminiV2) nodes, used google-genai dependency package that supports the gemini-2.0-flash-exp and gemini-2.5-pro-exp-03-25 models.
* Add QuarkNetdisk model download link.
@@ -716,7 +717,7 @@ Enhanced save image node. You can customize the directory where the picture is s
Node Options:
![image](image/saveimage_plus_node.jpg)
* iamge: The input image.
* image: The input image.
* custom_path<sup>*</sup>: User-defined directory, enter the directory name in the correct format. If empty, it is saved in the default output directory of ComfyUI.
* filename_prefix<sup>*</sup>: The prefix of file name.
* timestamp: Timestamp the file name, opting for date, time to seconds, and time to milliseconds.
@@ -729,6 +730,17 @@ Node Options:
<sup>*</sup> Enter```%date``` for the current date (YY-mm-dd) and ```%time``` for the current time (HH-MM-SS). You can enter ```/``` for subdirectories. For example, ```%date/name_%tiem``` will output the image to the ```YY-mm-dd``` folder, with ```name_HH-MM-SS``` as the file name prefix.
### <a id="table1">SaveImagePlusV2</a>
Added custom file name and dpi options on the SaveImagePlus node.
NodeOptions:
![image](image/saveimage_plus_v2_node.jpg)
* custom_filename<sup>*</sup>: User defined file name, if entered here, will be used as the file name. Please note that duplicate files will be overwritten. If this is empty, use the file name prefix and timestamp as the file name.
* dpi: Set the DPI value of the image file.
<sup>*</sup> Enter```%date``` for the current date (YY-mm-dd) and ```%time``` for the current time (HH-MM-SS). You can enter ```/``` for subdirectories. For example, ```%date/name_%tiem``` will output the image to the ```YY-mm-dd``` folder, with ```name_HH-MM-SS``` as the file name prefix.
### <a id="table1">AddBlindWaterMark</a>
+14 -1
View File
@@ -121,6 +121,7 @@ If this call came from a _pb2.py file, your generated code is out of date and mu
## 更新说明
**如果本插件更新后出现依赖包错误,请双击运行插件目录下的```install_requirements.bat```(官方便携包),或 ```install_requirements_aki.bat```(秋叶整合包) 重新安装依赖包。
* 添加 [SaveImagePLusV2](SaveImagePlusV2) 节点,增加自定义文件名和设置dpi。
* 添加多语言,除英语外,增加支持5种语言:中文、法语、日语、韩语和俄语。该功能使用[ComfyUI-Globalization-Node-Translation](https://github.com/yamanacn/ComfyUI-Globalization-Node-Translation)制作,感谢原作者。
* 添加 [GeminiImageEdit](#GeminiImageEdit) 节点,支持使用gemini-2.0-flash-exp-image-generation API进行图像编辑。
* 添加 [GeminiV2](#GeminiV2) 以及 [ObjectDetectorGeminiV2](#ObjectDetectorGeminiV2) 节点,使用了新的google-genai依赖包,支持最新的gemini-2.0-flash-exp以及gemini-2.5-pro-exp-03-25模型。
@@ -667,7 +668,7 @@ box_preview: 裁切位置预览。
节点选项说明:
![image](image/saveimage_plus_node.jpg)
* iamge: 输入的图片。
* image: 输入的图片。
* custom_path<sup>*</sup>: 用户自定义目录,请按正确的格式输入目录名。如果为空则保存在ComfyUI默认的output目录。
* filename_prefix<sup>*</sup>:文件名前缀。。
* timestamp: 为文件名加上时间戳,可选择日期、时间到秒和时间到毫秒。
@@ -680,6 +681,18 @@ box_preview: 裁切位置预览。
<sup>*</sup>输入```%date```表示当前日期(YY-mm-dd),```%time```表示当前时间(HH-MM-SS)。可以输入```/```表示子目录。例如```%date/name_%time``` 将输出图片到```YY-mm-dd```文件夹下,以```name_HH-MM-SS```为文件名前缀。
### <a id="table1">SaveImagePlusV2</a>
在SaveImagePlus节点上增加了自定义文件名和dpi选项。
节点选项说明:
![image](image/saveimage_plus_v2_node.jpg)
* custom_filename<sup>*</sup>: 用户自定义文件名,如果此处有输入,将以此为文件名,注意重名文件将被覆盖。此处为空则按照文件名前缀+时间戳作为文件名。
* dpi: 设置图片文件的dpi值。
<sup>*</sup>输入```%date```表示当前日期(YY-mm-dd),```%time```表示当前时间(HH-MM-SS)。可以输入```/```表示子目录。例如```%date/name_%time``` 将输出图片到```YY-mm-dd```文件夹下,以```name_HH-MM-SS```为文件名前缀。
### <a id="table1">AddBlindWaterMark</a>
![image](image/watermark_example.jpg)
+187 -7
View File
@@ -6,10 +6,10 @@ from PIL.PngImagePlugin import PngInfo
import datetime
from .imagefunc import *
NODE_NAME = 'SaveImagePlus'
class SaveImagePlus:
def __init__(self):
self.NODE_NAME = 'SaveImagePlus'
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
@@ -102,7 +102,7 @@ class SaveImagePlus:
try:
os.makedirs(custom_path)
except Exception as e:
log(f"Error: {NODE_NAME} skipped, because unable to create temporary folder.",
log(f"Error: {self.NODE_NAME} skipped, because unable to create temporary folder.",
message_type='warning')
raise FileNotFoundError(f"cannot create custom_path {custom_path}, {e}")
@@ -114,14 +114,14 @@ class SaveImagePlus:
os.makedirs(temp_dir)
except Exception as e:
print(e)
log(f"Error: {NODE_NAME} skipped, because unable to create temporary folder.",
log(f"Error: {self.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')
log(f"Error: {self.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}")):
@@ -142,7 +142,7 @@ class SaveImagePlus:
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}")
log(f"{self.NODE_NAME} -> Saving image to {image_file_name}")
if save_workflow_as_json:
try:
@@ -174,10 +174,190 @@ class SaveImagePlus:
return { "ui": { "images": results } }
class SaveImagePlusV2:
def __init__(self):
self.NODE_NAME = 'SaveImagePlusV2'
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": ""}),
"custom_filename": ("STRING", {"default": ""}),
"filename_prefix": ("STRING", {"default": "comfyui"}),
"timestamp": (["None", "second", "millisecond"],),
"dpi": ("INT", {"default": 96, "min": 1, "max": 10000, "step": 1}),
"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_v2"
OUTPUT_NODE = True
CATEGORY = '😺dzNodes/LayerUtility/SystemIO'
def save_image_plus_v2(self, images, custom_path, custom_filename, filename_prefix,
timestamp, dpi, 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 custom_filename == "":
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}'
else:
custom_filename = custom_filename.replace("%date", now.strftime("%Y-%m-%d"))
custom_filename = custom_filename.replace("%time", now.strftime("%H-%M-%S"))
filename = custom_filename
file = filename
preview_filename = ""
if custom_path != "":
if not os.path.exists(custom_path):
try:
os.makedirs(custom_path)
except Exception as e:
log(f"Error: {self.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: {self.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), pnginfo=metadata, compress_level=(100 - quality) // 10, dpi=(dpi, dpi))
except Exception as e:
print(e)
log(f"Error: {self.NODE_NAME} skipped, because unable to create temporary file.", message_type='warning')
# check if file exists, change filename
if custom_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, dpi=(dpi, dpi))
else:
if img.mode == "RGBA":
img = img.convert("RGB")
img.save(image_file_name, quality=quality, dpi=(dpi, dpi))
log(f"{self.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', encoding='utf-8') as workflow_file:
json.dump(workflow, workflow_file, ensure_ascii=False, indent=4)
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 } }
NODE_CLASS_MAPPINGS = {
"LayerUtility: SaveImagePlus": SaveImagePlus
"LayerUtility: SaveImagePlus": SaveImagePlus,
"LayerUtility: SaveImagePlusV2": SaveImagePlusV2
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: SaveImagePlus": "LayerUtility: SaveImage Plus(Advance)"
"LayerUtility: SaveImagePlus": "LayerUtility: SaveImage Plus(Advance)",
"LayerUtility: SaveImagePlusV2": "LayerUtility: SaveImagePlus V2(Advance)"
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_layerstyle_advance"
description = "The nodes detached from ComfyUI Layer Style are mainly those with complex requirements for dependency packages."
version = "2.0.17"
version = "2.0.18"
license = { text = "MIT License" }
dependencies = ["numpy", "matplotlib", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "blend_modes", "transformers", "diffusers", "loguru", "colour-science", "huggingface_hub", "segment_anything", "addict", "omegaconf", "yapf", "wget", "iopath", "mediapipe", "typer_config", "fastapi", "rich", "google-generativeai", "ultralytics", "transparent-background", "accelerate", "onnxruntime", "bitsandbytes", "peft", "protobuf", "hydra-core", "blind-watermark", "qrcode", "pyzbar", "psd-tools", "wandb", "zhipuai", "openai","google-genai"]