import cv2 import os import numpy as np from pathlib import Path from PIL import Image, ImageDraw, ImageFont import json import random import traceback class PosterInfo: def __init__(self, index, main_title, texts): self.index = index self.main_title = main_title self.texts = texts class PosterConfig: def __init__(self, config_path): self.config_path = config_path try: if isinstance(config_path, str) and os.path.exists(config_path): # 如果是文件路径,从文件读取 print(f"从文件加载配置: {config_path}") self.config = json.load(open(config_path, "r", encoding="utf-8")) else: # 如果是字符串但不是文件路径,尝试直接解析 print("尝试直接解析配置字符串") self.config = json.loads(config_path) self.img_list = [] for item in self.config: print(item) self.img_list.append([item['index'], item["main_title"], item["texts"]]) # self.img_list.append(PosterInfo(item['img_url'], item['main_title'], item['texts'])) except json.JSONDecodeError as e: print(f"JSON解析错误: {e}") print(f"尝试解析的内容: {config_path[:100]}...") # 只打印前100个字符 # 创建一个默认配置 self.config = [{"index": 0, "main_title": "景点风光", "texts": ["自然美景", "人文体验"]}] self.img_list = [[0, "景点风光", ["自然美景", "人文体验"]]] print("使用默认配置") except Exception as e: print(f"加载配置时出错: {e}") # 创建一个默认配置 self.config = [{"index": 0, "main_title": "景点风光", "texts": ["自然美景", "人文体验"]}] self.img_list = [[0, "景点风光", ["自然美景", "人文体验"]]] print("使用默认配置") def get_config(self): return self.config def get_config_by_index(self, index): if index >= len(self.config): print(f"警告: 索引 {index} 超出配置范围,使用默认配置") return self.config[0] return self.config[index] class PosterGenerator: def __init__(self, base_dir, output_dir=None): # 基础路径设置 if not base_dir or not os.path.isdir(base_dir): # Handle error: Base directory is essential raise ValueError(f"Poster assets base directory '{base_dir}' is invalid or not provided.") self.base_dir = base_dir print(f"Initializing PosterGenerator with asset base: {self.base_dir}") # Log the used base_dir self.font_dir = os.path.join(self.base_dir, "font") self.frame_dir = os.path.join(self.base_dir, "frames") # 边框素材目录 self.sticker_dir = os.path.join(self.base_dir, "stickers") # 贴纸素材目录 self.text_bg_dir = os.path.join(self.base_dir, "text_backgrounds") # 文本框底图目录 self.img_frame_posbility = 0.7 self.text_bg_posbility = 0 # 设置输出目录 if output_dir: self.output_dir = output_dir else: self.output_dir = os.path.join(self.base_dir, "output") # 初始化资源 self.fonts = self._initialize_fonts() self.frames = self._initialize_frames() self.stickers = self._initialize_stickers() self.text_bgs = self._initialize_text_backgrounds() # 创建输出目录 os.makedirs(self.output_dir, exist_ok=True) # 固定使用白色字体蓝色描边效果 self.selected_effect = "白色字体蓝色描边" # 海报计数器,用于控制添加边框的频率 self.poster_count = 0 self.default_font_path = os.path.join(self.font_dir, "华康海报体简.ttc") def _initialize_text_backgrounds(self): """初始化文本框底图""" try: text_bg_files = [f for f in os.listdir(self.text_bg_dir) if f.endswith(('.png'))] print(f"找到文本框底图: {text_bg_files}") return text_bg_files except Exception as e: print(f"初始化文本框底图失败: {e}") return [] def _initialize_fonts(self): """初始化字体""" fonts = [] try: font_files = [f for f in os.listdir(self.font_dir) if f.endswith(('.ttf', '.otf'))] print(f"找到字体文件: {font_files}") return font_files except Exception as e: print(f"初始化字体失败: {e}") return [] def _initialize_frames(self): """初始化边框素材""" frames = [] try: frame_files = [f for f in os.listdir(self.frame_dir) if f.endswith(('.png', '.jpg'))] print(f"找到边框素材: {len(frame_files)}个") return frame_files except Exception as e: print(f"初始化边框失败: {e}") return [] def _initialize_stickers(self): """初始化贴纸素材""" stickers = [] try: sticker_files = [f for f in os.listdir(self.sticker_dir) if f.endswith(('.png'))] print(f"找到贴纸素材: {sticker_files}") return sticker_files except Exception as e: print(f"初始化贴纸失败: {e}") return [] def get_random_font(self): """获取随机字体""" try: # 使用正确的字体目录 font_dir = self.font_dir # 获取所有支持的字体文件 font_files = [f for f in os.listdir(font_dir) if f.lower().endswith(('.ttf', '.otf', '.ttc', '.TTC', '.TTF', '.OTF'))] if not font_files: print("警告: 字体目录为空,使用默认字体") return os.path.join(font_dir, "华康海报体简.ttc") # 随机选择一个字体文件 font_file = random.choice(font_files) font_path = os.path.join(font_dir, font_file) # 验证字体文件是否存在且可读 if not os.path.isfile(font_path): print(f"警告: 字体文件 {font_file} 不存在,使用默认字体") return os.path.join(font_dir, "华康海报体简.ttc") print(f"使用字体: {font_file}") return font_path except Exception as e: print(f"获取字体时出错: {str(e)}") return os.path.join(font_dir, "华康海报体简.ttc") def create_base_layer(self, image_path, target_size): """创建底层(图片层) Args: image_path: 可以是图片文件路径字符串,也可以是已加载的 PIL Image 对象 target_size: 目标图片尺寸 (width, height) Returns: 调整大小后的 PIL Image 对象 """ try: # 检查输入类型 if isinstance(image_path, Image.Image): # 如果已经是 PIL Image 对象 print("输入已是 PIL Image 对象,无需加载") base_image = image_path.convert('RGBA') print(f"图像原始尺寸: {base_image.size}") else: # 否则,作为路径处理 # 先验证图片路径 if not image_path: raise ValueError("图片路径为空") if not os.path.exists(image_path): raise FileNotFoundError(f"图片文件不存在: {image_path}") print(f"尝试加载底图: {image_path}") base_image = Image.open(image_path).convert('RGBA') print(f"底图加载成功,原始尺寸: {base_image.size}") # 调整尺寸 base_image = base_image.resize(target_size, Image.Resampling.LANCZOS) print(f"底图调整尺寸完成: {target_size}") return base_image except FileNotFoundError as e: print(f"创建底层失败: {e}") print(f"当前工作目录: {os.getcwd()}") return Image.new('RGBA', target_size, (255, 255, 255, 255)) except Exception as e: print(f"创建底层失败: {e}") traceback.print_exc() return Image.new('RGBA', target_size, (255, 255, 255, 255)) def add_frame(self, image, target_size): """添加边框""" if not self.frames: print("没有可用的边框素材") return image try: # 随机选择一个边框 frame_file = random.choice(self.frames) frame_path = os.path.join(self.frame_dir, frame_file) # 加载边框图像 frame_image = Image.open(frame_path).convert('RGBA') # 调整边框大小以匹配目标尺寸 frame_image = frame_image.resize(target_size, Image.Resampling.LANCZOS) # 创建一个新图层用于合成 framed_image = Image.new('RGBA', target_size, (0, 0, 0, 0)) # 先放置基础图像 framed_image.paste(image, (0, 0)) # 再合成边框图像 framed_image.alpha_composite(frame_image) print(f"添加边框: {frame_file}") return framed_image except Exception as e: print(f"添加边框失败: {e}") return image def create_middle_layer(self, target_size): """创建中间层(文本框底图)""" try: middle_layer = Image.new('RGBA', target_size, (0, 0, 0, 0)) # 1. 首先确定固定的文本区域 width, height = target_size # 定义文本区域大小 header_height = int(height * 0.2) # 占顶部20% header_width = int(width * 0.9) # 占宽度的90% # 标题区域的位置(居中) title_x = (width - header_width) // 2 title_y = int(height * 0.16) # 从原来的0.05(5%)改为0.12(12%),向下移动 # 保存标题区域信息 self.title_area = { 'x': title_x, 'y': title_y, 'width': header_width, 'height': header_height } # 计算额外文本的区域(位于底部区域) additional_text_y = int(height * 0.65) # 位于底部35%的位置 additional_text_height = int(height * 0.1) # 占高度的10% # 保存额外文本区域信息 self.additional_text_area = { 'x': int(width * 0.1), # 左边距10% 'y': additional_text_y, 'width': int(width * 0.8), # 占宽度的80% 'height': additional_text_height } # 输出文本区域信息 print("文本区域信息:") print(f"主标题区域: x={self.title_area['x']}, y={self.title_area['y']}, " f"宽={self.title_area['width']}, 高={self.title_area['height']}") # 2. 检查是否使用文本框底图 use_text_bg = random.random() < self.text_bg_posbility print(f"环境变量USE_TEXT_BG设置为: {os.environ.get('USE_TEXT_BG', 'True')}, 是否使用文本框底图: {use_text_bg}") if use_text_bg and self.text_bgs: print("根据环境变量设置,使用文本框底图") # 随机选择文本背景 text_bg_path = os.path.join(self.text_bg_dir, random.choice(self.text_bgs)) text_bg = Image.open(text_bg_path).convert('RGBA') # 计算文本框底图的尺寸,适度放大 bg_width = int(width * 1.9) # 宽度为画布宽度的130% bg_height = int(height * 0.85) # 高度为画布高度的50% # 计算主标题的中心点 title_center_x = self.title_area['x'] + self.title_area['width'] // 2 title_center_y = self.title_area['y'] + self.title_area['height'] // 2 # 根据主标题中心点计算底图位置,确保完全对齐 bg_x = title_center_x - (bg_width // 2) # 从标题中心点向两侧扩展 bg_y = title_center_y - (bg_height // 2) # 从标题中心点向上下扩展 # 调整文本背景大小 text_bg = text_bg.resize((bg_width, bg_height), Image.Resampling.LANCZOS) # 创建临时图层并合并 temp = Image.new('RGBA', target_size, (0, 0, 0, 0)) temp.paste(text_bg, (bg_x, bg_y), text_bg) middle_layer.alpha_composite(temp) print(f"文本背景: 宽={bg_width}px (画布宽度的{bg_width/width:.1f}倍), " f"高={bg_height}px (画布高度的{bg_height/height:.1f}倍)") else: print("根据环境变量设置,不使用文本框底图,仅使用固定文本区域") return middle_layer except Exception as e: print(f"创建中间层时出错: {str(e)}") traceback.print_exc() # 打印详细错误信息 return Image.new('RGBA', target_size, (0, 0, 0, 0)) def create_text_layer(self, target_size, text_data=None): """创建文字层""" try: text_layer = Image.new('RGBA', target_size, (0, 0, 0, 0)) draw = ImageDraw.Draw(text_layer) # 使用固定效果:字体蓝色立体效果 self.selected_effect = "文字蓝色立体效果" print(f"使用文字效果: {self.selected_effect}") # 检查文字数据 if text_data is None: print("警告: 未提供文本数据,使用默认文本") text_data = {'title': '旅游景点', 'subtitle': '', 'additional_texts': []} print(f"处理文本数据: {text_data}") # 1. 处理主标题 if hasattr(self, 'title_area') and 'title' in text_data and text_data['title']: font_path = self._get_font_path() title = text_data['title'] # 使用柠檬黄色作为主标题颜色 lemon_yellow = (255, 250, 55, 255) # 柠檬黄色 # 使用统一定义的标题区域 font, text_width, text_height, x, y = self._calculate_text_layout( draw, font_path, title, self.title_area, size_factor=0.75) # 绘制主标题文字 self._draw_text_with_effects(draw, title, font, x, y, shadow_offset=8, color=lemon_yellow) # 打印调试信息 self._print_text_debug_info("主标题", font, text_width, x, y, font_path) print(f"- 主标题颜色: 柠檬黄色 RGB(255, 250, 55)") else: print("警告: 无法处理主标题,可能缺少标题数据或title_area未定义") # 2. 处理副标题(如果有) if hasattr(self, 'title_area') and 'subtitle' in text_data and text_data['subtitle']: subtitle = text_data['subtitle'] # 计算副标题区域(在主标题下方) subtitle_area = { 'x': self.title_area['x'], 'y': self.title_area['y'] + self.title_area['height'], 'width': self.title_area['width'], 'height': int(self.title_area['height'] * 0.5) # 副标题高度为主标题的一半 } # 获取副标题字体和布局 font, text_width, text_height, x, y = self._calculate_text_layout( draw, font_path, subtitle, subtitle_area, size_factor=0.6) # 副标题保持白色 white_color = (255, 255, 255, 255) # 绘制副标题 self._draw_text_with_effects(draw, subtitle, font, x, y, shadow_offset=8, color=white_color) # 打印调试信息 self._print_text_debug_info("副标题", font, text_width, x, y, font_path) # 3. 处理额外文本(如果有) if 'additional_texts' in text_data and text_data['additional_texts']: # 打印接收到的额外文本 print(f"接收到额外文本数据: {text_data['additional_texts']}") # 过滤掉空文本项 valid_additional_texts = [] for item in text_data['additional_texts']: if isinstance(item, dict) and 'text' in item and item['text']: valid_additional_texts.append(item) elif isinstance(item, str) and item: # 如果是字符串,转换为字典格式 valid_additional_texts.append({"text": item, "position": "bottom", "size_factor": 0.5}) print(f"有效额外文本项: {len(valid_additional_texts)}") if valid_additional_texts and hasattr(self, 'title_area'): # 获取主标题的字体大小 main_title_font_size = font.size if 'font' in locals() else 48 # 默认字体大小 # 使用固定字体 specific_font_path = self.default_font_path # specific_font_path = os.path.join(self.font_dir, "华康海报体简.ttc") if not os.path.isfile(specific_font_path): specific_font_path = font_path if 'font_path' in locals() else self._get_font_path() # 计算额外文本在屏幕上的位置 height = target_size[1] width = target_size[0] # 将垂直位置调整到主标题下方 title_bottom = self.title_area['y'] + self.title_area['height'] extra_text_y_start = title_bottom + int(height * 0.01) # 从原来的0.05(5%)减小到0.02(2%) extra_text_height = int(height * 0.2) # 安全边距 safe_margin_x = int(width * 0.05) max_text_width = width - (safe_margin_x * 2) # 总文本行数 total_lines = len(valid_additional_texts) line_height = extra_text_height // total_lines if total_lines > 0 else 0 print(f"额外文本区域: y={extra_text_y_start}, 高度={extra_text_height}, 每行高度={line_height}") print(f"文本安全宽度: {max_text_width}px (留出两侧各{safe_margin_x}px安全边距)") print(f"文本颜色: 统一白色") # 渲染每一行文本 for i, text_item in enumerate(valid_additional_texts): item_text = text_item['text'] # 检查文本内容 if not item_text: print(f"警告: 额外文本项 {i+1} 文本为空,跳过") continue # 设置字体大小为主标题的0.8倍或使用指定的size_factor size_factor = text_item.get('size_factor', 0.8) font_size = int(main_title_font_size * size_factor) text_font = ImageFont.truetype(specific_font_path, font_size) # 测量文本宽度并调整 text_width = draw.textlength(item_text, font=text_font) # 保护措施:如果文本宽度超出安全区域,逐步缩小字体直到适合 while text_width > max_text_width and font_size > int(main_title_font_size * 0.4): size_factor *= 0.95 font_size = int(main_title_font_size * size_factor) text_font = ImageFont.truetype(specific_font_path, font_size) text_width = draw.textlength(item_text, font=text_font) # 再次测量调整后的文本尺寸 text_bbox = draw.textbbox((0, 0), item_text, font=text_font) text_height = text_bbox[3] - text_bbox[1] # 获取位置参数 position = text_item.get('position', 'bottom') # 根据位置设置垂直位置 if position == 'top': line_y = int(height * 0.05) + (i * line_height) elif position == 'middle': line_y = int(height * 0.45) + (i * line_height) else: # position == 'bottom' 或其他 # 在底部区域,使用更大的垂直间距,比如整个海报高度的65%到85% bottom_start = int(height * 0.65) bottom_height = int(height * 0.2) bottom_line_height = bottom_height // total_lines if total_lines > 0 else 0 line_y = bottom_start + (i * bottom_line_height) # 水平居中位置 line_x = (width - text_width) // 2 # 统一使用白色文本 text_color = (255, 255, 255, 255) # 白色 # 绘制文本 self._draw_text_with_effects(draw, item_text, text_font, line_x, line_y, shadow_offset=3, color=text_color) # 打印调试信息 print(f"额外文本{i+1}: '{item_text}'") print(f"- 文本颜色: 白色") print(f"- 字体大小: {font_size}px (主标题的{size_factor:.2f}倍)") print(f"- 位置: x={line_x}, y={line_y}") else: print("无法处理额外文本:没有有效的额外文本项或title_area未定义") return text_layer except Exception as e: print(f"创建文字层时出错: {str(e)}") traceback.print_exc() return Image.new('RGBA', target_size, (0, 0, 0, 0)) def _get_font_path(self): """获取并验证字体路径""" # 获取随机字体 font_path = self.get_random_font() try: # 尝试加载字体 ImageFont.truetype(font_path, size=1) # 先用小尺寸测试字体是否可用 except Exception as e: print(f"加载字体失败: {str(e)},使用默认字体") font_path = self.default_font_path return font_path def _get_text_area(self, text_item, target_size, index): """获取文本区域""" width, height = target_size # 根据指定位置创建文本区域 position = text_item.get('position', 'bottom') if position == 'custom' and 'x' in text_item and 'y' in text_item: # 自定义位置 text_area = { 'x': text_item['x'], 'y': text_item['y'], 'width': text_item.get('width', width // 2), 'height': text_item.get('height', height // 10) } elif position == 'top': # 顶部区域 text_area = { 'x': int(width * 0.1), 'y': int(height * 0.05), 'width': int(width * 0.8), 'height': int(height * 0.1) } elif position == 'middle': # 中部区域 text_area = { 'x': int(width * 0.1), 'y': int(height * 0.45), 'width': int(width * 0.8), 'height': int(height * 0.1) } else: # 默认为底部 # 底部区域,增加随机性 # 从图像的一半高开始(height * 0.5)到底部的0.9位置之间随机选择 random_y_position = random.uniform(0.5, 0.85) # 水平位置也增加随机性,在10%到30%之间 random_x_position = random.uniform(0.05, 0.1) # 多个底部文本时的偏移 offset = index * 0.08 text_area = { 'x': int(width * random_x_position), 'y': int(height * (random_y_position + offset)), 'width': int(width * 0.8), 'height': int(height * 0.1) } return text_area def _calculate_text_layout(self, draw, font_path, text, text_area, size_factor=0.85, align_left=False): """计算文本布局、字体大小和位置 Args: draw: ImageDraw对象 font_path: 字体路径 text: 文本内容 text_area: 文本区域字典 size_factor: 字体大小因子(相对于区域高度) align_left: 是否左对齐 """ # 设置初始字体大小 initial_font_size = int(text_area['height'] * size_factor) font = ImageFont.truetype(font_path, initial_font_size) # 调整字体大小以适应宽度(目标是占据95%的宽度) target_width = text_area['width'] * 0.98 # 增加宽度利用率到98% text_width = draw.textlength(text, font=font) # 字体大小调整逻辑优化:先尝试增大字体以更好地填充宽度 if text_width < target_width * 0.9 and initial_font_size < int(text_area['height'] * 0.98): # 如果文本宽度小于目标宽度的90%,尝试增大字体 while text_width < target_width * 0.9 and initial_font_size < int(text_area['height'] * 0.98): initial_font_size += 2 font = ImageFont.truetype(font_path, initial_font_size) text_width = draw.textlength(text, font=font) # 如果字体太大,再缩小字体以适应宽度 while text_width > target_width and initial_font_size > 10: initial_font_size -= 2 font = ImageFont.truetype(font_path, initial_font_size) text_width = draw.textlength(text, font=font) # 获取文字高度 text_bbox = draw.textbbox((0, 0), text, font=font) text_height = text_bbox[3] - text_bbox[1] # 计算位置 if align_left: # 左对齐 x = text_area['x'] + int(text_area['width'] * 0.05) # 5% 的左边距 y = text_area['y'] + (text_area['height'] - text_height) // 2 else: # 居中对齐 x = text_area['x'] + (text_area['width'] - text_width) // 2 y = text_area['y'] + (text_area['height'] - text_height) // 2 return font, text_width, text_height, x, y def _draw_text_with_effects(self, draw, text, font, x, y, shadow_offset=8, color=(255, 255, 255, 255)): """绘制带立体效果的文字 Args: draw: ImageDraw对象 text: 文本内容 font: 字体对象 x, y: 文本坐标 shadow_offset: 立体深度 color: 文本表面颜色 """ # 文字颜色使用传入的color参数 text_color = color # 蓝色立体效果 - 只用一种深蓝色 blue_color = (29, 60, 171, 255) # 深蓝色 # 立体深度 - 使用更大的深度 depth = 8 # 8像素的立体深度 # 1. 首先绘制深蓝色立体部分 for i in range(1, depth + 1): # 下方 draw.text((x, y + i), text, font=font, fill=blue_color) # 右侧 draw.text((x + i, y), text, font=font, fill=blue_color) # 右下角 draw.text((x + i, y + i), text, font=font, fill=blue_color) # 2. 绘制蓝色描边 - 增强文字的边缘清晰度 stroke_thickness = 2 for dx in range(-stroke_thickness, stroke_thickness+1): for dy in range(-stroke_thickness, stroke_thickness+1): if dx == 0 and dy == 0: continue # 计算当前点到中心的距离 distance = (dx**2 + dy**2)**0.5 # 如果距离在描边范围内,绘制蓝色描边 if distance <= stroke_thickness: draw.text((x + dx, y + dy), text, font=font, fill=blue_color) # 3. 最后绘制主文字在最上层,使用传入的颜色 draw.text((x, y), text, font=font, fill=text_color) def _print_text_debug_info(self, text_type, font, text_width, x, y, font_path): """打印文本调试信息""" print(f"- {text_type} 字体大小: {font.size}px") print(f"- {text_type} 文本宽度: {text_width}px") print(f"- {text_type} 位置: x={x}, y={y}") print(f"- {text_type} 使用字体: {os.path.basename(font_path)}") def add_stickers(self, poster_image): """ 在海报上添加装饰性贴纸 Args: poster_image: 要添加贴纸的海报图像对象 Returns: 添加了贴纸的海报图像对象 """ if not hasattr(self, 'sticker_files') or not self.sticker_files: print("没有可用的贴纸素材,跳过贴纸添加") return poster_image try: # 获取海报尺寸 width, height = poster_image.size # 决定是否添加贴纸(50%概率) if random.random() < 0.5: print("随机决定不添加贴纸") return poster_image # 随机决定添加1-3个贴纸 sticker_count = random.randint(1, 3) print(f"准备添加 {sticker_count} 个贴纸") # 创建一个新图层用于合成 result_image = poster_image.copy() for i in range(sticker_count): # 随机选择一个贴纸 sticker_file = random.choice(self.sticker_files) sticker_path = os.path.join(self.sticker_dir, sticker_file) # 加载贴纸图像 sticker = Image.open(sticker_path).convert('RGBA') # 调整贴纸大小(原始尺寸的10%-30%) sticker_size_factor = random.uniform(0.1, 0.3) new_width = int(width * sticker_size_factor) new_height = int(new_width * sticker.height / sticker.width) # 保持纵横比 sticker = sticker.resize((new_width, new_height), Image.Resampling.LANCZOS) # 随机选择贴纸位置(避开中央区域) margin = int(width * 0.1) # 边缘区域的10% # 生成随机位置(避开中间区域) if random.random() < 0.5: # 左/右边缘 x = random.randint(margin, int(width * 0.25)) if random.random() < 0.5 else random.randint(int(width * 0.75), width - new_width - margin) y = random.randint(margin, height - new_height - margin) else: # 上/下边缘 x = random.randint(margin, width - new_width - margin) y = random.randint(margin, int(height * 0.25)) if random.random() < 0.5 else random.randint(int(height * 0.75), height - new_height - margin) # 将贴纸粘贴到结果图像上 result_image.paste(sticker, (x, y), sticker) print(f"添加贴纸 {i+1}: {sticker_file}, 大小: {new_width}x{new_height}, 位置: ({x}, {y})") return result_image except Exception as e: print(f"添加贴纸失败: {e}") traceback.print_exc() return poster_image def create_poster(self, image_input, text_data): """ Creates a poster by combining the base image, frame (optional), stickers (optional), and text layers. Args: image_input: 底图输入,可以是图片路径字符串或 PIL Image 对象 text_data: Dictionary containing text information ( { 'title': 'Main Title Text', 'subtitle': 'Subtitle Text (optional)', 'additional_texts': [{'text': '...', 'position': 'top/bottom/center', 'size_factor': 0.8}, ...] } ). Returns: A PIL Image object representing the final poster, or None if creation failed. """ target_size = (900, 1200) # TODO: Make target_size a parameter? print(f"\n--- Creating Poster --- ") if isinstance(image_input, Image.Image): print(f"Input: PIL Image 对象,尺寸 {image_input.size}") else: print(f"Input Image: {image_input}") print(f"Text Data: {text_data}") try: # 1. 创建底层(图片) base_layer = self.create_base_layer(image_input, target_size) if not base_layer: raise ValueError("Failed to create base layer.") print("Base layer created.") # 2. (可选) 添加边框 - 根据计数器决定 # if self.poster_count % 5 == 0: # 每5张海报添加一次边框 if random.random() < self.img_frame_posbility: print("Attempting to add frame...") base_layer = self.add_frame(base_layer, target_size) else: print("Skipping frame addition.") self.poster_count += 1 # 增加计数器 # 3. 创建中间层(文本框底图) # middle_layer 包含文本区域定义 (self.title_area, self.additional_text_area) middle_layer = self.create_middle_layer(target_size) print("Middle layer (text areas defined) created.") # 4. 创建文本层 text_layer = self.create_text_layer(target_size, text_data) print("Text layer created.") # 5. 合成图层 # Start with the base layer (which might already have a frame) final_poster = base_layer # Add middle layer (text backgrounds) final_poster.alpha_composite(middle_layer) # Add text layer final_poster.alpha_composite(text_layer) # (可选) 添加贴纸 final_poster = self.add_stickers(final_poster) print("Layers composed.") # 转换回 RGB 以保存为 JPG/PNG (移除 alpha通道) final_poster_rgb = final_poster.convert("RGB") # 移除保存逻辑,直接返回 Image 对象 # final_save_path = os.path.join(self.output_dir, output_name) # os.makedirs(os.path.dirname(final_save_path), exist_ok=True) # final_poster_rgb.save(final_save_path) # print(f"Final poster saved to: {final_save_path}") # return final_save_path return final_poster_rgb except Exception as e: print(f"Error creating poster: {e}") traceback.print_exc() return None def set_img_frame_possibility(self, img_frame_possibility): self.img_frame_posbility = img_frame_possibility def set_text_bg_possibility(self, text_bg_possibility): self.text_bg_posbility = text_bg_possibility def main(): # 设置是否使用文本框底图(True为使用,False为不使用) os.environ['USE_TEXT_BG'] = 'False' # 或 'True' print(f"设置环境变量USE_TEXT_BG为: {os.environ['USE_TEXT_BG']}") # 创建生成器实例 generator = PosterGenerator("/root/autodl-tmp/poster_baseboard_0403") poster_config_path = "/root/autodl-tmp/poster_generate_result/2025-04-16_20-49-32.json" poster_config = PosterConfig(poster_config_path) for item in poster_config.get_config(): text_data = { "title": f"{item['main_title']}", "subtitle": "", "additional_texts": [ {"text": f"{item['texts'][0]}", "position": "bottom", "size_factor": 0.5}, {"text": f"{item['texts'][1]}", "position": "bottom", "size_factor": 0.5} ] } # 处理目录中的所有图片 img_path = "/root/autodl-tmp/poster_baseboard_0403/output_collage/random_collage_1_collage.png" # 先加载图片,然后传递 PIL Image 对象 try: # 先加载图片 collage_img = Image.open(img_path).convert('RGBA') print(f"已加载拼贴图: {img_path}, 尺寸: {collage_img.size}") # 传递图片对象而不是路径 generator.create_poster(collage_img, text_data) except Exception as e: print(f"加载或处理图片时出错: {e}") traceback.print_exc() # 失败时回退到使用路径 generator.create_poster(img_path, text_data) if __name__ == "__main__": main()