125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
Collage风格(拼图风格)海报模板
|
|||
|
|
"""
|
|||
|
|
import logging
|
|||
|
|
import random
|
|||
|
|
from typing import List, Tuple, Optional
|
|||
|
|
|
|||
|
|
from PIL import Image, ImageDraw
|
|||
|
|
|
|||
|
|
from .base_template import BaseTemplate
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CollageTemplate(BaseTemplate):
|
|||
|
|
"""
|
|||
|
|
拼图风格模板,用于将多张图片组合成一张海报。
|
|||
|
|
支持多种布局样式,如网格、非对称、电影胶片等。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def generate(self,
|
|||
|
|
image_paths: List[str],
|
|||
|
|
style: str = 'grid_2x2',
|
|||
|
|
**kwargs) -> Image.Image:
|
|||
|
|
"""
|
|||
|
|
生成拼图海报
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
image_paths (List[str]): 用于制作拼图的图片路径列表
|
|||
|
|
style (str): 拼图样式 (例如 'grid_2x2', 'asymmetric', 'filmstrip')
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Image.Image: 生成的拼图海报图像
|
|||
|
|
"""
|
|||
|
|
if not self._validate_inputs(['image_paths'], image_paths=image_paths):
|
|||
|
|
return None
|
|||
|
|
if not image_paths:
|
|||
|
|
logger.error("生成拼图失败: 图片列表不能为空。")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
images = [self.image_processor.load_image(p) for p in image_paths if p]
|
|||
|
|
images = [img for img in images if img] # 过滤掉加载失败的图片
|
|||
|
|
|
|||
|
|
if not images:
|
|||
|
|
logger.error("所有图片都加载失败。")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
# 根据样式选择对应的私有方法
|
|||
|
|
style_method_name = f"_create_{style}_collage"
|
|||
|
|
style_method = getattr(self, style_method_name, self._create_grid_2x2_collage)
|
|||
|
|
|
|||
|
|
logger.info(f"使用 '{style}' 风格创建拼图...")
|
|||
|
|
return style_method(images, self.size)
|
|||
|
|
|
|||
|
|
def _resize_and_crop(self, img, target_size):
|
|||
|
|
"""辅助方法:调整并裁剪图片以适应目标尺寸"""
|
|||
|
|
return self.image_processor.resize_and_crop(img, target_size)
|
|||
|
|
|
|||
|
|
# --- 各种拼图样式的实现 ---
|
|||
|
|
|
|||
|
|
def _create_grid_2x2_collage(self, images: List[Image.Image], target_size: Tuple[int, int]) -> Image.Image:
|
|||
|
|
"""创建 2x2 网格拼图"""
|
|||
|
|
if len(images) < 4:
|
|||
|
|
logger.warning(f"2x2网格需要4张图,但只提供了{len(images)}张。将使用重复图片。")
|
|||
|
|
images.extend([random.choice(images) for _ in range(4 - len(images))])
|
|||
|
|
|
|||
|
|
w, h = target_size
|
|||
|
|
img_w, img_h = w // 2, h // 2
|
|||
|
|
|
|||
|
|
canvas = self.create_canvas()
|
|||
|
|
|
|||
|
|
resized_images = [self._resize_and_crop(img, (img_w, img_h)) for img in images[:4]]
|
|||
|
|
|
|||
|
|
canvas.paste(resized_images[0], (0, 0))
|
|||
|
|
canvas.paste(resized_images[1], (img_w, 0))
|
|||
|
|
canvas.paste(resized_images[2], (0, img_h))
|
|||
|
|
canvas.paste(resized_images[3], (img_w, img_h))
|
|||
|
|
|
|||
|
|
return canvas
|
|||
|
|
|
|||
|
|
def _create_asymmetric_collage(self, images: List[Image.Image], target_size: Tuple[int, int]) -> Image.Image:
|
|||
|
|
"""创建非对称拼图(左一右二)"""
|
|||
|
|
if len(images) < 3:
|
|||
|
|
logger.warning(f"非对称拼图需要3张图,将使用重复图片。")
|
|||
|
|
images.extend([random.choice(images) for _ in range(3 - len(images))])
|
|||
|
|
|
|||
|
|
w, h = target_size
|
|||
|
|
main_w = int(w * 0.6)
|
|||
|
|
side_w = w - main_w
|
|||
|
|
side_h = h // 2
|
|||
|
|
|
|||
|
|
canvas = self.create_canvas()
|
|||
|
|
|
|||
|
|
main_img = self._resize_and_crop(images[0], (main_w, h))
|
|||
|
|
side_img1 = self._resize_and_crop(images[1], (side_w, side_h))
|
|||
|
|
side_img2 = self._resize_and_crop(images[2], (side_w, side_h))
|
|||
|
|
|
|||
|
|
canvas.paste(main_img, (0, 0))
|
|||
|
|
canvas.paste(side_img1, (main_w, 0))
|
|||
|
|
canvas.paste(side_img2, (main_w, side_h))
|
|||
|
|
|
|||
|
|
return canvas
|
|||
|
|
|
|||
|
|
def _create_filmstrip_collage(self, images: List[Image.Image], target_size: Tuple[int, int]) -> Image.Image:
|
|||
|
|
"""创建电影胶片风格拼图"""
|
|||
|
|
num_images = min(len(images), 4) # 最多4张
|
|||
|
|
w, h = target_size
|
|||
|
|
padding = 10
|
|||
|
|
img_h = (h - (num_images + 1) * padding) // num_images
|
|||
|
|
|
|||
|
|
canvas = self.create_canvas(background_color=(30, 30, 30, 255))
|
|||
|
|
|
|||
|
|
current_y = padding
|
|||
|
|
for img in images[:num_images]:
|
|||
|
|
resized_img = self._resize_and_crop(img, (w - 2 * padding, img_h))
|
|||
|
|
canvas.paste(resized_img, (padding, current_y))
|
|||
|
|
current_y += img_h + padding
|
|||
|
|
|
|||
|
|
return canvas
|
|||
|
|
|
|||
|
|
def _validate_inputs(self, required_keys: list, **kwargs) -> bool:
|
|||
|
|
return super()._validate_inputs(required_keys, **kwargs)
|