PictureEdit/exporter.py
2025-09-04 12:58:13 +08:00

110 lines
4.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
作者:太一
微信taiyi1224
邮箱shuobo1224@qq.com
"""
from pathlib import Path
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from PIL import Image
from image_manager import ImageManager
import os
class Exporter:
"""导出器,处理批量导出功能"""
@staticmethod
def export_batch(project, output_dir=None, filename_template="{name}_{idx}",
format='png', quality=95, keep_exif=False, progress_callback=None):
"""批量导出所有前景图"""
# 如果没有指定输出目录默认在第一个图片所在目录下创建output文件夹
if output_dir is None and project.foregrounds:
first_image_dir = Path(project.foregrounds[0].file_path).parent
output_dir = first_image_dir / "output"
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
tasks = []
for idx, fg in enumerate(project.foregrounds):
base_name = Path(fg.file_path).stem
filename = filename_template.format(
name=base_name,
idx=idx + 1,
total=len(project.foregrounds),
date=datetime.now().strftime("%Y%m%d")
)
file_path = output_dir / f"{filename}.{format.lower()}"
tasks.append((fg, file_path))
# 使用线程池执行导出任务限制最大线程数为4避免系统卡顿
max_workers = min(4, os.cpu_count() or 1) # 最多使用4个线程或者CPU核心数取较小值
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for fg, path in tasks:
future = executor.submit(
Exporter._export_single,
project.template, fg, path, format, quality, keep_exif
)
future.add_done_callback(lambda f, i=len(futures):
progress_callback(i + 1, len(tasks)) if progress_callback else None)
futures.append(future)
# 检查是否有错误
errors = []
for future in as_completed(futures):
try:
result = future.result(timeout=30) # 设置30秒超时
if not result:
errors.append(1)
except Exception as e:
print(f"导出错误: {str(e)}")
errors.append(1)
return len(errors) == 0
@staticmethod
def _export_single(template, foreground, output_path, format, quality, keep_exif):
"""导出单个前景图"""
try:
# 加载原图
img = ImageManager.load_image(foreground.file_path)
if img is None:
return False
# 应用遮罩
masked_img = ImageManager.apply_mask(img, foreground.mask_shape)
# 使用基于顶点的图像变形方法
transformed_img, (x, y) = ImageManager.transform_with_vertices(
masked_img, foreground.vertices
)
# 创建模板大小的画布
canvas = Image.new('RGBA', (template.width_px, template.height_px),
color=template.bg_color + "FF") # 添加不透明度
# 绘制背景图
if template.bg_image:
# 缩放背景图以适应模板
bg_scaled = template.bg_image.resize(
(template.width_px, template.height_px),
Image.Resampling.LANCZOS
)
canvas.paste(bg_scaled, (0, 0))
# 粘贴前景图
# 确保transformed_img具有RGBA模式
if transformed_img.mode != 'RGBA':
transformed_img = transformed_img.convert('RGBA')
canvas.paste(transformed_img, (int(x), int(y)), transformed_img)
# 保存图像
return ImageManager.save_image(canvas, str(output_path), format, quality, keep_exif)
except Exception as e:
print(f"导出单个图像失败: {e}")
return False