设置通道
This commit is contained in:
@ -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:
|
||||
|
||||
@ -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,
|
||||
|
||||
120
konabot/plugins/fx_process/math_helper.py
Normal file
120
konabot/plugins/fx_process/math_helper.py
Normal 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
|
||||
Reference in New Issue
Block a user