psd method init
This commit is contained in:
parent
574650f3f0
commit
fcb02de2cb
BIN
scripts/__pycache__/compose_poster.cpython-38.pyc
Normal file
BIN
scripts/__pycache__/compose_poster.cpython-38.pyc
Normal file
Binary file not shown.
BIN
scripts/__pycache__/export_psd.cpython-38.pyc
Normal file
BIN
scripts/__pycache__/export_psd.cpython-38.pyc
Normal file
Binary file not shown.
84
scripts/compose_poster.py
Normal file
84
scripts/compose_poster.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# compose_poster.py - 合成图层
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 设置默认图片目录
|
||||||
|
DEFAULT_IMAGE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'images')
|
||||||
|
|
||||||
|
def compose_layers(layers, output_path, canvas_size=(1080, 1920)):
|
||||||
|
"""
|
||||||
|
合成多个图层为一个图像
|
||||||
|
|
||||||
|
Args:
|
||||||
|
layers: 图层文件名列表,从底到顶排序
|
||||||
|
output_path: 输出文件路径
|
||||||
|
canvas_size: 画布大小,默认为(1080, 1920)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 输出文件的路径
|
||||||
|
"""
|
||||||
|
logger.info(f"开始合成{len(layers)}个图层...")
|
||||||
|
|
||||||
|
# 创建空白画布
|
||||||
|
canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))
|
||||||
|
|
||||||
|
# 逐个添加图层
|
||||||
|
for i, layer_name in enumerate(layers):
|
||||||
|
try:
|
||||||
|
# 构建完整的图层路径
|
||||||
|
layer_path = os.path.join(DEFAULT_IMAGE_DIR, layer_name)
|
||||||
|
|
||||||
|
if os.path.exists(layer_path):
|
||||||
|
# 打开图层图像
|
||||||
|
layer = Image.open(layer_path).convert('RGBA')
|
||||||
|
|
||||||
|
# 调整图层大小以适应画布
|
||||||
|
# 如果图层尺寸与画布不同,将其居中
|
||||||
|
if layer.size != canvas_size:
|
||||||
|
new_layer = Image.new('RGBA', canvas_size, (0, 0, 0, 0))
|
||||||
|
paste_x = (canvas_size[0] - layer.width) // 2
|
||||||
|
paste_y = (canvas_size[1] - layer.height) // 2
|
||||||
|
new_layer.paste(layer, (paste_x, paste_y), layer)
|
||||||
|
layer = new_layer
|
||||||
|
|
||||||
|
# 合成图层
|
||||||
|
canvas = Image.alpha_composite(canvas, layer)
|
||||||
|
logger.info(f"已添加第{i+1}个图层: {layer_path}")
|
||||||
|
else:
|
||||||
|
logger.error(f"图层文件不存在: {layer_path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理图层{layer_path}时出错: {str(e)}")
|
||||||
|
|
||||||
|
# 设置默认输出目录
|
||||||
|
default_output_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'outputs')
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
if not os.path.exists(default_output_dir):
|
||||||
|
os.makedirs(default_output_dir)
|
||||||
|
|
||||||
|
# 构建输出文件的完整路径
|
||||||
|
output_path = os.path.join(default_output_dir, os.path.basename(output_path))
|
||||||
|
|
||||||
|
# 保存合成图像
|
||||||
|
canvas.save(output_path)
|
||||||
|
logger.info(f"图层合成完成,已保存到: {output_path}")
|
||||||
|
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 简单测试
|
||||||
|
from sys import argv
|
||||||
|
|
||||||
|
if len(argv) >= 3:
|
||||||
|
# 从命令行接收参数
|
||||||
|
layers = argv[1:-1] # 现在只需要提供文件名,不需要完整路径
|
||||||
|
output = argv[-1]
|
||||||
|
compose_layers(layers, output)
|
||||||
|
else:
|
||||||
|
print("用法: python compose_poster.py layer1.png layer2.png ... output.png")
|
||||||
|
print(f"图片将从默认目录加载: {DEFAULT_IMAGE_DIR}")
|
402
scripts/compose_poster_1.py
Normal file
402
scripts/compose_poster_1.py
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
"""
|
||||||
|
图层合成模块 - 将生成的各个图层组合成完整海报 (修改版)
|
||||||
|
|
||||||
|
提供增强的图层信息导出,便于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'])}个图层")
|
91
scripts/export_psd.py
Normal file
91
scripts/export_psd.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# export_psd.py - PSD导出 (适配psd-tools 1.10.0 API)
|
||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# 设置日志
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# 设置默认目录路径
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
DEFAULT_INPUT_DIR = os.path.join(project_root, 'images')
|
||||||
|
DEFAULT_OUTPUT_DIR = os.path.join(project_root, 'outputs')
|
||||||
|
|
||||||
|
def export_to_psd(layers, output_path, layer_names=None, canvas_size=(1080, 1920)):
|
||||||
|
# 先导入compose_poster中的函数
|
||||||
|
from compose_poster import compose_layers
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 导入psd-tools相关模块
|
||||||
|
from psd_tools import PSDImage
|
||||||
|
from psd_tools.api.layers import PixelLayer
|
||||||
|
from psd_tools.constants import Compression
|
||||||
|
except ImportError:
|
||||||
|
logger.error("未安装psd-tools库,无法导出PSD。请安装:pip install psd-tools>=1.10.0")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
logger.info(f"开始创建PSD文件,包含{len(layers)}个图层...")
|
||||||
|
|
||||||
|
# 1. 先使用compose_layers合成图层
|
||||||
|
temp_output = os.path.join(DEFAULT_OUTPUT_DIR, "temp_composed.png")
|
||||||
|
composed_path = compose_layers(layers, temp_output, canvas_size)
|
||||||
|
|
||||||
|
if not composed_path:
|
||||||
|
logger.error("图层合成失败")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 2. 处理输出路径
|
||||||
|
if not os.path.isabs(output_path):
|
||||||
|
output_path = os.path.join(DEFAULT_OUTPUT_DIR, output_path)
|
||||||
|
|
||||||
|
if not output_path.lower().endswith('.psd'):
|
||||||
|
output_path += '.psd'
|
||||||
|
|
||||||
|
# 确保输出目录存在
|
||||||
|
output_dir = os.path.dirname(output_path)
|
||||||
|
if output_dir and not os.path.exists(output_dir):
|
||||||
|
os.makedirs(output_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 3. 创建PSD文件
|
||||||
|
psd = PSDImage.new('RGB', canvas_size)
|
||||||
|
|
||||||
|
# 4. 添加合成后的图层
|
||||||
|
composed_image = Image.open(composed_path).convert('RGBA')
|
||||||
|
pixel_layer = PixelLayer.frompil(composed_image, psd, "Composed_Layer")
|
||||||
|
psd.append(pixel_layer)
|
||||||
|
|
||||||
|
# 5. 保存PSD文件
|
||||||
|
psd.save(output_path)
|
||||||
|
logger.info(f"PSD文件已成功创建并保存到: {output_path}")
|
||||||
|
|
||||||
|
# 6. 清理临时文件
|
||||||
|
if os.path.exists(temp_output):
|
||||||
|
os.remove(temp_output)
|
||||||
|
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"创建PSD文件时出错: {str(e)}")
|
||||||
|
logger.exception(e)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 简单测试
|
||||||
|
from sys import argv
|
||||||
|
|
||||||
|
if len(argv) >= 3:
|
||||||
|
# 从命令行接收参数
|
||||||
|
layers = argv[1:-1]
|
||||||
|
output = argv[-1]
|
||||||
|
result = export_to_psd(layers, output)
|
||||||
|
if result:
|
||||||
|
print(f"PSD文件已成功导出到: {result}")
|
||||||
|
else:
|
||||||
|
print("PSD文件导出失败")
|
||||||
|
else:
|
||||||
|
print("用法: python export_psd.py layer1.png layer2.png ... output.psd")
|
||||||
|
print(f"默认输入目录: {DEFAULT_INPUT_DIR}")
|
||||||
|
print(f"默认输出目录: {DEFAULT_OUTPUT_DIR}")
|
||||||
|
print("注意:如果只提供文件名,将从默认输入目录查找图片文件")
|
7
scripts/test1.py
Normal file
7
scripts/test1.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from compose_poster import compose_layers
|
||||||
|
|
||||||
|
# 将多个图层合成为一个图像
|
||||||
|
compose_layers(
|
||||||
|
['background.jpg','aaai.png', 'nankai.jpg'],
|
||||||
|
|
||||||
|
)
|
8
scripts/test2.py
Normal file
8
scripts/test2.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from export_psd import export_to_psd
|
||||||
|
|
||||||
|
# 将多个图层导出为PSD文件
|
||||||
|
export_to_psd(
|
||||||
|
['background.jpg','aaai.png', 'nankai.jpg'],
|
||||||
|
'output.psd',
|
||||||
|
['background', 'middle', 'front'] # 可选的图层名称
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user