From 2de3be271e3ff21103e7897c3752dba529cda2ff Mon Sep 17 00:00:00 2001 From: MixBadGun <1059129006@qq.com> Date: Wed, 3 Dec 2025 13:10:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=80=E6=96=B0=E6=9C=80=E7=83=AD=E6=A8=A1?= =?UTF-8?q?=E7=B3=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- konabot/docs/user/fx.txt | 11 +- konabot/plugins/fx_process/fx_handle.py | 321 +++++++++++++++++++++++ konabot/plugins/fx_process/fx_manager.py | 5 + 3 files changed, 335 insertions(+), 2 deletions(-) diff --git a/konabot/docs/user/fx.txt b/konabot/docs/user/fx.txt index 9361af4..5020f26 100644 --- a/konabot/docs/user/fx.txt +++ b/konabot/docs/user/fx.txt @@ -19,8 +19,6 @@ fx [滤镜名称] <参数1> <参数2> ... ## 可用滤镜列表 ### 基础滤镜 -* ```fx 模糊 <半径=10>``` -* ```fx 马赛克 <像素大小=10>``` * ```fx 轮廓``` * ```fx 锐化``` * ```fx 边缘增强``` @@ -33,6 +31,15 @@ fx [滤镜名称] <参数1> <参数2> ... * ```fx 素描``` * ```fx 阴影 <模糊量=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 黑白``` diff --git a/konabot/plugins/fx_process/fx_handle.py b/konabot/plugins/fx_process/fx_handle.py index a263c90..f3f488a 100644 --- a/konabot/plugins/fx_process/fx_handle.py +++ b/konabot/plugins/fx_process/fx_handle.py @@ -566,4 +566,325 @@ class ImageFilterImplement: result.paste(image, image_position, image) 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') diff --git a/konabot/plugins/fx_process/fx_manager.py b/konabot/plugins/fx_process/fx_manager.py index f5c91ed..7ae32c3 100644 --- a/konabot/plugins/fx_process/fx_manager.py +++ b/konabot/plugins/fx_process/fx_manager.py @@ -35,6 +35,11 @@ class ImageFilterManager: "素描": ImageFilterImplement.apply_sketch, "叠加颜色": ImageFilterImplement.apply_gradient_overlay, "阴影": ImageFilterImplement.apply_shadow, + "径向模糊": ImageFilterImplement.apply_radial_blur, + "旋转模糊": ImageFilterImplement.apply_spin_blur, + "方向模糊": ImageFilterImplement.apply_directional_blur, + "边缘模糊": ImageFilterImplement.apply_focus_blur, + "缩放模糊": ImageFilterImplement.apply_zoom_blur, } @classmethod