Add files via upload

This commit is contained in:
LaoMaoBoss
2025-09-02 16:40:34 +08:00
committed by GitHub
parent 33a9bc9aa6
commit 4ef7aad05f
+503
View File
@@ -0,0 +1,503 @@
"""
WBLESS 渐变工具节点
这个模块实现了强大的渐变生成功能,支持多种渐变类型和自定义参数。
"""
import time
import numpy as np
import torch
from PIL import Image, ImageDraw
import math
from typing import Tuple, List, Dict, Any
from cozy_comfyui.node import CozyBaseNode
# 颜色映射,参考 Text Block 节点的实现
COLOR_MAPPING = {
"white": (255, 255, 255),
"black": (0, 0, 0),
"red": (255, 0, 0),
"green": (0, 255, 0),
"blue": (0, 0, 255),
"yellow": (255, 255, 0),
"cyan": (0, 255, 255),
"magenta": (255, 0, 255),
"orange": (255, 165, 0),
"purple": (128, 0, 128),
"pink": (255, 192, 203),
"brown": (160, 85, 15),
"gray": (128, 128, 128),
"lightgray": (211, 211, 211),
"darkgray": (102, 102, 102),
"olive": (128, 128, 0),
"lime": (0, 128, 0),
"teal": (0, 128, 128),
"navy": (0, 0, 128),
"maroon": (128, 0, 0),
"fuchsia": (255, 0, 128),
"aqua": (0, 255, 128),
"silver": (192, 192, 192),
"gold": (255, 215, 0),
"turquoise": (64, 224, 208),
"lavender": (230, 230, 250),
"violet": (238, 130, 238),
"coral": (255, 127, 80),
"indigo": (75, 0, 130),
"custom": (255, 255, 255) # 自定义颜色的占位符
}
class GradientNode(CozyBaseNode):
"""
渐变工具节点 - 生成各种类型的渐变图像
支持的渐变类型:
- 线性渐变 (Linear)
- 径向渐变 (Radial)
- 角度渐变 (Angular)
- 菱形渐变 (Diamond)
- 椭圆渐变 (Elliptical)
"""
NAME = "Gradient"
# 渐变类型选项
GRADIENT_TYPES = [
"linear", # 线性渐变
"radial", # 径向渐变
"angular", # 角度渐变
"diamond", # 菱形渐变
"elliptical" # 椭圆渐变
]
@classmethod
def INPUT_TYPES(cls) -> Dict[str, Any]:
# 定义颜色选项,custom放在第一位
COLORS = ["custom"] + [color for color in COLOR_MAPPING.keys() if color != "custom"]
return {
"required": {
# 渐变类型
"gradient_type": (cls.GRADIENT_TYPES, {
"default": "linear"
}),
# 旋转角度(度)
"rotation_angle": ("FLOAT", {
"default": 0.0,
"min": -360.0,
"max": 360.0,
"step": 1.0,
"display": "number"
}),
# 渐变位置设置
"start_position": ("FLOAT", {
"default": 0.0,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
"end_position": ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
"center_position": ("FLOAT", {
"default": 0.5,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
# 起始颜色参数
"start_color": (COLORS, {"default": "custom"}),
"start_color_hex": ("STRING", {
"default": "#000000",
"multiline": False
}),
# 结束颜色参数
"end_color": (COLORS, {"default": "custom"}),
"end_color_hex": ("STRING", {
"default": "#FFFFFF",
"multiline": False
}),
# 透明度参数
"start_alpha": ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
"end_alpha": ("FLOAT", {
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.01,
"display": "number"
}),
# 输入图像,用于获取尺寸
"image": ("IMAGE", {}),
},
"optional": {
}
}
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "generate_gradient"
CATEGORY = f"🌈WBLESS"
@classmethod
def IS_CHANGED(cls, *args, **kwargs):
"""强制禁用缓存,确保每次都重新生成渐变"""
return time.time()
def hex_to_rgb(self, hex_color) -> Tuple[int, int, int]:
"""将十六进制颜色转换为RGB"""
# 处理列表格式的输入
if isinstance(hex_color, list):
hex_color = hex_color[0] if hex_color else "#000000"
# 确保是字符串类型
if not isinstance(hex_color, str):
hex_color = str(hex_color)
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6:
# 默认颜色
return (0, 0, 0)
try:
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return (0, 0, 0)
def get_color_values(self, color_name, color_hex) -> Tuple[int, int, int]:
"""获取颜色的RGB值,支持预设颜色和自定义十六进制颜色"""
# 处理可能的列表类型输入
if isinstance(color_name, list):
color_name = color_name[0] if color_name else "black"
if isinstance(color_hex, list):
color_hex = color_hex[0] if color_hex else "#000000"
if color_name == "custom":
return self.hex_to_rgb(color_hex)
else:
return COLOR_MAPPING.get(color_name, (0, 0, 0)) # 默认为黑色
def interpolate_color(self, color1: Tuple[int, int, int], alpha1: float,
color2: Tuple[int, int, int], alpha2: float,
t: float) -> Tuple[int, int, int, int]:
"""在两种颜色之间进行RGBA线性插值"""
r = int(color1[0] * (1 - t) + color2[0] * t)
g = int(color1[1] * (1 - t) + color2[1] * t)
b = int(color1[2] * (1 - t) + color2[2] * t)
a = int((alpha1 * (1 - t) + alpha2 * t) * 255)
return (r, g, b, a)
def get_gradient_value(self, x: int, y: int, width: int, height: int,
gradient_type: str, rotation_angle: float,
start_position: float, end_position: float, center_position: float) -> float:
"""计算指定位置的渐变值(0-1)"""
if gradient_type == "linear":
# 线性渐变 - 类似PS的渐变工具
angle_rad = math.radians(rotation_angle)
cos_a = math.cos(angle_rad)
sin_a = math.sin(angle_rad)
# 图像中心点
img_cx, img_cy = width / 2, height / 2
# 将坐标转换到图像中心为原点的系统
dx, dy = x - img_cx, y - img_cy
# 投影到渐变方向
projection = dx * cos_a + dy * sin_a
# 根据角度确定主要方向的尺寸
# 0度=水平,90度=垂直
angle_normalized = abs(rotation_angle % 180)
if angle_normalized <= 45 or angle_normalized >= 135:
# 主要是水平方向
main_dimension = width
else:
# 主要是垂直方向
main_dimension = height
# 计算起始和结束的实际位置
half_dim = main_dimension / 2
start_pos = (start_position - 0.5) * main_dimension
end_pos = (end_position - 0.5) * main_dimension
# 计算在起始和结束位置之间的比例
if abs(end_pos - start_pos) < 1e-6:
# 避免除零
raw_t = 0.5
else:
raw_t = (projection - start_pos) / (end_pos - start_pos)
# 应用中心点偏移(类似PS中的中间拉杆)
if center_position != 0.5:
if raw_t <= center_position:
# 在中心点左侧,压缩到0-0.5
if center_position > 0:
t = (raw_t / center_position) * 0.5
else:
t = 0.0
else:
# 在中心点右侧,压缩到0.5-1.0
if center_position < 1.0:
t = 0.5 + ((raw_t - center_position) / (1.0 - center_position)) * 0.5
else:
t = 1.0
else:
t = raw_t
elif gradient_type == "radial":
# 径向渐变 - 从图像中心向外
img_cx, img_cy = width / 2, height / 2
max_radius = math.sqrt(width**2 + height**2) / 2
distance = math.sqrt((x - img_cx)**2 + (y - img_cy)**2)
# 应用起始和结束位置
start_radius = start_position * max_radius
end_radius = end_position * max_radius
if abs(end_radius - start_radius) < 1e-6:
raw_t = 0.5
else:
raw_t = (distance - start_radius) / (end_radius - start_radius)
# 应用中心点偏移
if center_position != 0.5:
if raw_t <= center_position:
if center_position > 0:
t = (raw_t / center_position) * 0.5
else:
t = 0.0
else:
if center_position < 1.0:
t = 0.5 + ((raw_t - center_position) / (1.0 - center_position)) * 0.5
else:
t = 1.0
else:
t = raw_t
elif gradient_type == "angular":
# 角度渐变 - 围绕图像中心
img_cx, img_cy = width / 2, height / 2
angle = math.atan2(y - img_cy, x - img_cx)
raw_t = (angle + math.pi) / (2 * math.pi)
# 角度渐变的起始和结束位置表示角度范围
start_angle = start_position * 2 * math.pi
end_angle = end_position * 2 * math.pi
current_angle = raw_t * 2 * math.pi
if abs(end_angle - start_angle) < 1e-6:
raw_t = 0.5
else:
raw_t = (current_angle - start_angle) / (end_angle - start_angle)
# 应用中心点偏移
if center_position != 0.5:
if raw_t <= center_position:
if center_position > 0:
t = (raw_t / center_position) * 0.5
else:
t = 0.0
else:
if center_position < 1.0:
t = 0.5 + ((raw_t - center_position) / (1.0 - center_position)) * 0.5
else:
t = 1.0
else:
t = raw_t
elif gradient_type == "diamond":
# 菱形渐变 - 从图像中心
img_cx, img_cy = width / 2, height / 2
dx = abs(x - img_cx) / (width / 2)
dy = abs(y - img_cy) / (height / 2)
raw_t = max(dx, dy)
# 应用起始和结束位置
if abs(end_position - start_position) < 1e-6:
raw_t = 0.5
else:
raw_t = (raw_t - start_position) / (end_position - start_position)
# 应用中心点偏移
if center_position != 0.5:
if raw_t <= center_position:
if center_position > 0:
t = (raw_t / center_position) * 0.5
else:
t = 0.0
else:
if center_position < 1.0:
t = 0.5 + ((raw_t - center_position) / (1.0 - center_position)) * 0.5
else:
t = 1.0
else:
t = raw_t
elif gradient_type == "elliptical":
# 椭圆渐变 - 从图像中心
img_cx, img_cy = width / 2, height / 2
dx = (x - img_cx) / (width / 2)
dy = (y - img_cy) / (height / 2)
raw_t = math.sqrt(dx**2 + dy**2)
# 应用起始和结束位置
if abs(end_position - start_position) < 1e-6:
raw_t = 0.5
else:
raw_t = (raw_t - start_position) / (end_position - start_position)
# 应用中心点偏移
if center_position != 0.5:
if raw_t <= center_position:
if center_position > 0:
t = (raw_t / center_position) * 0.5
else:
t = 0.0
else:
if center_position < 1.0:
t = 0.5 + ((raw_t - center_position) / (1.0 - center_position)) * 0.5
else:
t = 1.0
else:
t = raw_t
else:
t = 0.0
# 限制在[0, 1]范围内
return max(0.0, min(1.0, t))
def generate_gradient(self, gradient_type: str, rotation_angle: float,
start_position: float, end_position: float, center_position: float,
start_color: str, start_color_hex: str, end_color: str, end_color_hex: str,
start_alpha: float, end_alpha: float, image) -> Tuple[torch.Tensor]:
"""生成渐变图像"""
# 从输入图像获取尺寸
if isinstance(image, list):
# 如果是列表,取第一个元素
img_tensor = image[0]
else:
img_tensor = image
# 处理不同的张量形状
if len(img_tensor.shape) == 4:
# 形状: (batch, height, width, channels)
img_tensor = img_tensor[0] # 取第一个批次
height, width = img_tensor.shape[:2]
elif len(img_tensor.shape) == 3:
# 形状: (height, width, channels)
height, width = img_tensor.shape[:2]
else:
raise ValueError(f"Unsupported image tensor shape: {img_tensor.shape}")
# 解析颜色
start_rgb = self.get_color_values(start_color, start_color_hex)
end_rgb = self.get_color_values(end_color, end_color_hex)
# 处理可能是列表格式的参数
if isinstance(start_alpha, list):
start_alpha = start_alpha[0] if start_alpha else 1.0
if isinstance(end_alpha, list):
end_alpha = end_alpha[0] if end_alpha else 1.0
if isinstance(gradient_type, list):
gradient_type = gradient_type[0] if gradient_type else "linear"
if isinstance(rotation_angle, list):
rotation_angle = rotation_angle[0] if rotation_angle else 0.0
if isinstance(start_position, list):
start_position = start_position[0] if start_position else 0.0
if isinstance(end_position, list):
end_position = end_position[0] if end_position else 1.0
if isinstance(center_position, list):
center_position = center_position[0] if center_position else 0.5
# 将输入图像转换为numpy数组
# 确保张量在CPU上并且形状正确
if img_tensor.device.type != 'cpu':
img_tensor = img_tensor.cpu()
# 确保张量是3维的 (height, width, channels)
if len(img_tensor.shape) == 4:
img_tensor = img_tensor[0] # 移除批次维度
input_array = (img_tensor.numpy() * 255).astype(np.uint8)
# 创建渐变遮罩数组 - 使用RGBA格式
gradient_array = np.zeros((height, width, 4), dtype=np.uint8)
# 生成渐变
for y in range(height):
for x in range(width):
# 获取渐变值
t = self.get_gradient_value(
x, y, width, height, gradient_type, rotation_angle,
start_position, end_position, center_position
)
# 计算颜色 - 双色渐变 (使用RGBA插值)
color = self.interpolate_color(start_rgb, start_alpha, end_rgb, end_alpha, t)
gradient_array[y, x] = color
# 将输入图像转换为RGBA格式(如果需要)
if input_array.shape[2] == 3:
# RGB转RGBA,添加完全不透明的alpha通道
alpha_channel = np.full((height, width, 1), 255, dtype=np.uint8)
input_array = np.concatenate([input_array, alpha_channel], axis=2)
# 执行alpha混合:将渐变叠加到输入图像上
# 使用标准的alpha混合公式:result = foreground * alpha + background * (1 - alpha)
gradient_alpha = gradient_array[:, :, 3:4] / 255.0 # 渐变的alpha通道
input_alpha = input_array[:, :, 3:4] / 255.0 # 输入图像的alpha通道
# 计算最终的颜色
blended_rgb = (gradient_array[:, :, :3] * gradient_alpha +
input_array[:, :, :3] * (1 - gradient_alpha))
# 计算最终的alpha通道
blended_alpha = gradient_alpha + input_alpha * (1 - gradient_alpha)
# 合并RGB和Alpha
final_array = np.concatenate([blended_rgb, blended_alpha * 255], axis=2).astype(np.uint8)
# 转换为torch张量
image_tensor = torch.from_numpy(final_array).float() / 255.0
image_tensor = image_tensor.unsqueeze(0) # 添加批次维度
# 生成遮罩 - 基于渐变的透明度
# 提取渐变的alpha通道作为遮罩
mask_array = gradient_array[:, :, 3] # 只取alpha通道
mask_tensor = torch.from_numpy(mask_array).float() / 255.0
mask_tensor = mask_tensor.unsqueeze(0) # 添加批次维度
return (image_tensor, mask_tensor)