feat: add jpeg damage filter to fx

This commit is contained in:
2026-03-14 00:52:34 +08:00
parent d273ed4b1a
commit 1980f8a895
4 changed files with 64 additions and 0 deletions

View File

@ -76,6 +76,8 @@ fx [滤镜名称] <参数1> <参数2> ...
* ```fx 设置遮罩``` * ```fx 设置遮罩```
* ```fx 色键 <目标颜色="rgb(255,0,0)"> <容差=60>``` * ```fx 色键 <目标颜色="rgb(255,0,0)"> <容差=60>```
* ```fx 晃动 <最大偏移量=5> <运动模糊=False>``` * ```fx 晃动 <最大偏移量=5> <运动模糊=False>```
* ```fx JPEG损坏 <质量=10>```
* 质量范围建议为 1~95数值越低压缩痕迹越重、效果越搞笑。
* ```fx 动图 <帧率=10>``` * ```fx 动图 <帧率=10>```
### 多图像处理器 ### 多图像处理器

View File

@ -1,4 +1,5 @@
import random import random
from io import BytesIO
from PIL import Image, ImageFilter, ImageDraw, ImageStat, ImageFont from PIL import Image, ImageFilter, ImageDraw, ImageStat, ImageFont
from PIL import ImageEnhance from PIL import ImageEnhance
from PIL import ImageChops from PIL import ImageChops
@ -167,6 +168,29 @@ class ImageFilterImplement:
return Image.fromarray(result, 'RGBA') return Image.fromarray(result, 'RGBA')
# JPEG 损坏感压缩
@staticmethod
def apply_jpeg_damage(image: Image.Image, quality: int = 10) -> Image.Image:
quality = max(1, min(95, int(quality)))
alpha = None
if image.mode in ('RGBA', 'LA') or (image.mode == 'P' and 'transparency' in image.info):
rgba_image = image.convert('RGBA')
alpha = rgba_image.getchannel('A')
rgb_image = Image.new('RGB', rgba_image.size, (255, 255, 255))
rgb_image.paste(rgba_image, mask=alpha)
else:
rgb_image = image.convert('RGB')
output = BytesIO()
rgb_image.save(output, format='JPEG', quality=quality, optimize=False)
output.seek(0)
damaged = Image.open(output).convert('RGB')
if alpha is not None:
return Image.merge('RGBA', (*damaged.split(), alpha))
return damaged.convert('RGBA')
# 缩放 # 缩放
@staticmethod @staticmethod
def apply_resize(image: Image.Image, scale: float = 1.5, scale_y = None) -> Image.Image: def apply_resize(image: Image.Image, scale: float = 1.5, scale_y = None) -> Image.Image:

View File

@ -50,6 +50,7 @@ class ImageFilterManager:
"描边": ImageFilterImplement.apply_stroke, "描边": ImageFilterImplement.apply_stroke,
"形状描边": ImageFilterImplement.apply_shape_stroke, "形状描边": ImageFilterImplement.apply_shape_stroke,
"半调": ImageFilterImplement.apply_halftone, "半调": ImageFilterImplement.apply_halftone,
"JPEG损坏": ImageFilterImplement.apply_jpeg_damage,
"设置通道": ImageFilterImplement.apply_set_channel, "设置通道": ImageFilterImplement.apply_set_channel,
"设置遮罩": ImageFilterImplement.apply_set_mask, "设置遮罩": ImageFilterImplement.apply_set_mask,
# 图像处理 # 图像处理

37
tests/test_fx_process.py Normal file
View File

@ -0,0 +1,37 @@
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
import nonebot
from PIL import Image
nonebot.init()
MODULE_PATH = Path(__file__).resolve().parents[1] / "konabot/plugins/fx_process/fx_handle.py"
SPEC = spec_from_file_location("test_fx_handle_module", MODULE_PATH)
assert SPEC is not None and SPEC.loader is not None
fx_handle = module_from_spec(SPEC)
SPEC.loader.exec_module(fx_handle)
ImageFilterImplement = fx_handle.ImageFilterImplement
def test_apply_jpeg_damage_keeps_size_and_rgba_mode():
image = Image.new("RGBA", (32, 24), (255, 0, 0, 128))
result = ImageFilterImplement.apply_jpeg_damage(image, 5)
assert result.size == image.size
assert result.mode == "RGBA"
assert result.getchannel("A").getextrema() == (128, 128)
def test_apply_jpeg_damage_clamps_quality_range():
image = Image.new("RGB", (16, 16), (123, 222, 111))
low = ImageFilterImplement.apply_jpeg_damage(image, -10)
high = ImageFilterImplement.apply_jpeg_damage(image, 999)
assert low.size == image.size
assert high.size == image.size
assert low.mode == "RGBA"
assert high.mode == "RGBA"