PictureEdit/exporter.py

110 lines
4.2 KiB
Python
Raw Normal View History

2025-09-04 12:55:33 +08:00
"""
作者太一
微信taiyi1224
邮箱shuobo1224@qq.com
"""
2025-09-02 16:49:39 +08:00
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