diff --git a/scripts/test2.py b/scripts/PSD_test1.py similarity index 100% rename from scripts/test2.py rename to scripts/PSD_test1.py diff --git a/scripts/compose_poster_1.py b/scripts/compose_poster_1.py deleted file mode 100644 index 60778e9..0000000 --- a/scripts/compose_poster_1.py +++ /dev/null @@ -1,402 +0,0 @@ -""" -图层合成模块 - 将生成的各个图层组合成完整海报 (修改版) - -提供增强的图层信息导出,便于PSD模块使用 -""" - -import os -import json -import logging -from typing import List, Dict, Any, Optional, Tuple -import numpy as np -from PIL import Image, ImageFont, ImageDraw - -# 配置日志 -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') -logger = logging.getLogger(__name__) - -class PosterComposer: - """海报合成类,负责将各个图层组合成完整海报""" - - def __init__(self, images_dir: str = "./images", output_dir: str = "./outputs"): - """ - 初始化海报合成器 - - 参数: - images_dir: 源图像目录 - output_dir: 合成图像输出目录 - """ - self.images_dir = images_dir - self.output_dir = output_dir - os.makedirs(output_dir, exist_ok=True) - logger.info(f"海报合成器初始化完成,图像目录: {images_dir}, 输出目录: {output_dir}") - - def apply_golden_ratio_positioning(self, - canvas_size: Tuple[int, int], - element_size: Tuple[int, int], - position: str = "right") -> Tuple[int, int]: - """ - 基于黄金分割比例(约1.618)计算位置 - - 参数: - canvas_size: 画布宽度和高度 - element_size: 要放置元素的宽度和高度 - position: 黄金分割位置 ("left", "right", "top", "bottom") - - 返回: - 元素的(x, y)坐标 - """ - width, height = canvas_size - elem_width, elem_height = element_size - - # 黄金比例约为1.618 - golden_ratio = 1.618 - - if position == "right": - # 水平方向右侧黄金分割点 - x = int((width / golden_ratio) - (elem_width / 2)) - y = int((height - elem_height) / 2) # 垂直居中 - - elif position == "left": - # 水平方向左侧黄金分割点 - x = int(width - (width / golden_ratio) - (elem_width / 2)) - y = int((height - elem_height) / 2) # 垂直居中 - - elif position == "top": - # 垂直方向上侧黄金分割点 - x = int((width - elem_width) / 2) # 水平居中 - y = int((height / golden_ratio) - (elem_height / 2)) - - elif position == "bottom": - # 垂直方向下侧黄金分割点 - x = int((width - elem_width) / 2) # 水平居中 - y = int(height - (height / golden_ratio) - (elem_height / 2)) - - else: # 默认居中 - x = int((width - elem_width) / 2) - y = int((height - elem_height) / 2) - - return (x, y) - - def calculate_position(self, - position_type: str, - canvas_size: Tuple[int, int], - element_size: Tuple[int, int], - offset: Tuple[int, int] = (0, 0), - reference: str = None) -> Tuple[int, int]: - """ - 根据指定的位置类型计算位置 - - 参数: - position_type: 位置类型 ("center", "golden_ratio", "corner", "custom") - canvas_size: 画布宽度和高度 - element_size: 要放置元素的宽度和高度 - offset: 从计算位置的(x, y)偏移量 - reference: 额外位置参考 (如 "top_left", "golden_right") - - 返回: - 元素的(x, y)坐标 - """ - width, height = canvas_size - elem_width, elem_height = element_size - offset_x, offset_y = offset - - if position_type == "center": - # 居中定位 - x = int((width - elem_width) / 2) + offset_x - y = int((height - elem_height) / 2) + offset_y - - elif position_type == "golden_ratio": - # 黄金分割定位 - golden_pos = reference.split("_")[1] if reference else "right" - x, y = self.apply_golden_ratio_positioning(canvas_size, element_size, golden_pos) - x += offset_x - y += offset_y - - elif position_type == "corner": - # 角落定位 - corner = reference if reference else "top_left" - - if corner == "top_left": - x, y = 0, 0 - elif corner == "top_right": - x, y = width - elem_width, 0 - elif corner == "bottom_left": - x, y = 0, height - elem_height - elif corner == "bottom_right": - x, y = width - elem_width, height - elem_height - - x += offset_x - y += offset_y - - elif position_type == "custom": - # 自定义位置,偏移量为绝对位置 - x, y = offset_x, offset_y - - else: # 默认居中 - x = int((width - elem_width) / 2) + offset_x - y = int((height - elem_height) / 2) + offset_y - - return (x, y) - - def fit_image_to_canvas(self, - image: Image.Image, - canvas_size: Tuple[int, int], - fit_method: str = "contain") -> Image.Image: - """ - 调整图像大小以适应画布尺寸 - - 参数: - image: 要调整大小的PIL图像 - canvas_size: 目标画布尺寸 - fit_method: 适应方法 ("contain", "cover", "stretch") - - 返回: - 调整大小后的PIL图像 - """ - canvas_width, canvas_height = canvas_size - img_width, img_height = image.size - - if fit_method == "contain": - # 缩放图像以适应画布同时保持纵横比 - ratio = min(canvas_width / img_width, canvas_height / img_height) - new_width = int(img_width * ratio) - new_height = int(img_height * ratio) - - elif fit_method == "cover": - # 缩放图像以覆盖画布同时保持纵横比 - ratio = max(canvas_width / img_width, canvas_height / img_height) - new_width = int(img_width * ratio) - new_height = int(img_height * ratio) - - elif fit_method == "stretch": - # 拉伸图像以匹配画布尺寸 - new_width = canvas_width - new_height = canvas_height - - else: # 默认为"contain" - ratio = min(canvas_width / img_width, canvas_height / img_height) - new_width = int(img_width * ratio) - new_height = int(img_height * ratio) - - # 调整图像大小 - return image.resize((new_width, new_height), Image.LANCZOS) - - def apply_overlay(self, - base_image: Image.Image, - overlay_image: Image.Image, - position: Tuple[int, int] = (0, 0), - opacity: float = 1.0) -> Image.Image: - """ - 将叠加图像应用到基础图像上 - - 参数: - base_image: 基础PIL图像 - overlay_image: 叠加PIL图像 - position: 放置叠加图的位置(x, y) - opacity: 叠加图像的不透明度(0.0到1.0) - - 返回: - 合并后的PIL图像 - """ - # 创建基础图像的副本 - result = base_image.copy() - - # 确保叠加图像有alpha通道 - if overlay_image.mode != 'RGBA': - overlay_image = overlay_image.convert('RGBA') - - # 如果不透明度小于1,调整alpha通道 - if opacity < 1.0: - overlay_data = list(overlay_image.getdata()) - new_data = [] - - for item in overlay_data: - # 通过不透明度调整alpha通道 - new_data.append((item[0], item[1], item[2], int(item[3] * opacity))) - - overlay_image.putdata(new_data) - - # 将叠加图粘贴到结果上 - result.paste(overlay_image, position, overlay_image) - - return result - - def load_image(self, path: str) -> Optional[Image.Image]: - """ - 从文件路径加载图像 - - 参数: - path: 图像文件的路径 - - 返回: - PIL图像对象,加载失败则返回None - """ - try: - if os.path.exists(path): - return Image.open(path).convert('RGBA') - else: - logger.warning(f"未找到图像文件: {path}") - return None - except Exception as e: - logger.error(f"从{path}加载图像时出错: {e}") - return None - - def compose_from_layout(self, - layout_config: Dict[str, Any], - output_filename: str = "composed_poster.png", - canvas_size: Tuple[int, int] = (1200, 1600)) -> Dict[str, Any]: - """ - 根据布局配置合成海报 - - 参数: - layout_config: 指定图层及其属性的配置 - output_filename: 输出图像文件的名称 - canvas_size: 输出画布的大小(宽度,高度) - - 返回: - 包含合成海报信息的字典 - """ - # 创建带alpha通道的空白画布 - canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) - - # 获取从底到顶的图层顺序 - layer_order = layout_config.get("layer_order", [ - "background", "main_subject", "secondary_subject", - "decorative_elements", "logo", "title", "subtitle" - ]) - - # 跟踪已处理的图层以供输出信息 - processed_layers = {} - - # 根据布局配置处理每个图层 - for layer_name in layer_order: - layer_config = layout_config.get(layer_name, {}) - - if not layer_config: - logger.warning(f"未找到图层'{layer_name}'的配置") - continue - - # 获取图层图像路径 - image_path = layer_config.get("image_path", "") - full_path = os.path.join(self.images_dir, image_path) if image_path else None - - if not full_path or not os.path.exists(full_path): - logger.warning(f"未找到图层'{layer_name}'的图像文件: {full_path}") - continue - - # 加载图层图像 - layer_img = self.load_image(full_path) - - if layer_img is None: - continue - - # 获取拟合方法 - fit_method = layer_config.get("fit_method", "contain") - - # 如果指定,调整图层大小以适应画布 - if fit_method != "none": - layer_img = self.fit_image_to_canvas(layer_img, canvas_size, fit_method) - - # 获取位置信息 - position_type = layer_config.get("position_type", "center") - position_reference = layer_config.get("position_reference", None) - position_offset = layer_config.get("position_offset", (0, 0)) - - # 计算位置 - position = self.calculate_position( - position_type, - canvas_size, - layer_img.size, - position_offset, - position_reference - ) - - # 获取不透明度 - opacity = layer_config.get("opacity", 1.0) - - # 将图层应用到画布 - canvas = self.apply_overlay(canvas, layer_img, position, opacity) - - # 记录处理信息 - processed_layers[layer_name] = { - "image_path": full_path, - "size": layer_img.size, - "position": position, - "opacity": opacity - } - - logger.info(f"已添加图层'{layer_name}',位置{position},大小{layer_img.size}") - - # 保存合成图像 - output_path = os.path.join(self.output_dir, output_filename) - canvas.save(output_path, format="PNG") - logger.info(f"已保存合成图像到{output_path}") - - # 保存图层顺序,用于PSD导出 - composition_result = { - "output_path": output_path, - "layers": processed_layers, - "canvas_size": canvas_size, - "layer_order": [layer for layer in layer_order if layer in processed_layers] # 只包含实际处理的图层 - } - - return composition_result - - def generate_layer_info_for_psd(self, composition_result: Dict[str, Any]) -> Dict[str, Any]: - """ - 生成PSD导出所需的完整图层信息 - - 参数: - composition_result: compose_from_layout的结果 - - 返回: - 包含图层位置、路径、透明度等信息的字典,供PSD导出使用 - """ - # 这个方法直接返回composition_result中已经包含的完整图层信息 - # 为了保持与原代码的API兼容性,我们保留这个方法 - - psd_info = { - "layer_order": composition_result.get("layer_order", []), - "layers": composition_result.get("layers", {}), - "canvas_size": composition_result.get("canvas_size", (1200, 1600)) - } - - logger.info(f"已生成PSD导出信息,包含{len(psd_info['layer_order'])}个图层") - return psd_info - - -def load_layout_config(config_path: str) -> Dict[str, Any]: - """ - 从JSON文件加载布局配置 - - 参数: - config_path: 布局配置JSON文件的路径 - - 返回: - 包含布局配置的字典 - """ - try: - with open(config_path, 'r', encoding='utf-8') as f: - return json.load(f) - except Exception as e: - logger.error(f"从{config_path}加载布局配置时出错: {e}") - return {} - - -if __name__ == "__main__": - # 示例用法 - layout_config_path = "../configs/layout_config.json" - - # 加载布局配置 - layout_config = load_layout_config(layout_config_path) - - # 创建合成器并合成海报 - composer = PosterComposer() - composition_result = composer.compose_from_layout(layout_config) - - print(f"海报成功合成到: {composition_result['output_path']}") - - # 为PSD导出生成图层信息 - psd_info = composer.generate_layer_info_for_psd(composition_result) - print(f"PSD导出信息准备完成,包含{len(psd_info['layer_order'])}个图层") \ No newline at end of file diff --git a/scripts/test1.py b/scripts/compose_test1.py similarity index 100% rename from scripts/test1.py rename to scripts/compose_test1.py