From 44bee9415f3fdb85ecdb072c5b364fa8abc069b5 Mon Sep 17 00:00:00 2001 From: jammyfu Date: Sun, 26 Jan 2025 21:41:08 +0800 Subject: [PATCH] Add Output To Text Converter utility node for flexible data conversion --- __init__.py | 3 + .../images/image_resolution_creator_plus.py | 126 ++++++++++++++---- modules/utils/output_converter.py | 117 ++++++++++++++++ web/js/image_resolution_creator_plus.js | 5 +- 4 files changed, 223 insertions(+), 28 deletions(-) create mode 100644 modules/utils/output_converter.py diff --git a/__init__.py b/__init__.py index 3914da8..e43d939 100644 --- a/__init__.py +++ b/__init__.py @@ -29,6 +29,7 @@ from .modules.switch.switch_node import MaskSwitch from .modules.switch.switch_node import LatentSwitch from .modules.web.web_image_loader import WebImageLoader from .modules.images.image_base64_converter import ImageToBase64 +from .modules.utils.output_converter import OutputToTextConverter @@ -54,6 +55,7 @@ NODE_CLASS_MAPPINGS = { "PaintingCoder::MaskSwitch": MaskSwitch, "PaintingCoder::LatentSwitch": LatentSwitch, "PaintingCoder::WebImageLoader": WebImageLoader, + "PaintingCoder::OutputToTextConverter": OutputToTextConverter, } # 节点显示名称映射 @@ -77,6 +79,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "PaintingCoder::MaskSwitch": "Mask Switch 🔄", "PaintingCoder::LatentSwitch": "Latent Switch 🔄", "PaintingCoder::WebImageLoader": "Web Image Loader 🌐(URL Or Base64)", + "PaintingCoder::OutputToTextConverter": "Output To Text Converter 📝", } # Web 目录配置 diff --git a/modules/images/image_resolution_creator_plus.py b/modules/images/image_resolution_creator_plus.py index d720289..f6b4fd2 100644 --- a/modules/images/image_resolution_creator_plus.py +++ b/modules/images/image_resolution_creator_plus.py @@ -11,19 +11,62 @@ class ImageSizeCreatorPlus(ImageSizeCreator): @classmethod def INPUT_TYPES(s): + """动态获取输入类型""" return { "required": { "mode": (["Landscape", "Portrait", "Square"],), "style": (["SDXL", "Midjourney"],), - "resolution": (s.get_resolution_options(),), + "resolution": (s.get_resolution_options(),), # 使用动态方法获取选项 "scale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}), }, } + RETURN_TYPES = ("STRING", "INT", "INT") + RETURN_NAMES = ("resolution", "width", "height") + FUNCTION = "create_size" + CATEGORY = "🎨Painting👓Coder/🖼️Image" + + @classmethod + def get_sdxl_resolution_options(cls): + """获取SDXL的分辨率选项""" + sdxl_resolutions = [ + (1024, 1024), # 1:1 + (1152, 896), # 9:7 + (896, 1152), # 7:9 + (1216, 832), # 3:2 + (832, 1216), # 2:3 + (1344, 768), # 7:4 + (768, 1344), # 4:7 + (1536, 640), # 12:5 + (640, 1536), # 5:12 + ] + return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in sdxl_resolutions] + + @classmethod + def get_midjourney_resolution_options(cls): + """获取Midjourney的分辨率选项""" + mj_resolutions = [ + (768, 1536), # 1:2 + (816, 1456), # 9:16 + (896, 1344), # 2:3 + (928, 1232), # 3:4 + (992, 1200), # 5:6 + (1024, 1024), # 1:1 + (1200, 992), # 6:5 + (1232, 928), # 4:3 + (1344, 896), # 3:2 + (1456, 816), # 16:9 + (1536, 768), # 2:1 + ] + return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in mj_resolutions] + @classmethod def get_resolution_options(cls): - """根据选择的风格返回对应的分辨率选项""" - return cls.get_sdxl_resolutions() # 默认返回SDXL的分辨率 + """获取所有可用的分辨率选项""" + # 合并SDXL和Midjourney的所有选项 + sdxl_options = cls.get_sdxl_resolution_options() + mj_options = cls.get_midjourney_resolution_options() + return list(set(sdxl_options + mj_options)) # 使用set去重 @staticmethod def get_sdxl_resolutions(): @@ -57,7 +100,7 @@ class ImageSizeCreatorPlus(ImageSizeCreator): (1456, 816), # 16:9 (1536, 768), # 2:1 ] - return [(w, h) for w, h in mj_resolutions] + return [f"{cls.get_aspect_ratio_string(w, h)} ({w}x{h})" for w, h in mj_resolutions] @staticmethod def get_aspect_ratio_string(width, height): @@ -94,44 +137,75 @@ class ImageSizeCreatorPlus(ImageSizeCreator): all_ratios = {**sdxl_ratios, **mj_ratios} return all_ratios.get((width, height), "1:1") + def get_valid_resolutions(self, style): + """根据风格获取有效的分辨率选项""" + if style == "Midjourney": + return self.get_midjourney_resolution_options() + return self.get_sdxl_resolution_options() + def create_size(self, mode, style, resolution, scale_factor=1.0): """创建图像尺寸,支持不同风格""" try: print(f"[ImageSizeCreatorPlus] Creating size with style: {style}") - return super().create_size(mode, resolution, scale_factor) + + # 获取当前风格的有效分辨率选项 + valid_resolutions = self.get_valid_resolutions(style) + + # 检查分辨率是否在有效选项中 + if resolution not in valid_resolutions: + # 如果不在有效选项中,使用默认分辨率 + resolution = valid_resolutions[0] + print(f"[ImageSizeCreatorPlus] Invalid resolution for {style}, using default: {resolution}") + + # 从分辨率字符串中提取宽度和高度 + import re + match = re.search(r'\((\d+)x(\d+)\)', resolution) + if not match: + raise ValueError(f"Invalid resolution format: {resolution}") + + width = int(match.group(1)) + height = int(match.group(2)) + + # 应用缩放因子 + width = int(width * scale_factor) + height = int(height * scale_factor) + + # 确保宽高为8的倍数 + width = (width // 8) * 8 + height = (height // 8) * 8 + + return (str(resolution), width, height) + except Exception as e: print(f"Error in ImageSizeCreatorPlus: {str(e)}") return ("1:1 (1024x1024)", 1024, 1024) + def validate_inputs(self, **kwargs): + """验证输入参数""" + style = kwargs.get("style", "SDXL") + resolution = kwargs.get("resolution", "") + valid_resolutions = self.get_valid_resolutions(style) + + if resolution not in valid_resolutions: + return {"resolution": valid_resolutions[0]} + return {} + class ImageLatentCreatorPlus(ImageLatentCreator, ImageSizeCreatorPlus): """增强版图像潜空间创建器""" @classmethod def INPUT_TYPES(s): - base_inputs = super().INPUT_TYPES() + base_inputs = ImageLatentCreator.INPUT_TYPES() base_inputs["required"]["style"] = (["SDXL", "Midjourney"],) + base_inputs["required"]["resolution"] = (s.get_resolution_options(),) # 使用动态方法 return base_inputs - def create_latent(self, mode, style, resolution, batch_size=1, scale_factor=1.0, extra_pnginfo=None): - """创建潜空间,支持不同风格""" - try: - print(f"[ImageLatentCreatorPlus] Creating latent with style: {style}") - resolution_list, width, height = self.create_size(mode, style, resolution, scale_factor) - - batch_size = max(1, int(batch_size)) - samples = torch.zeros([batch_size, 4, height // 8, width // 8]) - - latent = { - "samples": samples, - "batch_size": batch_size, - "batch_index": list(range(batch_size)) - } - - return (latent, resolution_list, width, height, batch_size) - - except Exception as e: - print(f"Error in ImageLatentCreatorPlus: {str(e)}") - raise e + RETURN_TYPES = ("LATENT", "STRING", "INT", "INT", "INT") + RETURN_NAMES = ("latent", "resolution", "width", "height", "batch_size") + + def validate_inputs(self, **kwargs): + """验证输入参数""" + return super().validate_inputs(**kwargs) # 注册节点 NODE_CLASS_MAPPINGS = { diff --git a/modules/utils/output_converter.py b/modules/utils/output_converter.py new file mode 100644 index 0000000..1202901 --- /dev/null +++ b/modules/utils/output_converter.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Filename: output_converter.py +# Developer: jammyfu +# Category: 🎨Painting👓Coder/🛠️Utils + +import json +import torch +import numpy as np +from PIL import Image + +class AnyType(str): + """自定义任意类型""" + def __ne__(self, __value: object) -> bool: + return False + +any_type = AnyType("*") + +class OutputToTextConverter: + """通用输出转文本转换器""" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "any_input": (any_type,), # 使用自定义的any_type + "format": (["Auto", "JSON", "Plain Text", "Raw"],), + "indent": ("INT", {"default": 2, "min": 0, "max": 8, "step": 1}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("text",) + FUNCTION = "convert" + CATEGORY = "🎨Painting👓Coder/🛠️Utils" + + def convert(self, any_input, format="Auto", indent=2): + """将任何输入转换为文本""" + try: + # 如果输入为None + if any_input is None: + return ("None",) + + # 如果输入已经是字符串 + if isinstance(any_input, str): + return (any_input,) + + # 根据不同类型进行转换 + if format == "Auto": + text = self._auto_convert(any_input, indent) + elif format == "JSON": + text = self._to_json(any_input, indent) + elif format == "Plain Text": + text = str(any_input) + else: # Raw + text = repr(any_input) + + print(f"[OutputConverter] Converted {type(any_input).__name__} to text") + return (text,) + + except Exception as e: + print(f"[OutputConverter] Error converting output: {str(e)}") + return (f"Error: {str(e)}",) + + def _auto_convert(self, obj, indent): + """自动选择最合适的转换方式""" + # 处理常见的数据类型 + if isinstance(obj, (dict, list, tuple, set)): + return self._to_json(obj, indent) + elif isinstance(obj, (int, float, bool)): + return str(obj) + elif isinstance(obj, (torch.Tensor, np.ndarray)): + return self._tensor_to_text(obj) + elif isinstance(obj, Image.Image): + return self._image_to_text(obj) + else: + # 尝试JSON序列化,如果失败则使用str + try: + return self._to_json(obj, indent) + except: + return str(obj) + + def _to_json(self, obj, indent): + """转换为JSON格式""" + def default_converter(o): + if isinstance(o, (torch.Tensor, np.ndarray)): + return self._tensor_to_text(o) + elif isinstance(o, Image.Image): + return self._image_to_text(o) + elif hasattr(o, '__dict__'): + return o.__dict__ + return str(o) + + return json.dumps(obj, indent=indent, default=default_converter) + + def _tensor_to_text(self, tensor): + """转换张量为文本描述""" + if isinstance(tensor, np.ndarray): + shape = tensor.shape + dtype = str(tensor.dtype) + else: # torch.Tensor + shape = tuple(tensor.size()) + dtype = str(tensor.dtype) + + return f"Tensor(shape={shape}, dtype={dtype})" + + def _image_to_text(self, image): + """转换图像为文本描述""" + return f"Image(size={image.size}, mode={image.mode})" + +# 注册节点 +NODE_CLASS_MAPPINGS = { + "PaintingCoder::OutputToTextConverter": OutputToTextConverter, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "PaintingCoder::OutputToTextConverter": "Output To Text 📝", +} \ No newline at end of file diff --git a/web/js/image_resolution_creator_plus.js b/web/js/image_resolution_creator_plus.js index 288d0a4..eb9cffa 100644 --- a/web/js/image_resolution_creator_plus.js +++ b/web/js/image_resolution_creator_plus.js @@ -41,10 +41,11 @@ const midjourneyResolutionOptions = { ] }; -// 更新分辨率选项的函数 +// 更新分辨率选项 function updatePlusResolutionOptions(node, mode, style, triggerUpdate = true) { console.log("[ImageSizeCreatorPlus] Updating resolution options for mode:", mode, "style:", style); + // 找到分辨率widget const resolutionWidget = node.widgets?.find(w => w.name === "resolution"); if (!resolutionWidget) { console.warn("[ImageSizeCreatorPlus] Resolution widget not found"); @@ -80,7 +81,7 @@ function updatePlusResolutionOptions(node, mode, style, triggerUpdate = true) { } } -// 设置Plus节点的函数 +// 设置Plus节点 function setupPlusSizeNode(nodeType, nodeData, app) { // 创建节点时的处理 const onNodeCreated = nodeType.prototype.onNodeCreated;