848 lines
37 KiB
Python
848 lines
37 KiB
Python
import numpy as np
|
||
from PIL import Image, ImageDraw, ImageFont, ImageFilter, ImageEnhance, ImageChops, ImageOps, ImageMath
|
||
import random
|
||
import colorsys
|
||
import math
|
||
|
||
class EffectsProcessor:
|
||
"""Processes and applies various text effects based on styles."""
|
||
|
||
def __init__(self, debug_output=False):
|
||
"""Initialize the effects processor.
|
||
|
||
Args:
|
||
debug_output: Whether to save debug images during processing
|
||
"""
|
||
self.debug_output = debug_output
|
||
|
||
def _save_debug_image(self, img, filename):
|
||
"""Save a debug image if debug_output is enabled."""
|
||
if self.debug_output:
|
||
img.save(filename)
|
||
|
||
def hex_to_rgba(self, hex_color, alpha=255):
|
||
"""Convert hex color to RGBA tuple."""
|
||
if not hex_color:
|
||
return (255, 255, 255, alpha)
|
||
|
||
hex_color = hex_color.lstrip('#')
|
||
length = len(hex_color)
|
||
|
||
if length == 6:
|
||
return (*tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)), alpha)
|
||
elif length == 8:
|
||
r, g, b, a = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4, 6))
|
||
return (r, g, b, alpha if alpha != 255 else a)
|
||
return (255, 255, 255, alpha)
|
||
|
||
def apply_all_effects(self, base_img, style, style_name="unknown"):
|
||
"""应用所有效果在同一图像上,确保正确的叠加顺序和透明度"""
|
||
print(f"[Style: {style_name}] 开始应用组合效果")
|
||
|
||
# 确保图像有alpha通道
|
||
img = base_img.convert('RGBA') if base_img.mode != 'RGBA' else base_img.copy()
|
||
|
||
# 获取文本区域mask,用于限制效果
|
||
original_r, original_g, original_b, text_mask = img.split()
|
||
|
||
# 创建一个底层图像,用于收集所有效果
|
||
result = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
|
||
# 保存各效果层的临时图像,用于调试
|
||
layers = {}
|
||
|
||
# 1. 首先应用阴影 (如果指定)
|
||
if 'shadow' in style:
|
||
shadow_img = self.apply_shadow(img, style)
|
||
# 保存用于调试
|
||
self._save_debug_image(shadow_img, f"debug_{style_name}_shadow.png")
|
||
layers['shadow'] = shadow_img
|
||
result = Image.alpha_composite(result, shadow_img)
|
||
print(f"[Style: {style_name}] 已应用阴影效果")
|
||
|
||
# 2. 应用外发光效果 (如果指定)
|
||
if 'glow' in style:
|
||
glow_img = self.apply_glow(img, style)
|
||
# 保存用于调试
|
||
self._save_debug_image(glow_img, f"debug_{style_name}_glow.png")
|
||
layers['glow'] = glow_img
|
||
result = Image.alpha_composite(result, glow_img)
|
||
print(f"[Style: {style_name}] 已应用发光效果")
|
||
|
||
# 3. 创建文本填充层 - 使用渐变或单色填充
|
||
text_fill = None
|
||
|
||
# 检查fill类型,确保渐变正确处理
|
||
fill_type = None
|
||
if 'fill' in style:
|
||
if isinstance(style['fill'], dict):
|
||
fill_type = style['fill'].get('type')
|
||
elif style['fill'] == 'gradient':
|
||
fill_type = 'gradient'
|
||
|
||
if 'gradient' in style or fill_type == 'gradient':
|
||
# 获取填充样式
|
||
fill = style.get('fill', {})
|
||
# 如果fill不是字典,创建一个默认字典
|
||
if not isinstance(fill, dict):
|
||
fill = {'type': 'gradient', 'colors': ['#FFFFFF', '#0066FF'], 'angle': 90}
|
||
|
||
# 确保colors存在
|
||
colors = fill.get('colors', ['#FFFFFF', '#0066FF']) # 默认为白色到蓝色渐变
|
||
if isinstance(colors, str):
|
||
colors = [colors, '#0066FF'] # 如果只有一个颜色,创建从该颜色到蓝色的渐变
|
||
|
||
# 处理渐变角度 - 支持SVG渐变方向
|
||
angle = 90 # 默认从上到下(SVG: y1=0% -> y2=100%)
|
||
|
||
# 检查是否有明确指定的SVG样式方向
|
||
svg_direction = fill.get('svg_direction', None)
|
||
if svg_direction:
|
||
# 解析SVG方向参数 x1,y1 -> x2,y2
|
||
try:
|
||
x1 = float(svg_direction.get('x1', 50)) / 100 # 转换为0-1范围
|
||
y1 = float(svg_direction.get('y1', 0)) / 100
|
||
x2 = float(svg_direction.get('x2', 50)) / 100
|
||
y2 = float(svg_direction.get('y2', 100)) / 100
|
||
|
||
# 计算角度 - arctan2 返回弧度,convert to degrees
|
||
if x2 != x1 or y2 != y1: # 避免除以零
|
||
angle_rad = math.atan2(y2 - y1, x2 - x1)
|
||
angle = math.degrees(angle_rad)
|
||
# 调整角度,使0度指向右侧(标准坐标系)
|
||
angle = (angle + 90) % 360 # 旋转坐标系使y轴向下
|
||
|
||
print(f"[Style: {style_name}] 从SVG方向解析的渐变角度: {angle}°")
|
||
except (ValueError, TypeError) as e:
|
||
print(f"无法解析SVG方向: {e}, 使用默认角度")
|
||
else:
|
||
# 使用直接指定的角度
|
||
angle = fill.get('angle', 90)
|
||
|
||
# 检查是否是径向渐变
|
||
if fill.get('type') == 'radial':
|
||
angle = 'radial'
|
||
|
||
print(f"[Style: {style_name}] 渐变填充颜色: {colors}, 角度/类型: {angle}")
|
||
|
||
# 创建渐变图像 - 使用整个图像尺寸
|
||
gradient_img = self._create_gradient(
|
||
img.size[0],
|
||
img.size[1],
|
||
[self.hex_to_rgba(color)[:3] for color in colors],
|
||
angle
|
||
)
|
||
|
||
# 创建一个只包含文本形状的二值蒙版(白色=文本区域,黑色=背景)
|
||
binary_mask = Image.new('1', img.size, 0) # 初始化全黑二值图像
|
||
binary_mask.paste(1, (0, 0), text_mask) # 在文本区域粘贴白色
|
||
|
||
# 使用numpy进行精确的像素级操作
|
||
gradient_array = np.array(gradient_img)
|
||
mask_array = np.array(binary_mask)
|
||
|
||
# 扩展mask数组以匹配渐变数组的形状
|
||
mask_array_3d = np.stack([mask_array] * 3, axis=2)
|
||
|
||
# 将渐变应用到文本区域(其他区域保持黑色)
|
||
gradient_masked = gradient_array * mask_array_3d
|
||
|
||
# 转换回PIL图像并添加透明通道
|
||
gradient_masked_img = Image.fromarray(gradient_masked.astype(np.uint8))
|
||
gradient_masked_rgba = gradient_masked_img.convert('RGBA')
|
||
|
||
# 将透明度通道设置为文本的透明度
|
||
r, g, b, _ = gradient_masked_rgba.split()
|
||
text_fill = Image.merge('RGBA', (r, g, b, text_mask))
|
||
|
||
# 保存用于调试
|
||
self._save_debug_image(text_fill, f"debug_{style_name}_gradient_fill.png")
|
||
layers['fill'] = text_fill
|
||
|
||
print(f"[Style: {style_name}] 已应用文本渐变效果")
|
||
else:
|
||
# 如果没有指定渐变,则使用单色填充
|
||
fill_color = (64, 150, 255) # 浅蓝色作为默认值
|
||
if 'fill' in style:
|
||
if isinstance(style['fill'], dict) and 'color' in style['fill']:
|
||
fill_color_hex = style['fill'].get('color', '#4096FF')
|
||
fill_color = self.hex_to_rgba(fill_color_hex)[:3]
|
||
elif isinstance(style['fill'], str):
|
||
# 如果fill是直接的颜色值
|
||
fill_color = self.hex_to_rgba(style['fill'])[:3]
|
||
|
||
print(f"[Style: {style_name}] 单色填充: {fill_color}")
|
||
|
||
color_pixels = Image.new('RGB', img.size, fill_color)
|
||
text_fill = Image.merge('RGBA', (color_pixels.split() + (text_mask,)))
|
||
|
||
# 保存用于调试
|
||
self._save_debug_image(text_fill, f"debug_{style_name}_solid_fill.png")
|
||
layers['fill'] = text_fill
|
||
|
||
print(f"[Style: {style_name}] 已应用文本颜色填充")
|
||
|
||
# 4. 准备描边效果
|
||
outline_img = None
|
||
if 'outline' in style:
|
||
outline = style['outline']
|
||
|
||
if outline.get('width', 0) > 0 and outline.get('opacity', 0) > 0:
|
||
# 创建基础图像用于描边
|
||
# 使用原始图像作为基础,确保描边位于正确位置
|
||
outline_base = img.copy()
|
||
|
||
# 应用描边
|
||
if 'gradient' in outline:
|
||
outline_img = self._apply_gradient_outline(outline_base, style, style_name)
|
||
print(f"[Style: {style_name}] 已应用渐变描边效果")
|
||
else:
|
||
outline_img = self.apply_outline(outline_base, style)
|
||
print(f"[Style: {style_name}] 已应用普通描边效果")
|
||
|
||
# 保存用于调试
|
||
self._save_debug_image(outline_img, f"debug_{style_name}_outline.png")
|
||
layers['outline'] = outline_img
|
||
|
||
# 5. 准备内阴影效果
|
||
inner_shadow_img = None
|
||
if 'inner_shadow' in style:
|
||
# 对于内阴影,我们使用原始文本形状
|
||
inner_shadow_base = img.copy()
|
||
inner_shadow_img = self.apply_inner_shadow(inner_shadow_base, style)
|
||
|
||
# 保存用于调试
|
||
self._save_debug_image(inner_shadow_img, f"debug_{style_name}_inner_shadow.png")
|
||
layers['inner_shadow'] = inner_shadow_img
|
||
|
||
print(f"[Style: {style_name}] 已应用内阴影效果")
|
||
|
||
# 打印当前所有层信息
|
||
print(f"[Style: {style_name}] 图层信息: {', '.join(layers.keys())}")
|
||
|
||
# 完全重构效果合成逻辑
|
||
# =====================================================
|
||
# 创建一个全新的透明底图
|
||
final_result = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
|
||
# 1. 底层 - 阴影和发光
|
||
shadow_glow_layer = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
if 'shadow' in layers:
|
||
shadow_glow_layer = Image.alpha_composite(shadow_glow_layer, layers['shadow'])
|
||
if 'glow' in layers:
|
||
shadow_glow_layer = Image.alpha_composite(shadow_glow_layer, layers['glow'])
|
||
|
||
final_result = Image.alpha_composite(final_result, shadow_glow_layer)
|
||
self._save_debug_image(final_result, f"debug_{style_name}_step1_shadow_glow.png")
|
||
print(f"[Style: {style_name}] 已合成阴影和发光图层")
|
||
|
||
# 2. 文本填充层 - 直接使用text_fill
|
||
if text_fill:
|
||
# 在黑色背景上查看填充效果用于调试
|
||
bg_debug = Image.new('RGBA', img.size, (0, 0, 0, 255))
|
||
fill_on_black = Image.alpha_composite(bg_debug, text_fill)
|
||
self._save_debug_image(fill_on_black, f"debug_{style_name}_fill_on_black.png")
|
||
|
||
# 正常合成到结果中
|
||
filled_result = Image.alpha_composite(final_result, text_fill)
|
||
final_result = filled_result
|
||
self._save_debug_image(final_result, f"debug_{style_name}_step2_with_fill.png")
|
||
print(f"[Style: {style_name}] 已合成填充图层")
|
||
|
||
# 3. 描边层 - 只添加文本外部的描边部分
|
||
if outline_img:
|
||
# 提取原始文本形状的alpha通道
|
||
text_alpha = np.array(text_mask)
|
||
|
||
# 提取描边图像的alpha通道
|
||
outline_alpha = np.array(outline_img.split()[3])
|
||
|
||
# 创建只包含描边部分的掩码(描边减去原始文本)
|
||
outline_only_mask_array = np.clip(outline_alpha - text_alpha, 0, 255)
|
||
|
||
# 转换为PIL Image
|
||
outline_only_mask = Image.fromarray(outline_only_mask_array)
|
||
|
||
# 如果未提供style_name,尝试从style字典中获取,如果都没有则使用默认值
|
||
if style_name is None:
|
||
style_name = style.get('name', 'unknown')
|
||
|
||
# 保存调试图像
|
||
self._save_debug_image(outline_only_mask, f"debug_{style_name}_outline_mask.png")
|
||
|
||
# 创建只含描边部分的图像
|
||
r, g, b, _ = outline_img.split()
|
||
outline_only = Image.merge('RGBA', (r, g, b, outline_only_mask))
|
||
self._save_debug_image(outline_only, f"debug_{style_name}_outline_only.png")
|
||
|
||
# 合成到结果中
|
||
outlined_result = Image.alpha_composite(final_result, outline_only)
|
||
final_result = outlined_result
|
||
self._save_debug_image(final_result, f"debug_{style_name}_step3_with_outline.png")
|
||
print(f"[Style: {style_name}] 已合成描边图层")
|
||
|
||
# 4. 内阴影层 - 只在文本区域内
|
||
if inner_shadow_img:
|
||
# 提取内阴影的RGB通道
|
||
inner_r, inner_g, inner_b, inner_a = inner_shadow_img.split()
|
||
|
||
# 确保内阴影只在文本区域内
|
||
inner_mask_array = np.minimum(np.array(text_mask), np.array(inner_a))
|
||
|
||
# 创建边缘区域蒙版 - 只在文本内部边缘显示内阴影
|
||
try:
|
||
from PIL import ImageFilter, ImageOps
|
||
import cv2
|
||
|
||
# 获取原始文本蒙版的NumPy数组
|
||
text_mask_array = np.array(text_mask)
|
||
|
||
# 转换为OpenCV可用的格式
|
||
text_mask_cv = text_mask_array.astype(np.uint8)
|
||
|
||
# 获取内阴影参数
|
||
inner_shadow = style.get('inner_shadow', {}) if style else {}
|
||
|
||
# 获取模糊值 - 直接使用SVG中定义的模糊参数
|
||
blur_amount = float(inner_shadow.get('blur', 2.0))
|
||
|
||
# 定义内部侵蚀的程度 - 与模糊值关联,确保内阴影宽度合适
|
||
# 根据模糊程度自适应调整侵蚀宽度,更大的模糊值对应更宽的内阴影
|
||
erosion_pixels = max(2, int(blur_amount * 1.5))
|
||
|
||
# 创建一个边缘检测使用的核
|
||
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
|
||
|
||
# 先侵蚀文本区域
|
||
eroded = cv2.erode(text_mask_cv, kernel, iterations=erosion_pixels)
|
||
|
||
# 计算边缘区域 = 原始文本区域 - 侵蚀后的区域
|
||
edge_only = text_mask_cv - eroded
|
||
|
||
# 应用内阴影偏移 - 实现真正的内阴影效果
|
||
# 获取偏移值
|
||
offset_x = float(inner_shadow.get('offset_x', 3.0))
|
||
offset_y = float(inner_shadow.get('offset_y', 3.0))
|
||
|
||
print(f"内阴影偏移值: X={offset_x}, Y={offset_y}")
|
||
|
||
# 创建原始边缘的副本
|
||
original_edge = edge_only.copy()
|
||
|
||
# 创建偏移矩阵
|
||
M = np.float32([[1, 0, offset_x], [0, 1, offset_y]])
|
||
|
||
# 应用偏移
|
||
edge_shifted = cv2.warpAffine(original_edge, M, (original_edge.shape[1], original_edge.shape[0]))
|
||
|
||
# 确保仍然在文本区域内
|
||
edge_shifted = cv2.bitwise_and(edge_shifted, text_mask_cv)
|
||
|
||
# 关键改进:连接原始边缘和偏移边缘之间的区域
|
||
# 创建多个中间偏移,并将它们合并以形成连续区域
|
||
steps = 8 # 中间步骤数
|
||
inner_shadow_area = original_edge.copy() # 从原始边缘开始
|
||
|
||
for step in range(1, steps + 1):
|
||
# 计算当前步骤的部分偏移
|
||
curr_offset_x = offset_x * step / steps
|
||
curr_offset_y = offset_y * step / steps
|
||
|
||
# 创建当前偏移矩阵
|
||
curr_M = np.float32([[1, 0, curr_offset_x], [0, 1, curr_offset_y]])
|
||
|
||
# 应用当前偏移
|
||
curr_shifted = cv2.warpAffine(original_edge, curr_M, (original_edge.shape[1], original_edge.shape[0]))
|
||
|
||
# 确保仍然在文本区域内
|
||
curr_shifted = cv2.bitwise_and(curr_shifted, text_mask_cv)
|
||
|
||
# 合并到内阴影区域
|
||
inner_shadow_area = cv2.bitwise_or(inner_shadow_area, curr_shifted)
|
||
|
||
# 使用连续的内阴影区域替代之前仅偏移的边缘
|
||
edge_shifted = inner_shadow_area
|
||
|
||
# 略微模糊边缘以获得平滑的过渡 - 使用SVG指定的模糊值
|
||
edge_shifted = cv2.GaussianBlur(edge_shifted, (5, 5), blur_amount / 2.0)
|
||
|
||
# 转回PIL格式
|
||
edge_mask = Image.fromarray(edge_shifted)
|
||
|
||
# 保存调试图像
|
||
self._save_debug_image(edge_mask, f"debug_{style_name}_inner_shadow_edge_mask.png")
|
||
|
||
# 使用边缘蒙版控制内阴影的应用范围 - 不需要反转掩码
|
||
inner_shadow_masked = Image.merge('RGBA', (inner_r, inner_g, inner_b, edge_mask))
|
||
|
||
self._save_debug_image(inner_shadow_masked, f"debug_{style_name}_inner_shadow_masked.png")
|
||
except Exception as e:
|
||
print(f"创建内阴影边缘蒙版失败,使用默认方式: {e}")
|
||
inner_mask = Image.fromarray(inner_mask_array)
|
||
inner_shadow_masked = Image.merge('RGBA', (inner_r, inner_g, inner_b, inner_mask))
|
||
|
||
# 添加高斯模糊以平滑边缘 - 使用SVG中定义的模糊值
|
||
try:
|
||
from PIL import ImageFilter
|
||
# 使用小半径的高斯模糊平滑边缘
|
||
blur_radius = blur_amount / 3.0 # 轻微降低模糊半径,保留细节但平滑边缘
|
||
inner_shadow_blurred = inner_shadow_masked.filter(ImageFilter.GaussianBlur(radius=blur_radius))
|
||
# 保存模糊后的内阴影用于调试
|
||
self._save_debug_image(inner_shadow_blurred, f"debug_{style_name}_inner_shadow_blurred.png")
|
||
except Exception as e:
|
||
print(f"应用高斯模糊失败: {e}")
|
||
inner_shadow_blurred = inner_shadow_masked # 如果模糊失败,使用原始内阴影
|
||
|
||
# 保存一份当前结果,以便在出错时回退
|
||
backup_result = final_result.copy()
|
||
|
||
# 使用标准alpha合成
|
||
try:
|
||
result_with_inner_shadow = Image.alpha_composite(final_result, inner_shadow_blurred)
|
||
final_result = result_with_inner_shadow
|
||
self._save_debug_image(final_result, f"debug_{style_name}_step4_with_inner_shadow.png")
|
||
print(f"[Style: {style_name}] 已合成内阴影图层")
|
||
except Exception as e:
|
||
print(f"内阴影合成出错: {e}")
|
||
print(f"回退到原始结果")
|
||
final_result = backup_result
|
||
|
||
# 最终结果
|
||
self._save_debug_image(final_result, f"debug_{style_name}_final.png")
|
||
|
||
# 带黑色背景版本
|
||
bg = Image.new('RGBA', img.size, (0, 0, 0, 255))
|
||
with_bg = Image.alpha_composite(bg, final_result)
|
||
self._save_debug_image(with_bg, f"debug_{style_name}_with_bg.png")
|
||
|
||
print(f"[Style: {style_name}] 所有效果应用完成")
|
||
return final_result
|
||
|
||
def _create_gradient(self, width, height, colors, angle=0):
|
||
"""通用渐变创建方法"""
|
||
gradient = np.zeros((height, width, 3), dtype=np.uint8)
|
||
|
||
# Use vectorized calculations instead of loops
|
||
x, y = np.meshgrid(np.arange(width), np.arange(height))
|
||
|
||
# 处理不同的渐变类型
|
||
if isinstance(angle, str):
|
||
if angle == 'radial': # 径向渐变
|
||
pos = np.sqrt((x - width/2)**2 + (y - height/2)**2)
|
||
pos = pos / np.max(pos) # 归一化到[0,1]范围
|
||
else:
|
||
# 如果是字符串但不是'radial',尝试转换为数字
|
||
try:
|
||
angle = float(angle)
|
||
except ValueError:
|
||
print(f"无法解析渐变角度: {angle},使用默认值90度")
|
||
angle = 90
|
||
|
||
if not isinstance(angle, str):
|
||
# 线性渐变 - 调整角度方向以匹配SVG
|
||
# SVG中,0度表示从左到右,90度表示从上到下
|
||
# 调整角度方向,使0度指向右侧(标准坐标系)
|
||
angle_rad = np.radians(angle - 90) # 旋转坐标系使y轴向下
|
||
|
||
# 创建梯度场
|
||
gradient_vector = np.array([np.cos(angle_rad), np.sin(angle_rad)])
|
||
|
||
# 计算每个点在渐变方向上的投影
|
||
x_norm = (x - width/2) / width if width > 0 else 0
|
||
y_norm = (y - height/2) / height if height > 0 else 0
|
||
|
||
# 计算渐变位置
|
||
pos = x_norm * gradient_vector[0] + y_norm * gradient_vector[1]
|
||
|
||
# 归一化到[0,1]
|
||
pos = (pos - np.min(pos)) / (np.max(pos) - np.min(pos) + 1e-8) if np.max(pos) > np.min(pos) else np.zeros_like(pos) + 0.5
|
||
|
||
# 打印渐变方向调试信息
|
||
print(f"渐变角度: {angle}度, 方向向量: [{gradient_vector[0]:.2f}, {gradient_vector[1]:.2f}]")
|
||
|
||
# 插值生成渐变
|
||
if len(colors) == 2:
|
||
# 简单的两点渐变
|
||
for i in range(3): # RGB通道
|
||
gradient[..., i] = np.interp(pos, [0, 1], [colors[0][i], colors[1][i]])
|
||
else:
|
||
# 多点渐变支持
|
||
color_positions = np.linspace(0, 1, len(colors))
|
||
for i in range(3): # RGB通道
|
||
gradient[..., i] = np.interp(pos, color_positions, [color[i] for color in colors])
|
||
|
||
return Image.fromarray(gradient, 'RGB')
|
||
|
||
def apply_shadow(self, img, style):
|
||
"""Apply shadow effect to text."""
|
||
if 'shadow' not in style:
|
||
return img
|
||
|
||
shadow = style['shadow']
|
||
color = shadow.get('color', '#000000')
|
||
blur = shadow.get('blur', 5)
|
||
opacity = shadow.get('opacity', 0.7)
|
||
|
||
# Support different offset value representations
|
||
if 'offset' in shadow:
|
||
offset = shadow['offset']
|
||
offset_x, offset_y = offset if isinstance(offset, list) else (offset, offset)
|
||
else:
|
||
offset_x = shadow.get('offset_x', 5)
|
||
offset_y = shadow.get('offset_y', 5)
|
||
|
||
print(f"[EffectsProcessor] 阴影偏移: X={offset_x}, Y={offset_y}")
|
||
|
||
# 确保透明度在有效范围内
|
||
if isinstance(opacity, str):
|
||
try:
|
||
opacity = float(opacity)
|
||
except ValueError:
|
||
opacity = 0.7
|
||
|
||
opacity = max(0.0, min(1.0, opacity))
|
||
|
||
shadow_color = self.hex_to_rgba(color, int(255 * opacity))
|
||
|
||
# Extract alpha channel
|
||
mask = img.split()[3]
|
||
|
||
# Create shadow layer
|
||
shadow_layer = Image.new('RGBA', img.size, shadow_color)
|
||
shadow_layer.putalpha(mask)
|
||
|
||
# Offset shadow layer
|
||
shadow_layer = ImageChops.offset(shadow_layer, int(round(offset_x)), int(round(offset_y)))
|
||
|
||
# Apply blur
|
||
if blur > 0:
|
||
shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(radius=blur))
|
||
|
||
# Create result image - first place the shadow, then the original image
|
||
shadow_img = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
shadow_img = Image.alpha_composite(shadow_img, shadow_layer)
|
||
|
||
result = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
result = Image.alpha_composite(result, shadow_img)
|
||
result = Image.alpha_composite(result, img)
|
||
|
||
return result
|
||
|
||
def apply_outline(self, img, style):
|
||
"""Apply outline to text."""
|
||
if 'outline' not in style:
|
||
return img
|
||
|
||
outline = style['outline']
|
||
outline_width = outline.get('width', 1)
|
||
outline_color = self.hex_to_rgba(outline.get('color', '#000000'))
|
||
|
||
if outline_width <= 0:
|
||
return img
|
||
|
||
# Create a mask from the alpha channel
|
||
mask = img.split()[3]
|
||
|
||
# Create outline layer
|
||
outline_img = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
|
||
# Apply dilation to the mask for each outline pixel
|
||
dilated = mask
|
||
for i in range(1, int(round(outline_width)) + 1):
|
||
dilated = dilated.filter(ImageFilter.MaxFilter(3))
|
||
|
||
# Create the outline
|
||
outline_layer = Image.new('RGBA', img.size, outline_color)
|
||
outline_layer.putalpha(dilated)
|
||
|
||
# Composite with the existing outline
|
||
outline_img = Image.alpha_composite(outline_img, outline_layer)
|
||
|
||
# Composite the original image on top of the outline
|
||
result = Image.alpha_composite(outline_img, img)
|
||
return result
|
||
|
||
def _apply_gradient_outline(self, img, style, style_name=None):
|
||
"""使用渐变效果应用描边。"""
|
||
# Get the text mask
|
||
mask = img.split()[3]
|
||
|
||
# 如果未提供style_name,尝试从style字典中获取,如果都没有则使用默认值
|
||
if style_name is None:
|
||
style_name = style.get('name', 'unknown')
|
||
|
||
# Handle gradient outline
|
||
outline_data = style.get('outline', {})
|
||
width = float(outline_data.get('width', 0))
|
||
opacity = float(outline_data.get('opacity', 1.0))
|
||
|
||
# 获取渐变对象,确保在此之后不要覆盖它
|
||
gradient = None
|
||
if 'gradient' in outline_data:
|
||
gradient = outline_data['gradient']
|
||
else:
|
||
gradient = outline_data
|
||
|
||
print(f"[EffectsProcessor] 渐变描边详情 - 宽度: {width}, 透明度: {opacity}, 渐变类型: {gradient.get('type', 'linear')}")
|
||
|
||
# 获取颜色信息,这里仅获取原始16进制颜色用于日志显示
|
||
hex_colors = gradient.get('colors', ['#EE2883', '#FFDC7D']) # 使用设计指定的粉色到黄色渐变作为默认值
|
||
print(f"[EffectsProcessor] 渐变颜色: {hex_colors}")
|
||
|
||
# 处理渐变角度 - 支持SVG渐变方向
|
||
angle = 0 # 默认从左到右(SVG: x1=0% -> x2=100%, y相同)
|
||
|
||
# 检查渐变方向类型
|
||
direction = gradient.get('direction', 'left_right')
|
||
|
||
# 如果是SVG定义的方向,检查是否包含x1,y1,x2,y2坐标
|
||
if direction == 'custom' and 'svg_coords' in gradient:
|
||
svg_coords = gradient['svg_coords']
|
||
try:
|
||
x1 = float(svg_coords.get('x1', 0)) / 100 # 转换为0-1范围
|
||
y1 = float(svg_coords.get('y1', 0)) / 100
|
||
x2 = float(svg_coords.get('x2', 100)) / 100
|
||
y2 = float(svg_coords.get('y2', 0)) / 100
|
||
|
||
# 计算角度 - arctan2 返回弧度,convert to degrees
|
||
if x2 != x1 or y2 != y1: # 避免除以零
|
||
dx = x2 - x1
|
||
dy = y2 - y1
|
||
|
||
# 在PIL中,y轴向下为正方向,所以反转y差值
|
||
angle_rad = math.atan2(-dy, dx) # 注意这里反转y轴方向
|
||
angle = math.degrees(angle_rad)
|
||
# 调整角度到0-360范围
|
||
angle = (angle + 360) % 360
|
||
|
||
print(f"[Style: {style_name}] 从SVG坐标解析的描边渐变角度: {angle}°, 方向向量: [{dx:.2f}, {dy:.2f}]")
|
||
except (ValueError, TypeError) as e:
|
||
print(f"无法解析描边SVG坐标: {e}, 使用默认角度")
|
||
# 处理预定义的方向
|
||
elif direction == 'left_right':
|
||
angle = 0 # 水平从左到右
|
||
elif direction == 'right_left':
|
||
angle = 180 # 水平从右到左
|
||
elif direction == 'top_bottom':
|
||
angle = 90 # 垂直从上到下
|
||
elif direction == 'bottom_top':
|
||
angle = 270 # 垂直从下到上
|
||
elif direction == 'diagonal':
|
||
angle = 45 # 对角线 左上到右下
|
||
elif direction == 'diagonal_reverse':
|
||
angle = 225 # 对角线 右下到左上
|
||
elif direction == 'diagonal_bottom':
|
||
angle = 315 # 对角线 左下到右上
|
||
elif direction == 'diagonal_bottom_reverse':
|
||
angle = 135 # 对角线 右上到左下
|
||
else:
|
||
# 使用直接指定的角度
|
||
angle = gradient.get('angle', 0)
|
||
|
||
# 检查是否是径向渐变
|
||
if gradient.get('type') == 'radial':
|
||
angle = 'radial'
|
||
|
||
# Create outline by dilating the mask
|
||
dilated_mask = mask.copy()
|
||
outline_img = img.copy()
|
||
|
||
# Apply the outline width (dilate the mask)
|
||
w = int(width)
|
||
print(f"[EffectsProcessor] 实际使用的描边宽度: {w}")
|
||
print(f"最终描边不透明度: {opacity}")
|
||
|
||
# Only apply if width > 0
|
||
if w > 0:
|
||
# Apply multiple dilations for better results
|
||
for _ in range(w):
|
||
dilated_mask = dilated_mask.filter(ImageFilter.MaxFilter(3))
|
||
|
||
# Create outline mask (only keep the dilated area, excluding the original text area)
|
||
outline_only_mask_array = np.clip(np.array(dilated_mask) - np.array(mask), 0, 255)
|
||
|
||
# 转换为PIL Image
|
||
outline_only_mask = Image.fromarray(outline_only_mask_array)
|
||
|
||
# 保存调试图像
|
||
self._save_debug_image(outline_only_mask, f"debug_{style_name}_outline_mask.png")
|
||
|
||
# 将HEX颜色转换为RGB (只处理一次颜色,不重复处理)
|
||
rgb_colors = []
|
||
for color in hex_colors:
|
||
if isinstance(color, str) and color.startswith('#'):
|
||
# Convert hex to RGB
|
||
r = int(color[1:3], 16)
|
||
g = int(color[3:5], 16)
|
||
b = int(color[5:7], 16)
|
||
rgb_colors.append((r, g, b))
|
||
elif isinstance(color, (list, tuple)) and len(color) == 3:
|
||
rgb_colors.append(color)
|
||
else:
|
||
rgb_colors.append((255, 46, 219)) # 默认紫色
|
||
|
||
if not rgb_colors:
|
||
# 只有在列表为空时才使用默认值
|
||
rgb_colors = [(238, 40, 131), (255, 220, 125)] # 使用粉色到黄色的默认渐变
|
||
|
||
# 打印最终使用的渐变信息进行调试
|
||
print(f"[Style: {style_name}] 描边渐变填充颜色: {hex_colors}, 角度/类型: {angle}, 方向: {direction}")
|
||
|
||
# 创建渐变并应用到描边
|
||
gradient_array = self._create_gradient(img.size[0], img.size[1], rgb_colors, angle)
|
||
gradient_img = gradient_array.convert('RGBA')
|
||
gradient_img.putalpha(outline_only_mask)
|
||
|
||
# Composite images
|
||
result = Image.alpha_composite(outline_img, gradient_img)
|
||
self._save_debug_image(result, f"debug_{style_name}_outline_only.png")
|
||
|
||
return result
|
||
|
||
def apply_glow(self, img, style):
|
||
"""Apply glow effect to text."""
|
||
glow_key = 'glow'
|
||
if glow_key not in style:
|
||
glow_key = 'outer_glow'
|
||
if glow_key not in style:
|
||
return img
|
||
|
||
glow = style[glow_key]
|
||
if glow is None:
|
||
return img
|
||
|
||
glow_color = self.hex_to_rgba(glow.get('color', '#FFFFFF'), 100)
|
||
radius = glow.get('radius', 10)
|
||
intensity = glow.get('intensity', 0.5)
|
||
|
||
# If radius or intensity is 0, skip the glow effect
|
||
if radius <= 0 or intensity <= 0:
|
||
return img
|
||
|
||
# 调整发光半径
|
||
font_size_scale = 1.0
|
||
radius = max(radius * font_size_scale, 5)
|
||
print(f"[EffectsProcessor] 最终发光半径: {radius:.2f} (基础半径: {radius})")
|
||
|
||
# Create mask from alpha channel
|
||
mask = img.split()[3]
|
||
expanded_mask = mask.filter(ImageFilter.MaxFilter(3))
|
||
|
||
# Create glow layer
|
||
glow_img = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
glow_layer = Image.new('RGBA', img.size, glow_color)
|
||
glow_layer.putalpha(expanded_mask)
|
||
|
||
# Apply blur for the glow effect
|
||
if radius > 0:
|
||
glow_layer = glow_layer.filter(ImageFilter.GaussianBlur(radius))
|
||
|
||
# Enhance glow intensity
|
||
enhanced_intensity = min(intensity * 1.2, 1.0)
|
||
|
||
# Adjust intensity
|
||
if enhanced_intensity < 1.0:
|
||
glow_r, glow_g, glow_b, glow_a = glow_layer.split()
|
||
glow_a = glow_a.point(lambda x: int(x * enhanced_intensity))
|
||
glow_layer = Image.merge('RGBA', (glow_r, glow_g, glow_b, glow_a))
|
||
|
||
# Apply the glow layer
|
||
glow_img = Image.alpha_composite(glow_img, glow_layer)
|
||
|
||
# For high-intensity glows, add an additional inner layer
|
||
if intensity > 0.6:
|
||
inner_glow = Image.new('RGBA', img.size, glow_color)
|
||
inner_glow.putalpha(mask)
|
||
inner_glow = inner_glow.filter(ImageFilter.GaussianBlur(radius * 0.5))
|
||
|
||
# Composite the glow
|
||
glow_img = Image.alpha_composite(glow_img, inner_glow)
|
||
print(f"[EffectsProcessor] 应用额外的内层发光,增强边缘亮度")
|
||
|
||
# Composite glow and original
|
||
result = Image.alpha_composite(glow_img, img)
|
||
|
||
return result
|
||
|
||
def apply_inner_shadow(self, img, style):
|
||
"""Apply inner shadow effect to text."""
|
||
if 'inner_shadow' not in style:
|
||
return img
|
||
|
||
inner_shadow = style['inner_shadow']
|
||
color = inner_shadow.get('color', '#000000')
|
||
opacity = inner_shadow.get('opacity', 1) # 修改默认透明度为1
|
||
|
||
# Support different offset value representations
|
||
if 'offset' in inner_shadow:
|
||
offset = inner_shadow['offset']
|
||
offset_x, offset_y = offset if isinstance(offset, list) else (offset, offset)
|
||
else:
|
||
offset_x = inner_shadow.get('offset_x', 2)
|
||
offset_y = inner_shadow.get('offset_y', 2)
|
||
|
||
blur = inner_shadow.get('blur', 0) # 修改默认模糊程度为0
|
||
|
||
print(f"[EffectsProcessor] 应用内阴影效果 - 颜色: {color}, 透明度: {opacity}, 模糊: {blur}")
|
||
print(f"[EffectsProcessor] 内阴影偏移: X={offset_x}, Y={offset_y}")
|
||
|
||
# 确保透明度在有效范围内
|
||
if isinstance(opacity, str):
|
||
try:
|
||
opacity = float(opacity)
|
||
except ValueError:
|
||
opacity = 0.5
|
||
|
||
opacity = max(0.1, min(1.0, opacity))
|
||
|
||
# 确保图像是RGBA模式
|
||
if img.mode != 'RGBA':
|
||
img = img.convert('RGBA')
|
||
|
||
# 复制原始图像
|
||
original = img.copy()
|
||
|
||
# 获取原始图像的alpha通道作为蒙版
|
||
r_orig, g_orig, b_orig, a_orig = original.split()
|
||
mask = a_orig.copy()
|
||
|
||
# 创建偏移蒙版 - 内阴影从边缘向内偏移
|
||
offset_mask = ImageChops.offset(mask, -int(round(offset_x)), -int(round(offset_y)))
|
||
|
||
# 创建内阴影蒙版 - 阴影区域是原始蒙版和偏移蒙版之间的差异
|
||
shadow_mask = ImageChops.subtract(mask, offset_mask)
|
||
|
||
# 应用高斯模糊
|
||
if blur > 0:
|
||
shadow_mask = shadow_mask.filter(ImageFilter.GaussianBlur(blur))
|
||
|
||
# 创建内阴影层
|
||
shadow_color = self.hex_to_rgba(color)
|
||
shadow_layer = Image.new('RGBA', img.size, shadow_color)
|
||
|
||
# 应用阴影蒙版到阴影层
|
||
r_shadow, g_shadow, b_shadow, a_shadow = shadow_layer.split()
|
||
|
||
# 应用不透明度到阴影蒙版
|
||
shadow_mask = shadow_mask.point(lambda x: int(x * opacity))
|
||
|
||
# 合并阴影层
|
||
shadow_layer = Image.merge('RGBA', (r_shadow, g_shadow, b_shadow, shadow_mask))
|
||
|
||
# 创建结果图像 - 保留原始图像,仅在边缘添加内阴影
|
||
result = Image.new('RGBA', img.size, (0, 0, 0, 0))
|
||
result = Image.alpha_composite(result, original)
|
||
result = Image.alpha_composite(result, shadow_layer)
|
||
|
||
return result
|
||
|
||
texture_img = texture_img.resize(img.size)
|
||
|
||
# Apply texture
|
||
result = Image.alpha_composite(img, texture_img)
|
||
|
||
# Save debug image
|
||
self._save_debug_image(result, f"debug_texture_result.png")
|
||
|
||
return result
|