This commit is contained in:
@ -19,8 +19,6 @@ fx [滤镜名称] <参数1> <参数2> ...
|
|||||||
## 可用滤镜列表
|
## 可用滤镜列表
|
||||||
|
|
||||||
### 基础滤镜
|
### 基础滤镜
|
||||||
* ```fx 模糊 <半径=10>```
|
|
||||||
* ```fx 马赛克 <像素大小=10>```
|
|
||||||
* ```fx 轮廓```
|
* ```fx 轮廓```
|
||||||
* ```fx 锐化```
|
* ```fx 锐化```
|
||||||
* ```fx 边缘增强```
|
* ```fx 边缘增强```
|
||||||
@ -33,6 +31,15 @@ fx [滤镜名称] <参数1> <参数2> ...
|
|||||||
* ```fx 素描```
|
* ```fx 素描```
|
||||||
* ```fx 阴影 <x偏移量=10> <y偏移量=10> <模糊量=10> <不透明度=0.5> <阴影颜色=black>```
|
* ```fx 阴影 <x偏移量=10> <y偏移量=10> <模糊量=10> <不透明度=0.5> <阴影颜色=black>```
|
||||||
|
|
||||||
|
### 模糊滤镜
|
||||||
|
* ```fx 模糊 <半径=10>```
|
||||||
|
* ```fx 马赛克 <像素大小=10>```
|
||||||
|
* ```fx 径向模糊 <强度=3.0> <采样量=6>```
|
||||||
|
* ```fx 旋转模糊 <强度=30.0> <采样量=6>```
|
||||||
|
* ```fx 方向模糊 <角度=0.0> <距离=20> <采样量=6>```
|
||||||
|
* ```fx 缩放模糊 <强度=0.1> <采样量=6>```
|
||||||
|
* ```fx 边缘模糊 <半径=10.0>```
|
||||||
|
|
||||||
### 色彩处理滤镜
|
### 色彩处理滤镜
|
||||||
* ```fx 反色```
|
* ```fx 反色```
|
||||||
* ```fx 黑白```
|
* ```fx 黑白```
|
||||||
|
|||||||
@ -566,4 +566,325 @@ class ImageFilterImplement:
|
|||||||
result.paste(image, image_position, image)
|
result.paste(image, image_position, image)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_radial_blur(image: Image.Image, strength: float = 3.0, samples: int = 6) -> Image.Image:
|
||||||
|
"""
|
||||||
|
快速径向模糊 - 使用预计算网格和向量化
|
||||||
|
"""
|
||||||
|
if image.mode != 'RGBA':
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
arr = np.array(image, dtype=np.uint8)
|
||||||
|
|
||||||
|
# 转换为float32并归一化
|
||||||
|
arr_float = arr.astype(np.float32) / 255.0
|
||||||
|
|
||||||
|
# 计算中心点
|
||||||
|
center_x = width / 2
|
||||||
|
center_y = height / 2
|
||||||
|
|
||||||
|
# 预计算坐标网格(只计算一次)
|
||||||
|
x_indices = np.arange(width, dtype=np.float32)
|
||||||
|
y_indices = np.arange(height, dtype=np.float32)
|
||||||
|
x_grid, y_grid = np.meshgrid(x_indices, y_indices)
|
||||||
|
|
||||||
|
# 预计算相对坐标和距离
|
||||||
|
dx = x_grid - center_x
|
||||||
|
dy = y_grid - center_y
|
||||||
|
distance = np.sqrt(dx*dx + dy*dy + 1e-6) # 避免除零
|
||||||
|
max_dist = np.max(distance)
|
||||||
|
|
||||||
|
# 生成采样强度
|
||||||
|
if samples > 1:
|
||||||
|
strengths = np.linspace(-strength/2, strength/2, samples) / 100
|
||||||
|
else:
|
||||||
|
strengths = np.array([0])
|
||||||
|
|
||||||
|
# 初始化结果
|
||||||
|
result = np.zeros_like(arr_float)
|
||||||
|
|
||||||
|
for s in strengths:
|
||||||
|
# 计算缩放因子
|
||||||
|
scale = 1.0 + s * distance / max_dist
|
||||||
|
|
||||||
|
# 计算变形坐标
|
||||||
|
new_x = np.clip(center_x + dx * scale, 0, width - 1)
|
||||||
|
new_y = np.clip(center_y + dy * scale, 0, height - 1)
|
||||||
|
|
||||||
|
# 快速双线性插值
|
||||||
|
x0 = np.floor(new_x).astype(np.int32)
|
||||||
|
x1 = np.minimum(x0 + 1, width - 1)
|
||||||
|
y0 = np.floor(new_y).astype(np.int32)
|
||||||
|
y1 = np.minimum(y0 + 1, height - 1)
|
||||||
|
|
||||||
|
# 计算权重
|
||||||
|
wx = new_x - x0
|
||||||
|
wy = new_y - y0
|
||||||
|
w00 = (1 - wx) * (1 - wy)
|
||||||
|
w01 = wx * (1 - wy)
|
||||||
|
w10 = (1 - wx) * wy
|
||||||
|
w11 = wx * wy
|
||||||
|
|
||||||
|
# 向量化插值所有通道
|
||||||
|
for c in range(4):
|
||||||
|
result[:, :, c] += (
|
||||||
|
arr_float[y0, x0, c] * w00 +
|
||||||
|
arr_float[y0, x1, c] * w01 +
|
||||||
|
arr_float[y1, x0, c] * w10 +
|
||||||
|
arr_float[y1, x1, c] * w11
|
||||||
|
)
|
||||||
|
|
||||||
|
# 平均并转换
|
||||||
|
result /= len(strengths)
|
||||||
|
result = np.clip(result * 255, 0, 255).astype(np.uint8)
|
||||||
|
|
||||||
|
return Image.fromarray(result, 'RGBA')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_spin_blur(image: Image.Image, strength: float = 30.0, samples: int = 6) -> Image.Image:
|
||||||
|
"""
|
||||||
|
快速旋转模糊
|
||||||
|
"""
|
||||||
|
if image.mode != 'RGBA':
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
arr = np.array(image, dtype=np.uint8)
|
||||||
|
arr_float = arr.astype(np.float32) / 255.0
|
||||||
|
|
||||||
|
# 计算中心点
|
||||||
|
center_x = width / 2
|
||||||
|
center_y = height / 2
|
||||||
|
|
||||||
|
# 预计算坐标网格
|
||||||
|
x_indices = np.arange(width, dtype=np.float32)
|
||||||
|
y_indices = np.arange(height, dtype=np.float32)
|
||||||
|
x_grid, y_grid = np.meshgrid(x_indices, y_indices)
|
||||||
|
|
||||||
|
# 预计算相对坐标
|
||||||
|
dx = x_grid - center_x
|
||||||
|
dy = y_grid - center_y
|
||||||
|
|
||||||
|
# 生成角度采样
|
||||||
|
if samples > 1:
|
||||||
|
angles = np.linspace(-strength/2, strength/2, samples) * np.pi / 180
|
||||||
|
else:
|
||||||
|
angles = np.array([0])
|
||||||
|
|
||||||
|
result = np.zeros_like(arr_float)
|
||||||
|
|
||||||
|
for angle in angles:
|
||||||
|
# 预计算三角函数值
|
||||||
|
cos_a = math.cos(angle)
|
||||||
|
sin_a = math.sin(angle)
|
||||||
|
|
||||||
|
# 计算旋转坐标
|
||||||
|
new_x = center_x + dx * cos_a - dy * sin_a
|
||||||
|
new_y = center_y + dx * sin_a + dy * cos_a
|
||||||
|
|
||||||
|
# 边界裁剪
|
||||||
|
new_x = np.clip(new_x, 0, width - 1)
|
||||||
|
new_y = np.clip(new_y, 0, height - 1)
|
||||||
|
|
||||||
|
# 快速双线性插值
|
||||||
|
x0 = np.floor(new_x).astype(np.int32)
|
||||||
|
x1 = np.minimum(x0 + 1, width - 1)
|
||||||
|
y0 = np.floor(new_y).astype(np.int32)
|
||||||
|
y1 = np.minimum(y0 + 1, height - 1)
|
||||||
|
|
||||||
|
wx = new_x - x0
|
||||||
|
wy = new_y - y0
|
||||||
|
w00 = (1 - wx) * (1 - wy)
|
||||||
|
w01 = wx * (1 - wy)
|
||||||
|
w10 = (1 - wx) * wy
|
||||||
|
w11 = wx * wy
|
||||||
|
|
||||||
|
# 向量化插值
|
||||||
|
for c in range(4):
|
||||||
|
result[:, :, c] += (
|
||||||
|
arr_float[y0, x0, c] * w00 +
|
||||||
|
arr_float[y0, x1, c] * w01 +
|
||||||
|
arr_float[y1, x0, c] * w10 +
|
||||||
|
arr_float[y1, x1, c] * w11
|
||||||
|
)
|
||||||
|
|
||||||
|
result /= len(angles)
|
||||||
|
result = np.clip(result * 255, 0, 255).astype(np.uint8)
|
||||||
|
|
||||||
|
return Image.fromarray(result, 'RGBA')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_directional_blur(image: Image.Image, angle: float = 0.0,
|
||||||
|
distance: int = 20, samples: int = 6) -> Image.Image:
|
||||||
|
"""
|
||||||
|
快速方向模糊 - 使用累积缓冲区技术
|
||||||
|
"""
|
||||||
|
if image.mode != 'RGBA':
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
arr = np.array(image, dtype=np.uint8)
|
||||||
|
arr_float = arr.astype(np.float32) / 255.0
|
||||||
|
|
||||||
|
# 计算角度和步长
|
||||||
|
rad = math.radians(angle)
|
||||||
|
cos_a = math.cos(rad)
|
||||||
|
sin_a = math.sin(rad)
|
||||||
|
|
||||||
|
# 生成偏移位置
|
||||||
|
if samples > 1:
|
||||||
|
offsets = np.linspace(-distance/2, distance/2, samples)
|
||||||
|
else:
|
||||||
|
offsets = np.array([0])
|
||||||
|
|
||||||
|
result = np.zeros_like(arr_float)
|
||||||
|
|
||||||
|
for offset in offsets:
|
||||||
|
# 计算偏移量
|
||||||
|
shift_x = offset * cos_a
|
||||||
|
shift_y = offset * sin_a
|
||||||
|
|
||||||
|
# 预计算变形坐标
|
||||||
|
x_indices = np.arange(width, dtype=np.float32)
|
||||||
|
y_indices = np.arange(height, dtype=np.float32)
|
||||||
|
x_grid, y_grid = np.meshgrid(x_indices, y_indices)
|
||||||
|
|
||||||
|
new_x = x_grid - shift_x
|
||||||
|
new_y = y_grid - shift_y
|
||||||
|
|
||||||
|
# 边界裁剪
|
||||||
|
new_x = np.clip(new_x, 0, width - 1)
|
||||||
|
new_y = np.clip(new_y, 0, height - 1)
|
||||||
|
|
||||||
|
# 快速双线性插值
|
||||||
|
x0 = np.floor(new_x).astype(np.int32)
|
||||||
|
x1 = np.minimum(x0 + 1, width - 1)
|
||||||
|
y0 = np.floor(new_y).astype(np.int32)
|
||||||
|
y1 = np.minimum(y0 + 1, height - 1)
|
||||||
|
|
||||||
|
wx = new_x - x0
|
||||||
|
wy = new_y - y0
|
||||||
|
w00 = (1 - wx) * (1 - wy)
|
||||||
|
w01 = wx * (1 - wy)
|
||||||
|
w10 = (1 - wx) * wy
|
||||||
|
w11 = wx * wy
|
||||||
|
|
||||||
|
# 向量化插值
|
||||||
|
for c in range(4):
|
||||||
|
result[:, :, c] += (
|
||||||
|
arr_float[y0, x0, c] * w00 +
|
||||||
|
arr_float[y0, x1, c] * w01 +
|
||||||
|
arr_float[y1, x0, c] * w10 +
|
||||||
|
arr_float[y1, x1, c] * w11
|
||||||
|
)
|
||||||
|
|
||||||
|
result /= len(offsets)
|
||||||
|
result = np.clip(result * 255, 0, 255).astype(np.uint8)
|
||||||
|
|
||||||
|
return Image.fromarray(result, 'RGBA')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def apply_zoom_blur(image: Image.Image, strength: float = 0.1, samples: int = 6) -> Image.Image:
|
||||||
|
"""
|
||||||
|
快速缩放模糊
|
||||||
|
"""
|
||||||
|
if image.mode != 'RGBA':
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
arr = np.array(image, dtype=np.uint8)
|
||||||
|
arr_float = arr.astype(np.float32) / 255.0
|
||||||
|
|
||||||
|
# 计算中心点
|
||||||
|
center_x = width / 2
|
||||||
|
center_y = height / 2
|
||||||
|
|
||||||
|
# 预计算坐标网格
|
||||||
|
x_indices = np.arange(width, dtype=np.float32)
|
||||||
|
y_indices = np.arange(height, dtype=np.float32)
|
||||||
|
x_grid, y_grid = np.meshgrid(x_indices, y_indices)
|
||||||
|
|
||||||
|
# 预计算相对坐标
|
||||||
|
dx = x_grid - center_x
|
||||||
|
dy = y_grid - center_y
|
||||||
|
|
||||||
|
# 生成缩放因子
|
||||||
|
if samples > 1:
|
||||||
|
scales = np.linspace(1 - strength, 1 + strength, samples)
|
||||||
|
else:
|
||||||
|
scales = np.array([1.0])
|
||||||
|
|
||||||
|
result = np.zeros_like(arr_float)
|
||||||
|
|
||||||
|
for scale in scales:
|
||||||
|
# 计算缩放坐标
|
||||||
|
new_x = center_x + dx / scale
|
||||||
|
new_y = center_y + dy / scale
|
||||||
|
|
||||||
|
# 边界裁剪
|
||||||
|
new_x = np.clip(new_x, 0, width - 1)
|
||||||
|
new_y = np.clip(new_y, 0, height - 1)
|
||||||
|
|
||||||
|
# 快速双线性插值
|
||||||
|
x0 = np.floor(new_x).astype(np.int32)
|
||||||
|
x1 = np.minimum(x0 + 1, width - 1)
|
||||||
|
y0 = np.floor(new_y).astype(np.int32)
|
||||||
|
y1 = np.minimum(y0 + 1, height - 1)
|
||||||
|
|
||||||
|
wx = new_x - x0
|
||||||
|
wy = new_y - y0
|
||||||
|
w00 = (1 - wx) * (1 - wy)
|
||||||
|
w01 = wx * (1 - wy)
|
||||||
|
w10 = (1 - wx) * wy
|
||||||
|
w11 = wx * wy
|
||||||
|
|
||||||
|
# 向量化插值
|
||||||
|
for c in range(4):
|
||||||
|
result[:, :, c] += (
|
||||||
|
arr_float[y0, x0, c] * w00 +
|
||||||
|
arr_float[y0, x1, c] * w01 +
|
||||||
|
arr_float[y1, x0, c] * w10 +
|
||||||
|
arr_float[y1, x1, c] * w11
|
||||||
|
)
|
||||||
|
|
||||||
|
result /= len(scales)
|
||||||
|
result = np.clip(result * 255, 0, 255).astype(np.uint8)
|
||||||
|
|
||||||
|
return Image.fromarray(result, 'RGBA')
|
||||||
|
|
||||||
|
# 中心清晰,边缘模糊
|
||||||
|
@staticmethod
|
||||||
|
def apply_focus_blur(image: Image.Image, radius: float = 10.0) -> Image.Image:
|
||||||
|
if image.mode != 'RGBA':
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
|
||||||
|
width, height = image.size
|
||||||
|
arr = np.array(image)
|
||||||
|
|
||||||
|
# 创建坐标网格
|
||||||
|
y_coords, x_coords = np.mgrid[0:height, 0:width]
|
||||||
|
|
||||||
|
# 计算中心点
|
||||||
|
center_x = width / 2
|
||||||
|
center_y = height / 2
|
||||||
|
|
||||||
|
# 计算归一化坐标
|
||||||
|
norm_x = (x_coords - center_x) / (width / 2)
|
||||||
|
norm_y = (y_coords - center_y) / (height / 2)
|
||||||
|
distance = np.sqrt(norm_x**2 + norm_y**2)
|
||||||
|
|
||||||
|
# 创建模糊图像
|
||||||
|
blurred_image = image.filter(ImageFilter.GaussianBlur(radius))
|
||||||
|
blurred_arr = np.array(blurred_image)
|
||||||
|
|
||||||
|
# 创建结果图像(向量化)
|
||||||
|
result = np.zeros_like(arr, dtype=np.float32)
|
||||||
|
|
||||||
|
# 根据距离混合原图和模糊图
|
||||||
|
for c in range(4): # 对每个通道
|
||||||
|
result[:, :, c] = arr[:, :, c] * (1 - distance) + blurred_arr[:, :, c] * distance
|
||||||
|
|
||||||
|
return Image.fromarray(np.clip(result, 0, 255).astype(np.uint8), 'RGBA')
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,11 @@ class ImageFilterManager:
|
|||||||
"素描": ImageFilterImplement.apply_sketch,
|
"素描": ImageFilterImplement.apply_sketch,
|
||||||
"叠加颜色": ImageFilterImplement.apply_gradient_overlay,
|
"叠加颜色": ImageFilterImplement.apply_gradient_overlay,
|
||||||
"阴影": ImageFilterImplement.apply_shadow,
|
"阴影": ImageFilterImplement.apply_shadow,
|
||||||
|
"径向模糊": ImageFilterImplement.apply_radial_blur,
|
||||||
|
"旋转模糊": ImageFilterImplement.apply_spin_blur,
|
||||||
|
"方向模糊": ImageFilterImplement.apply_directional_blur,
|
||||||
|
"边缘模糊": ImageFilterImplement.apply_focus_blur,
|
||||||
|
"缩放模糊": ImageFilterImplement.apply_zoom_blur,
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user