设置通道

This commit is contained in:
2025-12-10 19:28:34 +08:00
parent a829f035b3
commit 1d763dfc3c
3 changed files with 181 additions and 0 deletions

View File

@ -3,6 +3,7 @@ from PIL import Image, ImageFilter, ImageDraw, ImageStat
from PIL import ImageEnhance
from PIL import ImageChops
from PIL import ImageOps
import cv2
from konabot.plugins.fx_process.color_handle import ColorHandle
@ -12,6 +13,7 @@ from konabot.plugins.fx_process.gradient import GradientGenerator
import numpy as np
from konabot.plugins.fx_process.image_storage import ImageStorager
from konabot.plugins.fx_process.math_helper import expand_contours
from konabot.plugins.fx_process.types import SenderInfo, StoredInfo
class ImageFilterImplement:
@ -1126,6 +1128,45 @@ class ImageFilterImplement:
result = Image.alpha_composite(stroke_image, image)
return result
# 基于形状的描边
@staticmethod
def apply_shape_stroke(image: Image.Image, stroke_width: int = 5, stroke_color: str = 'black') -> Image.Image:
if image.mode != 'RGBA':
image = image.convert('RGBA')
img = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA)
# 提取alpha通道
alpha = img[:, :, 3]
# 应用阈值创建二值掩码
_, binary_mask = cv2.threshold(alpha, 0.5, 255, cv2.THRESH_BINARY)
# 寻找轮廓
contours, hierarchy = cv2.findContours(
binary_mask,
cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE
)
# # 减少轮廓点数,以实现尖角效果
# epsilon = 0.01 * cv2.arcLength(contours[0], True)
# contours = [cv2.approxPolyDP(cnt, epsilon, True) for cnt in contours]
# 将轮廓点沿法线方向外扩
expanded_contours = expand_contours(contours, stroke_width)
# 创建描边图像
stroke_img = np.zeros_like(img)
cv2.fillPoly(stroke_img, expanded_contours, ColorHandle.parse_color(stroke_color) + (255,))
# 轮廓图像转为PIL格式
stroke_pil = Image.fromarray(cv2.cvtColor(stroke_img, cv2.COLOR_BGRA2RGBA))
# 合并描边和原图
result = Image.alpha_composite(stroke_pil, image)
return result
# 半调
@staticmethod
def apply_halftone(image: Image.Image, dot_size: int = 5) -> Image.Image:
@ -1186,6 +1227,24 @@ class ImageFilterImplement:
output.putalpha(alpha)
return output
# 设置通道
@staticmethod
def apply_set_channel(image: Image.Image, apply_image: Image.Image, channel: str = 'R', value: int = 255) -> Image.Image:
if image.mode != 'RGBA':
image = image.convert('RGBA')
if apply_image.mode != 'RGBA':
apply_image = apply_image.convert('RGBA')
# 将 apply_image 的通道设置给 image
image_arr = np.array(image)
apply_arr = np.array(apply_image.resize(image.size, Image.Resampling.LANCZOS))
channel_index = {'R':0, 'G':1, 'B':2, 'A':3}.get(channel.upper(), 0)
image_arr[:, :, channel_index] = apply_arr[:, :, channel_index]
return Image.fromarray(image_arr, 'RGBA')
class ImageFilterEmpty:

View File

@ -48,7 +48,9 @@ class ImageFilterManager:
"动图": ImageFilterEmpty.empty_filter_param,
"像素抖动": ImageFilterImplement.apply_pixel_jitter,
"描边": ImageFilterImplement.apply_stroke,
"形状描边": ImageFilterImplement.apply_shape_stroke,
"半调": ImageFilterImplement.apply_halftone,
"设置通道": ImageFilterImplement.apply_set_channel,
# 图像处理
"存入图像": ImageFilterStorage.store_image,
"读取图像": ImageFilterStorage.load_image,

View File

@ -0,0 +1,120 @@
import cv2
from nonebot import logger
import numpy as np
from shapely.geometry import Polygon
from shapely.ops import unary_union
def fix_with_shapely(contour: 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)
def expand_contours(contours, stroke_width):
"""
将轮廓向外扩展指定宽度
参数:
contours: OpenCV轮廓列表
stroke_width: 扩展宽度(像素)
返回:
扩展后的轮廓列表
"""
expanded_contours = []
for cnt in contours:
# 将轮廓转换为点列表
points = cnt.reshape(-1, 2).astype(np.float32)
n = len(points)
if n < 3:
continue # 至少需要3个点才能形成多边形
expanded_points = []
for i in range(n):
# 获取当前点、前一个点和后一个点
p_curr = points[i]
p_prev = points[(i - 1) % n]
p_next = points[(i + 1) % n]
# 计算两条边的向量
v1 = p_curr - p_prev # 前一条边从prev到curr
v2 = p_next - p_curr # 后一条边从curr到next
# 归一化
norm1 = np.linalg.norm(v1)
norm2 = np.linalg.norm(v2)
if norm1 == 0 or norm2 == 0:
# 如果有零向量,直接沿着法线方向扩展
edge_dir = np.array([0, 0])
if norm1 > 0:
edge_dir = v1 / norm1
elif norm2 > 0:
edge_dir = v2 / norm2
normal = np.array([-edge_dir[1], edge_dir[0]])
expanded_point = p_curr + normal * stroke_width
else:
# 归一化向量
v1_norm = v1 / norm1
v2_norm = v2 / norm2
# 计算两条边的单位法向量(指向多边形外部)
n1 = np.array([-v1_norm[1], v1_norm[0]])
n2 = np.array([-v2_norm[1], v2_norm[0]])
# 计算角平分线方向(两个法向量的和)
bisector = n1 + n2
# 计算平分线的长度
bisector_norm = np.linalg.norm(bisector)
if bisector_norm == 0:
# 如果两条边平行(同向或反向),取任一法线方向
expanded_point = p_curr + n1 * stroke_width
else:
# 归一化平分线
bisector_normalized = bisector / bisector_norm
# 计算偏移距离(考虑夹角)
# 使用余弦定理计算正确的偏移距离
cos_angle = np.dot(v1_norm, v2_norm)
angle = np.arccos(np.clip(cos_angle, -1.0, 1.0))
if abs(np.pi - angle) < 1e-6: # 近似平角
# 接近直线的情况
offset_distance = stroke_width
else:
# 计算正确的偏移距离
offset_distance = stroke_width / np.sin(angle / 2)
# 计算扩展点
expanded_point = p_curr + bisector_normalized * offset_distance
expanded_points.append(expanded_point)
# 将扩展后的点转换为整数坐标
expanded_cnt = np.array(expanded_points, dtype=np.float32).reshape(-1, 1, 2)
expanded_contours.append(expanded_cnt.astype(np.int32))
expanded_contours = fix_with_shapely(expanded_contours)
expanded_contours = [cnt.astype(np.int32) for cnt in expanded_contours]
return expanded_contours