This commit is contained in:
@ -53,6 +53,7 @@ fx [滤镜名称] <参数1> <参数2> ...
|
||||
* ```fx 像素抖动 <最大偏移量=2>```
|
||||
* ```fx 半调 <半径=5>```
|
||||
* ```fx 描边 <半径=5> <颜色=black>```
|
||||
* ```fx 形状描边 <半径=5> <颜色=black> <粗糙度=None>```
|
||||
|
||||
### 几何变换滤镜
|
||||
* ```fx 平移 <x偏移量=10> <y偏移量=10>```
|
||||
@ -70,6 +71,8 @@ fx [滤镜名称] <参数1> <参数2> ...
|
||||
* ```fx 复制 <目标位置=(100,100)> <缩放=1.0> <源区域=(0,0,100,100)>(百分比)```
|
||||
|
||||
### 特殊效果滤镜
|
||||
* ```fx 设置通道 <通道=A>```
|
||||
* 可用 R、G、B、A。
|
||||
* ```fx 色键 <目标颜色="rgb(255,0,0)"> <容差=60>```
|
||||
* ```fx 晃动 <最大偏移量=5> <运动模糊=False>```
|
||||
* ```fx 动图 <帧率=10>```
|
||||
@ -77,7 +80,9 @@ fx [滤镜名称] <参数1> <参数2> ...
|
||||
### 多图像处理器
|
||||
* ```fx 存入图像 <目标名称>```
|
||||
* 目标名称是图像的代名词,图像最长可存 12 小时,如果公用容量满了图像也会被删除。
|
||||
* 该项仅可于首项使用。
|
||||
* ```fx 读取图像 <目标名称>```
|
||||
* 该项仅可于首项使用。
|
||||
* ```fx 暂存图像```
|
||||
* 此项默认插入存储在暂存列表中第一张图像的后面。
|
||||
* ```fx 交换图像 <交换项=2> <交换项=1>```
|
||||
@ -87,7 +92,11 @@ fx [滤镜名称] <参数1> <参数2> ...
|
||||
### 多图像混合
|
||||
* ```fx 混合图像 <模式=normal> <alpha=0.5>```
|
||||
* ```fx 覆盖图像```
|
||||
* ```fx 生成颜色 <颜色列表=[rgb(255,0,0)|(0,0)+rgb(0,255,0)|(0,100)+rgb(0,0,255)|(50,100)]>```
|
||||
|
||||
### 生成类
|
||||
* ```fx 覆加颜色 <颜色列表=[rgb(255,0,0)|(0,0)+rgb(0,255,0)|(0,100)+rgb(0,0,255)|(50,100)]>```
|
||||
* ```fx 生成图层 <宽度=512> <高度=512>```
|
||||
* ```fx 生成文本 <文本内容=请输入文本> <字体大小=32> <文字颜色=black> <字体文件=HarmonyOS_Sans_SC_Regular.ttf>```
|
||||
|
||||
## 颜色名称支持
|
||||
- **格式**:颜色列表采用 ```[颜色|位置+颜色|位置+颜色|位置]``` 的格式,位置是形如```(x百分比,y百分比)```的元组。
|
||||
|
||||
@ -131,6 +131,14 @@ def copy_images_by_index(images: list[Image.Image], index: int) -> list[Image.Im
|
||||
|
||||
return new_images
|
||||
|
||||
def generate_image(images: list[Image.Image], filters: list[FilterItem]) -> Image.Image:
|
||||
# 处理位于最前面的生成类滤镜
|
||||
while filters and filters[0].name.strip() in ImageFilterManager.generate_filter_map:
|
||||
gen_filter = filters.pop(0)
|
||||
gen_func = gen_filter.filter
|
||||
func_args = gen_filter.args[1:] # 去掉第一个 list 参数
|
||||
gen_func(None, images, *func_args)
|
||||
|
||||
def save_or_load_image(images: list[Image.Image], filters: list[FilterItem], sender_info: SenderInfo) -> StoredInfo | None:
|
||||
stored_info = None
|
||||
# 处理位于最前面的“读取图像”、“存入图像”
|
||||
@ -150,8 +158,9 @@ def save_or_load_image(images: list[Image.Image], filters: list[FilterItem], sen
|
||||
return stored_info
|
||||
|
||||
async def apply_filters_to_images(images: list[Image.Image], filters: list[FilterItem], sender_info: SenderInfo) -> BytesIO | StoredInfo:
|
||||
# 先处理存取图像的操作
|
||||
# 先处理存取图像、生成图像的操作
|
||||
stored_info = save_or_load_image(images, filters, sender_info)
|
||||
generate_image(images, filters)
|
||||
|
||||
if stored_info and len(filters) <= 0:
|
||||
return stored_info
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import random
|
||||
from PIL import Image, ImageFilter, ImageDraw, ImageStat
|
||||
from PIL import Image, ImageFilter, ImageDraw, ImageStat, ImageFont
|
||||
from PIL import ImageEnhance
|
||||
from PIL import ImageChops
|
||||
from PIL import ImageOps
|
||||
import cv2
|
||||
|
||||
from konabot.common.path import FONTS_PATH
|
||||
from konabot.plugins.fx_process.color_handle import ColorHandle
|
||||
|
||||
import math
|
||||
@ -1130,7 +1131,7 @@ class ImageFilterImplement:
|
||||
|
||||
# 基于形状的描边
|
||||
@staticmethod
|
||||
def apply_shape_stroke(image: Image.Image, stroke_width: int = 5, stroke_color: str = 'black') -> Image.Image:
|
||||
def apply_shape_stroke(image: Image.Image, stroke_width: int = 5, stroke_color: str = 'black', roughness: float = None) -> Image.Image:
|
||||
if image.mode != 'RGBA':
|
||||
image = image.convert('RGBA')
|
||||
|
||||
@ -1149,9 +1150,10 @@ class ImageFilterImplement:
|
||||
cv2.CHAIN_APPROX_SIMPLE
|
||||
)
|
||||
|
||||
# # 减少轮廓点数,以实现尖角效果
|
||||
# epsilon = 0.01 * cv2.arcLength(contours[0], True)
|
||||
# contours = [cv2.approxPolyDP(cnt, epsilon, True) for cnt in contours]
|
||||
# 减少轮廓点数,以实现尖角效果
|
||||
if roughness is not None:
|
||||
epsilon = roughness * cv2.arcLength(contours[0], True)
|
||||
contours = [cv2.approxPolyDP(cnt, epsilon, True) for cnt in contours]
|
||||
|
||||
# 将轮廓点沿法线方向外扩
|
||||
expanded_contours = expand_contours(contours, stroke_width)
|
||||
@ -1230,7 +1232,7 @@ class ImageFilterImplement:
|
||||
|
||||
# 设置通道
|
||||
@staticmethod
|
||||
def apply_set_channel(image: Image.Image, apply_image: Image.Image, channel: str = 'R', value: int = 255) -> Image.Image:
|
||||
def apply_set_channel(image: Image.Image, apply_image: Image.Image, channel: str = 'A') -> Image.Image:
|
||||
if image.mode != 'RGBA':
|
||||
image = image.convert('RGBA')
|
||||
|
||||
@ -1244,6 +1246,36 @@ class ImageFilterImplement:
|
||||
image_arr[:, :, channel_index] = apply_arr[:, :, channel_index]
|
||||
|
||||
return Image.fromarray(image_arr, 'RGBA')
|
||||
|
||||
@staticmethod
|
||||
def generate_empty(image: Image.Image, images: list[Image.Image], width: int = 512, height: int = 512) -> Image.Image:
|
||||
# 生成空白图像
|
||||
empty_image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
|
||||
images.append(empty_image)
|
||||
return image
|
||||
|
||||
@staticmethod
|
||||
def generate_text(image: Image.Image, images: list[Image.Image],
|
||||
text: str = "请输入文本",
|
||||
font_size: int = 32,
|
||||
font_color: str = "black",
|
||||
font_path: str = "HarmonyOS_Sans_SC_Regular.ttf") -> Image.Image:
|
||||
# 生成文本图像
|
||||
font = ImageFont.truetype(FONTS_PATH / font_path, font_size)
|
||||
# 获取文本边界框
|
||||
padding = 10
|
||||
temp_draw = ImageDraw.Draw(Image.new('RGBA', (1,1)))
|
||||
bbox = temp_draw.textbbox((0, 0), text, font=font)
|
||||
text_width = bbox[2] - bbox[0] + padding * 2
|
||||
text_height = bbox[3] - bbox[1] + padding * 2
|
||||
# 创建文本图像
|
||||
text_image = Image.new('RGBA', (text_width, text_height), (255, 255, 255, 0))
|
||||
draw = ImageDraw.Draw(text_image)
|
||||
draw_x = padding - bbox[0]
|
||||
draw_y = padding - bbox[1]
|
||||
draw.text((draw_x,draw_y), text, font=font, fill=ColorHandle.parse_color(font_color) + (255,))
|
||||
images.append(text_image)
|
||||
return image
|
||||
|
||||
|
||||
|
||||
|
||||
@ -62,15 +62,25 @@ class ImageFilterManager:
|
||||
"混合图像": ImageFilterImplement.apply_blend,
|
||||
"覆盖图像": ImageFilterImplement.apply_overlay,
|
||||
# 生成式
|
||||
"生成颜色": ImageFilterImplement.generate_solid
|
||||
"覆加颜色": ImageFilterImplement.generate_solid
|
||||
}
|
||||
|
||||
generate_filter_map = {
|
||||
"生成图层": ImageFilterImplement.generate_empty,
|
||||
"生成文本": ImageFilterImplement.generate_text
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_filter(cls, name: str) -> Optional[callable]:
|
||||
return cls.filter_map.get(name)
|
||||
if name in cls.filter_map:
|
||||
return cls.filter_map[name]
|
||||
elif name in cls.generate_filter_map:
|
||||
return cls.generate_filter_map[name]
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def has_filter(cls, name: str) -> bool:
|
||||
return name in cls.filter_map
|
||||
return name in cls.filter_map or name in cls.generate_filter_map
|
||||
|
||||
|
||||
@ -5,24 +5,44 @@ import numpy as np
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.ops import unary_union
|
||||
|
||||
def fix_with_shapely(contour: list) -> np.ndarray:
|
||||
def fix_with_shapely(contours: list) -> np.ndarray:
|
||||
"""
|
||||
使用Shapely库处理复杂自相交
|
||||
"""
|
||||
# 转换输入为正确的格式
|
||||
contour_array = np.array(contour, dtype=np.int32).reshape(-1, 2)
|
||||
# 转换为Shapely多边形
|
||||
polygon = Polygon(contour_array)
|
||||
|
||||
# 修复自相交
|
||||
fixed_polygon = polygon.buffer(0)
|
||||
|
||||
# 如果修复后是多部分,取最大的部分
|
||||
if fixed_polygon.geom_type == 'MultiPolygon':
|
||||
fixed_polygon = max(fixed_polygon.geoms, key=lambda p: p.area)
|
||||
logger.debug(f"轮廓修复后为多部分,取{fixed_polygon.area}面积最大的部分")
|
||||
fixed_points = np.array(fixed_polygon.exterior.coords, dtype=np.float32)
|
||||
return fixed_points.reshape(-1, 1, 2)
|
||||
fixed_results = []
|
||||
for contour in contours:
|
||||
# 转换输入为正确的格式
|
||||
contour_array = contour.reshape(-1, 2)
|
||||
# 转换为Shapely多边形
|
||||
polygon = Polygon(contour_array)
|
||||
|
||||
# 修复自相交
|
||||
if not polygon.is_valid:
|
||||
polygon = polygon.buffer(0) # 修复无效多边形
|
||||
|
||||
# 提取修复后的轮廓点
|
||||
if polygon.geom_type == 'Polygon':
|
||||
fixed_points = np.array(polygon.exterior.coords, dtype=np.int32)
|
||||
elif polygon.geom_type == 'MultiPolygon':
|
||||
# 处理多个多边形
|
||||
largest = max(polygon.geoms, key=lambda p: p.area)
|
||||
fixed_points = np.array(largest.exterior.coords, dtype=np.int32)
|
||||
|
||||
fixed_results.append(fixed_points.reshape(-1, 1, 2))
|
||||
# 接下来把所有轮廓合并为一个
|
||||
if len(fixed_results) > 1:
|
||||
merged_polygon = unary_union([Polygon(cnt.reshape(-1, 2)) for cnt in fixed_results])
|
||||
if merged_polygon.geom_type == 'Polygon':
|
||||
merged_points = np.array(merged_polygon.exterior.coords, dtype=np.int32)
|
||||
elif merged_polygon.geom_type == 'MultiPolygon':
|
||||
largest = max(merged_polygon.geoms, key=lambda p: p.area)
|
||||
merged_points = np.array(largest.exterior.coords, dtype=np.int32)
|
||||
return [merged_points.reshape(-1, 1, 2)]
|
||||
elif len(fixed_results) == 1:
|
||||
return [fixed_results[0]]
|
||||
else:
|
||||
logger.warning("No valid contours found after fixing with Shapely.")
|
||||
return [np.array([], dtype=np.int32).reshape(0, 1, 2)]
|
||||
|
||||
def expand_contours(contours, stroke_width):
|
||||
"""
|
||||
@ -115,6 +135,4 @@ def expand_contours(contours, stroke_width):
|
||||
|
||||
expanded_contours = fix_with_shapely(expanded_contours)
|
||||
|
||||
expanded_contours = [cnt.astype(np.int32) for cnt in expanded_contours]
|
||||
|
||||
return expanded_contours
|
||||
Reference in New Issue
Block a user