fix
This commit is contained in:
@@ -20,10 +20,9 @@
|
||||
|
||||
该节点可以保存图像并保留原始图像的元数据:
|
||||
|
||||
- 支持保存为原始格式或指定格式(avif、webp、jpg、png)
|
||||
- 支持保存为原始格式或指定格式(avif、webp、jpg、png、tiff)
|
||||
- 保留原始图像的DPI信息
|
||||
- 保留原始图像的EXIF数据(对支持EXIF的格式)
|
||||
- 支持批量图像保存
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -49,15 +48,15 @@ git clone https://github.com/sjh00/ComfyUI-LoadImageWithInfo.git
|
||||
### SaveImageWithInfo 节点
|
||||
|
||||
1. 在ComfyUI工作流中,找到`image`分类下的`Save Image With Info`节点
|
||||
2. 将要保存的图像连接到节点的`images`输入
|
||||
2. 将要保存的图像连接到节点的`image`输入
|
||||
3. 设置以下参数:
|
||||
- `filename`:保存的文件名(不含扩展名)
|
||||
- `format`:保存格式(original、avif、webp、jpg、png)
|
||||
- `format`:保存格式(original、avif、webp、jpg、png、tiff)
|
||||
- `original_format`:原始格式(当format设为original时使用)
|
||||
- `quality`:保存质量(1-100,默认100,png格式换算为0-9压缩率)
|
||||
- `quality`:保存质量(1-100,AVIF默认60,其他默认90,png/tiff格式不受影响)
|
||||
- `dpi`:图像DPI值
|
||||
- `exif`:EXIF元数据(JSON格式字符串)
|
||||
4. 节点将保存图像并返回保存后的图像和文件名
|
||||
4. 节点将保存图像
|
||||
|
||||
## 示例输出
|
||||
|
||||
@@ -100,9 +99,9 @@ LoadImageWithInfo:
|
||||
- exif -> SaveImageWithInfo.exif
|
||||
|
||||
SaveImageWithInfo:
|
||||
- images <- LoadImageWithInfo.image
|
||||
- image <- LoadImageWithInfo.image
|
||||
- filename <- LoadImageWithInfo.filename
|
||||
- format -> 选择保存格式(original/avif/webp/jpg/png)
|
||||
- format -> 选择保存格式(original/avif/webp/jpg/png/tiff)
|
||||
- original_format <- LoadImageWithInfo.format
|
||||
- quality -> 设置保存质量(1-100)
|
||||
- dpi <- LoadImageWithInfo.dpi
|
||||
@@ -121,14 +120,13 @@ SaveImageWithInfo:
|
||||
|
||||
### SaveImageWithInfo 节点
|
||||
|
||||
- 并非所有图像格式都支持EXIF数据,目前只有JPG和TIFF格式支持保存EXIF数据
|
||||
- 并非所有图像格式都支持EXIF数据,目前只有JPG格式支持保存EXIF数据
|
||||
- 如果选择的保存格式不支持EXIF,EXIF数据将被忽略
|
||||
- 当保存多张图像时(批处理),除第一张图像外,其他图像的文件名会自动添加索引号
|
||||
- 如果指定的格式无法保存,将自动回退到PNG格式
|
||||
- quality参数对不同格式的影响:
|
||||
- JPG/JPEG:直接影响图像质量(1-100,值越高质量越好)
|
||||
- PNG:影响压缩级别(quality值会被映射到PNG的compress_level,quality值越高质量越好)
|
||||
- WEBP/AVIF:影响图像质量(1-100,值越高质量越好)
|
||||
- PNG/TIFF:不受影响
|
||||
- WEBP/AVIF:影响图像质量(1-100,值越高质量越好,AVIF 60 is same quality as WebP 90)
|
||||
- 其他格式:如果格式支持quality参数则使用,否则忽略
|
||||
|
||||
## 许可证
|
||||
|
||||
+100
-82
@@ -1,6 +1,8 @@
|
||||
import hashlib
|
||||
import os
|
||||
import json
|
||||
from PIL import Image, ImageOps, ImageSequence, ExifTags
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
import pillow_avif
|
||||
import numpy as np
|
||||
import torch
|
||||
@@ -11,8 +13,8 @@ class LoadImageWithInfo:
|
||||
@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))]
|
||||
files = folder_paths.filter_files_content_types(files, ["image"])
|
||||
img_exts = [".png", ".jpg", ".jpeg", ".webp", ".bmp", ".PNG", ".JPG", ".JPEG", ".WEBP", ".BMP", ".avif", ".AVIF", ".tif", ".tiff", ".TIF", ".TIFF"]
|
||||
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f)) and os.path.splitext(f)[1] in img_exts]
|
||||
return {"required":
|
||||
{"image": (sorted(files), {"image_upload": True})},
|
||||
}
|
||||
@@ -39,7 +41,7 @@ class LoadImageWithInfo:
|
||||
try:
|
||||
dpi = img.info.get('dpi', (96, 96))[0]
|
||||
except:
|
||||
dpi = 96
|
||||
dpi = 0
|
||||
|
||||
# 获取EXIF信息
|
||||
exif_data = {}
|
||||
@@ -130,24 +132,33 @@ class SaveImageWithInfo:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE",),
|
||||
"image": ("IMAGE",),
|
||||
"filename": ("STRING", {"default": "image"}),
|
||||
"format": (["original", "avif", "webp", "jpg", "png"], {"default": "original"}),
|
||||
"format": (["original", "avif", "webp", "jpg", "png", "tiff"], {"default": "original"}),
|
||||
"original_format": ("STRING", {"default": "png"}),
|
||||
"quality": ("INT", {"default": 100, "min": 1, "max": 100}),
|
||||
"quality": ("INT", {"default": 90, "min": 1, "max": 100, "step": 1, "display": "silder", 'tooltip': "Quality for JPEG/WebP/AVIF formats; Quality is relative to each format. \n* Example: AVIF 60 is same quality as WebP 90. \n* PNG compression is fixed at 4 and not affected by this. PNG compression times skyrocket above level 4 for zero benefits on filesize."}),
|
||||
"dpi": ("INT", {"default": 96}),
|
||||
"exif": ("STRING", {"default": "{}"}),
|
||||
'image_preview': ('BOOLEAN', {'default': True, 'tooltip': "Turns the image preview on and off"}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
CATEGORY = "image"
|
||||
RETURN_TYPES = ("IMAGE", "STRING")
|
||||
RETURN_NAMES = ("images", "filename")
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_image"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def save_image(self, images, filename, format, original_format, quality, dpi, exif, prompt=None, extra_pnginfo=None):
|
||||
CATEGORY = "image"
|
||||
DESCRIPTION = "Saves the input image with to your ComfyUI output directory."
|
||||
|
||||
type = 'output'
|
||||
quality = 90
|
||||
quality_avif = 60
|
||||
# optimize_image only works for jpeg, png and TIFF, with like just 2% reduction in size; not used for PNG as it forces a level 9 compression.
|
||||
optimize_image = True
|
||||
|
||||
def save_image(self, image, filename, format, original_format, quality, dpi, exif, prompt=None, extra_pnginfo=None):
|
||||
# 确定保存格式
|
||||
save_format = original_format if format == "original" else format
|
||||
|
||||
@@ -167,7 +178,6 @@ class SaveImageWithInfo:
|
||||
try:
|
||||
if exif and exif != "{}":
|
||||
if isinstance(exif, str):
|
||||
import json
|
||||
exif_data = json.loads(exif)
|
||||
else:
|
||||
exif_data = exif
|
||||
@@ -177,80 +187,88 @@ class SaveImageWithInfo:
|
||||
exif_data = {}
|
||||
|
||||
# 保存图像
|
||||
results = []
|
||||
for i, image in enumerate(images):
|
||||
img = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
|
||||
|
||||
# 设置DPI
|
||||
if dpi > 0:
|
||||
img.info['dpi'] = (dpi, dpi)
|
||||
|
||||
# 构建单个图像的文件名
|
||||
if i > 0:
|
||||
current_filename = f"{filename}_{i}.{save_format}"
|
||||
current_path = os.path.join(output_dir, current_filename)
|
||||
else:
|
||||
current_filename = full_filename
|
||||
current_path = full_path
|
||||
|
||||
# 根据格式保存图像
|
||||
if save_format.lower() == 'png':
|
||||
# PNG的compress_level范围是0-9,将quality(1-100)映射到compress_level(0-9)
|
||||
# 注意:对于PNG,较低的compress_level意味着较低的压缩率和较高的质量
|
||||
compress_level = max(0, min(9, 9 - int(quality / 11)))
|
||||
img.save(current_path, format='PNG', compress_level=compress_level, pnginfo=None)
|
||||
elif save_format.lower() in ['jpg', 'jpeg']:
|
||||
# 添加EXIF数据
|
||||
if exif_data:
|
||||
exif_bytes = img.getexif()
|
||||
for k, v in exif_data.items():
|
||||
try:
|
||||
# 尝试找到EXIF标签的数字ID
|
||||
tag_id = None
|
||||
for tag, tag_name in ExifTags.TAGS.items():
|
||||
if tag_name == k:
|
||||
tag_id = tag
|
||||
break
|
||||
|
||||
if tag_id:
|
||||
exif_bytes[tag_id] = v
|
||||
else:
|
||||
# 如果找不到标签ID,尝试直接使用键名
|
||||
exif_bytes[k] = v
|
||||
except:
|
||||
pass
|
||||
img.save(current_path, format='JPEG', quality=quality, exif=exif_bytes)
|
||||
img.save(current_path, format='JPEG', quality=quality)
|
||||
img = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
|
||||
|
||||
# 设置DPI
|
||||
if dpi > 0:
|
||||
img.info['dpi'] = (dpi, dpi)
|
||||
|
||||
kwargs = dict()
|
||||
|
||||
# 根据格式保存图像
|
||||
if save_format.lower() == 'png':
|
||||
kwargs['compress_level'] = 4
|
||||
kwargs["pnginfo"] = self.genMetadataPng(prompt, extra_pnginfo)
|
||||
else:
|
||||
kwargs["optimize"] = self.optimize_image
|
||||
if save_format.lower() == 'avif':
|
||||
if quality == 100:
|
||||
kwargs["lossless"] = True
|
||||
else:
|
||||
if quality == 0 or quality > self.quality_avif:
|
||||
quality = self.quality_avif
|
||||
kwargs['quality'] = quality
|
||||
elif save_format.lower() == 'webp':
|
||||
img.save(current_path, format='WEBP', quality=quality)
|
||||
elif save_format.lower() == 'avif':
|
||||
img.save(current_path, format='AVIF', quality=quality)
|
||||
else:
|
||||
# 尝试使用原始格式保存
|
||||
try:
|
||||
# 尝试使用quality参数,如果格式不支持则忽略
|
||||
try:
|
||||
img.save(current_path, format=save_format.upper(), quality=quality)
|
||||
except TypeError:
|
||||
img.save(current_path, format=save_format.upper())
|
||||
except:
|
||||
# 如果失败,默认保存为PNG
|
||||
current_filename = f"{filename}_{i if i > 0 else ''}.png"
|
||||
current_path = os.path.join(output_dir, current_filename)
|
||||
compress_level = max(0, min(9, 9 - int(quality / 11)))
|
||||
img.save(current_path, format='PNG', compress_level=compress_level)
|
||||
|
||||
results.append({
|
||||
'filename': current_filename,
|
||||
'path': current_path
|
||||
})
|
||||
if quality == 100:
|
||||
kwargs["lossless"] = True
|
||||
else:
|
||||
if quality == 0:
|
||||
quality = self.quality
|
||||
kwargs['quality'] = quality
|
||||
elif save_format.lower() != 'tiff':
|
||||
if quality == 0:
|
||||
quality = self.quality
|
||||
kwargs['quality'] = quality
|
||||
|
||||
if save_format.lower() in ['jpg', 'jpeg']:
|
||||
# 添加EXIF数据
|
||||
if exif_data:
|
||||
exif_bytes = img.getexif()
|
||||
for k, v in exif_data.items():
|
||||
try:
|
||||
# 尝试找到EXIF标签的数字ID
|
||||
tag_id = None
|
||||
for tag, tag_name in ExifTags.TAGS.items():
|
||||
if tag_name == k:
|
||||
tag_id = tag
|
||||
break
|
||||
|
||||
if tag_id:
|
||||
exif_bytes[tag_id] = v
|
||||
else:
|
||||
# 如果找不到标签ID,尝试直接使用键名
|
||||
exif_bytes[k] = v
|
||||
except:
|
||||
pass
|
||||
kwargs['exif'] = exif_bytes
|
||||
kwargs["subsampling"] = 0
|
||||
else:
|
||||
# 默认保存为PNG
|
||||
kwargs['compress_level'] = 4
|
||||
kwargs["pnginfo"] = self.genMetadataPng(prompt, extra_pnginfo)
|
||||
full_filename = os.path.splitext(full_filename)[0] + '.png'
|
||||
full_path = os.path.splitext(full_path)[0] + '.png'
|
||||
|
||||
# 添加到ComfyUI的保存图像列表中
|
||||
for result in results:
|
||||
folder_paths.add_to_output_list(result['path'])
|
||||
img.save(full_path, **kwargs)
|
||||
|
||||
return (images, full_filename)
|
||||
results = [{
|
||||
'filename': full_filename,
|
||||
'path': full_path,
|
||||
'type': self.type
|
||||
}]
|
||||
|
||||
return { "ui": { "images": results } }
|
||||
|
||||
def genMetadataPng(self, prompt, extra_pnginfo=None):
|
||||
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]))
|
||||
|
||||
return metadata
|
||||
|
||||
# 注册节点
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
|
||||
Reference in New Issue
Block a user