From fcb02de2cb875250c0d54c18c45e874f75cf98fc Mon Sep 17 00:00:00 2001 From: cyborvirtue <2088953655@qq.com> Date: Tue, 20 May 2025 17:01:40 +0800 Subject: [PATCH 1/6] psd method init --- .../__pycache__/compose_poster.cpython-38.pyc | Bin 0 -> 2159 bytes scripts/__pycache__/export_psd.cpython-38.pyc | Bin 0 -> 2429 bytes scripts/compose_poster.py | 84 ++++ scripts/compose_poster_1.py | 402 ++++++++++++++++++ scripts/export_psd.py | 91 ++++ scripts/test1.py | 7 + scripts/test2.py | 8 + 7 files changed, 592 insertions(+) create mode 100644 scripts/__pycache__/compose_poster.cpython-38.pyc create mode 100644 scripts/__pycache__/export_psd.cpython-38.pyc create mode 100644 scripts/compose_poster.py create mode 100644 scripts/compose_poster_1.py create mode 100644 scripts/export_psd.py create mode 100644 scripts/test1.py create mode 100644 scripts/test2.py diff --git a/scripts/__pycache__/compose_poster.cpython-38.pyc b/scripts/__pycache__/compose_poster.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2326f4d6626111ddc648977d71478a0b5dd39b71 GIT binary patch literal 2159 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 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_poster_1.py b/scripts/compose_poster_1.py new file mode 100644 index 0000000..60778e9 --- /dev/null +++ b/scripts/compose_poster_1.py @@ -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'])}个图层") \ No newline at end of file diff --git a/scripts/export_psd.py b/scripts/export_psd.py new file mode 100644 index 0000000..2800a48 --- /dev/null +++ b/scripts/export_psd.py @@ -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("注意:如果只提供文件名,将从默认输入目录查找图片文件") \ No newline at end of file diff --git a/scripts/test1.py b/scripts/test1.py new file mode 100644 index 0000000..3ff1933 --- /dev/null +++ b/scripts/test1.py @@ -0,0 +1,7 @@ +from compose_poster import compose_layers + +# 将多个图层合成为一个图像 +compose_layers( + ['background.jpg','aaai.png', 'nankai.jpg'], + +) \ No newline at end of file diff --git a/scripts/test2.py b/scripts/test2.py new file mode 100644 index 0000000..78bd2d8 --- /dev/null +++ b/scripts/test2.py @@ -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'] # 可选的图层名称 +) \ No newline at end of file From cf0990d22dccc4f8c0c0d12aa820c519009514e9 Mon Sep 17 00:00:00 2001 From: cyborvirtue <2088953655@qq.com> Date: Tue, 20 May 2025 17:08:18 +0800 Subject: [PATCH 2/6] PSD init --- scripts/{test2.py => PSD_test1.py} | 0 scripts/compose_poster_1.py | 402 ------------------------- scripts/{test1.py => compose_test1.py} | 0 3 files changed, 402 deletions(-) rename scripts/{test2.py => PSD_test1.py} (100%) delete mode 100644 scripts/compose_poster_1.py rename scripts/{test1.py => compose_test1.py} (100%) 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 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 3/6] =?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 From 08ae3b6623fe38effa55b32a707690226d34dbbf Mon Sep 17 00:00:00 2001 From: cyborvirtue <2088953655@qq.com> Date: Thu, 22 May 2025 00:28:26 +0800 Subject: [PATCH 4/6] Add output files --- outputs/.DS_Store | Bin 0 -> 6148 bytes outputs/combined_output.psd | Bin 0 -> 3692310 bytes outputs/combined_output_预览.png | Bin 0 -> 70986 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 outputs/.DS_Store create mode 100644 outputs/combined_output.psd create mode 100644 outputs/combined_output_预览.png diff --git a/outputs/.DS_Store b/outputs/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0U@*Rg%g82?z#_Kmvy00V3EmBKix6K-8v{c7nEXKx}Pkg(Ov#RMj2M z+57By9?yJlCF%pC1Un`H3S}sP)@qHsvPtj)0WXlm3#o)dQp2fx&pz+}?Q?EbWrCo- z_k6$K&CNP{@3q%j-?zSbeQWJ{)$88;^GZ#b{OY?K zoa+8R@mrI|Pf@N?b}GNA{D87cc{hK*%hP4b%axZ>UdHdso?sis+M&f1yMlM=WHNaV z@TorQb;?|qDz84&8*I%Y=Q)=O#2}bXF_cgya0ZsmUll<`C3l&9reEae} zzx(#R|Mi;7UVhmRT=pApf5(6S?Q7n>`(3~J(sy3<+tYDME=Lt*+jA)oPO-S)o$_k` z?+T>`9{~%c=RE?8pPY7n;#fR=ip39s#k=1AuK)h_-#Lu|vylI`J*VPDMTe*VmejoP z*98N9!Qh3z{s;_SFD>}*3De=brWpJ?FnIghfq&Jzpg>J|`qtkT1-2-#MS(2}{Qs2# z>g17scR#8|oov-@(Lm&6OAfYZuq6jmi@Bu>Te`5N3tPG{rNEXhZ0W+5E^O(-CIzmuoWwB*@G>6uw@Un?7=1lw(P-{J=n4bo7%7yA8gr!EqkzK54P;VCIzmuoWL{*@G>6uw@Un?7=1lw(P-{J=n4bo7%7yA8gr!EqkzK54P;VCIzmuoWL{*@G>6uw@Un?7=1lw(P-{J=n4bo7(ViKR)1V`;O96UcukR%JZu~ zQ)wu!vZ(wRbuUwXntx2;x%}-@QflSyMYOt{)*3abl5^doyk7YYWqJ1Z3(BwZ_mj#q zl}l*(TIJP@@KWU^{9Q)f_t5vb%B{-Bc_($RQr-%r*C{`x43t>$!RBJVd6jaxas~gA zz~!?qU@r%{{PmPCDu1nfSb4MZNq#@9eDu^8vM&SDOOzi4`)k4WZNT^opRT>3_9L}dQgajKH)=mzdvWarwF_%MSo@yZ_tkzv z`4Cv&s@$x+U%83$e&rj=TJ3w4CbRl4%5BOAl|NGc8x*@3K7`QYbx=+IeC3Cr#;cWA zRy6(@-aQ+73kR-HevbB+!GmS`{v~w#B<-F~&6|MrA?PnuezjsMzeG!yewXm~O8#C1 zl#A*AI>lCW>YmBdGvVl;@%JL7fRC?KejiSLQkjRZ&sTnn(Qaj&7xCtK!1^O-|32j! z<=2si|G{0#NN)h{E0p)pLgej*jQ3$ic^Md7rF>5LW97Zz`EsP`3gCS|c>fr^P30Xx zeLtgL%Ltd!=VusOaJ!7Z9KQZf<$9IlCD6?+3!Ok)6L+{sG#HbU%}KSAfkG$c<3rCG->7m;3AK@nPh4Pc@T2WM=PS z#`14gT+ZG5pn%IiN7AG3HT-?NdLwT|)?caoi1Lu~HRYR%ryNm^)oQh~YTK0il~tsA zQhRpoS+%DiU9YPBGSc!plwYjhIM4TkXHrehq2+Rj&WN_I7^XLET&E z`3~gqirQ)WhGOtW;VZ#ehM940+lxZm5!fQ zewjZF8F(&y`~~LqRV3|4ndzJ9eGj_02v0AAIxkfIo$~LY!t_u2KNHHmlRm$sypg|` zQ1enCr1bZZxQ6n3%5gY1wE-`IqOYU82|m7=wwD3#3gxY}7ot1c_-A1D@b{bGcnPDt z6$uh3ufTp?4sV6em(Tus@TP|Zi3Cg~MeN?|u^4Y-?6+2J#r{mC<6V zPp|!`au6ALDR_&-T+a39=pz((b@lfuG~r^(X8-R&FU9`-TxAtCG)eHdta^V1QvKQr z>U?b*l5lQKf%2Pl{SL09tJkg?U3~*(L>W@#d3qgReaBVftH)OjtGdIpHlx|Iw3o8k zUh0p~Uiu!r>h7yoXMN>)wQ9R|wT!V+Jr8L!Vr&_IboC*Mj3eV6rB2?-^S?E`M&PFM zcNDDepve7Uu6I!m&Yp){H)p1DFKwmWLsyqqeTFjaQ(pB2?msqr9&$ZS*{Gh&s~);~ ze2uhw_}|*q<6SFPk9JOLq1Mh-%CzRdse164)k(W=(dwFM&u`Oe*UHY(uC<*q&S=+Y z=VaGpr*h2)xc~A_W!J%-!(9iszk#}wRSR|JkY_1t)QxwRyN>Q0SI-9-qujaenw>k3 zGtOw&&6M(*4d!$=chm6?%N>2jwA;n+q=IsI=lGgOVsQ&Czry=X41l(4y~1d6&3c8& zoxHtsRx9HQPIvCyIEBml8F-AY-q>}Iz^-~uTTd$Z0^{-Ee-%t04PWLY^Ls4#YmD~b z&h=dn@LuR~CJylan)O}xJ`9To!FK`_fh72^%_t$%5MBrs?uNdnv6y0gkh-J1`z+Vb z(oQJ3Ndv~wa3?at0N~JC6#dD@fA%=+2WBbb&B~zN<@3#(3(kPw@Nalta{v zDWBzD-i&rl7<;m_PYHNdDR)s0&H9$q9OT`BT_tr2)KGTb$#YEcC_eQn*UxkP>sg-( zH6N$$LzFZ7sJlAUXuFi%>aJDp{;Fy};km`A>MnKHgVps$#u`)m(5`a#@~*RX@7lF< zXF}aEaCN93bFb_Mlih2y{~ENto4)FGTSFGQeQDQvh0%z%QUv-c^&8dmbglwXAjo+4 z(*6+dAKLXG)LP&DRqmzVCiH(9Mn{?FDsz$%rA)CI14*8zbY0>80A+J_hp0!wD;i0k zCx(f-yUVER?p1z|Ft)ne;qFjXr|wpFAEJD_7I*HR&`;ex++FUvm7?x`AHRpI(PU&a zv}$qF?#-6p;Q5xSPFhOe8y}<91G|-7e@2n_@^|=*Hk);u*KGIX+B?8@bS)IRc6C1Q^K`uFyJ@>M-mhJIWcRpgdvy28 zZspo_inNsSSeOiX|4rT<-hJZQlmB9Dj$aFPPr*V~{#h7Ju6?k==s4IvHhmu2eXOG2 zx2MlzV|4%Sjqiv?<+Tr07#+KI<6B^~!n=PyM&oOh>x9Qx6vi1{w*uGYx<=u;dzB(r zd6xTWt+WvCuFT%tbL|A$RIN7ab|0qA0qP&DdL86f~OEKwX>Nce%>ghh_yGq|Nxc;i=X#3KCjDn!=ZNQdX-v z!C*SF^pr9>1DdjD{W@jO8t{g|mFs5f>2-MRmw7K^NxvzkGREquF;-6Xxc|Dzo|1R} zZ({e=>&pMnu$$}|GvjYXyC;s_1J{lBO#W4A_wBK}8<`0^8|_v1tW>}9r0%(qd!gO*?*KiJ z6534vZrkJTQD#pg?!H2~`BcqG?mkB`_jvSK;cmTpCu2I)+)ZJ&lydJEW}|*$*6JY7 z%yVxh@PWptKrUyn4$WFwv?#eBo_c$rx*N|R9iiv@9=8`%X1rVXoZ0#&o~Cm<#Mm-d zn^pn?8RE@td)BL3kui1e&4Sxr`TGQS)?TG*`;DshHh!h8y7z(V_vXEmS=*0uXI5<| zdmgB2Z{hbub$5T&b~>iI_x@SSF|F0TdDY@;6}-(BL*7i^O3nJi-rZOAn%1xE9q*I7 zrM)9YI#@m3e(L_u_bU7By_;>M-5s;`23L6tjD5l0wY{T#@MGW6y=(i1lu`98b@Dvg zw@O)|$o;gfygRXPo!-)R?O}bUZBDlx?>j=9X}jrF-fhCz9Dj3Mfh9#?3r=@%|G*5> z;of1@Pv$vozp?LFh2yuzdAN51uA4BQDBc_U?w|G9#O+MHN3$^n*Uk5G{~-0#c7oH! zzB{Qy8updU;Y|G1ed@lOp#Ntna_?2wVfA}-pR%{4hfn?KR>${^=Hz*aHqc?td5pI2 zqt~7LCVP*~+75W0wnZoQJyeZ5+3Qu~-n>tl8$Y7`4YRt{%#Y?y(t5P-19NBXo12@TKg9iHenQ)0b0gkP?>{g%+4m=N%KSHZ z+oc@j-EDK~+zs46a=i6@H_~f5&R4j*g|Ysa>w~ktcXHpG^&LI1m*~M@_djoq|KNj{iW^@ZN3$5pRHg&HeUBvb2v8lh1pn-#%T)g7U=ZF zS(~rY=g~MR;IGWBJ}y3|aX!pES7`T08XaLy6X@~z?*Nyt(C7aNT!!y*|)pK^ZN!u%P0n^bM5bdviIK<~R|bNDhec_eg4J6GwL?q-uFf0C(!V z+#lgi+N^PP=5Jp>FDUAQx)98+N2;rgdVsrx5-dD^yBklnkue_rclRTnT!^kFNtq+Kq%x)jR~+W6$HE z#qv9z#|C|-R9FMb>f%PD{8ydeNkAGYWA(kWcWOgz9O1cWtf`~MiHh3mB1=MXYNU+leROs&&uff4s-D+o z?5cdSF16QH10>#59z~3wd*DOK{R&upy{h}?WBvfR2!?`-l+|jcg84?{81?t74?PO= zDNUy|P#WK?FqmTYNU4!F_o&-66&jzMwY!_ENYI+9Xz)Z6ntzSw&8Jo7HN|Jbn=$2Z z#Z8f!X`9m$^Jwf9;0X32&1*=d=*Xl0yZg~q>JzW)&hR)qyQ=C9YN>saqN?{*zcT!n zxcjI&(f&;B@{2BLhi6x5Kh{1)xk(*qhq&IwHKC}g{30LP$0)a`ZSHTUs9K-jPgQqE zs$W%8wKeWOqM>z^+qAA$Yf@3unukx_soD}%kJs;Pt=RkrYY(=W%~9IwTCJ1r^O1_gOs%?D)b)Gd&HQZ;aA3z{?qYf++lO1 zuTeXq9O7wAnW&%V>0bu_;0*3W{ojlwa32Jt({R_o@ZnGu^>Y=}vN_R@RXtA6eA;s5 zF=u^`HfjDHJvV2*30YD9o?02>oAi5p$OoAFVaol$y_52F?x(*ua0h)DVN}syS_vPd z?iejMnqSwX&4Fgg{j{Chl(z|`3oW5-bEv}soiVtMxUT7w=9qGaKCYgR>PM(st=dT2 z5%obKl0>*UwGuz1h&1bY*W1KafrO#n~g>j|N zH=9aT_nqJ^^eE~3uY>zWHP$8#Hm@g}{{y^E=qD?D9~<`v!9(WrNZgg?x~?=oGaL6I z{eI?sAAFOx8_oNXxNnXBwEqU}g!adT-}K!q52yX{=7VRvo3>eR{wa0G_4QM@ALdzT zepDan56v)nY^+yw#TcKWh}{2ku&*`g_(ae@)_mwu=x;RNPn}F#L`6t{8hsHUnWVfC zLagf#RQDtOL`C$~88aS4fLC-(VySO$YMQam`}?UoR`r__SjLetJ_oZli7HotA=hKH zFZGF0>ZMU@XzR@zXM=Ax{tCcXY4nejkJ50$b)sX8iNds-beC~e{cHTL=;s=GQ)}v` z1!)z=Q*~8Om?2{~&(gq9RrNonF-Cbm*TZyY zEZwv#!|_j4Ac4f+?u=h57ny5|e6FY5gTyALojUpPGo&KYS9>e%-i0 z*VIO1VWHlHxhph!{3(>iS3z4)7p`7_Bp_kZ)o>5caSX%9{Em!IbNy4w9r}|TeR1!e zdCi=wHx1Z^=MEs+sa_2s9MT zWX_Z)>6UKjs>!IjSyACh+MMG>K@jeCbj^~nWCl~NPsbk`CzyC?O8pu$8qVN?sWq*e zRIpE4molnhiBzjiaMyH;i)LTvgrOIEd%Wfhm?^Ro?_TdG!HXw>KDo5-bZG#7&~ioyV6uiLa;rQAq4QlUMv&Ng5uqHSE4AhH*kpU?o&231CZ^fH2!qc+brs8WVD63Cq(DKr_`inUG|nMY_fq-JOj={d2^ zu^q>@EW5Ny`&pJ_Ym0{G!zg`0cP*yW3=`K+;$WYJSgq4xXsgH@I1D^&#{{eE-a2it8QA3ZmSJZ z<1q3Ugl^NqkV7P8<1{_1dZFh*5WizayEP`TVNc8zh#=rc_BP9QO7}v?b{*~@qvJF+ zw@jXqW?o%$T+414L1KAn94(@~rVh5&>cc2Ev~CB4&`#zhS3&EjJ#i*(X_w4HY=G5V zw38%C5@)f2Ham4Aj7*z~8VHrm9$9Wg^S#YNSu zxMZqUoE1UTN}3H5?7gVa4P?<Xu_#hUchum?dGHC)R>% zdJWiP7*Uei@W-j;Fp&zFa;F*e*rnZ(wiB+FY zMYUD?O0>^*pt&dvrqMD2OZTj1U~3C1lnC7-(o};9>7G4@g4#im#`c15H=v9;pNCP5 zDjA4`Y7Y_FF??LLw~MlKYnINB*xZAR<=HqVN2?pqAWjokYczG=((R@eC0^j?p>HlY z!J?IB>Oz|2Vcn21|B3M!2zh=Zh%%5PyktDnwOwq-d=rg^r-_f-cVN1U?!~DcL`mG# zEN9V8Qf)B}Qh(9(OqVXVGOB7%?6V!XU|Ji_t8p{fKlpFkjDi11bFj%G6-Ghkg&>U_ zGc96IGn~4c#QGx6B37GcOk0%bE)2qJs*!BRb6|n#Ac)v$Gm5eT8A>BtSDT*YSc`rV z`g#zhu4Y&=q_*g1dDzroxN0A?C+>4?!w|}WqHW>UZ9)#i^1LWR&Ewc_HgPW2vlnFy zlc7xl(?vf`RH+Fyf#MpD%yrcsIR`K*7-p1sJ5+Q^4CsdaQUF-4(=ePUMaXdXNjr6* zI%p#-k=JnZBy#HZ0t~A+%`!gAR$UXln+}%DuD3GxRak@!j_ubNmg{+- zO0XhvlBRK>&!O4Qf)ghJ3fT&AXD}>88qzFu7u{np{1%F8FWGN!TqZ6|Ks7xC$nqPF zC`_|R9BPv0riS2xt%HkIy)eVXYx6-8H$-n^tL`yD%evLZm^yb;{)Vzf!LT}C=ZeHS zUpYmaDs}o zGsa_6NHqKcBo59%8J1?H_6o;iZ38dEEOT`YHS#Fm)R0!6aQ(pSUg)Z(=>^lCgDPmWN4h~1Q3Wi{0FQs8uKBv z(i$o1vjCYEcX9BGEO5^2bojV83i?iImtf{`wO}Vc`5*(!Rf_5yW)61& z;sTS1sezHx>Xu(xuTU8>b?aE6M%{{&BGDI}z_8|uC=K=*VUjwkh*I5Hj3NG_B^qVk z>U?YRJP8l&`8ENu;e=_K{czoPOy6k6Sr(Y8?s#@X^HBqab!}_WL7Nxnyg1GsHH+f~ zf5FSrRM%|xG=V1e=fIw*#x0vaX1ZeR{5lfo8>*ibA$pI*>82YdS-fESmb(Cb{CeHs z+0%k7TCf*!0!g#^c=S1Df5Ca9Z|f|Y&>=A{R*3bgM;%0uiMTdANzyFP4Fq&9jq`M$ z=7g=-(Qs#Vr{RZ5)NG2!wr_Pl1GYoT-%`FrS)<%bxd(0>qrAZhTw?Pi%ZnmQB2BDZ z)69z425f-Iz(M3$Y!I*6stGxo4IMf~P98@KzKVZH0@ZRqz)&6~bgrBl3EQaaUean6 zd7go^>1zf!dm1$N0vCxdl0t9#_*Cp|M1&5h6DLi>MMpQ|Eb$v70*H0WL9mrzLheO= zhzJ3+>U@uD>Q;~xog&IYO~W+ndfZMN%`d(GBk&QF7>Pm=9o0+ow2m{$^2EkAEO<>D zS;A0xmV1k{;wbJIAVQ`w_lIZKwOPH8OzMZTXCd8D%61P`E7NiEBWY$g9S?sIC$Vpu zo{QPib+kEiH4lHRXGv^Zb-f64I2tYnRx`N|+Nfu>T3e&5*S&|Cf zFinoBJ84|t1Om%u{M67JdYa`?k@#3TT`383w6(iJ4v4EDk6ZQcy&7|;#hSN>IImSEMROCJadM) z5Tdnm4|92lp*~JI=8WLM$o&fU@W}mwvt3+U=}zo(420No%#m}UhgSHIJxvIWocehF z$Ldz#s6K{-V8yor;z(W3lOWzlRBJWTEKBC?MJs|JiNj(JMaxsdP4NqkE3&o*&=tzx za6jUTM7r2}_lp%$6Zgpys$vRBV~N8WIX`QA9*)KH@fT^U2%W`6jvh4-jCnVYTku_+ zG2FD6bXK=KnLfs+VWK5P5*M-NTa5ryZ8ZG=>IihP`YF+|a}s)A2t*h44+xcP9ON|-oaH+&%UgQHDS}XRS z0Zi!pW$tfu&SFL$!Ohv&ILGpiJ3ou*#r9&8@iJsJrf%A|>OQ1&8^pSz0=fnplR1g4 zn}i5JBcFm_N^Go!>Lq?i_JFsL_#|*PN&LUK7ZS_!D&??q5t!4+fx1KjGIBuOh*kz!YM z+DU})7+TzJq5hZ!w%)dBSWibLCWL(-GgxI}!a^4onm%LN$j#5VK7p#{6|BENy}kN*5NAcky-9A1~Yr#GU7N(7j4h*^d^{jDv}EwLRM&49%%3Rz7YBlP*&X?P+52e zz$pR;G4@Ai2w?9-sC>OKzi&Z{Pq;r5IGDq@$YR|z17G4@gxxl*f(1b%6so$OZXga3 zf<+9hLoQ2YzD}4B&bdB%tuv(XmfkW`hoZX2-O>@!@*OgQi}Q1)z3x8Ur=iePD6AVc z+y~Xtd8ym9a1ePzURpj}$)gtT*23;Kvn+R;C^)NWvD^;X#nL_-ToAh3T$%Jgpi++@ zwS++$oM{zwvVCjhJ`*#oiOXLw!%!IQ1dzL_WmyqfM(JMWyFLyrOM^Og-81T0o)Z$e z&H^;n4d3xff4hjtCaJ&ZPQ3epE|5pA;(g8?c}JnOkl;S|d{e{hrL9)bv`@HC^y*{mRZ!V*FSM;j(+Xr|C2kG{e40o%@@L{F`~|Dxn1@~WUTHLn3^QW5K04qT zW)Zjjy3pR%5<m zI0T%JQ$7mJFI4b1yf2cV!lV!(vXHGHo&W>78csoswLm~=`zPHOI*Tc0DpJuLgxvHC ziDaam)AUKykg-9|#Hp3`S(QXT2q2l{pKyiIGTBp<-lX^m_Zf~Gco>E#PV)lznEXn@ z{IPcdVw$$%V9pCH_yoD_Mw%9RBr7Hu4BVu!nx1X@uEFvvoF}FveGE6mT!=p6YO(IO ziDfy1*rj*hX<|Ei(@xu+tW6-RA*&;owVm$cu>u`4A|4HU#;OKY&RgV^WKhSB+Id3k zZm~>d%@?>@>FhvZalyV-dS{9D28ST?C&7D6Ie3cXLIntHHcdb87M)hsiZ!5KNxVTU zg4>H10&?@d*aWdhh6P`9i5D<{5}qba8!hyF1J7^O4RJFlMc`FJ=z0$UN#^@LuBYif z#bR}a4LS|yd|8(hrz2^3Q7253$ueBvm(B%wD^3>t5Su3|FmWz)btiA-g(Nkw<5rn& zOPUrLNca2frr3T$77;c6Ktx1Taw*=eXV88OCC1*7)3l#Avv!`6YO{TxupVuHvZsY% z-eT1j24GmaKWKV3sRlyaRvMUf%^)q*XIbiC?eM!QTMeL06m4ME2(Btz6(oJ^ed-kH z$KG8KeMI3cJN-LBp7>_i+Uj|*a>~XyX@1FKG7h-y7xjxu+1*f3Mow#3Vree zog3ZqQxxA%J<& z?sVF%qMJAZ&n>)9B8;kc!rM-;!)@u8!E=O+CW=Eo2{_2R7UKL_@SwS(4PHKPJP8sq2@n>hf)YO+=mL&IAYC>uB$kypR4g!y z%BZx?awVEB;Lbr$K@TVX^Tn>hqfm6v3u7Fn)?&4d48b>3&x3BCqx=6rvPT+Uq+uMA#aw)wnl23O<^*VGHsSnvSrJ@&cxhNX(>cst-wXz zxl{lNSTMK(6d+)D*djA@@gbI-^z6cIm$Hi{MjjtpPwm)KFR+cK0% z9!&Py*!V{EWEuwncbE6K-a7q-mv_baqVA1(cyK8z5eL(`h)XCOG}(8_QIf!9 zr?ZlGQIZY!8JGe5qmIsHBvg)&~Bxz@j2#d3 zID+c>;>c?*EEqvf?mvmdD#UKn!yHAbjlF|nKjsZ9Ohy$3BcJHrKQY7ZIKw``{Tgl7yzNow*>d`X zloKvELP`A4Bh%O>D;0$z65}lr3hajnq?G#0n z2@3#zqhRcxV`)LBU9`s8g#~N0NVVXOCwg1@+t^(x{j-FV6(xxJklob^pEd3egW)mg z5^+6BnfN>An_FxbSTGP-8wZ!j@N5s0^TUT{ zMW@?Fu43|3C@J91CocfP5;uqc3t*~0qE-;O(SHGUM?+y}4wh7f?nka;6bw=KA}7>h zUXcvjfN~R0Twd(EtTN3+Vxo4d+amc()+q2#dOLtpNrFJF`GM+x1Qd@@4*BPa0~T>D zT4npWtkOCuP6E+2T1D2k}2?y7%h~R$hrvodPUROq;B#bg3fD5QHkU^puEBT3IB2{W}%{qQL-=~ zzUe}w4vXui(@on&U`Afp^vmKEnaY@~BgtUfofVM$H%wdYrRy4U=<1q>w|OC2}k6up%(69xFL)dJrs%>IMcuoV@Z51Fo*u7*l8g`wUU&z07X2d7qW4rs*6_X zzlji|+in+ZiCaz~7OS2RmE%r2z8ZVJ<+WQ$o7ghMdWdyZeF&`jc4#+{?}#)jQ3w~IQBgOUF$L>nAja>I5OB5}x_ju^IdN!R%VR4twGfaw?QRK+1% z22pIzo^@)6c}is4u!(Q&05gJ9Bo@K@uzDqaWF%-WlYPKHr&2Z#P5*fW7*9{ALJ|U5 z53tXUS+LPmoeK;YJi$FgO%LZAvE+rgM6$Ul3lfx(&|y*5t#+KZeZog}V*?V==bAb( z8VsEQoP=iq$aTvQ?-D*G5N&62oMf=cTm}Yq#QkXlREp((N65@UaYl7UmoLT@+cjQ0GBLH`TE|Vjm z9tS_wD6njVPOMNm?X25?vQD!oIw@Oqp{24u7FW$G*Cu^S5F3WJo%h-;(?$gZWaYSt zz)|X{6e;1@CmSKo(F6Q1h!WbC8=)B)8_y9!*2E&_tQ=W)hz-{~pH!}F?7?%_Z4%gb zW0JU0BEc^rA0m;hc0WR{gl1I?Fb3R*an!f@uL{LD1~^WcBP=FTA+90SjGyc#@CFQw zgk*EDpN2m@iV`2{_>v2{Q0ASFqri$vg4vHvJMn}IF11IP^r zp@uLQrV(JZthB=_mD5`6+OzAQ>WpbhzLG_$7`!q#+XK3IxEoGUdW0jm8?EUFqi`7P z;3x|oo+u;urPy?ncH46nI0M!6giSVqNr4_^5PTM!dp z7HozPHDj5f9~vnUw*;b?p^flh1#A=^0r>5dQFtSM50d{fo>(k3>>(<0n>LB0j58+n zMJGY{9II#-NrPzJfVnCA5ZRXYDFxClPF3%gI=a#ZdiVD_DoF43GbY0 z6%FfD3e zm%?U@d3FtmY8!su?zK2d5OO4_+sQdY5|aKQwJp>ajji0dXi+c>|CCuzK;cNR!n6ld z;-idm3fUY5Z}XTS8Bw48T$s!(0xcCI0x^Z+9(LLb4Hofvrh_d@^{_=WB0&uc^gQ9T zgT&)3u@doUB6K3Vv_$C3$7H~}MDkEkj;To?4pcV3!Y;Jsv`Q>%wHPd96|uxKo0TN%V@6UZX9VZ%>wRIKPBpXk(-2;9lziAQkZ0Gv3F!(JgPb@oL! zyo*4?c4Bl`Ho`diRtDeai#2N}nofvMI>OPJS){Y%Xh()*Zfq-Pu}wktGjH{}X#nnI zizJ&woHsQk6Q5AzVGJ0DOH&4*s^mIcYZSbNBMz;|wxGW23JjkRww-neev@9OKsX?1 zh`BBD7PN-Eh7mJs;4=LJnz5pQ`Z?i>YN6R1XHfq>V#B|si1@z=S$4?*g?Mif zQxw+rihl003}EW0CBZ@ytl_3MIcv(75<04bLhEsd>{zdd{6!-A0hrfeLtE@CPL4c5 z+(`-B*vT@n-(paq6R%$m-9Est``%Cm|-uCEH5b%nHjnCwS6K;#6XoD1QrJsR23SSu` z<&{4Y1!8^~E-IG8ouP!NZSoDAMRemp>!N`TPGxcmiDN@$G_fziM|84&Qna;kdWx zJgHL_t=mODRl?m7R6eURN8bovM5cs=I%^Nx@i7;~Vo}s-bw$ky5-}ffv(;%OM%p4@ z^E5eo&BER+dK@-jCBPCOSPw7q9OOK+FmMbD$Ho<~xX)bG@blquJnpkN#2r_gB64;_ zf-X+Rxly-EfPr};`Q8Gm ziETyAD?T0s=6jjs4w<*uoDquyAwwDcCqiKZk7SWVC)g$hPF&t6oo#aVGC>eDEO+d; zyO6fqj|@zF$YyxA6*X-l<`iNuB8MV7omPjXCJ!5t5|1mQ9)+8%KOzzZ$Dr?e_;Zlb zLstnDQF+~`Ptom=7D)J_16pWIBJsODHVXTlNQ>RXZC0*>69m$rVjPp@ZEg|UqrEX| z6>lU1=2FS7%qpmbz*T~y;ZIbFf=KD3A*}w%eDzq77Xe}-79b*IZ3S_g%msM_ zBsffxCMyPvEql+vETi+87TJ_eyQA9Gg#2_pQPOoLHKnbnDk6jwmnf=Se+h?SNQ?`w zbk2IQNRGRm#3K^Oo17~~BqE)ZJE2*~0Y4}j7?=}|74}*Y6y~s-@X{wN=FoA(auB}- zGUI3z=+$ILVw!-5mPW5(Z^8~iu$z;UM#;>0&?+KQI!RN2hhfsh2a#8dSp7j#@}h}v z1Vy(UvOG$Xl7T1T1xCnPfS6qjVObeH5tCqvw?bW+*aQnw2FJtKLRt39gS>~h>YPW7 zBQxrEvZzb^MI?fM4w44*OS)~>ja#ij>Y)vZ<#+R50+#W~=mMG%FQpOQfjONB%IGfu z@?J7IN_Y~v0d6f$Pei|njLRu5KOgiZmO_aUS~ujpyHTL~IkuJ%q^>(H z@_nIiLxo%?29*__cqC4wu%;%l!{}u^!KxaFiyA!?VdZ7Mar%wuwZ0d3mIs|)Zg7|t z843&Hc_zzTg`iPtlIzNPVkbg5ZfVfU(>qzWu)rk`!}VxK#=Z(C-u#nHrwbf4`g}A7 z;zoQdxWniG;*rn7V{OW>`YM z3lrAYfFS$Gn~-6mtMTn7(!`QiPBH5l z#?5t-hze%ft0Ee5J)M5hVV#KE#-FzcuHYKv!R}+VBnV<}CqQuGS_Cb}u^Oq-DB2Or zUN3Y!ULo=0p&1Z&=7=vU&lyf|VE55Vy4@DZ3qd?}!%na0HzV8`n$Xu`&g^0gx*2N^ zPCJ~8&QFLkF)!kBWM&iezKn0E`1XZp8^*3oCNZ9i+GEi<1lZ})ATNVgcunjoq?H}y zrnoKFLXhGxv%^lipPHC=S-Nyd3=-Y7ihkR6!%=h|4CeHMsN8xqiq`>n@DyRsM>Rz~ z2_fPW(NFn&i6b99;DT^ztVLx?KIZ}xE5xY8W~J_|s)1gZaT=TX00WPvdpxEsONEOrM(`mr?me)nT;?d+Y90rHlUTDW|$7t zrPD3CYE<}k+HLoBQGH!{d7LVaN6|S1UF1OH3RKvxK&|k50qZCXHKqrV8bNxJwyfV} z*S&OJ&g^2=uN?QmWU`bON<^t|cUrx)8DybBxZO8UGh7I44{b9!hq5wJP{3Sj_s234cPl6jz ze>M7CbP<*r(y`qnfdrf$MOUPL(CVfH&seYwlM0xCC5!3d8gq{S5q~71A<22e3{r<( znO;*Q!HP*Y#fE4>Df6KtBZxB;nITk+KgM+eQhyu`i8V&EvP$iGRC$i*IS8g`Fd>61 zTM%P1Bt)d-^jK@;g%yXR;K~GY1WttIGYDfivyMO!Y>Mwq8U$f2i}R%kA-P9FK4-a$TFaU&3QA-Zgavn1pRyeGI@R-PMID4Mr#%hE55!lKM?w#RUVltIL+OsgA{upaE z)_1I57+Yrk+m-NzA>VBXX^|y4(9pwtW_(VG0i`+CFEP^$JI-zo9)zY<;0mLN;@a3Z z+CT^=X!l!8-WEw9QZ2|?nCxS)6CcMbU@I6;v7La?1Z+p)Dz;%1uFc*&7(I=&f`sNs zyO>hskBD_W;+PwU@(CA_Sk&BM{#Q5=k≶=TI(d3!#=6R~@T_rDY5Zr4x|ElLfdR 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