Files
rui40000andClaude Opus 4.8 5d3834503e fix: 八方向拆分不再切断角色;全仓库参数补齐中文 tooltip
【修复】角色被格线切断(脚、手杖、飘起的斗篷被削掉)
新增 expand_beyond_cell(默认开启):格子只用来判定「这是哪个方向」,
角色的实际范围由它自身的连通区域决定,按质心归属确保邻居不混入。
实测 8/8 方向的裁剪框边缘 alpha 从 1.00(内容顶到边界=被切断)
降到 0.00,S 方向高度 326→356、E 方向宽度 150→188 把缺的部分找了回来。
代价是需要两遍扫描(先求全序列并集框再提取),耗时 4.7s→14.9s。

【规则】每个参数都必须有中文 tooltip,作为以后的统一约定
全仓库 26 个节点 169 个参数,此前缺 115 个,现已 100% 覆盖。
tooltip 写「怎么调」而不只是「是什么」:给取值区间的实际影响、
推荐值与踩坑提示(如 OpenAI/ZenMux 的地址栏不能带 :// ,
素材拆分节点用于动画序列时顺序会漂移等)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 19:07:01 +08:00

199 lines
7.7 KiB
Python
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.
import torch
import numpy as np
import requests
import json
import base64
import io
from PIL import Image
import random
class QwenEditNode:
"""
使用阿里云千问编辑模型API进行图像生成的节点
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image1": ("IMAGE", {
"tooltip": "参考图 1(主参考)。控制模式决定它以什么方式影响生成,\n"
"例如 reference 取整体风格、pose 取人物姿态。"
}),
"image2": ("IMAGE", {
"tooltip": "参考图 2。多张参考会一并提交,用于综合约束生成结果。"
}),
"image3": ("IMAGE", {
"tooltip": "参考图 3。"
}),
"image4": ("IMAGE", {
"tooltip": "参考图 4。"
}),
"api_key": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "阿里云百炼(DashScope)的 API Key。\n"
"⚠ 工作流会连同此值一起保存,分享 json 前记得清空。"
}),
"base_url": ("STRING", {
"default": "https://dashscope.aliyuncs.com/api/v1/services/aigc/text2image-generation/generation",
"multiline": False,
"tooltip": "接口地址,一般不用改。\n"
"走代理或私有网关时替换成对应地址。"
}),
"seed": ("INT", {
"default": -1,
"min": -1,
"max": 2147483647,
"tooltip": "随机种子。-1 表示每次随机;\n"
"填固定值可复现同一结果,便于对比参数改动的影响。"
}),
"control_mode": (["reference", "sketch", "scribble", "pose", "canny", "depth", "hed", "mlsd", "normal", "seg"], {
"default": "reference",
"tooltip": "参考图的约束方式:\n"
"reference 整体风格参考(最常用)\n"
"pose 取人物姿态 / depth 取空间深度 / seg 取区域分割\n"
"canny·hed·mlsd·scribble·sketch 取不同粗细的线稿\n"
"normal 取法线朝向。约束越强,构图越贴近参考图。"
}),
"width": ("INT", {
"default": 1024,
"min": 512,
"max": 2048,
"step": 8,
"tooltip": "输出宽度。与高度共同决定画幅比例,\n"
"比例与参考图差异过大时构图容易变形。"
}),
"height": ("INT", {
"default": 1024,
"min": 512,
"max": 2048,
"step": 8,
"tooltip": "输出高度。"
}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "Rui-Node🐶/AI模型🤖"
def generate_image(self, image1, image2, image3, image4, api_key, base_url, seed, control_mode, width, height):
"""
使用千问编辑模型API生成图像
参数:
image1-4: 输入图像张量 (B, H, W, C) 格式
api_key: 阿里云API密钥
base_url: API基础URL
seed: 随机种子值,-1表示随机生成
control_mode: 控制模式
width: 输出图像宽度
height: 输出图像高度
返回:
生成的图像张量
"""
# 如果seed为-1,则随机生成种子
if seed == -1:
seed = random.randint(0, 2147483647)
# 准备图像数据
images = [image1, image2, image3, image4]
image_data = []
for i, img in enumerate(images):
if img is None or img.shape[0] == 0:
continue
# 取批次中的第一张图像
img_np = img[0].cpu().numpy()
# 确保值范围在 0-1 之间
img_np = np.clip(img_np, 0, 1)
# 转换为 PIL 图像 (值范围 0-255)
img_pil = Image.fromarray((img_np * 255).astype(np.uint8), 'RGB')
# 转换为base64编码
buffered = io.BytesIO()
img_pil.save(buffered, format="PNG")
img_base64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
# 添加到图像数据列表
image_data.append({
"image": img_base64,
"control_type": control_mode
})
# 准备API请求
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": "qwen-vl-plus",
"input": {
"images": image_data
},
"parameters": {
"seed": seed,
"width": width,
"height": height
}
}
try:
# 发送API请求
response = requests.post(base_url, headers=headers, data=json.dumps(payload))
response.raise_for_status()
# 解析响应
result = response.json()
# 检查是否成功
if "output" in result and "images" in result["output"] and len(result["output"]["images"]) > 0:
# 获取生成的图像
generated_image_base64 = result["output"]["images"][0]
# 解码base64图像
image_bytes = base64.b64decode(generated_image_base64)
img_pil = Image.open(io.BytesIO(image_bytes))
# 确保图像为 RGB 模式
if img_pil.mode != 'RGB':
img_pil = img_pil.convert('RGB')
# 转换为 NumPy 数组
img_np = np.array(img_pil).astype(np.float32) / 255.0
# 转换为 PyTorch 张量并添加批次维度
img_tensor = torch.from_numpy(img_np).unsqueeze(0)
return (img_tensor,)
else:
# 如果响应中没有图像,返回错误信息
print(f"API响应中没有图像: {result}")
# 创建一个默认的黑色图像作为返回值
default_img = np.zeros((height, width, 3), dtype=np.float32)
default_tensor = torch.from_numpy(default_img).unsqueeze(0)
return (default_tensor,)
except Exception as e:
# 如果API请求失败,打印错误信息并返回默认图像
print(f"API请求失败: {str(e)}")
# 创建一个默认的黑色图像作为返回值
default_img = np.zeros((height, width, 3), dtype=np.float32)
default_tensor = torch.from_numpy(default_img).unsqueeze(0)
return (default_tensor,)
# 节点映射字典,用于 ComfyUI 注册节点
NODE_CLASS_MAPPINGS = {
"QwenEditImageGeneration": QwenEditNode
}
# 节点显示名称映射,用于在 UI 中显示友好名称
NODE_DISPLAY_NAME_MAPPINGS = {
"QwenEditImageGeneration": "千问编辑图像生成 / Qwen Edit Image Generation"
}