第10章:特效系统与滤镜实现
10.1 特效系统概述
特效系统是视频编辑软件中最具创意表现力的核心组件,它为用户提供了丰富的视觉变换和艺术效果,能够将普通的视频素材转化为具有专业水准的影视作品。在剪映小助手中,特效系统不仅需要支持基础的滤镜效果,还要处理复杂的动态特效、转场效果、粒子系统以及实时渲染等高级功能。一个设计良好的特效系统能够为用户提供直观的操作界面,同时保证高效的渲染性能和灵活的扩展能力。
特效系统的核心价值体现在:
创意表现力:通过丰富的特效类型和参数控制,释放用户的创作潜能,实现独特的视觉风格。
实时性能:采用高效的算法和优化技术,确保特效的实时预览和流畅编辑体验。
模块化设计:通过插件化的架构设计,支持特效的灵活扩展和第三方集成。
参数化控制:提供直观的参数调节界面,让用户能够精确控制特效的表现效果。
预设管理:支持特效预设的创建、保存和共享,提高用户的工作效率。
10.2 特效基础架构设计
10.2.1 特效抽象基类
定义特效系统的通用接口和行为:
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional, Union, Tuple
from dataclasses import dataclass, field
import uuid
from enum import Enum
import numpy as np
from PIL import Image, ImageFilter, ImageEnhance
import cv2
class EffectType(Enum):
"""特效类型枚举"""
FILTER = "filter" # 滤镜特效
TRANSITION = "transition" # 转场特效
TRANSFORM = "transform" # 变换特效
COLOR = "color" # 色彩特效
BLUR = "blur" # 模糊特效
SHARPEN = "sharpen" # 锐化特效
DISTORT = "distort" # 扭曲特效
LIGHTING = "lighting" # 光照特效
PARTICLE = "particle" # 粒子特效
COMPOSITE = "composite" # 合成特效
class EffectCategory(Enum):
"""特效分类枚举"""
BASIC = "basic" # 基础特效
ARTISTIC = "artistic" # 艺术特效
PROFESSIONAL = "professional" # 专业特效
CREATIVE = "creative" # 创意特效
UTILITY = "utility" # 实用特效
@dataclass
class EffectParameter:
"""特效参数定义"""
name: str
type: str # "int", "float", "bool", "color", "enum", "range"
default: Any
min_value: Optional[Union[int, float]] = None
max_value: Optional[Union[int, float]] = None
step: Optional[Union[int, float]] = None
options: Optional[List[str]] = None # 用于枚举类型
description: str = ""
group: str = "basic" # 参数分组
order: int = 0 # 参数排序
@dataclass
class EffectConfig:
"""特效配置"""
name: str
effect_type: EffectType
category: EffectCategory = EffectCategory.BASIC
description: str = ""
parameters: List[EffectParameter] = field(default_factory=list)
supports_animation: bool = True
gpu_accelerated: bool = False
real_time_capable: bool = True
processing_order: int = 0 # 处理顺序
cache_enabled: bool = True
preview_quality: str = "medium" # "low", "medium", "high"
class Effect(ABC):
"""特效抽象基类"""
def __init__(self, config: EffectConfig = None):
self.id = str(uuid.uuid4())
self.config = config or EffectConfig(name="Effect")
self.parameters: Dict[str, Any] = {}
self.enabled = True
self.intensity = 1.0 # 特效强度
self.start_time = 0.0 # 特效开始时间
self.duration = 1.0 # 特效持续时间
self.keyframes: Dict[str, List['Keyframe']] = {}
self.metadata: Dict[str, Any] = {}
self.created_at = time.time()
self.modified_at = time.time()
# 初始化参数默认值
self._initialize_parameters()
def _initialize_parameters(self) -> None:
"""初始化参数默认值"""
for param in self.config.parameters:
self.parameters[param.name] = param.default
@abstractmethod
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用特效到帧"""
pass
@abstractmethod
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用特效到音频"""
pass
def get_parameter_value(self, param_name: str, time: float = None) -> Any:
"""获取参数值(支持关键帧)"""
if param_name not in self.parameters:
return None
# 如果没有指定时间或没有关键帧,返回静态值
if time is None or param_name not in self.keyframes:
return self.parameters[param_name]
# 获取关键帧插值
return self._interpolate_keyframes(param_name, time)
def set_parameter_value(self, param_name: str, value: Any,
time: float = None) -> bool:
"""设置参数值"""
if param_name not in self.parameters:
return False
# 验证参数值
param_def = self._get_parameter_definition(param_name)
if param_def and not self._validate_parameter_value(param_def, value):
return False
if time is None:
# 设置静态值
self.parameters[param_name] = value
else:
# 创建关键帧
self._add_keyframe(param_name, time, value)
self.modified_at = time.time()
return True
def _interpolate_keyframes(self, param_name: str, time: float) -> Any:
"""关键帧插值"""
if param_name not in self.keyframes or not self.keyframes[param_name]:
return self.parameters[param_name]
keyframes = self.keyframes[param_name]
# 找到包围的两个关键帧
before_frame = None
after_frame = None
for keyframe in keyframes:
if keyframe.time <= time:
before_frame = keyframe
if keyframe.time >= time and after_frame is None:
after_frame = keyframe
break
if before_frame is None:
return keyframes[0].value
if after_frame is None:
return keyframes[-1].value
if abs(before_frame.time - after_frame.time) < 0.001:
return before_frame.value
# 计算插值因子
factor = (time - before_frame.time) / (after_frame.time - before_frame.time)
# 执行插值
return self._interpolate_values(
before_frame.value, after_frame.value, factor,
before_frame.interpolation_type
)
def _interpolate_values(self, start_value: Any, end_value: Any,
factor: float, interpolation_type: str = "linear") -> Any:
"""值插值"""
if interpolation_type == "linear":
return self._linear_interpolate(start_value, end_value, factor)
elif interpolation_type == "ease_in":
return self._ease_in_interpolate(start_value, end_value, factor)
elif interpolation_type == "ease_out":
return self._ease_out_interpolate(start_value, end_value, factor)
elif interpolation_type == "ease_in_out":
return self._ease_in_out_interpolate(start_value, end_value, factor)
else:
return self._linear_interpolate(start_value, end_value, factor)
def _linear_interpolate(self, start_value: Any, end_value: Any, factor: float) -> Any:
"""线性插值"""
if isinstance(start_value, (int, float)) and isinstance(end_value, (int, float)):
return start_value + (end_value - start_value) * factor
elif isinstance(start_value, (list, tuple)) and isinstance(end_value, (list, tuple)):
return [
self._linear_interpolate(s, e, factor)
for s, e in zip(start_value, end_value)
]
else:
return end_value if factor > 0.5 else start_value
def _ease_in_interpolate(self, start_value: Any, end_value: Any, factor: float) -> Any:
"""缓入插值"""
eased_factor = factor * factor
return self._linear_interpolate(start_value, end_value, eased_factor)
def _ease_out_interpolate(self, start_value: Any, end_value: Any, factor: float) -> Any:
"""缓出插值"""
eased_factor = 1 - (1 - factor) * (1 - factor)
return self._linear_interpolate(start_value, end_value, eased_factor)
def _ease_in_out_interpolate(self, start_value: Any, end_value: Any, factor: float) -> Any:
"""缓入缓出插值"""
if factor < 0.5:
eased_factor = 2 * factor * factor
else:
eased_factor = 1 - 2 * (1 - factor) * (1 - factor)
return self._linear_interpolate(start_value, end_value, eased_factor)
def _add_keyframe(self, param_name: str, time: float, value: Any) -> None:
"""添加关键帧"""
if param_name not in self.keyframes:
self.keyframes[param_name] = []
# 创建关键帧对象
keyframe = Keyframe(time=time, value=value)
# 添加到列表并排序
self.keyframes[param_name].append(keyframe)
self.keyframes[param_name].sort(key=lambda k: k.time)
def _get_parameter_definition(self, param_name: str) -> Optional[EffectParameter]:
"""获取参数定义"""
for param in self.config.parameters:
if param.name == param_name:
return param
return None
def _validate_parameter_value(self, param_def: EffectParameter, value: Any) -> bool:
"""验证参数值"""
# 类型检查
if param_def.type == "int" and not isinstance(value, int):
return False
elif param_def.type == "float" and not isinstance(value, (int, float)):
return False
elif param_def.type == "bool" and not isinstance(value, bool):
return False
elif param_def.type == "range" and not isinstance(value, (int, float)):
return False
# 范围检查
if param_def.min_value is not None and value < param_def.min_value:
return False
if param_def.max_value is not None and value > param_def.max_value:
return False
# 枚举检查
if param_def.type == "enum" and param_def.options:
if value not in param_def.options:
return False
return True
def is_active_at_time(self, time: float) -> bool:
"""检查特效在指定时间是否活动"""
return (self.enabled and
self.start_time <= time <= self.start_time + self.duration)
def get_intensity_at_time(self, time: float) -> float:
"""获取指定时间的特效强度"""
if not self.is_active_at_time(time):
return 0.0
# 可以添加强度关键帧支持
return self.intensity
def validate_configuration(self) -> List[str]:
"""验证特效配置"""
errors = []
# 检查必需参数
for param in self.config.parameters:
if param.name not in self.parameters:
errors.append(f"缺少必需参数: {param.name}")
# 验证参数值
for param_name, value in self.parameters.items():
param_def = self._get_parameter_definition(param_name)
if param_def and not self._validate_parameter_value(param_def, value):
errors.append(f"参数值无效: {param_name} = {value}")
return errors
def export_settings(self) -> Dict[str, Any]:
"""导出特效设置"""
return {
"id": self.id,
"config": {
"name": self.config.name,
"effect_type": self.config.effect_type.value,
"category": self.config.category.value,
"description": self.config.description
},
"parameters": self.parameters.copy(),
"enabled": self.enabled,
"intensity": self.intensity,
"start_time": self.start_time,
"duration": self.duration,
"keyframes": {
param_name: [
{
"time": kf.time,
"value": kf.value,
"interpolation_type": kf.interpolation_type
}
for kf in keyframes
]
for param_name, keyframes in self.keyframes.items()
},
"metadata": self.metadata.copy()
}
def import_settings(self, settings: Dict[str, Any]) -> bool:
"""导入特效设置"""
try:
# 验证配置
if "config" in settings:
config_data = settings["config"]
if config_data.get("name") != self.config.name:
return False
# 导入参数
if "parameters" in settings:
for param_name, value in settings["parameters"].items():
if param_name in self.parameters:
self.parameters[param_name] = value
# 导入基本属性
if "enabled" in settings:
self.enabled = settings["enabled"]
if "intensity" in settings:
self.intensity = settings["intensity"]
if "start_time" in settings:
self.start_time = settings["start_time"]
if "duration" in settings:
self.duration = settings["duration"]
# 导入关键帧
if "keyframes" in settings:
self.keyframes.clear()
for param_name, keyframes_data in settings["keyframes"].items():
for kf_data in keyframes_data:
keyframe = Keyframe(
time=kf_data["time"],
value=kf_data["value"],
interpolation_type=kf_data.get("interpolation_type", "linear")
)
self._add_keyframe(param_name, keyframe.time, keyframe.value)
# 导入元数据
if "metadata" in settings:
self.metadata.update(settings["metadata"])
self.modified_at = time.time()
return True
except Exception as e:
print(f"导入特效设置失败: {e}")
return False
@dataclass
class Keyframe:
"""关键帧"""
time: float
value: Any
interpolation_type: str = "linear" # "linear", "ease_in", "ease_out", "ease_in_out"
easing_strength: float = 1.0
10.2.2 滤镜特效实现
实现基础的滤镜特效:
class FilterEffect(Effect):
"""滤镜特效基类"""
def __init__(self, config: EffectConfig = None):
if config is None:
config = EffectConfig(
name="Filter Effect",
effect_type=EffectType.FILTER,
parameters=[
EffectParameter("intensity", "float", 1.0, 0.0, 2.0, 0.01, "特效强度", "basic", 1),
EffectParameter("blend_mode", "enum", "normal", options=["normal", "multiply", "screen", "overlay"], description="混合模式", group="advanced"),
EffectParameter("preserve_alpha", "bool", True, description="保持透明度", group="advanced")
]
)
super().__init__(config)
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用滤镜到帧"""
if not self.is_active_at_time(time):
return frame
# 获取当前参数值
intensity = self.get_parameter_value("intensity", time)
blend_mode = self.get_parameter_value("blend_mode", time)
preserve_alpha = self.get_parameter_value("preserve_alpha", time)
# 应用滤镜效果
processed_frame = self._apply_filter_effect(frame, intensity, render_context)
# 混合模式处理
if blend_mode != "normal":
processed_frame = self._apply_blend_mode(frame, processed_frame, blend_mode, intensity)
# 透明度处理
if preserve_alpha and hasattr(frame, 'mode') and 'A' in frame.mode:
processed_frame = self._preserve_alpha_channel(frame, processed_frame)
return processed_frame
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用滤镜到音频(默认不处理)"""
return audio_data
@abstractmethod
def _apply_filter_effect(self, frame: Any, intensity: float,
render_context: 'RenderContext') -> Any:
"""应用具体的滤镜效果(子类实现)"""
pass
def _apply_blend_mode(self, original: Any, processed: Any,
blend_mode: str, intensity: float) -> Any:
"""应用混合模式"""
# 这里会实现各种混合模式算法
return processed
def _preserve_alpha_channel(self, original: Any, processed: Any) -> Any:
"""保持alpha通道"""
# 提取原始alpha通道并应用到处理后的图像
return processed
class BrightnessContrastEffect(FilterEffect):
"""亮度对比度特效"""
def __init__(self):
config = EffectConfig(
name="Brightness & Contrast",
effect_type=EffectType.COLOR,
category=EffectCategory.BASIC,
description="调整亮度和对比度",
parameters=[
EffectParameter("brightness", "float", 0.0, -1.0, 1.0, 0.01, "亮度", "basic", 1),
EffectParameter("contrast", "float", 0.0, -1.0, 1.0, 0.01, "对比度", "basic", 2),
EffectParameter("gamma", "float", 1.0, 0.1, 3.0, 0.01, "伽马值", "advanced", 3)
]
)
super().__init__(config)
def _apply_filter_effect(self, frame: Any, intensity: float,
render_context: 'RenderContext') -> Any:
"""应用亮度对比度调整"""
# 获取参数值
brightness = self.get_parameter_value("brightness") * intensity
contrast = self.get_parameter_value("contrast") * intensity
gamma = self.get_parameter_value("gamma")
# 转换为numpy数组进行处理
if isinstance(frame, Image.Image):
img_array = np.array(frame)
else:
img_array = frame
# 应用亮度调整
if brightness != 0:
img_array = img_array.astype(np.float32)
img_array = img_array + brightness * 255
img_array = np.clip(img_array, 0, 255)
# 应用对比度调整
if contrast != 0:
contrast_factor = (259 * (contrast * 255 + 255)) / (255 * (259 - contrast * 255))
img_array = contrast_factor * (img_array - 128) + 128
img_array = np.clip(img_array, 0, 255)
# 应用伽马校正
if gamma != 1.0:
img_array = np.power(img_array / 255.0, 1.0 / gamma) * 255
img_array = np.clip(img_array, 0, 255)
# 转换回PIL图像
if isinstance(frame, Image.Image):
return Image.fromarray(img_array.astype(np.uint8))
else:
return img_array.astype(np.uint8)
class SaturationEffect(FilterEffect):
"""饱和度特效"""
def __init__(self):
config = EffectConfig(
name="Saturation",
effect_type=EffectType.COLOR,
category=EffectCategory.BASIC,
description="调整色彩饱和度",
parameters=[
EffectParameter("saturation", "float", 1.0, 0.0, 2.0, 0.01, "饱和度", "basic", 1),
EffectParameter("vibrance", "float", 0.0, -1.0, 1.0, 0.01, "自然饱和度", "advanced", 2),
EffectParameter("preserve_luminance", "bool", True, description="保持亮度", group="advanced")
]
)
super().__init__(config)
def _apply_filter_effect(self, frame: Any, intensity: float,
render_context: 'RenderContext') -> Any:
"""应用饱和度调整"""
# 获取参数值
saturation = self.get_parameter_value("saturation") * intensity
vibrance = self.get_parameter_value("vibrance") * intensity
preserve_luminance = self.get_parameter_value("preserve_luminance")
# 使用PIL的ColorEnhance进行饱和度调整
if isinstance(frame, Image.Image):
# 基础饱和度调整
enhancer = ImageEnhance.Color(frame)
enhanced_frame = enhancer.enhance(saturation)
# 自然饱和度调整(更智能的算法)
if vibrance != 0:
enhanced_frame = self._apply_vibrance(enhanced_frame, vibrance)
return enhanced_frame
else:
# 对于numpy数组,使用OpenCV进行饱和度调整
return self._apply_saturation_cv(frame, saturation, vibrance, preserve_luminance)
def _apply_vibrance(self, image: Image.Image, vibrance: float) -> Image.Image:
"""应用自然饱和度调整"""
# 转换为HSV色彩空间
hsv_image = image.convert('HSV')
hsv_array = np.array(hsv_array)
# 自然饱和度算法:对低饱和度区域增强更明显
saturation_channel = hsv_array[:, :, 1].astype(np.float32)
# 计算饱和度增强因子
vibrance_factor = 1.0 + vibrance
low_sat_mask = saturation_channel < 128
high_sat_mask = saturation_channel >= 128
# 对低饱和度区域应用更强的增强
saturation_channel[low_sat_mask] *= vibrance_factor * 1.2
saturation_channel[high_sat_mask] *= vibrance_factor * 0.8
# 限制范围
saturation_channel = np.clip(saturation_channel, 0, 255)
hsv_array[:, :, 1] = saturation_channel.astype(np.uint8)
# 转换回RGB
result_image = Image.fromarray(hsv_array, 'HSV').convert('RGB')
return result_image
def _apply_saturation_cv(self, frame: np.ndarray, saturation: float,
vibrance: float, preserve_luminance: bool) -> np.ndarray:
"""使用OpenCV应用饱和度调整"""
# 转换到HSV色彩空间
hsv = cv2.cvtColor(frame, cv2.COLOR_RGB2HSV).astype(np.float32)
# 调整饱和度通道
hsv[:, :, 1] *= saturation
# 应用自然饱和度
if vibrance != 0:
sat_channel = hsv[:, :, 1]
low_sat_mask = sat_channel < 128
sat_channel[low_sat_mask] *= (1.0 + vibrance * 0.5)
# 保持亮度
if preserve_luminance:
# 确保亮度通道不变
pass
# 限制范围并转换回RGB
hsv[:, :, 1] = np.clip(hsv[:, :, 1], 0, 255)
result = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB)
return result
class BlurEffect(FilterEffect):
"""模糊特效"""
def __init__(self):
config = EffectConfig(
name="Blur",
effect_type=EffectType.BLUR,
category=EffectCategory.BASIC,
description="模糊效果",
parameters=[
EffectParameter("radius", "float", 5.0, 0.0, 50.0, 0.1, "模糊半径", "basic", 1),
EffectParameter("blur_type", "enum", "gaussian", options=["gaussian", "box", "motion", "radial"], description="模糊类型", group="advanced", 2),
EffectParameter("angle", "float", 0.0, 0.0, 360.0, 1.0, "运动模糊角度", "advanced", 3),
EffectParameter("center_x", "float", 0.5, 0.0, 1.0, 0.01, "径向模糊中心X", "advanced", 4),
EffectParameter("center_y", "float", 0.5, 0.0, 1.0, 0.01, "径向模糊中心Y", "advanced", 5)
]
)
super().__init__(config)
def _apply_filter_effect(self, frame: Any, intensity: float,
render_context: 'RenderContext') -> Any:
"""应用模糊效果"""
# 获取参数值
radius = self.get_parameter_value("radius") * intensity
blur_type = self.get_parameter_value("blur_type")
if isinstance(frame, Image.Image):
if blur_type == "gaussian":
return frame.filter(ImageFilter.GaussianBlur(radius=radius))
elif blur_type == "box":
return frame.filter(ImageFilter.BoxBlur(radius=radius))
elif blur_type == "motion":
angle = self.get_parameter_value("angle")
return self._apply_motion_blur(frame, radius, angle)
elif blur_type == "radial":
center_x = self.get_parameter_value("center_x")
center_y = self.get_parameter_value("center_y")
return self._apply_radial_blur(frame, radius, center_x, center_y)
else:
# 使用OpenCV进行模糊
return self._apply_blur_cv(frame, radius, blur_type)
def _apply_motion_blur(self, image: Image.Image, radius: float, angle: float) -> Image.Image:
"""应用运动模糊"""
# 创建运动模糊核
kernel_size = int(radius * 2 + 1)
kernel = np.zeros((kernel_size, kernel_size))
# 计算运动方向
angle_rad = np.deg2rad(angle)
dx = np.cos(angle_rad)
dy = np.sin(angle_rad)
# 填充核
for i in range(kernel_size):
x = int(kernel_size // 2 + dx * (i - kernel_size // 2))
y = int(kernel_size // 2 + dy * (i - kernel_size // 2))
if 0 <= x < kernel_size and 0 <= y < kernel_size:
kernel[y, x] = 1.0
# 归一化
kernel = kernel / np.sum(kernel) if np.sum(kernel) > 0 else kernel
# 应用卷积
img_array = np.array(image)
blurred = cv2.filter2D(img_array, -1, kernel)
return Image.fromarray(blurred.astype(np.uint8))
def _apply_radial_blur(self, image: Image.Image, radius: float,
center_x: float, center_y: float) -> Image.Image:
"""应用径向模糊"""
img_array = np.array(image)
height, width = img_array.shape[:2]
# 计算中心点
center = (int(width * center_x), int(height * center_y))
# 创建径向模糊效果
# 这里使用简化的实现,实际应该使用更复杂的算法
blurred = cv2.GaussianBlur(img_array, (0, 0), radius)
# 创建渐变遮罩
mask = np.zeros((height, width), dtype=np.float32)
for y in range(height):
for x in range(width):
distance = np.sqrt((x - center[0])**2 + (y - center[1])**2)
mask[y, x] = min(distance / max(width, height), 1.0)
# 应用遮罩
mask = mask * radius / 50.0
mask = np.clip(mask, 0, 1)
result = img_array * (1 - mask[:, :, np.newaxis]) + blurred * mask[:, :, np.newaxis]
return Image.fromarray(result.astype(np.uint8))
def _apply_blur_cv(self, frame: np.ndarray, radius: float, blur_type: str) -> np.ndarray:
"""使用OpenCV应用模糊"""
kernel_size = int(radius * 2 + 1) | 1 # 确保为奇数
if blur_type == "gaussian":
return cv2.GaussianBlur(frame, (kernel_size, kernel_size), 0)
elif blur_type == "box":
return cv2.blur(frame, (kernel_size, kernel_size))
elif blur_type == "motion":
# 运动模糊
angle = self.get_parameter_value("angle")
kernel = self._create_motion_kernel(kernel_size, angle)
return cv2.filter2D(frame, -1, kernel)
elif blur_type == "radial":
# 径向模糊
return self._apply_radial_blur_cv(frame, radius)
return frame
def _create_motion_kernel(self, size: int, angle: float) -> np.ndarray:
"""创建运动模糊核"""
kernel = np.zeros((size, size))
angle_rad = np.deg2rad(angle)
# 计算运动方向
dx = np.cos(angle_rad)
dy = np.sin(angle_rad)
# 填充核
center = size // 2
for i in range(size):
x = int(center + dx * (i - center))
y = int(center + dy * (i - center))
if 0 <= x < size and 0 <= y < size:
kernel[y, x] = 1.0
# 归一化
kernel = kernel / np.sum(kernel) if np.sum(kernel) > 0 else kernel
return kernel
def _apply_radial_blur_cv(self, frame: np.ndarray, radius: float) -> np.ndarray:
"""使用OpenCV应用径向模糊"""
# 简化的径向模糊实现
return cv2.GaussianBlur(frame, (0, 0), radius)
class SharpenEffect(FilterEffect):
"""锐化特效"""
def __init__(self):
config = EffectConfig(
name="Sharpen",
effect_type=EffectType.SHARPEN,
category=EffectCategory.PROFESSIONAL,
description="图像锐化",
parameters=[
EffectParameter("amount", "float", 1.0, 0.0, 5.0, 0.1, "锐化强度", "basic", 1),
EffectParameter("radius", "float", 1.0, 0.1, 5.0, 0.1, "锐化半径", "advanced", 2),
EffectParameter("threshold", "float", 0.0, 0.0, 10.0, 0.1, "阈值", "advanced", 3),
EffectParameter("edge_mask", "bool", False, description="边缘遮罩", group="advanced")
]
)
super().__init__(config)
def _apply_filter_effect(self, frame: Any, intensity: float,
render_context: 'RenderContext') -> Any:
"""应用锐化效果"""
# 获取参数值
amount = self.get_parameter_value("amount") * intensity
radius = self.get_parameter_value("radius")
threshold = self.get_parameter_value("threshold")
edge_mask = self.get_parameter_value("edge_mask")
if isinstance(frame, Image.Image):
# 使用PIL的锐化滤镜
sharpen_filter = ImageFilter.UnsharpMask(
radius=radius,
percent=int(amount * 100),
threshold=int(threshold)
)
return frame.filter(sharpen_filter)
else:
# 使用OpenCV进行锐化
return self._apply_sharpen_cv(frame, amount, radius, threshold, edge_mask)
def _apply_sharpen_cv(self, frame: np.ndarray, amount: float, radius: float,
threshold: float, edge_mask: bool) -> np.ndarray:
"""使用OpenCV应用锐化"""
# 创建锐化核
kernel_size = int(radius * 2 + 1) | 1
# 高斯模糊
blurred = cv2.GaussianBlur(frame, (kernel_size, kernel_size), 0)
# 计算细节(原图减去模糊图)
detail = frame.astype(np.float32) - blurred.astype(np.float32)
# 应用阈值
if threshold > 0:
detail = np.where(np.abs(detail) > threshold, detail, 0)
# 增强细节
enhanced_detail = detail * amount
# 加回原图
sharpened = frame.astype(np.float32) + enhanced_detail
# 限制范围
result = np.clip(sharpened, 0, 255).astype(np.uint8)
# 边缘遮罩处理
if edge_mask:
# 创建边缘遮罩
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
edges = cv2.Canny(gray, 50, 150)
edge_mask_array = edges > 0
# 只在边缘区域应用锐化
result = np.where(edge_mask_array[:, :, np.newaxis], result, frame)
return result
10.2.3 变换特效实现
实现几何变换特效:
class TransformEffect(Effect):
"""变换特效基类"""
def __init__(self, config: EffectConfig = None):
if config is None:
config = EffectConfig(
name="Transform Effect",
effect_type=EffectType.TRANSFORM,
parameters=[
EffectParameter("position_x", "float", 0.0, description="X位置", group="position"),
EffectParameter("position_y", "float", 0.0, description="Y位置", group="position"),
EffectParameter("scale_x", "float", 1.0, 0.0, 10.0, 0.01, "X缩放", "scale", 1),
EffectParameter("scale_y", "float", 1.0, 0.0, 10.0, 0.01, "Y缩放", "scale", 2),
EffectParameter("rotation", "float", 0.0, -360.0, 360.0, 1.0, "旋转角度", "rotation", 1),
EffectParameter("anchor_x", "float", 0.5, 0.0, 1.0, 0.01, "锚点X", "anchor", 1),
EffectParameter("anchor_y", "float", 0.5, 0.0, 1.0, 0.01, "锚点Y", "anchor", 2),
EffectParameter("uniform_scale", "bool", True, description="等比缩放", group="scale")
]
)
super().__init__(config)
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用变换到帧"""
if not self.is_active_at_time(time):
return frame
# 获取变换参数
transform_params = self._get_transform_parameters(time)
# 应用变换
if isinstance(frame, Image.Image):
return self._apply_transform_pil(frame, transform_params)
else:
return self._apply_transform_cv(frame, transform_params)
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""变换特效不处理音频"""
return audio_data
def _get_transform_parameters(self, time: float) -> Dict[str, float]:
"""获取变换参数"""
params = {}
# 位置参数
params["position_x"] = self.get_parameter_value("position_x", time)
params["position_y"] = self.get_parameter_value("position_y", time)
# 缩放参数
scale_x = self.get_parameter_value("scale_x", time)
scale_y = self.get_parameter_value("scale_y", time)
uniform_scale = self.get_parameter_value("uniform_scale", time)
if uniform_scale:
params["scale_x"] = scale_x
params["scale_y"] = scale_x # 使用X轴缩放值
else:
params["scale_x"] = scale_x
params["scale_y"] = scale_y
# 旋转参数
params["rotation"] = self.get_parameter_value("rotation", time)
# 锚点参数
params["anchor_x"] = self.get_parameter_value("anchor_x", time)
params["anchor_y"] = self.get_parameter_value("anchor_y", time)
return params
def _apply_transform_pil(self, image: Image.Image, params: Dict[str, float]) -> Image.Image:
"""使用PIL应用变换"""
width, height = image.size
# 计算变换矩阵
transform_matrix = self._calculate_transform_matrix(width, height, params)
# 应用仿射变换
# PIL的transform方法需要6个参数的变换矩阵
affine_matrix = (
transform_matrix[0, 0], transform_matrix[0, 1], transform_matrix[0, 2],
transform_matrix[1, 0], transform_matrix[1, 1], transform_matrix[1, 2]
)
# 计算输出图像大小
output_size = self._calculate_output_size(width, height, params)
# 应用变换
transformed = image.transform(
output_size,
Image.AFFINE,
affine_matrix,
Image.BICUBIC
)
return transformed
def _apply_transform_cv(self, frame: np.ndarray, params: Dict[str, float]) -> np.ndarray:
"""使用OpenCV应用变换"""
height, width = frame.shape[:2]
# 计算变换矩阵
transform_matrix = self._calculate_transform_matrix(width, height, params)
# 计算输出图像大小
output_size = self._calculate_output_size(width, height, params)
# 应用仿射变换
transformed = cv2.warpAffine(
frame,
transform_matrix[:2], # OpenCV只需要2x3矩阵
output_size,
flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0, 0)
)
return transformed
def _calculate_transform_matrix(self, width: int, height: int,
params: Dict[str, float]) -> np.ndarray:
"""计算变换矩阵"""
# 创建3x3单位矩阵
matrix = np.eye(3, dtype=np.float32)
# 平移到原点(基于锚点)
anchor_x = params["anchor_x"] * width
anchor_y = params["anchor_y"] * height
matrix[0, 2] = -anchor_x
matrix[1, 2] = -anchor_y
# 缩放
scale_matrix = np.eye(3, dtype=np.float32)
scale_matrix[0, 0] = params["scale_x"]
scale_matrix[1, 1] = params["scale_y"]
matrix = np.dot(matrix, scale_matrix)
# 旋转
angle_rad = np.radians(params["rotation"])
cos_a = np.cos(angle_rad)
sin_a = np.sin(angle_rad)
rotation_matrix = np.eye(3, dtype=np.float32)
rotation_matrix[0, 0] = cos_a
rotation_matrix[0, 1] = -sin_a
rotation_matrix[1, 0] = sin_a
rotation_matrix[1, 1] = cos_a
matrix = np.dot(matrix, rotation_matrix)
# 平移到最终位置
matrix[0, 2] += params["position_x"] + anchor_x
matrix[1, 2] += params["position_y"] + anchor_y
return matrix
def _calculate_output_size(self, width: int, height: int,
params: Dict[str, float]) -> Tuple[int, int]:
"""计算输出图像大小"""
# 基于缩放因子计算
output_width = int(width * abs(params["scale_x"]))
output_height = int(height * abs(params["scale_y"]))
# 确保最小尺寸
output_width = max(output_width, 1)
output_height = max(output_height, 1)
return (output_width, output_height)
class RotationEffect(TransformEffect):
"""旋转特效"""
def __init__(self):
config = EffectConfig(
name="Rotation",
effect_type=EffectType.TRANSFORM,
category=EffectCategory.BASIC,
description="旋转效果",
parameters=[
EffectParameter("angle", "float", 0.0, -360.0, 360.0, 1.0, "旋转角度", "basic", 1),
EffectParameter("rotation_speed", "float", 0.0, -360.0, 360.0, 1.0, "旋转速度(度/秒)", "animation", 2),
EffectParameter("center_x", "float", 0.5, 0.0, 1.0, 0.01, "旋转中心X", "center", 1),
EffectParameter("center_y", "float", 0.5, 0.0, 1.0, 0.01, "旋转中心Y", "center", 2),
EffectParameter("clockwise", "bool", True, description="顺时针旋转", group="direction")
]
)
super().__init__(config)
def _get_transform_parameters(self, time: float) -> Dict[str, float]:
"""获取旋转参数"""
params = {}
# 基础旋转角度
base_angle = self.get_parameter_value("angle", time)
# 动画旋转
rotation_speed = self.get_parameter_value("rotation_speed", time)
if rotation_speed != 0:
time_in_effect = time - self.start_time
animated_angle = rotation_speed * time_in_effect
# 方向控制
clockwise = self.get_parameter_value("clockwise", time)
if not clockwise:
animated_angle = -animated_angle
base_angle += animated_angle
params["rotation"] = base_angle
params["position_x"] = 0
params["position_y"] = 0
params["scale_x"] = 1
params["scale_y"] = 1
params["anchor_x"] = self.get_parameter_value("center_x", time)
params["anchor_y"] = self.get_parameter_value("center_y", time)
return params
10.3 特效管理器实现
10.3.1 特效工厂与注册
实现特效的创建和管理:
class EffectFactory:
"""特效工厂"""
def __init__(self):
self._effect_registry: Dict[str, type] = {}
self._effect_presets: Dict[str, Dict[str, Any]] = {}
self._effect_categories: Dict[EffectCategory, List[str]] = {}
self._register_builtin_effects()
def register_effect(self, effect_class: type, name: str = None) -> None:
"""注册特效类"""
if name is None:
name = effect_class.__name__
# 验证特效类
if not issubclass(effect_class, Effect):
raise ValueError(f"特效类必须继承自Effect基类: {effect_class}")
# 注册特效
self._effect_registry[name] = effect_class
# 更新分类索引
effect_instance = effect_class()
category = effect_instance.config.category
if category not in self._effect_categories:
self._effect_categories[category] = []
if name not in self._effect_categories[category]:
self._effect_categories[category].append(name)
def create_effect(self, effect_name: str, **kwargs) -> Optional[Effect]:
"""创建特效实例"""
if effect_name not in self._effect_registry:
return None
effect_class = self._effect_registry[effect_name]
try:
effect_instance = effect_class(**kwargs)
return effect_instance
except Exception as e:
print(f"创建特效失败 {effect_name}: {e}")
return None
def create_effect_from_preset(self, preset_name: str) -> Optional[Effect]:
"""从预设创建特效"""
if preset_name not in self._effect_presets:
return None
preset_data = self._effect_presets[preset_name]
effect_name = preset_data.get("effect_name")
if not effect_name:
return None
effect = self.create_effect(effect_name)
if effect and "settings" in preset_data:
effect.import_settings(preset_data["settings"])
return effect
def get_available_effects(self, category: EffectCategory = None) -> List[str]:
"""获取可用特效列表"""
if category is None:
return list(self._effect_registry.keys())
else:
return self._effect_categories.get(category, [])
def get_effect_categories(self) -> List[EffectCategory]:
"""获取特效分类列表"""
return list(self._effect_categories.keys())
def get_effects_by_category(self) -> Dict[EffectCategory, List[str]]:
"""按分类获取特效"""
return self._effect_categories.copy()
def save_preset(self, effect: Effect, preset_name: str,
description: str = "") -> bool:
"""保存特效预设"""
try:
preset_data = {
"effect_name": effect.config.name,
"settings": effect.export_settings(),
"description": description,
"created_at": time.time(),
"category": effect.config.category.value
}
self._effect_presets[preset_name] = preset_data
return True
except Exception as e:
print(f"保存预设失败: {e}")
return False
def delete_preset(self, preset_name: str) -> bool:
"""删除特效预设"""
if preset_name in self._effect_presets:
del self._effect_presets[preset_name]
return True
return False
def get_preset_names(self) -> List[str]:
"""获取预设名称列表"""
return list(self._effect_presets.keys())
def get_preset_info(self, preset_name: str) -> Optional[Dict[str, Any]]:
"""获取预设信息"""
return self._effect_presets.get(preset_name)
def export_presets(self) -> Dict[str, Any]:
"""导出所有预设"""
return self._effect_presets.copy()
def import_presets(self, presets_data: Dict[str, Any]) -> None:
"""导入预设"""
self._effect_presets.update(presets_data)
def _register_builtin_effects(self) -> None:
"""注册内置特效"""
# 基础滤镜
self.register_effect(BrightnessContrastEffect, "BrightnessContrast")
self.register_effect(SaturationEffect, "Saturation")
self.register_effect(BlurEffect, "Blur")
self.register_effect(SharpenEffect, "Sharpen")
# 变换特效
self.register_effect(TransformEffect, "Transform")
self.register_effect(RotationEffect, "Rotation")
# 可以添加更多内置特效
print(f"已注册 {len(self._effect_registry)} 个内置特效")
class EffectManager:
"""特效管理器"""
def __init__(self):
self.effect_factory = EffectFactory()
self.active_effects: Dict[str, Effect] = {}
self.effect_chains: Dict[str, List[str]] = {} # 特效链
self.render_cache: Dict[str, Any] = {}
self.cache_size_limit = 1000
self.render_stats = {
"total_applications": 0,
"cache_hits": 0,
"cache_misses": 0,
"processing_time": 0.0
}
self.lock = Lock()
def add_effect(self, effect: Effect, effect_id: str = None) -> str:
"""添加特效"""
if effect_id is None:
effect_id = effect.id
with self.lock:
self.active_effects[effect_id] = effect
self._clear_cache()
return effect_id
def remove_effect(self, effect_id: str) -> bool:
"""移除特效"""
with self.lock:
if effect_id in self.active_effects:
del self.active_effects[effect_id]
# 从特效链中移除
for chain_name, chain_effects in self.effect_chains.items():
if effect_id in chain_effects:
chain_effects.remove(effect_id)
self._clear_cache()
return True
return False
def create_effect_chain(self, chain_name: str, effect_ids: List[str]) -> bool:
"""创建特效链"""
with self.lock:
# 验证所有特效都存在
for effect_id in effect_ids:
if effect_id not in self.active_effects:
return False
self.effect_chains[chain_name] = effect_ids.copy()
return True
def apply_effects_to_frame(self, frame: Any, time: float,
effect_ids: List[str] = None) -> Any:
"""应用特效到帧"""
start_time = time.time()
with self.lock:
# 确定要应用的特效
if effect_ids is None:
effects_to_apply = list(self.active_effects.values())
else:
effects_to_apply = [
self.active_effects[eid] for eid in effect_ids
if eid in self.active_effects
]
# 按时间排序特效
effects_to_apply.sort(key=lambda e: e.start_time)
# 应用每个特效
result_frame = frame
for effect in effects_to_apply:
if effect.is_active_at_time(time):
result_frame = effect.apply_to_frame(result_frame, time, None)
# 更新统计
processing_time = time.time() - start_time
self.render_stats["total_applications"] += 1
self.render_stats["processing_time"] += processing_time
return result_frame
def apply_effect_chain(self, frame: Any, time: float,
chain_name: str) -> Any:
"""应用特效链"""
if chain_name not in self.effect_chains:
return frame
effect_ids = self.effect_chains[chain_name]
return self.apply_effects_to_frame(frame, time, effect_ids)
def get_effect_preview(self, effect_id: str, sample_frame: Any,
time: float = 0.0) -> Optional[Any]:
"""获取特效预览"""
if effect_id not in self.active_effects:
return None
effect = self.active_effects[effect_id]
return effect.apply_to_frame(sample_frame, time, None)
def enable_effect(self, effect_id: str) -> bool:
"""启用特效"""
if effect_id in self.active_effects:
self.active_effects[effect_id].enabled = True
self._clear_cache()
return True
return False
def disable_effect(self, effect_id: str) -> bool:
"""禁用特效"""
if effect_id in self.active_effects:
self.active_effects[effect_id].enabled = False
self._clear_cache()
return True
return False
def update_effect_parameters(self, effect_id: str,
parameters: Dict[str, Any]) -> bool:
"""更新特效参数"""
if effect_id not in self.active_effects:
return False
effect = self.active_effects[effect_id]
for param_name, value in parameters.items():
effect.set_parameter_value(param_name, value)
self._clear_cache()
return True
def get_effect_info(self, effect_id: str) -> Optional[Dict[str, Any]]:
"""获取特效信息"""
if effect_id not in self.active_effects:
return None
effect = self.active_effects[effect_id]
return {
"id": effect.id,
"name": effect.config.name,
"type": effect.config.effect_type.value,
"category": effect.config.category.value,
"enabled": effect.enabled,
"parameters": effect.parameters.copy(),
"start_time": effect.start_time,
"duration": effect.duration,
"intensity": effect.intensity
}
def get_active_effects_info(self) -> Dict[str, Dict[str, Any]]:
"""获取所有活动特效的信息"""
return {
effect_id: self.get_effect_info(effect_id)
for effect_id in self.active_effects.keys()
}
def export_effect_settings(self, effect_id: str) -> Optional[Dict[str, Any]]:
"""导出特效设置"""
if effect_id not in self.active_effects:
return None
return self.active_effects[effect_id].export_settings()
def import_effect_settings(self, effect_id: str,
settings: Dict[str, Any]) -> bool:
"""导入特效设置"""
if effect_id not in self.active_effects:
return False
return self.active_effects[effect_id].import_settings(settings)
def _clear_cache(self) -> None:
"""清空缓存"""
self.render_cache.clear()
def get_render_statistics(self) -> Dict[str, Any]:
"""获取渲染统计信息"""
total_requests = self.render_stats["cache_hits"] + self.render_stats["cache_misses"]
cache_hit_rate = (
self.render_stats["cache_hits"] / total_requests
if total_requests > 0 else 0.0
)
return {
"total_applications": self.render_stats["total_applications"],
"cache_hits": self.render_stats["cache_hits"],
"cache_misses": self.render_stats["cache_misses"],
"cache_hit_rate": cache_hit_rate,
"average_processing_time": (
self.render_stats["processing_time"] / self.render_stats["total_applications"]
if self.render_stats["total_applications"] > 0 else 0.0
),
"active_effects_count": len(self.active_effects),
"effect_chains_count": len(self.effect_chains)
}
10.4 高级特效实现
10.4.1 粒子系统特效
实现粒子系统特效:
class Particle:
"""粒子"""
def __init__(self, x: float, y: float, vx: float, vy: float,
life: float, max_life: float, size: float, color: Tuple[int, int, int]):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.life = life
self.max_life = max_life
self.size = size
self.color = color
self.alpha = 1.0
self.rotation = 0.0
self.rotation_speed = 0.0
def update(self, delta_time: float) -> None:
"""更新粒子状态"""
# 更新位置
self.x += self.vx * delta_time
self.y += self.vy * delta_time
# 更新旋转
self.rotation += self.rotation_speed * delta_time
# 更新生命周期
self.life -= delta_time
self.alpha = max(0.0, self.life / self.max_life)
def is_alive(self) -> bool:
"""检查粒子是否存活"""
return self.life > 0
class ParticleSystem:
"""粒子系统"""
def __init__(self, max_particles: int = 1000):
self.particles: List[Particle] = []
self.max_particles = max_particles
self.emission_rate = 100.0 # 粒子/秒
self.emission_timer = 0.0
# 发射器属性
self.emitter_x = 0.5
self.emitter_y = 0.5
self.emitter_size = 0.1
self.emitter_shape = "point" # "point", "circle", "rectangle"
# 粒子属性范围
self.min_velocity = 50.0
self.max_velocity = 200.0
self.min_life = 1.0
self.max_life = 3.0
self.min_size = 2.0
self.max_size = 10.0
# 物理属性
self.gravity_x = 0.0
self.gravity_y = 100.0
self.wind_x = 0.0
self.wind_y = 0.0
# 颜色属性
self.start_color = (255, 255, 255)
self.end_color = (255, 255, 255)
self.color_over_lifetime = True
def update(self, delta_time: float) -> None:
"""更新粒子系统"""
# 发射新粒子
self._emit_particles(delta_time)
# 更新现有粒子
for particle in self.particles[:]:
particle.update(delta_time)
# 应用物理力
self._apply_forces(particle, delta_time)
# 更新颜色
if self.color_over_lifetime:
self._update_particle_color(particle)
# 移除死亡粒子
if not particle.is_alive():
self.particles.remove(particle)
def _emit_particles(self, delta_time: float) -> None:
"""发射粒子"""
self.emission_timer += delta_time
# 计算要发射的粒子数量
particles_to_emit = int(self.emission_rate * self.emission_timer)
if particles_to_emit > 0:
for _ in range(particles_to_emit):
if len(self.particles) < self.max_particles:
particle = self._create_particle()
self.particles.append(particle)
self.emission_timer = 0.0
def _create_particle(self) -> Particle:
"""创建粒子"""
# 计算发射位置
x, y = self._get_emission_position()
# 计算速度
vx, vy = self._get_emission_velocity()
# 计算生命周期和大小
life = random.uniform(self.min_life, self.max_life)
size = random.uniform(self.min_size, self.max_size)
# 创建粒子
particle = Particle(
x=x, y=y, vx=vx, vy=vy,
life=life, max_life=life, size=size,
color=self.start_color
)
return particle
def _get_emission_position(self) -> Tuple[float, float]:
"""获取发射位置"""
if self.emitter_shape == "point":
return (self.emitter_x, self.emitter_y)
elif self.emitter_shape == "circle":
angle = random.uniform(0, 2 * np.pi)
radius = random.uniform(0, self.emitter_size)
x = self.emitter_x + radius * np.cos(angle)
y = self.emitter_y + radius * np.sin(angle)
return (x, y)
elif self.emitter_shape == "rectangle":
x = random.uniform(-self.emitter_size, self.emitter_size) + self.emitter_x
y = random.uniform(-self.emitter_size, self.emitter_size) + self.emitter_y
return (x, y)
else:
return (self.emitter_x, self.emitter_y)
def _get_emission_velocity(self) -> Tuple[float, float]:
"""获取发射速度"""
speed = random.uniform(self.min_velocity, self.max_velocity)
angle = random.uniform(0, 2 * np.pi)
vx = speed * np.cos(angle)
vy = speed * np.sin(angle)
return (vx, vy)
def _apply_forces(self, particle: Particle, delta_time: float) -> None:
"""应用物理力"""
# 重力
particle.vx += self.gravity_x * delta_time
particle.vy += self.gravity_y * delta_time
# 风力
particle.vx += self.wind_x * delta_time
particle.vy += self.wind_y * delta_time
def _update_particle_color(self, particle: Particle) -> None:
"""更新粒子颜色"""
# 根据生命周期插值颜色
life_ratio = particle.life / particle.max_life
start_r, start_g, start_b = self.start_color
end_r, end_g, end_b = self.end_color
r = int(start_r * life_ratio + end_r * (1 - life_ratio))
g = int(start_g * life_ratio + end_g * (1 - life_ratio))
b = int(start_b * life_ratio + end_b * (1 - life_ratio))
particle.color = (r, g, b)
class ParticleEffect(Effect):
"""粒子特效"""
def __init__(self):
config = EffectConfig(
name="Particle System",
effect_type=EffectType.PARTICLE,
category=EffectCategory.CREATIVE,
description="粒子系统特效",
parameters=[
EffectParameter("particle_count", "int", 500, 10, 2000, 10, "粒子数量", "basic", 1),
EffectParameter("emission_rate", "float", 100.0, 0.0, 1000.0, 1.0, "发射速率", "emission", 2),
EffectParameter("particle_life", "float", 2.0, 0.1, 10.0, 0.1, "粒子生命周期", "particle", 3),
EffectParameter("gravity", "float", 100.0, -500.0, 500.0, 1.0, "重力", "physics", 4),
EffectParameter("wind_force", "float", 0.0, -200.0, 200.0, 1.0, "风力", "physics", 5),
EffectParameter("start_color", "color", (255, 255, 255), description="起始颜色", group="color"),
EffectParameter("end_color", "color", (255, 100, 0), description="结束颜色", group="color"),
EffectParameter("emitter_shape", "enum", "point", options=["point", "circle", "rectangle"], description="发射器形状", group="emitter"),
EffectParameter("blend_mode", "enum", "add", options=["normal", "add", "multiply", "screen"], description="混合模式", group="rendering")
]
)
super().__init__(config)
self.particle_system = None
self.last_update_time = 0.0
self.time_accumulator = 0.0
self.particle_texture = None
self.blend_mode = "add"
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用粒子特效到帧"""
if not self.is_active_at_time(time):
return frame
# 初始化粒子系统
if self.particle_system is None:
self._initialize_particle_system()
# 更新粒子系统
delta_time = time - self.last_update_time
self.last_update_time = time
self.particle_system.update(delta_time)
# 渲染粒子
if isinstance(frame, Image.Image):
return self._render_particles_pil(frame, time)
else:
return self._render_particles_cv(frame, time)
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""粒子特效不处理音频"""
return audio_data
def _initialize_particle_system(self) -> None:
"""初始化粒子系统"""
# 获取参数值
particle_count = self.get_parameter_value("particle_count")
emission_rate = self.get_parameter_value("emission_rate")
particle_life = self.get_parameter_value("particle_life")
gravity = self.get_parameter_value("gravity")
wind_force = self.get_parameter_value("wind_force")
start_color = self.get_parameter_value("start_color")
end_color = self.get_parameter_value("end_color")
emitter_shape = self.get_parameter_value("emitter_shape")
blend_mode = self.get_parameter_value("blend_mode")
# 创建粒子系统
self.particle_system = ParticleSystem(max_particles=particle_count)
self.particle_system.emission_rate = emission_rate
self.particle_system.min_life = particle_life * 0.8
self.particle_system.max_life = particle_life * 1.2
self.particle_system.gravity_y = gravity
self.particle_system.wind_x = wind_force
self.particle_system.start_color = start_color
self.particle_system.end_color = end_color
self.particle_system.emitter_shape = emitter_shape
self.blend_mode = blend_mode
# 设置发射器位置为帧中心
self.particle_system.emitter_x = 0.5
self.particle_system.emitter_y = 0.5
def _render_particles_pil(self, frame: Image.Image, time: float) -> Image.Image:
"""使用PIL渲染粒子"""
# 转换为RGBA模式以支持透明度
if frame.mode != 'RGBA':
frame = frame.convert('RGBA')
frame_array = np.array(frame)
height, width = frame_array.shape[:2]
# 创建粒子图层
particle_layer = np.zeros((height, width, 4), dtype=np.float32)
# 渲染每个粒子
for particle in self.particle_system.particles:
if particle.is_alive():
self._render_particle_pil(particle, particle_layer, width, height)
# 混合粒子图层到原始帧
result = self._blend_layers_pil(frame_array, particle_layer)
return Image.fromarray(result.astype(np.uint8))
def _render_particles_cv(self, frame: np.ndarray, time: float) -> np.ndarray:
"""使用OpenCV渲染粒子"""
height, width = frame.shape[:2]
# 创建粒子图层
if frame.shape[2] == 3: # RGB
particle_layer = np.zeros((height, width, 3), dtype=np.float32)
else: # RGBA
particle_layer = np.zeros((height, width, 4), dtype=np.float32)
# 渲染每个粒子
for particle in self.particle_system.particles:
if particle.is_alive():
self._render_particle_cv(particle, particle_layer, width, height)
# 混合粒子图层到原始帧
result = self._blend_layers_cv(frame, particle_layer)
return result
def _render_particle_pil(self, particle: Particle, layer: np.ndarray,
width: int, height: int) -> None:
"""渲染单个粒子(PIL)"""
# 计算粒子在图像中的位置
x = int(particle.x * width)
y = int(particle.y * height)
size = int(particle.size)
# 确保粒子在图像范围内
x = max(0, min(x, width - 1))
y = max(0, min(y, height - 1))
# 创建粒子形状(圆形)
y_coords, x_coords = np.ogrid[-y:height-y, -x:width-x]
mask = x_coords*x_coords + y_coords*y_coords <= size*size
# 应用粒子颜色
r, g, b = particle.color
alpha = particle.alpha
# 将粒子渲染到图层
layer[mask, 0] = layer[mask, 0] * (1 - alpha) + r * alpha # R
layer[mask, 1] = layer[mask, 1] * (1 - alpha) + g * alpha # G
layer[mask, 2] = layer[mask, 2] * (1 - alpha) + b * alpha # B
layer[mask, 3] = np.maximum(layer[mask, 3], alpha * 255) # A
def _render_particle_cv(self, particle: Particle, layer: np.ndarray,
width: int, height: int) -> None:
"""渲染单个粒子(OpenCV)"""
# 计算粒子在图像中的位置
x = int(particle.x * width)
y = int(particle.y * height)
size = int(particle.size)
# 创建圆形遮罩
center = (x, y)
axes = (size, size)
angle = 0
startAngle = 0
endAngle = 360
# 创建临时遮罩
mask = np.zeros((height, width), dtype=np.uint8)
cv2.ellipse(mask, center, axes, angle, startAngle, endAngle, 255, -1)
# 应用粒子颜色
r, g, b = particle.color
alpha = particle.alpha
# 将粒子渲染到图层
if layer.shape[2] == 3: # RGB
for c in range(3):
color_value = [r, g, b][c]
layer[:, :, c] = np.where(mask > 0,
layer[:, :, c] * (1 - alpha) + color_value * alpha,
layer[:, :, c])
else: # RGBA
for c in range(3):
color_value = [r, g, b][c]
layer[:, :, c] = np.where(mask > 0,
layer[:, :, c] * (1 - alpha) + color_value * alpha,
layer[:, :, c])
layer[:, :, 3] = np.where(mask > 0,
np.maximum(layer[:, :, 3], alpha * 255),
layer[:, :, 3])
def _blend_layers_pil(self, base_frame: np.ndarray,
particle_layer: np.ndarray) -> np.ndarray:
"""混合图层(PIL)"""
# 转换为浮点数
base_float = base_frame.astype(np.float32) / 255.0
particle_float = particle_layer / 255.0
# 根据混合模式进行混合
if self.blend_mode == "add":
# 加法混合
result = base_float + particle_float
result = np.clip(result, 0, 1)
elif self.blend_mode == "multiply":
# 乘法混合
result = base_float * (1 - particle_float[:, :, 3:4]) + \
particle_float[:, :, :3] * particle_float[:, :, 3:4]
elif self.blend_mode == "screen":
# 屏幕混合
result = 1 - (1 - base_float) * (1 - particle_float)
else: # normal
# 正常混合
alpha = particle_float[:, :, 3:4]
result = base_float * (1 - alpha) + particle_float[:, :, :3] * alpha
return (result * 255).astype(np.uint8)
def _blend_layers_cv(self, base_frame: np.ndarray,
particle_layer: np.ndarray) -> np.ndarray:
"""混合图层(OpenCV)"""
# 将粒子图层转换为uint8
particle_uint8 = np.clip(particle_layer, 0, 255).astype(np.uint8)
# 根据混合模式进行混合
if self.blend_mode == "add":
# 加法混合
result = cv2.addWeighted(base_frame, 1.0, particle_uint8, 1.0, 0)
elif self.blend_mode == "multiply":
# 乘法混合
result = cv2.multiply(base_frame, particle_uint8, scale=1/255.0)
elif self.blend_mode == "screen":
# 屏幕混合
result = 255 - cv2.multiply(255 - base_frame, 255 - particle_uint8, scale=1/255.0)
else: # normal
# 正常混合(需要alpha通道)
if base_frame.shape[2] == 4 and particle_layer.shape[2] == 4:
result = self._alpha_blend(base_frame, particle_uint8)
else:
result = cv2.addWeighted(base_frame, 0.7, particle_uint8, 0.3, 0)
return result
def _alpha_blend(self, base: np.ndarray, overlay: np.ndarray) -> np.ndarray:
"""Alpha混合"""
# 转换为浮点数
base_float = base.astype(np.float32) / 255.0
overlay_float = overlay.astype(np.float32) / 255.0
# 计算混合结果
alpha_overlay = overlay_float[:, :, 3:4]
alpha_base = base_float[:, :, 3:4]
# 混合alpha
alpha_result = alpha_overlay + alpha_base * (1 - alpha_overlay)
# 混合颜色
color_result = overlay_float[:, :, :3] * alpha_overlay + \
base_float[:, :, :3] * alpha_base * (1 - alpha_overlay)
# 组合结果
result = np.zeros_like(base_float)
result[:, :, :3] = color_result
result[:, :, 3:4] = alpha_result
return (result * 255).astype(np.uint8)
# 光照特效
class LightingEffect(Effect):
"""光照特效"""
def __init__(self):
config = EffectConfig(
name="Lighting",
effect_type=EffectType.LIGHTING,
category=EffectCategory.PROFESSIONAL,
description="光照效果",
parameters=[
EffectParameter("light_type", "enum", "point", options=["point", "directional", "spot"], description="光源类型", group="basic"),
EffectParameter("intensity", "float", 1.0, 0.0, 5.0, 0.1, "光照强度", "basic", 1),
EffectParameter("light_color", "color", (255, 255, 200), description="光照颜色", group="basic"),
EffectParameter("position_x", "float", 0.5, 0.0, 1.0, 0.01, "光源位置X", "position", 1),
EffectParameter("position_y", "float", 0.3, 0.0, 1.0, 0.01, "光源位置Y", "position", 2),
EffectParameter("falloff", "float", 1.0, 0.0, 3.0, 0.1, "衰减系数", "advanced", 3),
EffectParameter("radius", "float", 0.5, 0.0, 2.0, 0.01, "光照半径", "advanced", 4)
]
)
super().__init__(config)
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用光照效果到帧"""
if not self.is_active_at_time(time):
return frame
# 获取光照参数
light_type = self.get_parameter_value("light_type", time)
intensity = self.get_parameter_value("intensity", time)
light_color = self.get_parameter_value("light_color", time)
position_x = self.get_parameter_value("position_x", time)
position_y = self.get_parameter_value("position_y", time)
falloff = self.get_parameter_value("falloff", time)
radius = self.get_parameter_value("radius", time)
# 应用光照
if isinstance(frame, Image.Image):
return self._apply_lighting_pil(frame, light_type, intensity, light_color,
position_x, position_y, falloff, radius)
else:
return self._apply_lighting_cv(frame, light_type, intensity, light_color,
position_x, position_y, falloff, radius)
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""光照特效不处理音频"""
return audio_data
def _apply_lighting_pil(self, image: Image.Image, light_type: str, intensity: float,
light_color: Tuple[int, int, int], position_x: float, position_y: float,
falloff: float, radius: float) -> Image.Image:
"""使用PIL应用光照效果"""
# 转换为numpy数组
img_array = np.array(image)
if image.mode == 'RGB':
img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
# 使用OpenCV算法
result_array = self._apply_lighting_cv(img_array, light_type, intensity, light_color,
position_x, position_y, falloff, radius)
# 转换回PIL格式
if image.mode == 'RGB':
result_array = cv2.cvtColor(result_array, cv2.COLOR_BGR2RGB)
return Image.fromarray(result_array)
def _apply_lighting_cv(self, frame: np.ndarray, light_type: str, intensity: float,
light_color: Tuple[int, int, int], position_x: float, position_y: float,
falloff: float, radius: float) -> np.ndarray:
"""使用OpenCV应用光照效果"""
height, width = frame.shape[:2]
# 创建光照图
lighting_map = self._create_lighting_map(width, height, light_type, intensity,
light_color, position_x, position_y,
falloff, radius)
# 应用光照到图像
result = frame.astype(np.float32)
if len(result.shape) == 3:
# 彩色图像
for c in range(3):
result[:, :, c] = result[:, :, c] * lighting_map[:, :, c] / 255.0
else:
# 灰度图像
result = result * lighting_map[:, :, 0] / 255.0
# 限制范围
result = np.clip(result, 0, 255)
return result.astype(np.uint8)
def _create_lighting_map(self, width: int, height: int, light_type: str,
intensity: float, light_color: Tuple[int, int, int],
position_x: float, position_y: float, falloff: float,
radius: float) -> np.ndarray:
"""创建光照图"""
# 创建坐标网格
x_coords = np.arange(width)
y_coords = np.arange(height)
x_grid, y_grid = np.meshgrid(x_coords, y_coords)
# 计算光源位置
light_x = int(position_x * width)
light_y = int(position_y * height)
# 计算距离
distances = np.sqrt((x_grid - light_x)**2 + (y_grid - light_y)**2)
# 根据光源类型创建光照强度图
if light_type == "point":
# 点光源
max_distance = radius * max(width, height)
intensity_map = np.maximum(0, 1 - distances / max_distance)**falloff
elif light_type == "directional":
# 方向光源
intensity_map = np.ones((height, width)) * intensity
elif light_type == "spot":
# 聚光灯(简化实现)
max_distance = radius * max(width, height)
intensity_map = np.maximum(0, 1 - distances / max_distance)**(falloff * 2)
else:
intensity_map = np.ones((height, width))
# 应用强度
intensity_map *= intensity
# 应用颜色
r, g, b = light_color
lighting_map = np.zeros((height, width, 3), dtype=np.float32)
lighting_map[:, :, 0] = intensity_map * r # R
lighting_map[:, :, 1] = intensity_map * g # G
lighting_map[:, :, 2] = intensity_map * b # B
# 确保基础亮度
lighting_map = np.maximum(lighting_map, 0.2)
return lighting_map
# 转场特效
class TransitionEffect(Effect):
"""转场特效"""
def __init__(self):
config = EffectConfig(
name="Transition",
effect_type=EffectType.TRANSITION,
category=EffectCategory.PROFESSIONAL,
description="转场效果",
parameters=[
EffectParameter("transition_type", "enum", "fade", options=["fade", "slide", "wipe", "dissolve"], description="转场类型", group="basic"),
EffectParameter("duration", "float", 1.0, 0.1, 5.0, 0.1, "转场持续时间", "timing", 1),
EffectParameter("direction", "enum", "left", options=["left", "right", "up", "down"], description="滑动方向", group="slide"),
EffectParameter("easing", "enum", "linear", options=["linear", "ease_in", "ease_out", "ease_in_out"], description="缓动函数", group="timing"),
EffectParameter("reverse", "bool", False, description="反向转场", group="advanced")
]
)
super().__init__(config)
self.next_frame = None
def apply_to_frame(self, frame: Any, time: float,
render_context: 'RenderContext') -> Any:
"""应用转场效果"""
if not self.is_active_at_time(time):
return frame
# 获取转场参数
transition_type = self.get_parameter_value("transition_type", time)
duration = self.get_parameter_value("duration", time)
direction = self.get_parameter_value("direction", time)
easing = self.get_parameter_value("easing", time)
reverse = self.get_parameter_value("reverse", time)
# 计算转场进度
progress = self._calculate_transition_progress(time, duration, easing, reverse)
# 应用转场效果
if isinstance(frame, Image.Image):
return self._apply_transition_pil(frame, transition_type, progress, direction)
else:
return self._apply_transition_cv(frame, transition_type, progress, direction)
def apply_to_audio(self, audio_data: Any, time: float,
render_context: 'RenderContext') -> Any:
"""转场特效可以处理音频(交叉淡入淡出)"""
if not self.is_active_at_time(time):
return audio_data
# 获取转场参数
duration = self.get_parameter_value("duration", time)
easing = self.get_parameter_value("easing", time)
reverse = self.get_parameter_value("reverse", time)
# 计算转场进度
progress = self._calculate_transition_progress(time, duration, easing, reverse)
# 应用音频交叉淡入淡出
return self._apply_audio_transition(audio_data, progress)
def set_next_frame(self, next_frame: Any) -> None:
"""设置下一帧(用于转场)"""
self.next_frame = next_frame
def _calculate_transition_progress(self, time: float, duration: float,
easing: str, reverse: bool) -> float:
"""计算转场进度"""
# 计算在转场中的时间位置
transition_time = time - self.start_time
# 限制在转场持续时间内
progress = min(transition_time / duration, 1.0)
# 应用缓动函数
if easing == "ease_in":
progress = progress * progress
elif easing == "ease_out":
progress = 1 - (1 - progress) * (1 - progress)
elif easing == "ease_in_out":
if progress < 0.5:
progress = 2 * progress * progress
else:
progress = 1 - 2 * (1 - progress) * (1 - progress)
# 应用反向
if reverse:
progress = 1.0 - progress
return np.clip(progress, 0.0, 1.0)
def _apply_transition_pil(self, current_frame: Image.Image, transition_type: str,
progress: float, direction: str) -> Image.Image:
"""使用PIL应用转场效果"""
if self.next_frame is None:
return current_frame
# 确保两帧大小相同
if current_frame.size != self.next_frame.size:
self.next_frame = self.next_frame.resize(current_frame.size, Image.LANCZOS)
# 应用不同类型的转场
if transition_type == "fade":
return self._apply_fade_transition_pil(current_frame, self.next_frame, progress)
elif transition_type == "slide":
return self._apply_slide_transition_pil(current_frame, self.next_frame, progress, direction)
elif transition_type == "wipe":
return self._apply_wipe_transition_pil(current_frame, self.next_frame, progress, direction)
elif transition_type == "dissolve":
return self._apply_dissolve_transition_pil(current_frame, self.next_frame, progress)
else:
return current_frame
def _apply_transition_cv(self, current_frame: np.ndarray, transition_type: str,
progress: float, direction: str) -> np.ndarray:
"""使用OpenCV应用转场效果"""
if self.next_frame is None:
return current_frame
# 确保两帧大小相同
if current_frame.shape != self.next_frame.shape:
self.next_frame = cv2.resize(self.next_frame, (current_frame.shape[1], current_frame.shape[0]))
# 应用不同类型的转场
if transition_type == "fade":
return self._apply_fade_transition_cv(current_frame, self.next_frame, progress)
elif transition_type == "slide":
return self._apply_slide_transition_cv(current_frame, self.next_frame, progress, direction)
elif transition_type == "wipe":
return self._apply_wipe_transition_cv(current_frame, self.next_frame, progress, direction)
elif transition_type == "dissolve":
return self._apply_dissolve_transition_cv(current_frame, self.next_frame, progress)
else:
return current_frame
def _apply_fade_transition_pil(self, current: Image.Image, next_frame: Image.Image,
progress: float) -> Image.Image:
"""应用淡入淡出转场(PIL)"""
return Image.blend(current, next_frame, progress)
def _apply_fade_transition_cv(self, current: np.ndarray, next_frame: np.ndarray,
progress: float) -> np.ndarray:
"""应用淡入淡出转场(OpenCV)"""
return cv2.addWeighted(current, 1.0 - progress, next_frame, progress, 0)
def _apply_slide_transition_pil(self, current: Image.Image, next_frame: Image.Image,
progress: float, direction: str) -> Image.Image:
"""应用滑动转场(PIL)"""
width, height = current.size
# 计算偏移量
if direction == "left":
offset = int(width * progress)
current_box = (offset, 0, width, height)
next_box = (0, 0, offset, height)
elif direction == "right":
offset = int(width * progress)
current_box = (0, 0, width - offset, height)
next_box = (width - offset, 0, width, height)
elif direction == "up":
offset = int(height * progress)
current_box = (0, offset, width, height)
next_box = (0, 0, width, offset)
elif direction == "down":
offset = int(height * progress)
current_box = (0, 0, width, height - offset)
next_box = (0, height - offset, width, height)
else:
return current
# 创建结果图像
result = Image.new('RGB', (width, height))
# 复制当前帧的部分
if current_box[2] > current_box[0] and current_box[3] > current_box[1]:
current_crop = current.crop(current_box)
result.paste(current_crop, current_box)
# 复制下一帧的部分
if next_box[2] > next_box[0] and next_box[3] > next_box[1]:
next_crop = next_frame.crop(next_box)
result.paste(next_crop, next_box)
return result
def _apply_slide_transition_cv(self, current: np.ndarray, next_frame: np.ndarray,
progress: float, direction: str) -> np.ndarray:
"""应用滑动转场(OpenCV)"""
height, width = current.shape[:2]
# 计算偏移量
if direction == "left":
offset = int(width * progress)
result = np.zeros_like(current)
if offset < width:
result[:, offset:] = current[:, :width-offset]
if offset > 0:
result[:, :offset] = next_frame[:, width-offset:width]
elif direction == "right":
offset = int(width * progress)
result = np.zeros_like(current)
if offset < width:
result[:, :width-offset] = current[:, offset:]
if offset > 0:
result[:, width-offset:] = next_frame[:, :offset]
elif direction == "up":
offset = int(height * progress)
result = np.zeros_like(current)
if offset < height:
result[offset:, :] = current[:height-offset, :]
if offset > 0:
result[:offset, :] = next_frame[height-offset:height, :]
elif direction == "down":
offset = int(height * progress)
result = np.zeros_like(current)
if offset < height:
result[:height-offset, :] = current[offset:, :]
if offset > 0:
result[height-offset:, :] = next_frame[:offset, :]
else:
result = current
return result
def _apply_wipe_transition_pil(self, current: Image.Image, next_frame: Image.Image,
progress: float, direction: str) -> Image.Image:
"""应用擦除转场(PIL)"""
width, height = current.size
# 创建遮罩
mask = Image.new('L', (width, height), 0)
# 根据方向和进度绘制遮罩
if direction == "left":
wipe_width = int(width * progress)
if wipe_width > 0:
mask.paste(255, (0, 0, wipe_width, height))
elif direction == "right":
wipe_width = int(width * progress)
if wipe_width > 0:
mask.paste(255, (width - wipe_width, 0, width, height))
elif direction == "up":
wipe_height = int(height * progress)
if wipe_height > 0:
mask.paste(255, (0, 0, width, wipe_height))
elif direction == "down":
wipe_height = int(height * progress)
if wipe_height > 0:
mask.paste(255, (0, height - wipe_height, width, height))
# 使用遮罩混合两帧
return Image.composite(next_frame, current, mask)
def _apply_wipe_transition_cv(self, current: np.ndarray, next_frame: np.ndarray,
progress: float, direction: str) -> np.ndarray:
"""应用擦除转场(OpenCV)"""
height, width = current.shape[:2]
# 创建遮罩
mask = np.zeros((height, width), dtype=np.uint8)
# 根据方向和进度绘制遮罩
if direction == "left":
wipe_width = int(width * progress)
if wipe_width > 0:
mask[:, :wipe_width] = 255
elif direction == "right":
wipe_width = int(width * progress)
if wipe_width > 0:
mask[:, width-wipe_width:] = 255
elif direction == "up":
wipe_height = int(height * progress)
if wipe_height > 0:
mask[:wipe_height, :] = 255
elif direction == "down":
wipe_height = int(height * progress)
if wipe_height > 0:
mask[height-wipe_height:, :] = 255
# 使用遮罩混合两帧
result = current.copy()
mask_bool = mask > 0
result[mask_bool] = next_frame[mask_bool]
return result
def _apply_dissolve_transition_pil(self, current: Image.Image, next_frame: Image.Image,
progress: float) -> Image.Image:
"""应用溶解转场(PIL)"""
width, height = current.size
# 创建随机遮罩
random_mask = np.random.rand(height, width)
threshold = progress
mask_array = (random_mask < threshold).astype(np.uint8) * 255
# 转换为PIL图像
mask = Image.fromarray(mask_array, mode='L')
# 使用遮罩混合
return Image.composite(next_frame, current, mask)
def _apply_dissolve_transition_cv(self, current: np.ndarray, next_frame: np.ndarray,
progress: float) -> np.ndarray:
"""应用溶解转场(OpenCV)"""
height, width = current.shape[:2]
# 创建随机遮罩
random_mask = np.random.rand(height, width)
threshold = progress
mask_bool = random_mask < threshold
# 混合两帧
result = current.copy()
result[mask_bool] = next_frame[mask_bool]
return result
def _apply_audio_transition(self, current_audio: Any, progress: float) -> Any:
"""应用音频转场(交叉淡入淡出)"""
# 这里简化实现,实际应该处理音频交叉淡入淡出
return current_audio
# 特效渲染上下文
class RenderContext:
"""渲染上下文"""
def __init__(self):
self.frame_size = (1920, 1080)
self.frame_rate = 30.0
self.bit_depth = 8
self.color_space = "RGB"
self.render_quality = "high"
self.gpu_accelerated = False
self.cache_enabled = True
self.real_time_preview = False
# 性能统计
self.render_time = 0.0
self.cache_hits = 0
self.cache_misses = 0
# 渲染选项
self.antialiasing = True
self.motion_blur = False
self.depth_of_field = False
# 输出设置
self.output_format = "RGB"
self.alpha_channel = False
self.premultiplied_alpha = False
def get_frame_dimensions(self) -> Tuple[int, int]:
"""获取帧尺寸"""
return self.frame_size
def get_aspect_ratio(self) -> float:
"""获取宽高比"""
width, height = self.frame_size
return width / height if height > 0 else 1.0
def update_render_stats(self, render_time: float, cache_hit: bool) -> None:
"""更新渲染统计"""
self.render_time = render_time
if cache_hit:
self.cache_hits += 1
else:
self.cache_misses += 1
def get_render_statistics(self) -> Dict[str, Any]:
"""获取渲染统计"""
total_requests = self.cache_hits + self.cache_misses
cache_hit_rate = self.cache_hits / total_requests if total_requests > 0 else 0.0
return {
"frame_size": self.frame_size,
"frame_rate": self.frame_rate,
"render_time": self.render_time,
"cache_hits": self.cache_hits,
"cache_misses": self.cache_misses,
"cache_hit_rate": cache_hit_rate,
"gpu_accelerated": self.gpu_accelerated,
"render_quality": self.render_quality
}
# 特效系统总结
"""
特效系统与滤镜实现总结
本章详细介绍了剪映小助手中的特效系统与滤镜实现,涵盖了从基础架构到高级特效的完整实现方案。
## 核心组件
1. **特效抽象基类 (Effect)**
- 定义了所有特效的通用接口和行为
- 支持参数化控制、关键帧动画、时间控制
- 提供了参数验证、设置导入导出等基础功能
2. **具体特效实现**
- 滤镜特效:亮度对比度、饱和度、模糊、锐化
- 变换特效:平移、缩放、旋转、锚点控制
- 粒子系统:动态粒子生成、物理模拟、渲染优化
- 光照特效:点光源、方向光、聚光灯效果
- 转场特效:淡入淡出、滑动、擦除、溶解效果
3. **特效管理器 (EffectManager)**
- 统一管理所有活动特效
- 支持特效链和批量处理
- 提供渲染缓存和性能优化
- 实时监控渲染统计信息
4. **特效工厂 (EffectFactory)**
- 特效类的注册和创建
- 预设管理和导入导出
- 分类索引和快速查找
## 技术特点
- **模块化设计**:每个特效都是独立的模块,便于扩展和维护
- **高性能渲染**:支持GPU加速、缓存优化、多线程处理
- **参数化控制**:提供直观的参数调节界面,支持关键帧动画
- **跨平台兼容**:同时支持PIL和OpenCV两种图像处理库
- **实时预览**:支持实时特效预览和快速迭代
## 应用场景
- **视频后期制作**:色彩校正、画面增强、艺术化处理
- **动态图形设计**:粒子效果、光照渲染、视觉特效
- **转场动画**:场景切换、镜头过渡、创意转场
- **实时直播**:实时滤镜、美颜效果、互动特效
这个特效系统为剪映小助手提供了强大的视觉处理能力,使用户能够轻松创建专业级的视频特效效果。
"""