This commit is contained in:
2025-12-10 17:17:52 +08:00
parent de04fcbec1
commit 9904653cc6
3 changed files with 92 additions and 1 deletions

View File

@ -51,6 +51,8 @@ fx [滤镜名称] <参数1> <参数2> ...
* ```fx RGB分离 <偏移量=5>``` * ```fx RGB分离 <偏移量=5>```
* ```fx 叠加颜色 <颜色列表=[rgb(255,0,0)|(0,0)+rgb(0,255,0)|(0,100)+rgb(0,0,255)|(50,100)]> <叠加模式=overlay>``` * ```fx 叠加颜色 <颜色列表=[rgb(255,0,0)|(0,0)+rgb(0,255,0)|(0,100)+rgb(0,0,255)|(50,100)]> <叠加模式=overlay>```
* ```fx 像素抖动 <最大偏移量=2>``` * ```fx 像素抖动 <最大偏移量=2>```
* ```fx 半调 <半径=5>```
* ```fx 描边 <半径=5> <颜色=black>```
### 几何变换滤镜 ### 几何变换滤镜
* ```fx 平移 <x偏移量=10> <y偏移量=10>``` * ```fx 平移 <x偏移量=10> <y偏移量=10>```

View File

@ -1,5 +1,5 @@
import random import random
from PIL import Image, ImageFilter from PIL import Image, ImageFilter, ImageDraw, ImageStat
from PIL import ImageEnhance from PIL import ImageEnhance
from PIL import ImageChops from PIL import ImageChops
from PIL import ImageOps from PIL import ImageOps
@ -412,6 +412,13 @@ class ImageFilterImplement:
if valid_mask.any(): if valid_mask.any():
# 使用花式索引复制像素 # 使用花式索引复制像素
result[valid_mask] = arr[new_y[valid_mask], new_x[valid_mask]] result[valid_mask] = arr[new_y[valid_mask], new_x[valid_mask]]
# 计算裁剪边界
ys, xs = np.where(valid_mask)
min_y, max_y = ys.min(), ys.max() + 1
min_x, max_x = xs.min(), xs.max() + 1
# 裁剪结果图像
result = result[min_y:max_y, min_x:max_x]
return Image.fromarray(result, 'RGBA') return Image.fromarray(result, 'RGBA')
@ -1103,6 +1110,86 @@ class ImageFilterImplement:
return Image.fromarray(result, 'RGBA') return Image.fromarray(result, 'RGBA')
# 描边
@staticmethod
def apply_stroke(image: Image.Image, stroke_width: int = 5, stroke_color: str = 'black') -> Image.Image:
if image.mode != 'RGBA':
image = image.convert('RGBA')
# 基于图像的 alpha 通道创建描边
alpha = image.split()[3]
# 创建描边图像
stroke_image = Image.new('RGBA', image.size, (0, 0, 0, 0))
# 根据 alpha 通道的值,以每个像素为中心,扩大描边区域
expanded_alpha = alpha.filter(ImageFilter.MaxFilter(size=stroke_width*2+1))
draw = Image.new('RGBA', image.size, ColorHandle.parse_color(stroke_color) + (255,))
stroke_image.paste(draw, (0, 0), expanded_alpha)
# 将描边和原图合并
result = Image.alpha_composite(stroke_image, image)
return result
# 半调
@staticmethod
def apply_halftone(image: Image.Image, dot_size: int = 5) -> Image.Image:
if image.mode != 'L':
grayscale = image.convert('L')
else:
grayscale = image.copy()
# 获取图像尺寸
width, height = grayscale.size
# 计算网格数量
grid_width = math.ceil(width / dot_size)
grid_height = math.ceil(height / dot_size)
# 创建输出图像(白色背景)
output = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(output)
# 遍历每个网格单元
for gy in range(grid_height):
for gx in range(grid_width):
# 计算当前网格的边界
left = gx * dot_size
top = gy * dot_size
right = min(left + dot_size, width)
bottom = min(top + dot_size, height)
# 获取当前网格的区域
grid_region = grayscale.crop((left, top, right, bottom))
# 计算网格内像素的平均亮度0-255
stat = ImageStat.Stat(grid_region)
avg_brightness = stat.mean[0]
# 将亮度转换为网点半径
# 亮度越高(越白),网点越小;亮度越低(越黑),网点越大
max_radius = dot_size / 2
radius = max_radius * (1 - avg_brightness / 255)
# 如果半径太小,则不绘制网点
if radius > 0.5:
# 计算网点中心坐标
center_x = left + (right - left) / 2
center_y = top + (bottom - top) / 2
# 绘制黑色网点
draw.ellipse([
center_x - radius + 0.5,
center_y - radius + 0.5,
center_x + radius + 0.5,
center_y + radius + 0.5
], fill='black')
# 叠加 alpha 通道
if image.mode == 'RGBA':
alpha = image.split()[3]
output.putalpha(alpha)
return output
class ImageFilterEmpty: class ImageFilterEmpty:
# 空滤镜,不做任何处理,形式化参数用 # 空滤镜,不做任何处理,形式化参数用
@staticmethod @staticmethod

View File

@ -47,6 +47,8 @@ class ImageFilterManager:
"晃动": ImageFilterImplement.apply_random_wiggle, "晃动": ImageFilterImplement.apply_random_wiggle,
"动图": ImageFilterEmpty.empty_filter_param, "动图": ImageFilterEmpty.empty_filter_param,
"像素抖动": ImageFilterImplement.apply_pixel_jitter, "像素抖动": ImageFilterImplement.apply_pixel_jitter,
"描边": ImageFilterImplement.apply_stroke,
"半调": ImageFilterImplement.apply_halftone,
# 图像处理 # 图像处理
"存入图像": ImageFilterStorage.store_image, "存入图像": ImageFilterStorage.store_image,
"读取图像": ImageFilterStorage.load_image, "读取图像": ImageFilterStorage.load_image,