PictureEdit/image_manager.py

224 lines
7.9 KiB
Python
Raw Normal View History

2025-09-02 16:49:39 +08:00
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageOps
class ImageManager:
"""图像管理工具类,处理图像加载、变换和保存"""
@staticmethod
def load_image(file_path):
"""加载图像并转换为RGBA格式"""
try:
return Image.open(file_path).convert("RGBA")
except Exception as e:
print(f"加载图像失败: {e}")
return None
@staticmethod
def apply_mask(image, shape):
"""应用遮罩到图像"""
if shape == "rect":
return image # 矩形不需要遮罩
# 创建与图像相同大小的透明遮罩
mask = Image.new('L', image.size, 0)
draw = ImageDraw.Draw(mask)
# 根据形状绘制遮罩
if shape == "round":
# 圆形遮罩
draw.ellipse((0, 0, image.size[0], image.size[1]), fill=255)
elif shape == "heart":
# 心形遮罩(简化版)
x, y = image.size
points = [
(x // 2, y // 5),
(x // 5, y // 2),
(x // 5, y * 3 // 4),
(x // 2, y * 4 // 5),
(x * 4 // 5, y * 3 // 4),
(x * 4 // 5, y // 2),
(x // 2, y // 5)
]
draw.polygon(points, fill=255)
elif shape == "star":
# 星形遮罩(简化版)
x, y = image.size
center_x, center_y = x // 2, y // 2
radius = min(center_x, center_y)
points = []
for i in range(10):
angle = 0.1 * i * 3.14159
r = radius if i % 2 == 0 else radius * 0.4
points.append((
center_x + r * (1 if i % 4 < 2 else -1) * abs(angle % (2 * 3.14159) - 3.14159 / 2) ** 0.5,
center_y + r * (1 if i < 5 else -1) * abs(angle % (2 * 3.14159) - 3.14159) ** 0.5
))
draw.polygon(points, fill=255)
# 应用遮罩
return Image.composite(image, Image.new('RGBA', image.size, (0, 0, 0, 0)), mask)
@staticmethod
def transform_image(image, x, y, width, height, angle):
"""应用缩放、旋转和位置变换"""
import math
# 确保宽度和高度大于0
width = max(1, width)
height = max(1, height)
# 缩放
scaled = image.resize((int(width), int(height)), Image.Resampling.LANCZOS)
# 旋转
if angle != 0:
# 创建一个更大的画布以容纳旋转后的图像
rotated_size = int(math.sqrt(width**2 + height**2)) * 2
temp_canvas = Image.new('RGBA', (rotated_size, rotated_size), (0, 0, 0, 0))
# 将缩放后的图像居中放置在临时画布上
temp_x = (rotated_size - scaled.width) // 2
temp_y = (rotated_size - scaled.height) // 2
temp_canvas.paste(scaled, (temp_x, temp_y))
# 旋转
rotated = temp_canvas.rotate(angle, expand=True, resample=Image.Resampling.BILINEAR)
# 计算新的偏移量
offset_x = x - (rotated_size - scaled.width) // 2
offset_y = y - (rotated_size - scaled.height) // 2
return rotated, (offset_x, offset_y)
else:
# 如果没有旋转,直接返回缩放后的图像和位置
return scaled, (x, y)
@staticmethod
def perspective_transform(image, vertices, output_size):
"""应用透视变换,将图像映射到指定的四边形顶点"""
try:
import numpy as np
import cv2
# 获取图像尺寸
img_width, img_height = image.size
# 源坐标:图像的四个角
src_points = np.float32([
[0, 0],
[img_width, 0],
[0, img_height],
[img_width, img_height]
])
# 目标坐标:指定的四个顶点
# 确保顶点坐标是列表而不是元组
dst_points = np.float32([list(v) for v in vertices])
# 计算透视变换矩阵
matrix = cv2.getPerspectiveTransform(src_points, dst_points)
# 计算输出图像的尺寸
output_width, output_height = output_size
# 应用透视变换
result = cv2.warpPerspective(
np.array(image),
matrix,
(output_width, output_height),
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0, 0) # 透明背景
)
# 转换回PIL图像
return Image.fromarray(result)
except ImportError:
# 如果没有安装opencv返回原始图像
return image
@staticmethod
def transform_with_vertices(image, vertices):
"""根据顶点坐标对图像进行变形"""
try:
import numpy as np
import cv2
# 获取图像尺寸
img_width, img_height = image.size
# 源坐标:图像的四个角
src_points = np.float32([
[0, 0],
[img_width, 0],
[0, img_height],
[img_width, img_height]
])
# 目标坐标:指定的四个顶点
dst_points = np.float32([list(v) for v in vertices])
# 计算包围盒
min_x = min(v[0] for v in dst_points)
min_y = min(v[1] for v in dst_points)
max_x = max(v[0] for v in dst_points)
max_y = max(v[1] for v in dst_points)
# 调整目标坐标为相对于包围盒的坐标
adjusted_dst_points = dst_points - np.array([min_x, min_y])
# 计算输出尺寸
output_width = int(max_x - min_x)
output_height = int(max_y - min_y)
# 确保输出尺寸大于0
output_width = max(1, output_width)
output_height = max(1, output_height)
# 计算透视变换矩阵
matrix = cv2.getPerspectiveTransform(src_points, adjusted_dst_points)
# 应用透视变换,使用透明背景
result = cv2.warpPerspective(
np.array(image),
matrix,
(output_width, output_height),
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0, 0) # 透明背景而不是黑色背景
)
# 转换回PIL图像
if len(result.shape) == 3 and result.shape[2] == 4: # 如果有alpha通道
return Image.fromarray(result), (min_x, min_y)
else: # 如果没有alpha通道
return Image.fromarray(result).convert('RGBA'), (min_x, min_y)
except ImportError:
# 如果没有安装opencv使用简单的变换
min_x = min(v[0] for v in vertices)
min_y = min(v[1] for v in vertices)
# 确保返回的图像具有RGBA模式
if image.mode != 'RGBA':
image = image.convert('RGBA')
return image, (min_x, min_y)
@staticmethod
def save_image(image, file_path, format='png', quality=95, keep_exif=False):
"""保存图像到文件"""
try:
# 如果是JPEG格式转换为RGB
if format.lower() == 'jpeg':
image = image.convert('RGB')
# 保存图像
image.save(
file_path,
format=format.upper(),
quality=quality,
exif=image.info.get('exif') if keep_exif else None
)
return True
except Exception as e:
print(f"保存图像失败: {e}")
return False