diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50ec79d --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +ENV/ +env/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# ComfyUI specific +*.ckpt +*.safetensors +*.vae +*.pt +*.pth +*.bin +*.onnx +*.engine + +# OS specific +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b53a8a3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ComfyUI-UniversalToolkit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index ebced26..eeb05b2 100644 --- a/README.md +++ b/README.md @@ -1 +1,70 @@ -# ComfyUI-ImageToolkit \ No newline at end of file +# ComfyUI-UniversalToolkit + +## 简介 +本插件为 ComfyUI 提供多尺寸空白单元(image/mask/latent)生成节点,结构完全遵循 ComfyUI 插件标准。 + +## 目录结构 +``` +ComfyUI-UniversalToolkit/ +├── main.py +├── image_utils.py +├── requirements.txt +├── README.md +└── nodes/ + ├── __init__.py + └── image_nodes.py +``` + +## 安装方法 +1. 将本目录放入 ComfyUI 的 custom_nodes 目录下。 +2. 安装依赖: + ```bash + pip install -r requirements.txt + ``` +3. 重启 ComfyUI。 + +## 节点说明 +### 空单元生成器(EmptyUnitGenerator) +- 输入参数: + - 图像大小:small/medium/large + - 构图比例:1:1/3:4/16:9 + - 颜色:white/black/gray/red/green/blue +- 输出: + - image:空白图像 + - mask:空白蒙版 + - latent:空白潜空间 + +## 依赖 +- Pillow +- numpy + +## 功能特点 +- 模块化设计,各功能独立封装 +- 支持多种基础数据类型处理 +- 提供直观的节点界面 +- 完善的错误处理和参数验证 + +## 已实现功能 +- 多尺寸、多比例空白图像/mask/latent 一键生成 + - 支持 small(sd1.5)、medium(flux/sdxl)、large(high resolution)三种尺寸 + - 支持 1:1、3:4、16:9 等常见比例 + - 支持自定义颜色 + +## 使用方法 +1. 启动 ComfyUI +2. 在节点菜单中找到 "UniversalToolkit" 分类 +3. 选择需要的节点并配置参数 +4. 连接节点并运行工作流 + +## 开发计划 +- [ ] 添加更多图像处理节点 +- [ ] 添加数据类型转换节点 +- [ ] 添加逻辑处理节点 +- [ ] 优化节点界面 +- [ ] 添加更多示例工作流 + +## 贡献指南 +欢迎提交 Issue 和 Pull Request! + +## 许可证 +MIT License \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..59a9117 --- /dev/null +++ b/__init__.py @@ -0,0 +1,9 @@ +from .universal_toolkit_node import EmptyUnitGenerator + +NODE_CLASS_MAPPINGS = { + "EmptyUnitGenerator": EmptyUnitGenerator, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "EmptyUnitGenerator": "空单元生成器", +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..62bb017 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Pillow +numpy \ No newline at end of file diff --git a/universal_toolkit_node.py b/universal_toolkit_node.py new file mode 100644 index 0000000..b33cc86 --- /dev/null +++ b/universal_toolkit_node.py @@ -0,0 +1,88 @@ +from PIL import Image +import numpy as np +import torch +import re + +class EmptyUnitGenerator: + CATEGORY = "UniversalToolkit" + @classmethod + def INPUT_TYPES(cls): + # 标准比例选项 + STANDARD_RATIOS = [ + ("1:1", [(1024, 1024), (2048, 2048)]), + ("3:2", [(1200, 800), (800, 1200)]), + ("4:3", [(1600, 1200), (1200, 1600)]), + ("8:5", [(1280, 800), (800, 1280)]), + ("16:9", [(1920, 1080), (1080, 1920)]), + ("21:9", [(2520, 1080), (1080, 2520)]), + ] + standard_options = [] + for ratio, sizes in STANDARD_RATIOS: + for w, h in sizes: + orientation = "横向" if w >= h else "纵向" + standard_options.append(f"{ratio} {orientation} ({w}x{h})") + + # 社交媒体分辨率 + social_options = [ + "Instagram Portrait - 1080x1350", + "Instagram Square - 1080x1080", + "Instagram Landscape - 1080x608", + "Instagram Stories/Reels - 1080x1920", + "Facebook Landscape - 1080x1350", + "Facebook Marketplace - 1200x1200", + "Facebook Stories - 1080x1920", + "TikTok - 1080x1920", + "YouTube Banner - 2560x1440", + "LinkedIn Profile Banner - 1584x396", + "LinkedIn Page Cover - 1128x191", + "LinkedIn Post - 1200x627", + "Pinterest Pin Image - 1000x1500", + "CivitAI Cover - 1600x400", + "OpenArt App - 1500x1000", + ] + + return { + "required": { + "ratio_type": (["standard", "social media"], {"default": "standard", "label": "比例类型"}), + "ratio": (standard_options, {"default": standard_options[0], "label": "尺寸/比例", "dynamic": True, "depends_on": ["ratio_type"]}), + "image_color": (["white", "black", "gray", "red", "green", "blue"], {"default": "white", "label": "Image Color"}), + "batch": ("INT", {"default": 1, "min": 1, "max": 16, "step": 1, "label": "输出组数(batch)"}), + }, + "optional": {}, + "dynamic": { + "ratio": lambda params: standard_options if params.get("ratio_type", "standard") == "standard" else social_options + } + } + + RETURN_TYPES = ("IMAGE", "MASK", "LATENT") + RETURN_NAMES = ("image", "mask", "latent") + FUNCTION = "generate" + + def generate(self, ratio_type, ratio, image_color, batch): + # 解析分辨率 + if ratio_type == "standard": + m = re.search(r"\((\d+)x(\d+)\)", ratio) + width, height = int(m.group(1)), int(m.group(2)) + else: + m = re.search(r"(\d+)x(\d+)", ratio) + width, height = int(m.group(1)), int(m.group(2)) + COLOR_OPTIONS = { + "white": (255, 255, 255), + "black": (0, 0, 0), + "gray": (128, 128, 128), + "red": (255, 0, 0), + "green": (0, 255, 0), + "blue": (0, 0, 255), + } + color_rgb = COLOR_OPTIONS[image_color] + images = [] + masks = [] + latents = [] + for _ in range(batch): + image = torch.from_numpy(np.array(Image.new("RGB", (width, height), color_rgb))).float() / 255.0 + mask = torch.from_numpy(np.array(Image.new("L", (width, height), 0))).unsqueeze(-1).float() / 255.0 + latent = torch.from_numpy(np.zeros((height, width, 4), dtype=np.float32)) + images.append(image) + masks.append(mask) + latents.append(latent) + return tuple(images), tuple(masks), tuple(latents) \ No newline at end of file diff --git a/web/universal_toolkit.js b/web/universal_toolkit.js new file mode 100644 index 0000000..0f5179e --- /dev/null +++ b/web/universal_toolkit.js @@ -0,0 +1,19 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "ComfyUI.UniversalToolkit", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "MultiBlankGenerator") { + // 自定义节点UI + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; + + // 添加节点说明 + this.help = "生成指定尺寸和比例的空白图像、mask和latent"; + + return result; + }; + } + } +}); \ No newline at end of file