feat: add JPEG damage filter to fx #56

Merged
Passthem merged 1 commits from pi-agent/konabot:feat/fx-jpeg-damage into master 2026-03-14 01:01:39 +08:00
4 changed files with 64 additions and 0 deletions

View File

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

View File

@ -1,4 +1,5 @@
import random
from io import BytesIO
from PIL import Image, ImageFilter, ImageDraw, ImageStat, ImageFont
from PIL import ImageEnhance
from PIL import ImageChops
@ -167,6 +168,29 @@ class ImageFilterImplement:
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
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_shape_stroke,
"半调": ImageFilterImplement.apply_halftone,
"JPEG损坏": ImageFilterImplement.apply_jpeg_damage,
"设置通道": ImageFilterImplement.apply_set_channel,
"设置遮罩": 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"