Files

545 lines
20 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.
"""
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)
输出说明:
- image: 渐变与输入图像混合后的结果
- mask: 基于渐变透明度的遮罩,可用于后处理
反转选项:
- invert_alpha: 反转透明度渐变(交换起始和结束透明度)
- invert_colors: 反转渐变颜色(交换起始和结束颜色)
"""
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", {}),
# 反转选项
"invert_alpha": ("BOOLEAN", {
"default": False,
"label_on": "true",
"label_off": "false"
}),
"invert_colors": ("BOOLEAN", {
"default": False,
"label_on": "true",
"label_off": "false"
}),
},
"optional": {
}
}
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "generate_gradient"
CATEGORY = f"🌈WBLESS"
OUTPUT_NODE = False
# 输出说明:
# - image: 渐变与输入图像混合后的结果
# - mask: 基于渐变透明度的遮罩
# 反转选项:
# - invert_alpha: 反转透明度渐变(交换起始和结束透明度)
# - invert_colors: 反转渐变颜色(交换起始和结束颜色)
@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,
invert_alpha: bool, invert_colors: bool) -> Tuple[torch.Tensor, 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}")
# 处理可能是列表格式的参数
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
if isinstance(invert_alpha, list):
invert_alpha = invert_alpha[0] if invert_alpha else False
if isinstance(invert_colors, list):
invert_colors = invert_colors[0] if invert_colors else False
# 解析颜色
start_rgb = self.get_color_values(start_color, start_color_hex)
end_rgb = self.get_color_values(end_color, end_color_hex)
# 应用颜色反转 - 交换起始和结束颜色
if invert_colors:
start_rgb, end_rgb = end_rgb, start_rgb
# 应用透明度反转 - 交换起始和结束透明度
if invert_alpha:
start_alpha, end_alpha = end_alpha, start_alpha
# 将输入图像转换为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)