From 4cd8eec947f015d083c10ccff71bb881f6eb1236 Mon Sep 17 00:00:00 2001 From: cyborvirtue <2088953655@qq.com> Date: Thu, 22 May 2025 00:22:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=AF=BC=E5=87=BAPSD,?= =?UTF-8?q?=E9=80=9A=E8=BF=87=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/PSD_test1.py | 20 +- scripts/__pycache__/export_psd.cpython-38.pyc | Bin 2429 -> 2352 bytes .../__pycache__/export_psd2.cpython-38.pyc | Bin 0 -> 1954 bytes scripts/compose_poster.py | 84 -------- scripts/compose_test1.py | 7 - scripts/export_psd.py | 191 +++++++++--------- 6 files changed, 113 insertions(+), 189 deletions(-) create mode 100644 scripts/__pycache__/export_psd2.cpython-38.pyc delete mode 100644 scripts/compose_poster.py delete mode 100644 scripts/compose_test1.py diff --git a/scripts/PSD_test1.py b/scripts/PSD_test1.py index 78bd2d8..8556279 100644 --- a/scripts/PSD_test1.py +++ b/scripts/PSD_test1.py @@ -1,8 +1,14 @@ -from export_psd import export_to_psd +from export_psd import create_psd_from_images -# 将多个图层导出为PSD文件 -export_to_psd( - ['background.jpg','aaai.png', 'nankai.jpg'], - 'output.psd', - ['background', 'middle', 'front'] # 可选的图层名称 -) \ No newline at end of file +if __name__ == "__main__": + image_list = [ + '../images/background.jpg', + '../images/nankai.jpg', + '../images/aaai.png', + # Add more images as needed + ] + + create_psd_from_images( + image_paths=image_list, + output_path='../outputs/combined_output.psd' + ) \ No newline at end of file diff --git a/scripts/__pycache__/export_psd.cpython-38.pyc b/scripts/__pycache__/export_psd.cpython-38.pyc index eddd5939dde510d307c1fd5a9ba85aa7bf1b935d..d0150147a704ca027ad07a7230a5ed71a8b54a9f 100644 GIT binary patch literal 2352 zcmZ8i>u(!H5Z}FrALsKUPUs6%9U!2Vf{l>iLqG^YD}+>`3W^FR(CO;iCYPMg$KJV3 ztuqp|(6psZ1ucc96?RpifM^R6ZAjYkf9zL$PW*{aNJu=y>^V&gXYJ1H?EGeCXJ^N+ zXR``|ul>j4QxB>L{mK`^UlJD2!|*?b$wE4^uq8OSCg=j@yy%EENta??a^zYuEg@Blc}k?3j8QZlk8SRvP2;{k~<)9-jXflqHJX>^{A}pNx{m_ z2=ug-v+`D9Qq)ICo{U%{R`C?~F29CKV~tY~n5HScpH8k`KiB*8Sa*8kb^#8M=!f?~A^Q2|hJ_zK=q`TWJ9~HK&NZ;yhk#HCvlQsRTr(#L z@bWz-^J2Z`QR4e{-F>AbFlo2#2Tb1E@EoqE?6GHwv)epO=r@ABDk5ZSFfsAfmtH8z zEMs$fhGzy-KFib_f!7G)70Xmj_n_$;zTG5Ds?{yR5|jiD>Q*Hl&)Qc2nuGBo4F4)d zC_vK~hR_z{SqgCne*BVoiqGKYob*lCOWv4 z3BqgE>9IN=Ci@#x3+4v)Y}Z$>ROl&0^4GbpEU zD;s73r>@~Lp}LNfA$CBua$rvlbL;X^D;sPIb4NBjB>4(X{d|1t4Cv=V8BUQ8Gt--a z7JzO63b9Ua1zH5E038LI0lO-X^9+7xd>?Y3lu)p3MmdA&WsF*RjG9HTHx}ma`QL}0 zLe_n){JZ%#(3{8=-hdSp`liMsa9vPd8hpGw*Xo)V@BgZsm*>6e9xmjl{b4lUUNf;q zqWSlG=Wa}_3pH)1#3rqq|3=IqqvnbEy6%wC*pLqVGRje_<;Zjt)>m`a>l; zp@HVg+2v^QNOa}v=*B$6+-u*CZqLIz+Yw#66D@r;uJynB3}|V{1TX9uJ9x+7rGNS2 z>hT|YS37`RQ{2lRADM-RWz=UVWDWHNK5?#@aQNfWnxgi&)_4>o>mf(Ce(cSiiB5bm zNGI_DV7g*8{>TKCQ==qD4}j;1g^a9K<{Ke8{$fDLpYo?8&KsrS_XX;T9;5TTdRK(9vv%@;0N3ukPN(5~c?@OP74} zAYrmcZ5Lp^=hy+64fJ#s0CgW0Omx7$6o(@@vGKND)ilhHaRpvLY48OA9tjgD34*AUXSO7t%2hlp*SpuyGf_ryu~DhN)C!NyD(} zRm0HJ{B%LR?)Y(4>EfQ3cgLlqkFM{Ot9928OgHf3lA)tuLr(;UJ=>k6GPj!GlCced z>DlG@3jMg`nCJ(TiBMGQS>V@f7)%-TE(czIe0@XH4zS5m&qDubIOI9r3H&Eul4KRK jtKt+ch-w01^p}7WSb%&BSol+YCM5#J!rxLdrvLm4o{1uA literal 2429 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 literal 0 HcmV?d00001 diff --git a/scripts/compose_poster.py b/scripts/compose_poster.py deleted file mode 100644 index 47434c1..0000000 --- a/scripts/compose_poster.py +++ /dev/null @@ -1,84 +0,0 @@ -# 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}") \ No newline at end of file diff --git a/scripts/compose_test1.py b/scripts/compose_test1.py deleted file mode 100644 index 3ff1933..0000000 --- a/scripts/compose_test1.py +++ /dev/null @@ -1,7 +0,0 @@ -from compose_poster import compose_layers - -# 将多个图层合成为一个图像 -compose_layers( - ['background.jpg','aaai.png', 'nankai.jpg'], - -) \ No newline at end of file diff --git a/scripts/export_psd.py b/scripts/export_psd.py index 2800a48..0e1dbda 100644 --- a/scripts/export_psd.py +++ b/scripts/export_psd.py @@ -1,91 +1,100 @@ -# 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("注意:如果只提供文件名,将从默认输入目录查找图片文件") \ No newline at end of file +""" +测试文件:从多个图片创建PSD文件 +将图片列表作为输入,从底到顶添加为PSD图层,并将图片居中放置 +""" + +from psd_tools import PSDImage +from PIL import Image +from psd_tools.constants import Compression +import os +from typing import List, Tuple + +# 导入PixelLayer类,用于从PIL图像创建图层 +from psd_tools.api.layers import PixelLayer + + +def create_psd_from_images( + image_paths: List[str], + output_path: str, + canvas_size: Tuple[int, int] = (1000, 800), + mode: str = 'RGB' +) -> None: + """ + 从图片列表创建PSD文件,将图片从底到顶堆叠 + + 参数: + image_paths: 图片路径列表 + output_path: 保存PSD文件的路径 + canvas_size: PSD画布大小,格式为(宽度, 高度) + mode: PSD文件的颜色模式 + """ + # 确保输出目录存在 + os.makedirs(os.path.dirname(os.path.abspath(output_path)), exist_ok=True) + + try: + # 1. 创建一个新的PSD文件 + psd = PSDImage.new(mode, canvas_size) + + # 2. 打开并添加每个图片作为图层 + for i, img_path in enumerate(image_paths): + # 打开图片 + image = Image.open(img_path) + + # 计算居中位置 + left = (canvas_size[0] - image.width) // 2 + top = (canvas_size[1] - image.height) // 2 + + # 根据图片文件名创建图层名称 + layer_name = f"layer {i+1} - {os.path.basename(img_path)}" + + # 创建并添加图层 + layer = PixelLayer.frompil(image, psd, layer_name, top, left, Compression.RLE) + # 确保图层可见 + layer.visible = True + psd.append(layer) + + # 确保所有图层都是可见的 + for layer in psd: + if not layer.visible: + print(f"图层 {layer.name} 不可见,正在设置为可见") + layer.visible = True + + # 生成合成图像 + composite_image = psd.composite(force=True) + + # 更新PSD文件的图像数据 + psd._record.image_data.set_data([channel.tobytes() for channel in composite_image.split()], psd._record.header) + + # 3. 保存PSD文件 + psd.save(output_path) + print(f"PSD文件已成功创建,保存在: {output_path}") + + # 4. 生成并保存预览 + preview_path = os.path.splitext(output_path)[0] + "_预览.png" + composite_image.save(preview_path) + print(f"预览已保存在: {preview_path}") + + # 5. 验证PSD文件结构 + saved_psd = PSDImage.open(output_path) + print(f"PSD文件信息: {saved_psd}") + print(f"图层数量: {len(saved_psd)}") + for i, layer in enumerate(saved_psd): + print(f"图层 {i}: {layer.name}, 位置: ({layer.left}, {layer.top}), 大小: {layer.width}x{layer.height}") + + except Exception as e: + print(f"创建PSD文件时出错: {e}") + + +if __name__ == "__main__": + # 示例用法 + image_list = [ + '../images/background.jpg', # 底层图片 + '../images/nankai.jpg', # 中间图片 + '../images/aaai.png', # 顶层图片 + # 可以根据需要添加更多图片 + ] + + create_psd_from_images( + image_paths=image_list, + output_path='../outputs/combined_output.psd' + ) \ No newline at end of file