2076 lines
208 KiB
JSON
Raw Normal View History

2025-07-31 15:35:23 +08:00
{
"file_path": "poster/templates/vibrant_template.py",
"file_size": 49202,
"line_count": 1122,
"functions": [
{
"name": "__init__",
"line_start": 26,
"line_end": 50,
"args": [
{
"name": "self"
},
{
"name": "size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self, size: Tuple[int, int] = (900, 1200)):\n super().__init__(size)\n self.config = {\n 'colors': {\n 'ocean_deep': [(0, 30, 80), (20, 120, 220)],\n 'sunset_warm': [(255, 94, 77), (255, 154, 0)],\n 'cool_mint': [(64, 224, 208), (127, 255, 212)],\n 'royal_purple': [(75, 0, 130), (138, 43, 226)],\n 'forest_green': [(34, 139, 34), (144, 238, 144)],\n 'fire_red': [(220, 20, 60), (255, 69, 0)],\n \"gray_gradient\": [(128, 128, 128), (211, 211, 211)],\n \"dark_gray\": [(15, 15, 15), (30, 30, 30)]\n },\n 'glass_effect': {\n 'max_opacity': 240,\n 'blur_radius': 22,\n 'transition_height': 80,\n 'intensity_multiplier': 1.5\n },\n }\n # 设置中文字体路径\n self.chinese_font_path = \"/root/TravelContentCreator/assets/font/兰亭粗黑简.TTF\"\n \n # 重写text_renderer的字体加载方法以支持中文\n self._patch_text_renderer_for_chinese()",
"code_hash": "588184602b75d940237b34db6d4e37c1"
},
{
"name": "_patch_text_renderer_for_chinese",
"line_start": 52,
"line_end": 64,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "重写text_renderer的字体加载方法以支持中文",
"is_async": false,
"decorators": [],
"code": " def _patch_text_renderer_for_chinese(self):\n \"\"\"重写text_renderer的字体加载方法以支持中文\"\"\"\n original_load_font = self.text_renderer._load_default_font\n \n def load_chinese_font(size: int):\n try:\n return ImageFont.truetype(self.chinese_font_path, size)\n except:\n logger.warning(f\"无法加载中文字体,使用默认字体\")\n return original_load_font(size)\n \n # 替换字体加载方法\n self.text_renderer._load_default_font = load_chinese_font",
"code_hash": "f2557860be39d9005c09fa4fb8492a05"
},
{
"name": "generate",
"line_start": 66,
"line_end": 106,
"args": [
{
"name": "self"
},
{
"name": "images",
"type_hint": "List"
},
{
"name": "content",
"type_hint": "Optional[Dict[str, Any]]"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
},
{
"name": "glass_intensity",
"type_hint": "float"
},
{
"name": "num_variations",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "生成Vibrant风格海报\n\nArgs:\n images (List): 主图\n content (Optional[Dict[str, Any]]): 包含所有文本信息的字典\n theme_color (Optional[str]): 预设颜色主题的名称\n glass_intensity (float): 毛玻璃效果强度\n num_variations (int): 生成海报数量\n\nReturns:\n Image.Image: 生成的海报图像",
"is_async": false,
"decorators": [],
"code": " def generate(self,\n images: List,\n content: Optional[Dict[str, Any]] = None,\n theme_color: Optional[str] = None,\n glass_intensity: float = 1.5,\n num_variations: int = 1,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Vibrant风格海报\n\n Args:\n images (List): 主图\n content (Optional[Dict[str, Any]]): 包含所有文本信息的字典\n theme_color (Optional[str]): 预设颜色主题的名称\n glass_intensity (float): 毛玻璃效果强度\n num_variations (int): 生成海报数量\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n if content is None:\n content = self._get_default_content()\n\n self.config['glass_effect']['intensity_multiplier'] = glass_intensity\n\n main_image = images\n logger.info(f\"main_image的类型: {np.shape(main_image)}\")\n if not main_image:\n logger.error(f\"无法加载图片: \")\n return None\n\n main_image = self.image_processor.resize_image(image=main_image, target_size=self.size)\n\n estimated_height = self._estimate_content_height(content)\n gradient_start = self._detect_gradient_start_position(main_image, estimated_height)\n\n canvas = self._create_composite_image(main_image, gradient_start, theme_color)\n canvas = self._render_texts(canvas, content, gradient_start)\n\n final_image = canvas.resize((1350, 1800), Image.LANCZOS)\n return final_image",
"code_hash": "882c4eab44dec9248b43763ab4803404"
},
{
"name": "_get_default_content",
"line_start": 108,
"line_end": 127,
"args": [
{
"name": "self"
}
],
"return_type": "Dict[str, Any]",
"docstring": "获取默认的测试内容",
"is_async": false,
"decorators": [],
"code": " def _get_default_content(self) -> Dict[str, Any]:\n \"\"\"获取默认的测试内容\"\"\"\n return {\n \"title\": \"正佳极地海洋世界\",\n \"slogan\": \"都说海洋馆是约会圣地!那锦峰夜场将是绝杀!\",\n \"price\": \"199\",\n \"ticket_type\": \"夜场票\",\n \"content_button\": \"套餐内容\",\n \"content_items\": [\n \"正佳极地海洋世界夜场票1张\",\n \"有效期至2025.06.02\",\n \"多种动物表演全部免费\"\n ],\n \"remarks\": [\n \"工作日可直接入园\",\n \"周末请提前1天预约\"\n ],\n \"tag\": \"#520特惠\",\n \"pagination\": \"\"\n }",
"code_hash": "5a3ba4ddbde0e7683f770cfd4b134e7f"
},
{
"name": "_estimate_content_height",
"line_start": 129,
"line_end": 150,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "预估内容高度",
"is_async": false,
"decorators": [],
"code": " def _estimate_content_height(self, content: Dict[str, Any]) -> int:\n \"\"\"预估内容高度\"\"\"\n standard_margin = 25\n title_height = 100\n subtitle_height = 80\n button_height = 40\n content_items = content.get(\"content_items\", [])\n content_line_height = 32\n content_list_height = len(content_items) * content_line_height\n price_height = 90\n ticket_height = 60\n remarks = content.get(\"remarks\", [])\n if isinstance(remarks, str):\n remarks = [remarks]\n remarks_height = len(remarks) * 25 + 10\n footer_height = 40\n total_height = (\n 20 + title_height + standard_margin + subtitle_height + standard_margin +\n button_height + 15 + content_list_height + price_height + ticket_height +\n remarks_height + footer_height + 30\n )\n return total_height",
"code_hash": "9c7407bb70e0287cea14b52f38746370"
},
{
"name": "_detect_gradient_start_position",
"line_start": 152,
"line_end": 170,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "estimated_height",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "动态检测渐变起始位置",
"is_async": false,
"decorators": [],
"code": " def _detect_gradient_start_position(self, image: Image.Image, estimated_height: int) -> int:\n \"\"\"动态检测渐变起始位置\"\"\"\n width, height = image.size\n center_x = width // 2\n gradient_start = None\n for y in range(height // 2, height):\n try:\n pixel = image.getpixel((center_x, y))\n if isinstance(pixel, (tuple, list)) and len(pixel) >= 3:\n brightness = sum(pixel[:3]) / 3\n if brightness > 50:\n gradient_start = max(y - 20, height // 2)\n break\n except:\n continue\n if gradient_start is None:\n bottom_margin = 60\n gradient_start = max(height - estimated_height - bottom_margin, height // 2)\n return gradient_start",
"code_hash": "dd12b5f07214739f2e0c66972f835bec"
},
{
"name": "_create_composite_image",
"line_start": 172,
"line_end": 189,
"args": [
{
"name": "self"
},
{
"name": "main_image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
}
],
"return_type": "Image.Image",
"docstring": "创建毛玻璃背景和复合图像",
"is_async": false,
"decorators": [],
"code": " def _create_composite_image(self, main_image: Image.Image, \n gradient_start: int, \n theme_color: Optional[str]) -> Image.Image:\n \"\"\"创建毛玻璃背景和复合图像\"\"\"\n if theme_color and theme_color in self.config['colors']:\n top_color, bottom_color = self.config['colors'][theme_color]\n else:\n top_color, bottom_color = self._extract_glass_colors_from_image(main_image, gradient_start)\n \n logger.info(f\"使用毛玻璃颜色: 顶部={top_color}, 底部={bottom_color}\")\n \n gradient_overlay = self._create_frosted_glass_overlay(top_color, bottom_color, gradient_start)\n \n composite_img = Image.new('RGBA', self.size, (0, 0, 0, 0))\n composite_img.paste(main_image, (0, 0))\n composite_img = Image.alpha_composite(composite_img, gradient_overlay)\n \n return composite_img",
"code_hash": "ddb80c7467dcbeb648c2300f8c7d21c0"
},
{
"name": "_create_frosted_glass_overlay",
"line_start": 191,
"line_end": 228,
"args": [
{
"name": "self"
},
{
"name": "top_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "bottom_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "创建高级毛玻璃效果覆盖层",
"is_async": false,
"decorators": [],
"code": " def _create_frosted_glass_overlay(self, top_color: Tuple[int, int, int], \n bottom_color: Tuple[int, int, int], \n gradient_start: int) -> Image.Image:\n \"\"\"创建高级毛玻璃效果覆盖层\"\"\"\n overlay = Image.new('RGBA', self.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(overlay)\n\n gradient_height = self.height - gradient_start\n glass_config = self.config['glass_effect']\n intensity = glass_config['intensity_multiplier']\n\n enhanced_opacity = min(255, int(glass_config['max_opacity'] * intensity))\n enhanced_blur = int(glass_config['blur_radius'] * intensity)\n\n def enhance_color(color, multiplier):\n factor = min(1.5, 1.0 + (multiplier - 1.0) * 0.3)\n return tuple(min(255, max(0, int(c * factor))) for c in color)\n\n top_c = enhance_color(top_color, intensity)\n bottom_c = enhance_color(bottom_color, intensity)\n top_c_arr = np.array(top_c)\n bottom_c_arr = np.array(bottom_c)\n\n for y in range(gradient_start, self.height):\n ratio = (y - gradient_start) / gradient_height if gradient_height > 0 else 0\n smooth_ratio = 0.5 - 0.5 * math.cos(ratio * math.pi)\n color = (1 - smooth_ratio) * top_c_arr + smooth_ratio * bottom_c_arr\n alpha_smooth = ratio ** (1.1 / intensity)\n alpha = int(enhanced_opacity * (0.02 + 0.98 * alpha_smooth))\n\n if (y - gradient_start) < glass_config['transition_height']:\n transition_ratio = (y - gradient_start) / glass_config['transition_height']\n alpha = int(alpha * (0.5 - 0.5 * math.cos(transition_ratio * math.pi)))\n\n color_tuple = tuple(int(c) for c in color) + (alpha,)\n draw.line([(0, y), (self.width, y)], fill=color_tuple)\n \n return overlay.filter(ImageFilter.GaussianBlur(radius=enhanced_blur))",
"code_hash": "006716948f1590cb1549126a77345345"
},
{
"name": "_extract_glass_colors_from_image",
"line_start": 230,
"line_end": 251,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "tuple",
"docstring": "从图片中提取用于毛玻璃背景的颜色",
"is_async": false,
"decorators": [],
"code": " def _extract_glass_colors_from_image(self, image: Image.Image, gradient_start: int) -> tuple:\n \"\"\"从图片中提取用于毛玻璃背景的颜色\"\"\"\n if image.mode != 'RGB':\n image = image.convert('RGB')\n \n width, height = image.size\n top_samples, bottom_samples = [], []\n\n top_y = min(gradient_start + 20, height - 1)\n for x in range(0, width, 20):\n if sum(pixel := image.getpixel((x, top_y))) > 30:\n top_samples.append(pixel)\n \n bottom_y = min(height - 50, height - 1)\n for x in range(0, width, 20):\n if sum(pixel := image.getpixel((x, bottom_y))) > 30:\n bottom_samples.append(pixel)\n\n top_color = tuple(max(0, int(c * 0.1)) for c in np.mean(top_samples, axis=0)) if top_samples else (0, 5, 15)\n bottom_color = tuple(max(0, int(c * 0.2)) for c in np.mean(bottom_samples, axis=0)) if bottom_samples else (0, 25, 50)\n \n return top_color, bottom_color",
"code_hash": "f9fb4e3f5ca270b128ed181514126bf3"
},
{
"name": "_calculate_optimal_font_size_enhanced",
"line_start": 253,
"line_end": 329,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "target_width",
"type_hint": "int"
},
{
"name": "max_size",
"type_hint": "int"
},
{
"name": "min_size",
"type_hint": "int"
}
],
"return_type": "Tuple[int, int]",
"docstring": "计算文本的最佳字体大小使其宽度接近目标宽度增强版本与demo一致\n\n返回:\n(字体大小, 实际文本宽度)",
"is_async": false,
"decorators": [],
"code": " def _calculate_optimal_font_size_enhanced(self, text: str, target_width: int, \n max_size: int = 120, min_size: int = 10) -> Tuple[int, int]:\n \"\"\"\n 计算文本的最佳字体大小使其宽度接近目标宽度增强版本与demo一致\n \n 返回:\n (字体大小, 实际文本宽度)\n \"\"\"\n # 二分查找最佳字体大小\n low = min_size\n high = max_size\n best_size = min_size\n best_width = 0\n tolerance = 0.08 # 容差值,使文本宽度更接近目标值\n \n # 首先尝试最大字体大小\n try:\n font = self.text_renderer._load_default_font(max_size)\n max_width, _ = self.text_renderer.get_text_size(text, font)\n except:\n max_width = target_width * 2 # 如果出错,设置一个大值\n \n # 如果最大字体大小下的宽度仍小于目标宽度的108%,直接使用最大字体\n if max_width < target_width * (1 + tolerance):\n best_size = max_size\n best_width = max_width\n else:\n # 记录最接近目标宽度的字体大小\n closest_size = min_size\n closest_diff = target_width\n \n while low <= high:\n mid = (low + high) // 2\n try:\n font = self.text_renderer._load_default_font(mid)\n width, _ = self.text_renderer.get_text_size(text, font)\n except:\n width = target_width * 2 # 如果出错,设置一个大值\n \n # 计算与目标宽度的差距\n diff = abs(width - target_width)\n \n # 更新最接近的字体大小\n if diff < closest_diff:\n closest_diff = diff\n closest_size = mid\n \n # 如果宽度在目标宽度的允许范围内,认为找到了最佳匹配\n if target_width * (1 - tolerance) <= width <= target_width * (1 + tolerance):\n best_size = mid\n best_width = width\n break\n \n # 如果当前宽度小于目标宽度,尝试更大的字体\n if width < target_width:\n if width > best_width:\n best_width = width\n best_size = mid\n low = mid + 1\n else:\n # 如果当前宽度大于目标宽度,尝试更小的字体\n high = mid - 1\n \n # 如果没有找到在容差范围内的字体大小,使用最接近的字体大小\n if best_width == 0:\n best_size = closest_size\n \n # 确保返回的宽度是使用最终字体计算的实际宽度\n try:\n best_font = self.text_renderer._load_default_font(best_size)\n final_width, _ = self.text_renderer.get_text_size(text, best_font)\n except:\n final_width = best_width\n \n logger.info(f\"文本'{text[:min(10, len(text))]}...'的最佳字体大小: {best_size},目标宽度: {target_width},实际宽度: {final_width},差距: {abs(final_width-target_width)}\")\n \n return best_size, final_width",
"code_hash": "49c84dbf08fd2e88666470230602be09"
},
{
"name": "_render_texts",
"line_start": 331,
"line_end": 353,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "渲染所有文本元素,采用双栏布局",
"is_async": false,
"decorators": [],
"code": " def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], gradient_start: int) -> Image.Image:\n \"\"\"渲染所有文本元素,采用双栏布局\"\"\"\n draw = ImageDraw.Draw(canvas)\n width, height = canvas.size\n center_x = width // 2\n\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return canvas",
"code_hash": "88c9fbdb1fb06d7e0216ba80f0b9ed98"
},
{
"name": "_calculate_content_margins",
"line_start": 355,
"line_end": 390,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
}
],
"return_type": "Tuple[int, int]",
"docstring": "计算内容区域的左右边距增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _calculate_content_margins(self, content: Dict[str, Any], width: int, center_x: int) -> Tuple[int, int]:\n \"\"\"计算内容区域的左右边距增强版本与demo一致\"\"\"\n # 计算标题位置\n title_text = content.get(\"title\", \"\")\n title_target_width = int(width * 0.95)\n title_size, title_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, min_size=40, max_size=130\n )\n title_x = center_x - title_width // 2\n \n # 计算副标题位置\n slogan_text = content.get(\"slogan\", \"\")\n subtitle_target_width = int(width * 0.9)\n subtitle_size, subtitle_width = self._calculate_optimal_font_size_enhanced(\n slogan_text, subtitle_target_width, max_size=50, min_size=20\n )\n subtitle_x = center_x - subtitle_width // 2\n \n # 计算内容区域边距 - 减小额外的边距,让内容区域更宽\n padding = 20 # 从30减小到20\n content_left_margin = min(title_x, subtitle_x) - padding\n content_right_margin = max(title_x + title_width, subtitle_x + subtitle_width) + padding\n \n # 确保边距不超出合理范围,但允许更宽的内容区域\n content_left_margin = max(40, content_left_margin)\n content_right_margin = min(width - 40, content_right_margin)\n \n # 如果内容区域太窄,强制使用更宽的区域\n min_content_width = int(width * 0.75) # 至少使用75%的宽度\n current_width = content_right_margin - content_left_margin\n if current_width < min_content_width:\n extra_width = min_content_width - current_width\n content_left_margin = max(30, content_left_margin - extra_width // 2)\n content_right_margin = min(width - 30, content_right_margin + extra_width // 2)\n \n return content_left_margin, content_right_margin",
"code_hash": "c1f80261fd729be7715bac300aed8eca"
},
{
"name": "_render_footer",
"line_start": 392,
"line_end": 399,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "left",
"type_hint": "int"
},
{
"name": "right",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染页脚文本",
"is_async": false,
"decorators": [],
"code": " def _render_footer(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, left: int, right: int):\n \"\"\"渲染页脚文本\"\"\"\n font = self.text_renderer._load_default_font(18)\n if tag := content.get(\"tag\"):\n draw.text((left, y), tag, font=font, fill=(255, 255, 255))\n if pagination := content.get(\"pagination\"):\n width, _ = self.text_renderer.get_text_size(pagination, font)\n draw.text((right - width, y), pagination, font=font, fill=(255, 255, 255))",
"code_hash": "9621a05ad6bae4098fe7460315f2a995"
},
{
"name": "_render_title_subtitle",
"line_start": 401,
"line_end": 439,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left",
"type_hint": "int"
},
{
"name": "right",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "渲染标题和副标题增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _render_title_subtitle(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, center_x: int, left: int, right: int) -> int:\n \"\"\"渲染标题和副标题增强版本与demo一致\"\"\"\n # 标题\n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right - left) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n title_font = self.text_renderer._load_default_font(title_size)\n \n text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)\n title_x = center_x - text_w // 2\n \n self.text_renderer.draw_text_with_outline(draw, (title_x, y), title_text, title_font, text_color=(255, 255, 255, 255), outline_color=(0, 30, 80, 200), outline_width=4)\n \n # 副标题 (slogan)\n subtitle_text = content.get(\"slogan\", \"\")\n subtitle_target_width = int((right - left) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n \n sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font)\n subtitle_x = center_x - sub_text_w // 2\n \n title_spacing = 30\n \n # 在标题下方添加装饰线\n line_y = y + text_h + 5\n line_start_x = title_x - text_w * 0.025\n line_end_x = title_x + text_w * 1.025\n draw.line([(line_start_x, line_y), (line_end_x, line_y)], fill=(215, 215, 215, 80), width=3)\n\n subtitle_y = y + text_h + title_spacing\n \n self.text_renderer.draw_text_with_shadow(draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font, text_color=(255, 255, 255, 255), shadow_color=(0, 0, 0, 180), shadow_offset=(2, 2))\n \n return subtitle_y + sub_text_h",
"code_hash": "c2251fa3da2779e5aae68e2f51ec4b97"
},
{
"name": "_render_left_column",
"line_start": 441,
"line_end": 463,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "canvas_height",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染左栏内容:按钮和项目列表",
"is_async": false,
"decorators": [],
"code": " def _render_left_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, width: int, canvas_height: int):\n \"\"\"渲染左栏内容:按钮和项目列表\"\"\"\n button_font = self.text_renderer._load_default_font(30)\n button_text = content.get(\"content_button\", \"套餐内容\")\n button_width, _ = self.text_renderer.get_text_size(button_text, button_font)\n button_width += 40\n button_height = 50\n self.text_renderer.draw_rounded_rectangle(draw=draw, position=(x, y), size=(button_width, button_height), radius=20, fill_color=(0, 140, 210, 180), outline_color=(255, 255, 255, 255), outline_width=1)\n draw.text((x + 20, y + (button_height - 30) // 2), button_text, font=button_font, fill=(255, 255, 255))\n\n items = content.get(\"content_items\", [])\n if not items: return\n \n font = self.text_renderer._load_default_font(28)\n list_y = y + button_height + 20\n available_h = canvas_height - 30 - (len(content.get(\"remarks\", [])) * 25 + 10) - list_y - 20\n total_items_h = len(items) * 36\n extra_space_per_item = max(0, min(17, (available_h - total_items_h) / max(1, len(items) - 1)))\n line_spacing = 8 + extra_space_per_item\n \n for i, item in enumerate(items):\n item_y = list_y + i * (28 + line_spacing)\n draw.text((x, item_y), \" \" + item, font=font, fill=(255, 255, 255))",
"code_hash": "214ed6460a15065f6dabcaa756c86f25"
},
{
"name": "_render_right_column",
"line_start": 465,
"line_end": 504,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染右栏内容价格、票种和备注增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _render_right_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, right_margin: int):\n \"\"\"渲染右栏内容价格、票种和备注增强版本与demo一致\"\"\"\n price_text = str(content.get('price', '')) # 确保价格是字符串类型\n price_target_width = int((right_margin - x) * 0.7)\n price_size, price_actual_width = self._calculate_optimal_font_size_enhanced(\n price_text, price_target_width, max_size=120, min_size=40\n )\n \n price_font = self.text_renderer._load_default_font(price_size)\n suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3))\n _, price_height = self.text_renderer.get_text_size(price_text, price_font)\n suffix_width, suffix_height = self.text_renderer.get_text_size(\"CNY起\", suffix_font)\n \n price_x = right_margin - price_actual_width - suffix_width\n self.text_renderer.draw_text_with_shadow(draw, (price_x, y), price_text, price_font)\n \n suffix_y = y + price_height - suffix_height\n draw.text((price_x + price_actual_width, suffix_y), \"CNY起\", font=suffix_font, fill=(255, 255, 255))\n \n underline_y = y + price_height + 18\n draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2)\n \n ticket_text = content.get(\"ticket_type\", \"\")\n ticket_target_width = int((right_margin - x) * 0.7)\n ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced(\n ticket_text, ticket_target_width, max_size=60, min_size=30\n )\n ticket_font = self.text_renderer._load_default_font(ticket_size)\n ticket_x = right_margin - ticket_actual_width\n ticket_y = y + price_height + 35\n self.text_renderer.draw_text_with_shadow(draw, (ticket_x, ticket_y), ticket_text, ticket_font)\n _, ticket_height = self.text_renderer.get_text_size(ticket_text, ticket_font)\n\n remarks = content.get(\"remarks\", [])\n if remarks:\n remarks_font = self.text_renderer._load_default_font(16)\n remarks_y = ticket_y + ticket_height + 30\n for i, remark in enumerate(remarks):\n remark_width, _ = self.text_renderer.get_text_size(remark, remarks_font)\n draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255, 200)) ",
"code_hash": "3259213e888d90776809f6595c48805c"
},
{
"name": "generate_layered_psd",
"line_start": 506,
"line_end": 604,
"args": [
{
"name": "self"
},
{
"name": "images"
},
{
"name": "content",
"type_hint": "Optional[Dict[str, Any]]"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
},
{
"name": "glass_intensity",
"type_hint": "float"
},
{
"name": "output_path",
"type_hint": "str"
}
],
"return_type": "str",
"docstring": "生成分层的PSD文件方便后续修改\n\nArgs:\n images: 主图\n content: 包含所有文本信息的字典\n theme_color: 预设颜色主题的名称\n glass_intensity: 毛玻璃效果强度\n output_path: PSD文件输出路径\n \nReturns:\n str: 生成的PSD文件路径",
"is_async": false,
"decorators": [],
"code": " def generate_layered_psd(self,\n images,\n content: Optional[Dict[str, Any]] = None,\n theme_color: Optional[str] = None,\n glass_intensity: float = 1.5,\n output_path: str = \"layered_poster.psd\",\n **kwargs) -> str:\n \"\"\"\n 生成分层的PSD文件方便后续修改\n \n Args:\n images: 主图\n content: 包含所有文本信息的字典\n theme_color: 预设颜色主题的名称\n glass_intensity: 毛玻璃效果强度\n output_path: PSD文件输出路径\n \n Returns:\n str: 生成的PSD文件路径\n \"\"\"\n try:\n from psd_tools import PSDImage\n from psd_tools.api.layers import PixelLayer\n except ImportError:\n logger.error(\"需要安装psd-tools库: pip install psd-tools\")\n return None\n \n logger.info(\"开始生成分层PSD文件...\")\n \n if content is None:\n content = self._get_default_content()\n\n self.config['glass_effect']['intensity_multiplier'] = glass_intensity\n\n main_image = images\n if not main_image:\n logger.error(\"无法加载图片\")\n return None\n\n # 参考原始合成模式的PSD版本实现\n main_image = self.image_processor.resize_image(image=main_image, target_size=self.size)\n estimated_height = self._estimate_content_height(content)\n gradient_start = self._detect_gradient_start_position(main_image, estimated_height)\n\n # === 第一步创建与常规模式完全相同的最终结果作为Reference ===\n canvas = self._create_composite_image(main_image, gradient_start, theme_color)\n canvas = self._render_texts(canvas, content, gradient_start)\n final_canvas = canvas.resize((1350, 1800), Image.LANCZOS)\n \n # === 第二步为PSD创建可编辑的图层版本 ===\n final_size = (1350, 1800)\n psd = PSDImage.new(\"RGBA\", final_size, color=(0, 0, 0, 0))\n logger.info(f\"创建PSD文档尺寸: {final_size}\")\n\n # 图层1完美一致的参考图层\n composite_layer = PixelLayer.frompil(final_canvas, psd, \"Perfect Reference\")\n psd.append(composite_layer)\n logger.info(\"✓ 添加完美参考图层\")\n \n # === 第三步:提供丰富的可编辑图层 ===\n \n # 图层2纯背景图层无任何效果\n background_only = main_image.resize(final_size, Image.LANCZOS)\n bg_layer = PixelLayer.frompil(background_only, psd, \"Background Image\")\n psd.append(bg_layer)\n logger.info(\"✓ 添加纯背景图层\")\n \n # 图层3毛玻璃效果图层\n glass_overlay = self._create_glass_overlay_layer(main_image, gradient_start, theme_color)\n if glass_overlay:\n glass_scaled = glass_overlay.resize(final_size, Image.LANCZOS)\n glass_layer = PixelLayer.frompil(glass_scaled, psd, \"Glass Overlay\")\n psd.append(glass_layer)\n logger.info(\"✓ 添加毛玻璃效果图层\")\n \n # 图层4-9分离的文字图层\n detailed_text_layers = self._create_detailed_text_layers(content, gradient_start, self.size)\n for layer_name, layer_image in detailed_text_layers.items():\n if layer_image:\n text_scaled = layer_image.resize(final_size, Image.LANCZOS)\n text_layer = PixelLayer.frompil(text_scaled, psd, layer_name)\n psd.append(text_layer)\n logger.info(f\"✓ 添加文字图层: {layer_name}\")\n \n # 10\n text_only_canvas = self._create_text_only_layer(content, gradient_start, self.size)\n if text_only_canvas:\n
"code_hash": "f2168a1e8137fdc83d925a4340e548cc"
},
{
"name": "_create_glass_overlay_layer",
"line_start": 606,
"line_end": 619,
"args": [
{
"name": "self"
},
{
"name": "main_image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建毛玻璃效果的独立图层",
"is_async": false,
"decorators": [],
"code": " def _create_glass_overlay_layer(self, main_image: Image.Image, gradient_start: int, theme_color: Optional[str]) -> Optional[Image.Image]:\n \"\"\"创建毛玻璃效果的独立图层\"\"\"\n try:\n if theme_color and theme_color in self.config['colors']:\n top_color, bottom_color = self.config['colors'][theme_color]\n else:\n top_color, bottom_color = self._extract_glass_colors_from_image(main_image, gradient_start)\n \n # 创建透明背景的毛玻璃层\n overlay = self._create_frosted_glass_overlay(top_color, bottom_color, gradient_start)\n return overlay\n except Exception as e:\n logger.error(f\"创建毛玻璃层失败: {e}\")\n return None",
"code_hash": "6e53f640e1d6bc5c5021da976ee5ba6d"
},
{
"name": "_create_text_layers",
"line_start": 623,
"line_end": 665,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Dict[str, Optional[Image.Image]]",
"docstring": "创建各个文字图层",
"is_async": false,
"decorators": [],
"code": " def _create_text_layers(self, content: Dict[str, Any], gradient_start: int) -> Dict[str, Optional[Image.Image]]:\n \"\"\"创建各个文字图层\"\"\"\n layers = {}\n \n try:\n # 创建透明画布\n canvas_size = self.size\n \n # 计算布局参数\n width, height = canvas_size\n center_x = width // 2\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 1. 标题层\n layers[\"Title Text\"] = self._create_title_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 2. 副标题层 \n layers[\"Subtitle Text\"] = self._create_subtitle_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 3. 装饰线层\n layers[\"Decorations\"] = self._create_decoration_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 4. 左栏内容层\n title_y = gradient_start + 40\n subtitle_height = 80 + 30 # 预估副标题高度\n content_start_y = title_y + subtitle_height + 30\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n \n layers[\"Left Content\"] = self._create_left_column_layer(content, content_start_y, left_margin, left_column_width, canvas_size)\n \n # 5. 右栏内容层\n right_column_x = left_margin + left_column_width\n layers[\"Right Content\"] = self._create_right_column_layer(content, content_start_y, right_column_x, right_margin, canvas_size)\n \n # 6. 页脚层\n footer_y = height - 30\n layers[\"Footer Info\"] = self._create_footer_layer(content, footer_y, left_margin, right_margin, canvas_size)\n \n except Exception as e:\n logger.error(f\"创建文字图层失败: {e}\")\n \n return layers",
"code_hash": "a736f347c4342d9bb2aa309bbf909a48"
},
{
"name": "_create_text_only_layer",
"line_start": 667,
"line_end": 700,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建纯文字图层透明背景模拟_render_texts的渲染逻辑",
"is_async": false,
"decorators": [],
"code": " def _create_text_only_layer(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Optional[Image.Image]:\n \"\"\"创建纯文字图层透明背景模拟_render_texts的渲染逻辑\"\"\"\n try:\n # 创建透明画布\n text_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(text_canvas)\n \n width, height = canvas_size\n center_x = width // 2\n\n # 使用与原始方法相同的布局计算\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 页脚\n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n # 标题和副标题\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n # 左右栏内容\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return text_canvas\n except Exception as e:\n logger.error(f\"创建纯文字图层失败: {e}\")\n return None",
"code_hash": "0b501f8aa6b1e6f29ca310c84041e531"
},
{
"name": "_create_text_only_layer",
"line_start": 702,
"line_end": 735,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建纯文字图层透明背景模拟_render_texts的渲染逻辑",
"is_async": false,
"decorators": [],
"code": " def _create_text_only_layer(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Optional[Image.Image]:\n \"\"\"创建纯文字图层透明背景模拟_render_texts的渲染逻辑\"\"\"\n try:\n # 创建透明画布\n text_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(text_canvas)\n \n width, height = canvas_size\n center_x = width // 2\n\n # 使用与原始方法相同的布局计算\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 页脚\n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n # 标题和副标题\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n # 左右栏内容\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return text_canvas\n except Exception as e:\n logger.error(f\"创建纯文字图层失败: {e}\")\n return None",
"code_hash": "0b501f8aa6b1e6f29ca310c84041e531"
},
{
"name": "_create_detailed_text_layers",
"line_start": 737,
"line_end": 810,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Dict[str, Optional[Image.Image]]",
"docstring": "创建详细的分离文字图层,每个文字元素独立可编辑",
"is_async": false,
"decorators": [],
"code": " def _create_detailed_text_layers(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Dict[str, Optional[Image.Image]]:\n \"\"\"创建详细的分离文字图层,每个文字元素独立可编辑\"\"\"\n layers = {}\n \n try:\n width, height = canvas_size\n center_x = width // 2\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 1. 标题图层\n title_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n title_draw = ImageDraw.Draw(title_canvas)\n title_y = gradient_start + 40\n \n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)\n title_x = center_x - text_w // 2\n \n self.text_renderer.draw_text_with_outline(title_draw, (title_x, title_y), title_text, title_font, \n text_color=(255, 255, 255, 255), outline_color=(0, 30, 80, 200), outline_width=4)\n layers[\"Title Text\"] = title_canvas\n \n # 2. 副标题图层\n subtitle_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n subtitle_draw = ImageDraw.Draw(subtitle_canvas)\n \n subtitle_text = content.get(\"slogan\", \"\")\n if subtitle_text:\n subtitle_target_width = int((right_margin - left_margin) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n try:\n subtitle_font = ImageFont.truetype(self.chinese_font_path, subtitle_size)\n except:\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font)\n subtitle_x = center_x - sub_text_w // 2\n subtitle_y = title_y + text_h + 30\n \n self.text_renderer.draw_text_with_shadow(subtitle_draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font,\n text_color=(255, 255, 255, 255), shadow_color=(0, 0, 0, 180), shadow_offset=(2, 2))\n layers[\"Subtitle Text\"] = subtitle_canvas\n \n # 3. 页脚图层\n footer_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n footer_draw = ImageDraw.Draw(footer_canvas)\n \n footer_y = height - 30\n try:\n footer_font = ImageFont.truetype(self.chinese_font_path, 18)\n except:\n footer_font = self.text_renderer._load_default_font(18)\n \n if tag := content.get(\"tag\"):\n footer_draw.text((left_margin, footer_y), tag, font=footer_font, fill=(255, 255, 255))\n if pagination := content.get(\"pagination\"):\n width_pg, _ = self.text_renderer.get_text_size(pagination, footer_font)\n footer_draw.text((right_margin - width_pg, footer_y), pagination, font=footer_font, fill=(255, 255, 255))\n \n layers[\"Footer\"] = footer_canvas\n \n except Exception as e:\n logger.error(f\"创建详细文字图层失败: {e}\")\n \n retur
"code_hash": "01592c7eda1481584550243c373e5877"
},
{
"name": "_draw_text_with_outline_simple",
"line_start": 812,
"line_end": 828,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "简单的文本描边绘制方法",
"is_async": false,
"decorators": [],
"code": " def _draw_text_with_outline_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int], \n text: str, font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255),\n outline_width: int = 2):\n \"\"\"简单的文本描边绘制方法\"\"\"\n x, y = position\n \n # 绘制描边\n for dx in range(-outline_width, outline_width + 1):\n for dy in range(-outline_width, outline_width + 1):\n if dx == 0 and dy == 0:\n continue\n draw.text((x + dx, y + dy), text, font=font, fill=outline_color)\n \n # 绘制主文本\n draw.text((x, y), text, font=font, fill=text_color)",
"code_hash": "d8fd46b12e75264ec27fadc24271c890"
},
{
"name": "_draw_text_with_shadow_simple",
"line_start": 830,
"line_end": 843,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_offset",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "简单的文本阴影绘制方法",
"is_async": false,
"decorators": [],
"code": " def _draw_text_with_shadow_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int],\n text: str, font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),\n shadow_offset: Tuple[int, int] = (2, 2)):\n \"\"\"简单的文本阴影绘制方法\"\"\"\n x, y = position\n shadow_x, shadow_y = shadow_offset\n \n # 绘制阴影\n draw.text((x + shadow_x, y + shadow_y), text, font=font, fill=shadow_color)\n \n # 绘制主文本\n draw.text((x, y), text, font=font, fill=text_color)",
"code_hash": "69090a0b530ec6a3cc583e5f123880f3"
},
{
"name": "_create_title_layer",
"line_start": 845,
"line_end": 884,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建标题图层",
"is_async": false,
"decorators": [],
"code": " def _create_title_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建标题图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n \n # 使用指定的中文字体\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n \n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n \n # 重新计算实际尺寸\n bbox = title_font.getbbox(title_text)\n text_w = bbox[2] - bbox[0]\n text_h = bbox[3] - bbox[1]\n title_x = center_x - text_w // 2\n title_y = gradient_start + 40\n \n # 绘制带描边的标题\n self._draw_text_with_outline_simple(\n draw, (title_x, title_y), title_text, title_font, \n text_color=(255, 255, 255, 255), \n outline_color=(0, 30, 80, 200), \n outline_width=4\n )\n \n return canvas\n except Exception as e:\n logger.error(f\"创建标题层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "33dc83d05170d04ea4003d6003a49498"
},
{
"name": "_create_subtitle_layer",
"line_start": 886,
"line_end": 926,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建副标题图层",
"is_async": false,
"decorators": [],
"code": " def _create_subtitle_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建副标题图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n subtitle_text = content.get(\"slogan\", \"\")\n if not subtitle_text:\n return None\n \n subtitle_target_width = int((right_margin - left_margin) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n \n # 使用指定的中文字体\n try:\n subtitle_font = ImageFont.truetype(self.chinese_font_path, subtitle_size)\n except:\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n \n bbox = subtitle_font.getbbox(subtitle_text)\n sub_text_w = bbox[2] - bbox[0]\n sub_text_h = bbox[3] - bbox[1]\n subtitle_x = center_x - sub_text_w // 2\n subtitle_y = gradient_start + 40 + 100 + 30 # title_y + title_height + spacing\n \n # 绘制带阴影的副标题\n self._draw_text_with_shadow_simple(\n draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font, \n text_color=(255, 255, 255, 255), \n shadow_color=(0, 0, 0, 180), \n shadow_offset=(2, 2)\n )\n \n return canvas\n except Exception as e:\n logger.error(f\"创建副标题层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "ae1ef6b0faff4354e2fdecbe32cf21ba"
},
{
"name": "_create_decoration_layer",
"line_start": 928,
"line_end": 964,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建装饰元素图层",
"is_async": false,
"decorators": [],
"code": " def _create_decoration_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建装饰元素图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 获取标题信息用于装饰线定位\n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n \n # 使用中文字体计算标题尺寸\n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n \n bbox = title_font.getbbox(title_text)\n text_w = bbox[2] - bbox[0]\n text_h = bbox[3] - bbox[1]\n title_x = center_x - text_w // 2\n title_y = gradient_start + 40\n \n # 在标题下方添加装饰线\n line_y = title_y + text_h + 5\n line_start_x = title_x - text_w * 0.025\n line_end_x = title_x + text_w * 1.025\n draw.line([(line_start_x, line_y), (line_end_x, line_y)], fill=(215, 215, 215, 80), width=3)\n \n return canvas\n except Exception as e:\n logger.error(f\"创建装饰层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "e28e698781edbdaf148659d8d548b40b"
},
{
"name": "_create_left_column_layer",
"line_start": 966,
"line_end": 1012,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建左栏内容图层",
"is_async": false,
"decorators": [],
"code": " def _create_left_column_layer(self, content: Dict[str, Any], y: int, x: int, width: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建左栏内容图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n \n # 按钮\n try:\n button_font = ImageFont.truetype(self.chinese_font_path, 30)\n except:\n button_font = self.text_renderer._load_default_font(30)\n \n button_text = content.get(\"content_button\", \"套餐内容\")\n bbox = button_font.getbbox(button_text)\n button_text_width = bbox[2] - bbox[0]\n button_width = button_text_width + 40\n button_height = 50\n \n # 绘制简单的矩形按钮\n draw.rounded_rectangle([x, y, x + button_width, y + button_height], \n radius=20, fill=(0, 140, 210, 180), \n outline=(255, 255, 255, 255), width=1)\n draw.text((x + 20, y + (button_height - 30) // 2), button_text, font=button_font, fill=(255, 255, 255))\n\n # 项目列表\n items = content.get(\"content_items\", [])\n if items:\n try:\n list_font = ImageFont.truetype(self.chinese_font_path, 28)\n except:\n list_font = self.text_renderer._load_default_font(28)\n \n list_y = y + button_height + 20\n line_spacing = 36\n \n for i, item in enumerate(items):\n item_y = list_y + i * line_spacing\n draw.text((x, item_y), \"• \" + item, font=list_font, fill=(255, 255, 255))\n \n return canvas\n except Exception as e:\n logger.error(f\"创建左栏内容层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "993d6f77656e470e8695efd21791d24e"
},
{
"name": "_create_right_column_layer",
"line_start": 1014,
"line_end": 1092,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建右栏内容图层",
"is_async": false,
"decorators": [],
"code": " def _create_right_column_layer(self, content: Dict[str, Any], y: int, x: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建右栏内容图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n \n # 价格\n price_text = str(content.get('price', '')) # 确保价格是字符串类型\n if price_text:\n price_target_width = int((right_margin - x) * 0.7)\n price_size, price_actual_width = self._calculate_optimal_font_size_enhanced(\n price_text, price_target_width, max_size=120, min_size=40\n )\n \n try:\n price_font = ImageFont.truetype(self.chinese_font_path, price_size)\n suffix_font = ImageFont.truetype(self.chinese_font_path, int(price_size * 0.3))\n except:\n price_font = self.text_renderer._load_default_font(price_size)\n suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3))\n \n price_bbox = price_font.getbbox(price_text)\n price_height = price_bbox[3] - price_bbox[1]\n suffix_bbox = suffix_font.getbbox(\"CNY起\")\n suffix_width = suffix_bbox[2] - suffix_bbox[0]\n suffix_height = suffix_bbox[3] - suffix_bbox[1]\n \n price_x = right_margin - price_actual_width - suffix_width\n self._draw_text_with_shadow_simple(draw, (price_x, y), price_text, price_font)\n \n suffix_y = y + price_height - suffix_height\n draw.text((price_x + price_actual_width, suffix_y), \"CNY起\", font=suffix_font, fill=(255, 255, 255))\n \n # 下划线\n underline_y = y + price_height + 18\n draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2)\n \n # 票种\n ticket_text = content.get(\"ticket_type\", \"\")\n if ticket_text:\n ticket_target_width = int((right_margin - x) * 0.7)\n ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced(\n ticket_text, ticket_target_width, max_size=60, min_size=30\n )\n \n try:\n ticket_font = ImageFont.truetype(self.chinese_font_path, ticket_size)\n except:\n ticket_font = self.text_renderer._load_default_font(ticket_size)\n \n ticket_x = right_margin - ticket_actual_width\n ticket_y = y + price_height + 35\n self._draw_text_with_shadow_simple(draw, (ticket_x, ticket_y), ticket_text, ticket_font)\n \n ticket_bbox = ticket_font.getbbox(ticket_text)\n ticket_height = ticket_bbox[3] - ticket_bbox[1]\n\n # 备注\n remarks = content.get(\"remarks\", [])\n if remarks:\n try:\n remarks_font = ImageFont.truetype(self.chinese_font_path, 16)\n except:\n remarks_font = self.text_renderer._load_default_font(16)\n \n remarks_y = ticket_y + ticket_height + 30\n for i, remark in enumerate(remarks):\n remark_bbox = remarks_font.getbbox(remark)\n remark_width = remark_bbox[2] - remark_bbox[0]\n draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255,
"code_hash": "caddd95f96510da77af0e9c27cd417c1"
},
{
"name": "_create_footer_layer",
"line_start": 1094,
"line_end": 1123,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "footer_y",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建页脚图层",
"is_async": false,
"decorators": [],
"code": " def _create_footer_layer(self, content: Dict[str, Any], footer_y: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建页脚图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n try:\n font = ImageFont.truetype(self.chinese_font_path, 18)\n except:\n font = self.text_renderer._load_default_font(18)\n \n # 标签(左下角)\n tag_text = content.get(\"tag\", \"\")\n if tag_text:\n draw.text((left_margin, footer_y), tag_text, font=font, fill=(255, 255, 255))\n \n # 分页信息(右下角)\n pagination_text = content.get(\"pagination\", \"\")\n if pagination_text:\n pagination_bbox = font.getbbox(pagination_text)\n pagination_width = pagination_bbox[2] - pagination_bbox[0]\n draw.text((right_margin - pagination_width, footer_y), pagination_text, font=font, fill=(255, 255, 255))\n \n return canvas\n except Exception as e:\n logger.error(f\"创建页脚层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None ",
"code_hash": "a36e657e4dd3c792ada931823ae2bb1c"
},
{
"name": "load_chinese_font",
"line_start": 56,
"line_end": 61,
"args": [
{
"name": "size",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def load_chinese_font(size: int):\n try:\n return ImageFont.truetype(self.chinese_font_path, size)\n except:\n logger.warning(f\"无法加载中文字体,使用默认字体\")\n return original_load_font(size)",
"code_hash": "eb3e654791ac65777cb9e46760d987fa"
},
{
"name": "enhance_color",
"line_start": 205,
"line_end": 207,
"args": [
{
"name": "color"
},
{
"name": "multiplier"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def enhance_color(color, multiplier):\n factor = min(1.5, 1.0 + (multiplier - 1.0) * 0.3)\n return tuple(min(255, max(0, int(c * factor))) for c in color)",
"code_hash": "2af76daaa9f55515f39cfc9c157beaa5"
}
],
"classes": [
{
"name": "VibrantTemplate",
"line_start": 21,
"line_end": 1123,
"bases": [
"BaseTemplate"
],
"methods": [
{
"name": "__init__",
"line_start": 26,
"line_end": 50,
"args": [
{
"name": "self"
},
{
"name": "size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def __init__(self, size: Tuple[int, int] = (900, 1200)):\n super().__init__(size)\n self.config = {\n 'colors': {\n 'ocean_deep': [(0, 30, 80), (20, 120, 220)],\n 'sunset_warm': [(255, 94, 77), (255, 154, 0)],\n 'cool_mint': [(64, 224, 208), (127, 255, 212)],\n 'royal_purple': [(75, 0, 130), (138, 43, 226)],\n 'forest_green': [(34, 139, 34), (144, 238, 144)],\n 'fire_red': [(220, 20, 60), (255, 69, 0)],\n \"gray_gradient\": [(128, 128, 128), (211, 211, 211)],\n \"dark_gray\": [(15, 15, 15), (30, 30, 30)]\n },\n 'glass_effect': {\n 'max_opacity': 240,\n 'blur_radius': 22,\n 'transition_height': 80,\n 'intensity_multiplier': 1.5\n },\n }\n # 设置中文字体路径\n self.chinese_font_path = \"/root/TravelContentCreator/assets/font/兰亭粗黑简.TTF\"\n \n # 重写text_renderer的字体加载方法以支持中文\n self._patch_text_renderer_for_chinese()",
"code_hash": "588184602b75d940237b34db6d4e37c1"
},
{
"name": "_patch_text_renderer_for_chinese",
"line_start": 52,
"line_end": 64,
"args": [
{
"name": "self"
}
],
"return_type": null,
"docstring": "重写text_renderer的字体加载方法以支持中文",
"is_async": false,
"decorators": [],
"code": " def _patch_text_renderer_for_chinese(self):\n \"\"\"重写text_renderer的字体加载方法以支持中文\"\"\"\n original_load_font = self.text_renderer._load_default_font\n \n def load_chinese_font(size: int):\n try:\n return ImageFont.truetype(self.chinese_font_path, size)\n except:\n logger.warning(f\"无法加载中文字体,使用默认字体\")\n return original_load_font(size)\n \n # 替换字体加载方法\n self.text_renderer._load_default_font = load_chinese_font",
"code_hash": "f2557860be39d9005c09fa4fb8492a05"
},
{
"name": "generate",
"line_start": 66,
"line_end": 106,
"args": [
{
"name": "self"
},
{
"name": "images",
"type_hint": "List"
},
{
"name": "content",
"type_hint": "Optional[Dict[str, Any]]"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
},
{
"name": "glass_intensity",
"type_hint": "float"
},
{
"name": "num_variations",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "生成Vibrant风格海报\n\nArgs:\n images (List): 主图\n content (Optional[Dict[str, Any]]): 包含所有文本信息的字典\n theme_color (Optional[str]): 预设颜色主题的名称\n glass_intensity (float): 毛玻璃效果强度\n num_variations (int): 生成海报数量\n\nReturns:\n Image.Image: 生成的海报图像",
"is_async": false,
"decorators": [],
"code": " def generate(self,\n images: List,\n content: Optional[Dict[str, Any]] = None,\n theme_color: Optional[str] = None,\n glass_intensity: float = 1.5,\n num_variations: int = 1,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Vibrant风格海报\n\n Args:\n images (List): 主图\n content (Optional[Dict[str, Any]]): 包含所有文本信息的字典\n theme_color (Optional[str]): 预设颜色主题的名称\n glass_intensity (float): 毛玻璃效果强度\n num_variations (int): 生成海报数量\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n if content is None:\n content = self._get_default_content()\n\n self.config['glass_effect']['intensity_multiplier'] = glass_intensity\n\n main_image = images\n logger.info(f\"main_image的类型: {np.shape(main_image)}\")\n if not main_image:\n logger.error(f\"无法加载图片: \")\n return None\n\n main_image = self.image_processor.resize_image(image=main_image, target_size=self.size)\n\n estimated_height = self._estimate_content_height(content)\n gradient_start = self._detect_gradient_start_position(main_image, estimated_height)\n\n canvas = self._create_composite_image(main_image, gradient_start, theme_color)\n canvas = self._render_texts(canvas, content, gradient_start)\n\n final_image = canvas.resize((1350, 1800), Image.LANCZOS)\n return final_image",
"code_hash": "882c4eab44dec9248b43763ab4803404"
},
{
"name": "_get_default_content",
"line_start": 108,
"line_end": 127,
"args": [
{
"name": "self"
}
],
"return_type": "Dict[str, Any]",
"docstring": "获取默认的测试内容",
"is_async": false,
"decorators": [],
"code": " def _get_default_content(self) -> Dict[str, Any]:\n \"\"\"获取默认的测试内容\"\"\"\n return {\n \"title\": \"正佳极地海洋世界\",\n \"slogan\": \"都说海洋馆是约会圣地!那锦峰夜场将是绝杀!\",\n \"price\": \"199\",\n \"ticket_type\": \"夜场票\",\n \"content_button\": \"套餐内容\",\n \"content_items\": [\n \"正佳极地海洋世界夜场票1张\",\n \"有效期至2025.06.02\",\n \"多种动物表演全部免费\"\n ],\n \"remarks\": [\n \"工作日可直接入园\",\n \"周末请提前1天预约\"\n ],\n \"tag\": \"#520特惠\",\n \"pagination\": \"\"\n }",
"code_hash": "5a3ba4ddbde0e7683f770cfd4b134e7f"
},
{
"name": "_estimate_content_height",
"line_start": 129,
"line_end": 150,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "预估内容高度",
"is_async": false,
"decorators": [],
"code": " def _estimate_content_height(self, content: Dict[str, Any]) -> int:\n \"\"\"预估内容高度\"\"\"\n standard_margin = 25\n title_height = 100\n subtitle_height = 80\n button_height = 40\n content_items = content.get(\"content_items\", [])\n content_line_height = 32\n content_list_height = len(content_items) * content_line_height\n price_height = 90\n ticket_height = 60\n remarks = content.get(\"remarks\", [])\n if isinstance(remarks, str):\n remarks = [remarks]\n remarks_height = len(remarks) * 25 + 10\n footer_height = 40\n total_height = (\n 20 + title_height + standard_margin + subtitle_height + standard_margin +\n button_height + 15 + content_list_height + price_height + ticket_height +\n remarks_height + footer_height + 30\n )\n return total_height",
"code_hash": "9c7407bb70e0287cea14b52f38746370"
},
{
"name": "_detect_gradient_start_position",
"line_start": 152,
"line_end": 170,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "estimated_height",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "动态检测渐变起始位置",
"is_async": false,
"decorators": [],
"code": " def _detect_gradient_start_position(self, image: Image.Image, estimated_height: int) -> int:\n \"\"\"动态检测渐变起始位置\"\"\"\n width, height = image.size\n center_x = width // 2\n gradient_start = None\n for y in range(height // 2, height):\n try:\n pixel = image.getpixel((center_x, y))\n if isinstance(pixel, (tuple, list)) and len(pixel) >= 3:\n brightness = sum(pixel[:3]) / 3\n if brightness > 50:\n gradient_start = max(y - 20, height // 2)\n break\n except:\n continue\n if gradient_start is None:\n bottom_margin = 60\n gradient_start = max(height - estimated_height - bottom_margin, height // 2)\n return gradient_start",
"code_hash": "dd12b5f07214739f2e0c66972f835bec"
},
{
"name": "_create_composite_image",
"line_start": 172,
"line_end": 189,
"args": [
{
"name": "self"
},
{
"name": "main_image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
}
],
"return_type": "Image.Image",
"docstring": "创建毛玻璃背景和复合图像",
"is_async": false,
"decorators": [],
"code": " def _create_composite_image(self, main_image: Image.Image, \n gradient_start: int, \n theme_color: Optional[str]) -> Image.Image:\n \"\"\"创建毛玻璃背景和复合图像\"\"\"\n if theme_color and theme_color in self.config['colors']:\n top_color, bottom_color = self.config['colors'][theme_color]\n else:\n top_color, bottom_color = self._extract_glass_colors_from_image(main_image, gradient_start)\n \n logger.info(f\"使用毛玻璃颜色: 顶部={top_color}, 底部={bottom_color}\")\n \n gradient_overlay = self._create_frosted_glass_overlay(top_color, bottom_color, gradient_start)\n \n composite_img = Image.new('RGBA', self.size, (0, 0, 0, 0))\n composite_img.paste(main_image, (0, 0))\n composite_img = Image.alpha_composite(composite_img, gradient_overlay)\n \n return composite_img",
"code_hash": "ddb80c7467dcbeb648c2300f8c7d21c0"
},
{
"name": "_create_frosted_glass_overlay",
"line_start": 191,
"line_end": 228,
"args": [
{
"name": "self"
},
{
"name": "top_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "bottom_color",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "创建高级毛玻璃效果覆盖层",
"is_async": false,
"decorators": [],
"code": " def _create_frosted_glass_overlay(self, top_color: Tuple[int, int, int], \n bottom_color: Tuple[int, int, int], \n gradient_start: int) -> Image.Image:\n \"\"\"创建高级毛玻璃效果覆盖层\"\"\"\n overlay = Image.new('RGBA', self.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(overlay)\n\n gradient_height = self.height - gradient_start\n glass_config = self.config['glass_effect']\n intensity = glass_config['intensity_multiplier']\n\n enhanced_opacity = min(255, int(glass_config['max_opacity'] * intensity))\n enhanced_blur = int(glass_config['blur_radius'] * intensity)\n\n def enhance_color(color, multiplier):\n factor = min(1.5, 1.0 + (multiplier - 1.0) * 0.3)\n return tuple(min(255, max(0, int(c * factor))) for c in color)\n\n top_c = enhance_color(top_color, intensity)\n bottom_c = enhance_color(bottom_color, intensity)\n top_c_arr = np.array(top_c)\n bottom_c_arr = np.array(bottom_c)\n\n for y in range(gradient_start, self.height):\n ratio = (y - gradient_start) / gradient_height if gradient_height > 0 else 0\n smooth_ratio = 0.5 - 0.5 * math.cos(ratio * math.pi)\n color = (1 - smooth_ratio) * top_c_arr + smooth_ratio * bottom_c_arr\n alpha_smooth = ratio ** (1.1 / intensity)\n alpha = int(enhanced_opacity * (0.02 + 0.98 * alpha_smooth))\n\n if (y - gradient_start) < glass_config['transition_height']:\n transition_ratio = (y - gradient_start) / glass_config['transition_height']\n alpha = int(alpha * (0.5 - 0.5 * math.cos(transition_ratio * math.pi)))\n\n color_tuple = tuple(int(c) for c in color) + (alpha,)\n draw.line([(0, y), (self.width, y)], fill=color_tuple)\n \n return overlay.filter(ImageFilter.GaussianBlur(radius=enhanced_blur))",
"code_hash": "006716948f1590cb1549126a77345345"
},
{
"name": "_extract_glass_colors_from_image",
"line_start": 230,
"line_end": 251,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "tuple",
"docstring": "从图片中提取用于毛玻璃背景的颜色",
"is_async": false,
"decorators": [],
"code": " def _extract_glass_colors_from_image(self, image: Image.Image, gradient_start: int) -> tuple:\n \"\"\"从图片中提取用于毛玻璃背景的颜色\"\"\"\n if image.mode != 'RGB':\n image = image.convert('RGB')\n \n width, height = image.size\n top_samples, bottom_samples = [], []\n\n top_y = min(gradient_start + 20, height - 1)\n for x in range(0, width, 20):\n if sum(pixel := image.getpixel((x, top_y))) > 30:\n top_samples.append(pixel)\n \n bottom_y = min(height - 50, height - 1)\n for x in range(0, width, 20):\n if sum(pixel := image.getpixel((x, bottom_y))) > 30:\n bottom_samples.append(pixel)\n\n top_color = tuple(max(0, int(c * 0.1)) for c in np.mean(top_samples, axis=0)) if top_samples else (0, 5, 15)\n bottom_color = tuple(max(0, int(c * 0.2)) for c in np.mean(bottom_samples, axis=0)) if bottom_samples else (0, 25, 50)\n \n return top_color, bottom_color",
"code_hash": "f9fb4e3f5ca270b128ed181514126bf3"
},
{
"name": "_calculate_optimal_font_size_enhanced",
"line_start": 253,
"line_end": 329,
"args": [
{
"name": "self"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "target_width",
"type_hint": "int"
},
{
"name": "max_size",
"type_hint": "int"
},
{
"name": "min_size",
"type_hint": "int"
}
],
"return_type": "Tuple[int, int]",
"docstring": "计算文本的最佳字体大小使其宽度接近目标宽度增强版本与demo一致\n\n返回:\n(字体大小, 实际文本宽度)",
"is_async": false,
"decorators": [],
"code": " def _calculate_optimal_font_size_enhanced(self, text: str, target_width: int, \n max_size: int = 120, min_size: int = 10) -> Tuple[int, int]:\n \"\"\"\n 计算文本的最佳字体大小使其宽度接近目标宽度增强版本与demo一致\n \n 返回:\n (字体大小, 实际文本宽度)\n \"\"\"\n # 二分查找最佳字体大小\n low = min_size\n high = max_size\n best_size = min_size\n best_width = 0\n tolerance = 0.08 # 容差值,使文本宽度更接近目标值\n \n # 首先尝试最大字体大小\n try:\n font = self.text_renderer._load_default_font(max_size)\n max_width, _ = self.text_renderer.get_text_size(text, font)\n except:\n max_width = target_width * 2 # 如果出错,设置一个大值\n \n # 如果最大字体大小下的宽度仍小于目标宽度的108%,直接使用最大字体\n if max_width < target_width * (1 + tolerance):\n best_size = max_size\n best_width = max_width\n else:\n # 记录最接近目标宽度的字体大小\n closest_size = min_size\n closest_diff = target_width\n \n while low <= high:\n mid = (low + high) // 2\n try:\n font = self.text_renderer._load_default_font(mid)\n width, _ = self.text_renderer.get_text_size(text, font)\n except:\n width = target_width * 2 # 如果出错,设置一个大值\n \n # 计算与目标宽度的差距\n diff = abs(width - target_width)\n \n # 更新最接近的字体大小\n if diff < closest_diff:\n closest_diff = diff\n closest_size = mid\n \n # 如果宽度在目标宽度的允许范围内,认为找到了最佳匹配\n if target_width * (1 - tolerance) <= width <= target_width * (1 + tolerance):\n best_size = mid\n best_width = width\n break\n \n # 如果当前宽度小于目标宽度,尝试更大的字体\n if width < target_width:\n if width > best_width:\n best_width = width\n best_size = mid\n low = mid + 1\n else:\n # 如果当前宽度大于目标宽度,尝试更小的字体\n high = mid - 1\n \n # 如果没有找到在容差范围内的字体大小,使用最接近的字体大小\n if best_width == 0:\n best_size = closest_size\n \n # 确保返回的宽度是使用最终字体计算的实际宽度\n try:\n best_font = self.text_renderer._load_default_font(best_size)\n final_width, _ = self.text_renderer.get_text_size(text, best_font)\n except:\n final_width = best_width\n \n logger.info(f\"文本'{text[:min(10, len(text))]}...'的最佳字体大小: {best_size},目标宽度: {target_width},实际宽度: {final_width},差距: {abs(final_width-target_width)}\")\n \n return best_size, final_width",
"code_hash": "49c84dbf08fd2e88666470230602be09"
},
{
"name": "_render_texts",
"line_start": 331,
"line_end": 353,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Image.Image",
"docstring": "渲染所有文本元素,采用双栏布局",
"is_async": false,
"decorators": [],
"code": " def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], gradient_start: int) -> Image.Image:\n \"\"\"渲染所有文本元素,采用双栏布局\"\"\"\n draw = ImageDraw.Draw(canvas)\n width, height = canvas.size\n center_x = width // 2\n\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return canvas",
"code_hash": "88c9fbdb1fb06d7e0216ba80f0b9ed98"
},
{
"name": "_calculate_content_margins",
"line_start": 355,
"line_end": 390,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
}
],
"return_type": "Tuple[int, int]",
"docstring": "计算内容区域的左右边距增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _calculate_content_margins(self, content: Dict[str, Any], width: int, center_x: int) -> Tuple[int, int]:\n \"\"\"计算内容区域的左右边距增强版本与demo一致\"\"\"\n # 计算标题位置\n title_text = content.get(\"title\", \"\")\n title_target_width = int(width * 0.95)\n title_size, title_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, min_size=40, max_size=130\n )\n title_x = center_x - title_width // 2\n \n # 计算副标题位置\n slogan_text = content.get(\"slogan\", \"\")\n subtitle_target_width = int(width * 0.9)\n subtitle_size, subtitle_width = self._calculate_optimal_font_size_enhanced(\n slogan_text, subtitle_target_width, max_size=50, min_size=20\n )\n subtitle_x = center_x - subtitle_width // 2\n \n # 计算内容区域边距 - 减小额外的边距,让内容区域更宽\n padding = 20 # 从30减小到20\n content_left_margin = min(title_x, subtitle_x) - padding\n content_right_margin = max(title_x + title_width, subtitle_x + subtitle_width) + padding\n \n # 确保边距不超出合理范围,但允许更宽的内容区域\n content_left_margin = max(40, content_left_margin)\n content_right_margin = min(width - 40, content_right_margin)\n \n # 如果内容区域太窄,强制使用更宽的区域\n min_content_width = int(width * 0.75) # 至少使用75%的宽度\n current_width = content_right_margin - content_left_margin\n if current_width < min_content_width:\n extra_width = min_content_width - current_width\n content_left_margin = max(30, content_left_margin - extra_width // 2)\n content_right_margin = min(width - 30, content_right_margin + extra_width // 2)\n \n return content_left_margin, content_right_margin",
"code_hash": "c1f80261fd729be7715bac300aed8eca"
},
{
"name": "_render_footer",
"line_start": 392,
"line_end": 399,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "left",
"type_hint": "int"
},
{
"name": "right",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染页脚文本",
"is_async": false,
"decorators": [],
"code": " def _render_footer(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, left: int, right: int):\n \"\"\"渲染页脚文本\"\"\"\n font = self.text_renderer._load_default_font(18)\n if tag := content.get(\"tag\"):\n draw.text((left, y), tag, font=font, fill=(255, 255, 255))\n if pagination := content.get(\"pagination\"):\n width, _ = self.text_renderer.get_text_size(pagination, font)\n draw.text((right - width, y), pagination, font=font, fill=(255, 255, 255))",
"code_hash": "9621a05ad6bae4098fe7460315f2a995"
},
{
"name": "_render_title_subtitle",
"line_start": 401,
"line_end": 439,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left",
"type_hint": "int"
},
{
"name": "right",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "渲染标题和副标题增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _render_title_subtitle(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, center_x: int, left: int, right: int) -> int:\n \"\"\"渲染标题和副标题增强版本与demo一致\"\"\"\n # 标题\n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right - left) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n title_font = self.text_renderer._load_default_font(title_size)\n \n text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)\n title_x = center_x - text_w // 2\n \n self.text_renderer.draw_text_with_outline(draw, (title_x, y), title_text, title_font, text_color=(255, 255, 255, 255), outline_color=(0, 30, 80, 200), outline_width=4)\n \n # 副标题 (slogan)\n subtitle_text = content.get(\"slogan\", \"\")\n subtitle_target_width = int((right - left) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n \n sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font)\n subtitle_x = center_x - sub_text_w // 2\n \n title_spacing = 30\n \n # 在标题下方添加装饰线\n line_y = y + text_h + 5\n line_start_x = title_x - text_w * 0.025\n line_end_x = title_x + text_w * 1.025\n draw.line([(line_start_x, line_y), (line_end_x, line_y)], fill=(215, 215, 215, 80), width=3)\n\n subtitle_y = y + text_h + title_spacing\n \n self.text_renderer.draw_text_with_shadow(draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font, text_color=(255, 255, 255, 255), shadow_color=(0, 0, 0, 180), shadow_offset=(2, 2))\n \n return subtitle_y + sub_text_h",
"code_hash": "c2251fa3da2779e5aae68e2f51ec4b97"
},
{
"name": "_render_left_column",
"line_start": 441,
"line_end": 463,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "canvas_height",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染左栏内容:按钮和项目列表",
"is_async": false,
"decorators": [],
"code": " def _render_left_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, width: int, canvas_height: int):\n \"\"\"渲染左栏内容:按钮和项目列表\"\"\"\n button_font = self.text_renderer._load_default_font(30)\n button_text = content.get(\"content_button\", \"套餐内容\")\n button_width, _ = self.text_renderer.get_text_size(button_text, button_font)\n button_width += 40\n button_height = 50\n self.text_renderer.draw_rounded_rectangle(draw=draw, position=(x, y), size=(button_width, button_height), radius=20, fill_color=(0, 140, 210, 180), outline_color=(255, 255, 255, 255), outline_width=1)\n draw.text((x + 20, y + (button_height - 30) // 2), button_text, font=button_font, fill=(255, 255, 255))\n\n items = content.get(\"content_items\", [])\n if not items: return\n \n font = self.text_renderer._load_default_font(28)\n list_y = y + button_height + 20\n available_h = canvas_height - 30 - (len(content.get(\"remarks\", [])) * 25 + 10) - list_y - 20\n total_items_h = len(items) * 36\n extra_space_per_item = max(0, min(17, (available_h - total_items_h) / max(1, len(items) - 1)))\n line_spacing = 8 + extra_space_per_item\n \n for i, item in enumerate(items):\n item_y = list_y + i * (28 + line_spacing)\n draw.text((x, item_y), \" \" + item, font=font, fill=(255, 255, 255))",
"code_hash": "214ed6460a15065f6dabcaa756c86f25"
},
{
"name": "_render_right_column",
"line_start": 465,
"line_end": 504,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "渲染右栏内容价格、票种和备注增强版本与demo一致",
"is_async": false,
"decorators": [],
"code": " def _render_right_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, right_margin: int):\n \"\"\"渲染右栏内容价格、票种和备注增强版本与demo一致\"\"\"\n price_text = str(content.get('price', '')) # 确保价格是字符串类型\n price_target_width = int((right_margin - x) * 0.7)\n price_size, price_actual_width = self._calculate_optimal_font_size_enhanced(\n price_text, price_target_width, max_size=120, min_size=40\n )\n \n price_font = self.text_renderer._load_default_font(price_size)\n suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3))\n _, price_height = self.text_renderer.get_text_size(price_text, price_font)\n suffix_width, suffix_height = self.text_renderer.get_text_size(\"CNY起\", suffix_font)\n \n price_x = right_margin - price_actual_width - suffix_width\n self.text_renderer.draw_text_with_shadow(draw, (price_x, y), price_text, price_font)\n \n suffix_y = y + price_height - suffix_height\n draw.text((price_x + price_actual_width, suffix_y), \"CNY起\", font=suffix_font, fill=(255, 255, 255))\n \n underline_y = y + price_height + 18\n draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2)\n \n ticket_text = content.get(\"ticket_type\", \"\")\n ticket_target_width = int((right_margin - x) * 0.7)\n ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced(\n ticket_text, ticket_target_width, max_size=60, min_size=30\n )\n ticket_font = self.text_renderer._load_default_font(ticket_size)\n ticket_x = right_margin - ticket_actual_width\n ticket_y = y + price_height + 35\n self.text_renderer.draw_text_with_shadow(draw, (ticket_x, ticket_y), ticket_text, ticket_font)\n _, ticket_height = self.text_renderer.get_text_size(ticket_text, ticket_font)\n\n remarks = content.get(\"remarks\", [])\n if remarks:\n remarks_font = self.text_renderer._load_default_font(16)\n remarks_y = ticket_y + ticket_height + 30\n for i, remark in enumerate(remarks):\n remark_width, _ = self.text_renderer.get_text_size(remark, remarks_font)\n draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255, 200)) ",
"code_hash": "3259213e888d90776809f6595c48805c"
},
{
"name": "generate_layered_psd",
"line_start": 506,
"line_end": 604,
"args": [
{
"name": "self"
},
{
"name": "images"
},
{
"name": "content",
"type_hint": "Optional[Dict[str, Any]]"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
},
{
"name": "glass_intensity",
"type_hint": "float"
},
{
"name": "output_path",
"type_hint": "str"
}
],
"return_type": "str",
"docstring": "生成分层的PSD文件方便后续修改\n\nArgs:\n images: 主图\n content: 包含所有文本信息的字典\n theme_color: 预设颜色主题的名称\n glass_intensity: 毛玻璃效果强度\n output_path: PSD文件输出路径\n \nReturns:\n str: 生成的PSD文件路径",
"is_async": false,
"decorators": [],
"code": " def generate_layered_psd(self,\n images,\n content: Optional[Dict[str, Any]] = None,\n theme_color: Optional[str] = None,\n glass_intensity: float = 1.5,\n output_path: str = \"layered_poster.psd\",\n **kwargs) -> str:\n \"\"\"\n 生成分层的PSD文件方便后续修改\n \n Args:\n images: 主图\n content: 包含所有文本信息的字典\n theme_color: 预设颜色主题的名称\n glass_intensity: 毛玻璃效果强度\n output_path: PSD文件输出路径\n \n Returns:\n str: 生成的PSD文件路径\n \"\"\"\n try:\n from psd_tools import PSDImage\n from psd_tools.api.layers import PixelLayer\n except ImportError:\n logger.error(\"需要安装psd-tools库: pip install psd-tools\")\n return None\n \n logger.info(\"开始生成分层PSD文件...\")\n \n if content is None:\n content = self._get_default_content()\n\n self.config['glass_effect']['intensity_multiplier'] = glass_intensity\n\n main_image = images\n if not main_image:\n logger.error(\"无法加载图片\")\n return None\n\n # 参考原始合成模式的PSD版本实现\n main_image = self.image_processor.resize_image(image=main_image, target_size=self.size)\n estimated_height = self._estimate_content_height(content)\n gradient_start = self._detect_gradient_start_position(main_image, estimated_height)\n\n # === 第一步创建与常规模式完全相同的最终结果作为Reference ===\n canvas = self._create_composite_image(main_image, gradient_start, theme_color)\n canvas = self._render_texts(canvas, content, gradient_start)\n final_canvas = canvas.resize((1350, 1800), Image.LANCZOS)\n \n # === 第二步为PSD创建可编辑的图层版本 ===\n final_size = (1350, 1800)\n psd = PSDImage.new(\"RGBA\", final_size, color=(0, 0, 0, 0))\n logger.info(f\"创建PSD文档尺寸: {final_size}\")\n\n # 图层1完美一致的参考图层\n composite_layer = PixelLayer.frompil(final_canvas, psd, \"Perfect Reference\")\n psd.append(composite_layer)\n logger.info(\"✓ 添加完美参考图层\")\n \n # === 第三步:提供丰富的可编辑图层 ===\n \n # 图层2纯背景图层无任何效果\n background_only = main_image.resize(final_size, Image.LANCZOS)\n bg_layer = PixelLayer.frompil(background_only, psd, \"Background Image\")\n psd.append(bg_layer)\n logger.info(\"✓ 添加纯背景图层\")\n \n # 图层3毛玻璃效果图层\n glass_overlay = self._create_glass_overlay_layer(main_image, gradient_start, theme_color)\n if glass_overlay:\n glass_scaled = glass_overlay.resize(final_size, Image.LANCZOS)\n glass_layer = PixelLayer.frompil(glass_scaled, psd, \"Glass Overlay\")\n psd.append(glass_layer)\n logger.info(\"✓ 添加毛玻璃效果图层\")\n \n # 图层4-9分离的文字图层\n detailed_text_layers = self._create_detailed_text_layers(content, gradient_start, self.size)\n for layer_name, layer_image in detailed_text_layers.items():\n if layer_image:\n text_scaled = layer_image.resize(final_size, Image.LANCZOS)\n text_layer = PixelLayer.frompil(text_scaled, psd, layer_name)\n psd.append(text_layer)\n logger.info(f\"✓ 添加文字图层: {layer_name}\")\n \n # 10\n text_only_canvas = self._create_text_only_layer(content, gradient_start, self.size)\n if text_only_canvas:\n
"code_hash": "f2168a1e8137fdc83d925a4340e548cc"
},
{
"name": "_create_glass_overlay_layer",
"line_start": 606,
"line_end": 619,
"args": [
{
"name": "self"
},
{
"name": "main_image",
"type_hint": "Image.Image"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "theme_color",
"type_hint": "Optional[str]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建毛玻璃效果的独立图层",
"is_async": false,
"decorators": [],
"code": " def _create_glass_overlay_layer(self, main_image: Image.Image, gradient_start: int, theme_color: Optional[str]) -> Optional[Image.Image]:\n \"\"\"创建毛玻璃效果的独立图层\"\"\"\n try:\n if theme_color and theme_color in self.config['colors']:\n top_color, bottom_color = self.config['colors'][theme_color]\n else:\n top_color, bottom_color = self._extract_glass_colors_from_image(main_image, gradient_start)\n \n # 创建透明背景的毛玻璃层\n overlay = self._create_frosted_glass_overlay(top_color, bottom_color, gradient_start)\n return overlay\n except Exception as e:\n logger.error(f\"创建毛玻璃层失败: {e}\")\n return None",
"code_hash": "6e53f640e1d6bc5c5021da976ee5ba6d"
},
{
"name": "_create_text_layers",
"line_start": 623,
"line_end": 665,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
}
],
"return_type": "Dict[str, Optional[Image.Image]]",
"docstring": "创建各个文字图层",
"is_async": false,
"decorators": [],
"code": " def _create_text_layers(self, content: Dict[str, Any], gradient_start: int) -> Dict[str, Optional[Image.Image]]:\n \"\"\"创建各个文字图层\"\"\"\n layers = {}\n \n try:\n # 创建透明画布\n canvas_size = self.size\n \n # 计算布局参数\n width, height = canvas_size\n center_x = width // 2\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 1. 标题层\n layers[\"Title Text\"] = self._create_title_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 2. 副标题层 \n layers[\"Subtitle Text\"] = self._create_subtitle_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 3. 装饰线层\n layers[\"Decorations\"] = self._create_decoration_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size)\n \n # 4. 左栏内容层\n title_y = gradient_start + 40\n subtitle_height = 80 + 30 # 预估副标题高度\n content_start_y = title_y + subtitle_height + 30\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n \n layers[\"Left Content\"] = self._create_left_column_layer(content, content_start_y, left_margin, left_column_width, canvas_size)\n \n # 5. 右栏内容层\n right_column_x = left_margin + left_column_width\n layers[\"Right Content\"] = self._create_right_column_layer(content, content_start_y, right_column_x, right_margin, canvas_size)\n \n # 6. 页脚层\n footer_y = height - 30\n layers[\"Footer Info\"] = self._create_footer_layer(content, footer_y, left_margin, right_margin, canvas_size)\n \n except Exception as e:\n logger.error(f\"创建文字图层失败: {e}\")\n \n return layers",
"code_hash": "a736f347c4342d9bb2aa309bbf909a48"
},
{
"name": "_create_text_only_layer",
"line_start": 667,
"line_end": 700,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建纯文字图层透明背景模拟_render_texts的渲染逻辑",
"is_async": false,
"decorators": [],
"code": " def _create_text_only_layer(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Optional[Image.Image]:\n \"\"\"创建纯文字图层透明背景模拟_render_texts的渲染逻辑\"\"\"\n try:\n # 创建透明画布\n text_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(text_canvas)\n \n width, height = canvas_size\n center_x = width // 2\n\n # 使用与原始方法相同的布局计算\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 页脚\n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n # 标题和副标题\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n # 左右栏内容\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return text_canvas\n except Exception as e:\n logger.error(f\"创建纯文字图层失败: {e}\")\n return None",
"code_hash": "0b501f8aa6b1e6f29ca310c84041e531"
},
{
"name": "_create_text_only_layer",
"line_start": 702,
"line_end": 735,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建纯文字图层透明背景模拟_render_texts的渲染逻辑",
"is_async": false,
"decorators": [],
"code": " def _create_text_only_layer(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Optional[Image.Image]:\n \"\"\"创建纯文字图层透明背景模拟_render_texts的渲染逻辑\"\"\"\n try:\n # 创建透明画布\n text_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(text_canvas)\n \n width, height = canvas_size\n center_x = width // 2\n\n # 使用与原始方法相同的布局计算\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 页脚\n footer_y = height - 30\n self._render_footer(draw, content, footer_y, left_margin, right_margin)\n\n # 标题和副标题\n title_y = gradient_start + 40\n current_y = self._render_title_subtitle(draw, content, title_y, center_x, left_margin, right_margin)\n\n # 左右栏内容\n content_area_width = right_margin - left_margin\n left_column_width = int(content_area_width * 0.5)\n right_column_x = left_margin + left_column_width\n\n content_start_y = current_y + 30\n self._render_left_column(draw, content, content_start_y, left_margin, left_column_width, height)\n self._render_right_column(draw, content, content_start_y, right_column_x, right_margin)\n\n return text_canvas\n except Exception as e:\n logger.error(f\"创建纯文字图层失败: {e}\")\n return None",
"code_hash": "0b501f8aa6b1e6f29ca310c84041e531"
},
{
"name": "_create_detailed_text_layers",
"line_start": 737,
"line_end": 810,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "tuple"
}
],
"return_type": "Dict[str, Optional[Image.Image]]",
"docstring": "创建详细的分离文字图层,每个文字元素独立可编辑",
"is_async": false,
"decorators": [],
"code": " def _create_detailed_text_layers(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Dict[str, Optional[Image.Image]]:\n \"\"\"创建详细的分离文字图层,每个文字元素独立可编辑\"\"\"\n layers = {}\n \n try:\n width, height = canvas_size\n center_x = width // 2\n left_margin, right_margin = self._calculate_content_margins(content, width, center_x)\n \n # 1. 标题图层\n title_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n title_draw = ImageDraw.Draw(title_canvas)\n title_y = gradient_start + 40\n \n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)\n title_x = center_x - text_w // 2\n \n self.text_renderer.draw_text_with_outline(title_draw, (title_x, title_y), title_text, title_font, \n text_color=(255, 255, 255, 255), outline_color=(0, 30, 80, 200), outline_width=4)\n layers[\"Title Text\"] = title_canvas\n \n # 2. 副标题图层\n subtitle_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n subtitle_draw = ImageDraw.Draw(subtitle_canvas)\n \n subtitle_text = content.get(\"slogan\", \"\")\n if subtitle_text:\n subtitle_target_width = int((right_margin - left_margin) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n try:\n subtitle_font = ImageFont.truetype(self.chinese_font_path, subtitle_size)\n except:\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font)\n subtitle_x = center_x - sub_text_w // 2\n subtitle_y = title_y + text_h + 30\n \n self.text_renderer.draw_text_with_shadow(subtitle_draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font,\n text_color=(255, 255, 255, 255), shadow_color=(0, 0, 0, 180), shadow_offset=(2, 2))\n layers[\"Subtitle Text\"] = subtitle_canvas\n \n # 3. 页脚图层\n footer_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n footer_draw = ImageDraw.Draw(footer_canvas)\n \n footer_y = height - 30\n try:\n footer_font = ImageFont.truetype(self.chinese_font_path, 18)\n except:\n footer_font = self.text_renderer._load_default_font(18)\n \n if tag := content.get(\"tag\"):\n footer_draw.text((left_margin, footer_y), tag, font=footer_font, fill=(255, 255, 255))\n if pagination := content.get(\"pagination\"):\n width_pg, _ = self.text_renderer.get_text_size(pagination, footer_font)\n footer_draw.text((right_margin - width_pg, footer_y), pagination, font=footer_font, fill=(255, 255, 255))\n \n layers[\"Footer\"] = footer_canvas\n \n except Exception as e:\n logger.error(f\"创建详细文字图层失败: {e}\")\n \n r
"code_hash": "01592c7eda1481584550243c373e5877"
},
{
"name": "_draw_text_with_outline_simple",
"line_start": 812,
"line_end": 828,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "outline_width",
"type_hint": "int"
}
],
"return_type": null,
"docstring": "简单的文本描边绘制方法",
"is_async": false,
"decorators": [],
"code": " def _draw_text_with_outline_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int], \n text: str, font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255),\n outline_width: int = 2):\n \"\"\"简单的文本描边绘制方法\"\"\"\n x, y = position\n \n # 绘制描边\n for dx in range(-outline_width, outline_width + 1):\n for dy in range(-outline_width, outline_width + 1):\n if dx == 0 and dy == 0:\n continue\n draw.text((x + dx, y + dy), text, font=font, fill=outline_color)\n \n # 绘制主文本\n draw.text((x, y), text, font=font, fill=text_color)",
"code_hash": "d8fd46b12e75264ec27fadc24271c890"
},
{
"name": "_draw_text_with_shadow_simple",
"line_start": 830,
"line_end": 843,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "position",
"type_hint": "Tuple[int, int]"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "font",
"type_hint": "ImageFont.FreeTypeFont"
},
{
"name": "text_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_color",
"type_hint": "Tuple[int, int, int, int]"
},
{
"name": "shadow_offset",
"type_hint": "Tuple[int, int]"
}
],
"return_type": null,
"docstring": "简单的文本阴影绘制方法",
"is_async": false,
"decorators": [],
"code": " def _draw_text_with_shadow_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int],\n text: str, font: ImageFont.FreeTypeFont,\n text_color: Tuple[int, int, int, int] = (255, 255, 255, 255),\n shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128),\n shadow_offset: Tuple[int, int] = (2, 2)):\n \"\"\"简单的文本阴影绘制方法\"\"\"\n x, y = position\n shadow_x, shadow_y = shadow_offset\n \n # 绘制阴影\n draw.text((x + shadow_x, y + shadow_y), text, font=font, fill=shadow_color)\n \n # 绘制主文本\n draw.text((x, y), text, font=font, fill=text_color)",
"code_hash": "69090a0b530ec6a3cc583e5f123880f3"
},
{
"name": "_create_title_layer",
"line_start": 845,
"line_end": 884,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建标题图层",
"is_async": false,
"decorators": [],
"code": " def _create_title_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建标题图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n \n # 使用指定的中文字体\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n \n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n \n # 重新计算实际尺寸\n bbox = title_font.getbbox(title_text)\n text_w = bbox[2] - bbox[0]\n text_h = bbox[3] - bbox[1]\n title_x = center_x - text_w // 2\n title_y = gradient_start + 40\n \n # 绘制带描边的标题\n self._draw_text_with_outline_simple(\n draw, (title_x, title_y), title_text, title_font, \n text_color=(255, 255, 255, 255), \n outline_color=(0, 30, 80, 200), \n outline_width=4\n )\n \n return canvas\n except Exception as e:\n logger.error(f\"创建标题层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "33dc83d05170d04ea4003d6003a49498"
},
{
"name": "_create_subtitle_layer",
"line_start": 886,
"line_end": 926,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建副标题图层",
"is_async": false,
"decorators": [],
"code": " def _create_subtitle_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建副标题图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n subtitle_text = content.get(\"slogan\", \"\")\n if not subtitle_text:\n return None\n \n subtitle_target_width = int((right_margin - left_margin) * 0.95)\n subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(\n subtitle_text, subtitle_target_width, max_size=75, min_size=20\n )\n \n # 使用指定的中文字体\n try:\n subtitle_font = ImageFont.truetype(self.chinese_font_path, subtitle_size)\n except:\n subtitle_font = self.text_renderer._load_default_font(subtitle_size)\n \n bbox = subtitle_font.getbbox(subtitle_text)\n sub_text_w = bbox[2] - bbox[0]\n sub_text_h = bbox[3] - bbox[1]\n subtitle_x = center_x - sub_text_w // 2\n subtitle_y = gradient_start + 40 + 100 + 30 # title_y + title_height + spacing\n \n # 绘制带阴影的副标题\n self._draw_text_with_shadow_simple(\n draw, (subtitle_x, subtitle_y), subtitle_text, subtitle_font, \n text_color=(255, 255, 255, 255), \n shadow_color=(0, 0, 0, 180), \n shadow_offset=(2, 2)\n )\n \n return canvas\n except Exception as e:\n logger.error(f\"创建副标题层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "ae1ef6b0faff4354e2fdecbe32cf21ba"
},
{
"name": "_create_decoration_layer",
"line_start": 928,
"line_end": 964,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "gradient_start",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建装饰元素图层",
"is_async": false,
"decorators": [],
"code": " def _create_decoration_layer(self, content: Dict[str, Any], gradient_start: int, center_x: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建装饰元素图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 获取标题信息用于装饰线定位\n title_text = content.get(\"title\", \"默认标题\")\n title_target_width = int((right_margin - left_margin) * 0.98)\n title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(\n title_text, title_target_width, max_size=140, min_size=40\n )\n \n # 使用中文字体计算标题尺寸\n try:\n title_font = ImageFont.truetype(self.chinese_font_path, title_size)\n except:\n title_font = self.text_renderer._load_default_font(title_size)\n \n bbox = title_font.getbbox(title_text)\n text_w = bbox[2] - bbox[0]\n text_h = bbox[3] - bbox[1]\n title_x = center_x - text_w // 2\n title_y = gradient_start + 40\n \n # 在标题下方添加装饰线\n line_y = title_y + text_h + 5\n line_start_x = title_x - text_w * 0.025\n line_end_x = title_x + text_w * 1.025\n draw.line([(line_start_x, line_y), (line_end_x, line_y)], fill=(215, 215, 215, 80), width=3)\n \n return canvas\n except Exception as e:\n logger.error(f\"创建装饰层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "e28e698781edbdaf148659d8d548b40b"
},
{
"name": "_create_left_column_layer",
"line_start": 966,
"line_end": 1012,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建左栏内容图层",
"is_async": false,
"decorators": [],
"code": " def _create_left_column_layer(self, content: Dict[str, Any], y: int, x: int, width: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建左栏内容图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n \n # 按钮\n try:\n button_font = ImageFont.truetype(self.chinese_font_path, 30)\n except:\n button_font = self.text_renderer._load_default_font(30)\n \n button_text = content.get(\"content_button\", \"套餐内容\")\n bbox = button_font.getbbox(button_text)\n button_text_width = bbox[2] - bbox[0]\n button_width = button_text_width + 40\n button_height = 50\n \n # 绘制简单的矩形按钮\n draw.rounded_rectangle([x, y, x + button_width, y + button_height], \n radius=20, fill=(0, 140, 210, 180), \n outline=(255, 255, 255, 255), width=1)\n draw.text((x + 20, y + (button_height - 30) // 2), button_text, font=button_font, fill=(255, 255, 255))\n\n # 项目列表\n items = content.get(\"content_items\", [])\n if items:\n try:\n list_font = ImageFont.truetype(self.chinese_font_path, 28)\n except:\n list_font = self.text_renderer._load_default_font(28)\n \n list_y = y + button_height + 20\n line_spacing = 36\n \n for i, item in enumerate(items):\n item_y = list_y + i * line_spacing\n draw.text((x, item_y), \"• \" + item, font=list_font, fill=(255, 255, 255))\n \n return canvas\n except Exception as e:\n logger.error(f\"创建左栏内容层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None",
"code_hash": "993d6f77656e470e8695efd21791d24e"
},
{
"name": "_create_right_column_layer",
"line_start": 1014,
"line_end": 1092,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "x",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建右栏内容图层",
"is_async": false,
"decorators": [],
"code": " def _create_right_column_layer(self, content: Dict[str, Any], y: int, x: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建右栏内容图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n \n # 价格\n price_text = str(content.get('price', '')) # 确保价格是字符串类型\n if price_text:\n price_target_width = int((right_margin - x) * 0.7)\n price_size, price_actual_width = self._calculate_optimal_font_size_enhanced(\n price_text, price_target_width, max_size=120, min_size=40\n )\n \n try:\n price_font = ImageFont.truetype(self.chinese_font_path, price_size)\n suffix_font = ImageFont.truetype(self.chinese_font_path, int(price_size * 0.3))\n except:\n price_font = self.text_renderer._load_default_font(price_size)\n suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3))\n \n price_bbox = price_font.getbbox(price_text)\n price_height = price_bbox[3] - price_bbox[1]\n suffix_bbox = suffix_font.getbbox(\"CNY起\")\n suffix_width = suffix_bbox[2] - suffix_bbox[0]\n suffix_height = suffix_bbox[3] - suffix_bbox[1]\n \n price_x = right_margin - price_actual_width - suffix_width\n self._draw_text_with_shadow_simple(draw, (price_x, y), price_text, price_font)\n \n suffix_y = y + price_height - suffix_height\n draw.text((price_x + price_actual_width, suffix_y), \"CNY起\", font=suffix_font, fill=(255, 255, 255))\n \n # 下划线\n underline_y = y + price_height + 18\n draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2)\n \n # 票种\n ticket_text = content.get(\"ticket_type\", \"\")\n if ticket_text:\n ticket_target_width = int((right_margin - x) * 0.7)\n ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced(\n ticket_text, ticket_target_width, max_size=60, min_size=30\n )\n \n try:\n ticket_font = ImageFont.truetype(self.chinese_font_path, ticket_size)\n except:\n ticket_font = self.text_renderer._load_default_font(ticket_size)\n \n ticket_x = right_margin - ticket_actual_width\n ticket_y = y + price_height + 35\n self._draw_text_with_shadow_simple(draw, (ticket_x, ticket_y), ticket_text, ticket_font)\n \n ticket_bbox = ticket_font.getbbox(ticket_text)\n ticket_height = ticket_bbox[3] - ticket_bbox[1]\n\n # 备注\n remarks = content.get(\"remarks\", [])\n if remarks:\n try:\n remarks_font = ImageFont.truetype(self.chinese_font_path, 16)\n except:\n remarks_font = self.text_renderer._load_default_font(16)\n \n remarks_y = ticket_y + ticket_height + 30\n for i, remark in enumerate(remarks):\n remark_bbox = remarks_font.getbbox(remark)\n remark_width = remark_bbox[2] - remark_bbox[0]\n draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 2
"code_hash": "caddd95f96510da77af0e9c27cd417c1"
},
{
"name": "_create_footer_layer",
"line_start": 1094,
"line_end": 1123,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "footer_y",
"type_hint": "int"
},
{
"name": "left_margin",
"type_hint": "int"
},
{
"name": "right_margin",
"type_hint": "int"
},
{
"name": "canvas_size",
"type_hint": "Tuple[int, int]"
}
],
"return_type": "Optional[Image.Image]",
"docstring": "创建页脚图层",
"is_async": false,
"decorators": [],
"code": " def _create_footer_layer(self, content: Dict[str, Any], footer_y: int, left_margin: int, right_margin: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]:\n \"\"\"创建页脚图层\"\"\"\n try:\n canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(canvas)\n \n # 使用中文字体\n try:\n font = ImageFont.truetype(self.chinese_font_path, 18)\n except:\n font = self.text_renderer._load_default_font(18)\n \n # 标签(左下角)\n tag_text = content.get(\"tag\", \"\")\n if tag_text:\n draw.text((left_margin, footer_y), tag_text, font=font, fill=(255, 255, 255))\n \n # 分页信息(右下角)\n pagination_text = content.get(\"pagination\", \"\")\n if pagination_text:\n pagination_bbox = font.getbbox(pagination_text)\n pagination_width = pagination_bbox[2] - pagination_bbox[0]\n draw.text((right_margin - pagination_width, footer_y), pagination_text, font=font, fill=(255, 255, 255))\n \n return canvas\n except Exception as e:\n logger.error(f\"创建页脚层失败: {e}\")\n import traceback\n traceback.print_exc()\n return None ",
"code_hash": "a36e657e4dd3c792ada931823ae2bb1c"
}
],
"docstring": "活力风格模板,适用于色彩鲜艳、内容丰富的场景,如旅游、美食分享。\n特点是底部有毛玻璃效果的文案区域。",
"decorators": [],
"code": "class VibrantTemplate(BaseTemplate):\n \"\"\"\n 活力风格模板,适用于色彩鲜艳、内容丰富的场景,如旅游、美食分享。\n 特点是底部有毛玻璃效果的文案区域。\n \"\"\"\n def __init__(self, size: Tuple[int, int] = (900, 1200)):\n super().__init__(size)\n self.config = {\n 'colors': {\n 'ocean_deep': [(0, 30, 80), (20, 120, 220)],\n 'sunset_warm': [(255, 94, 77), (255, 154, 0)],\n 'cool_mint': [(64, 224, 208), (127, 255, 212)],\n 'royal_purple': [(75, 0, 130), (138, 43, 226)],\n 'forest_green': [(34, 139, 34), (144, 238, 144)],\n 'fire_red': [(220, 20, 60), (255, 69, 0)],\n \"gray_gradient\": [(128, 128, 128), (211, 211, 211)],\n \"dark_gray\": [(15, 15, 15), (30, 30, 30)]\n },\n 'glass_effect': {\n 'max_opacity': 240,\n 'blur_radius': 22,\n 'transition_height': 80,\n 'intensity_multiplier': 1.5\n },\n }\n # 设置中文字体路径\n self.chinese_font_path = \"/root/TravelContentCreator/assets/font/兰亭粗黑简.TTF\"\n \n # 重写text_renderer的字体加载方法以支持中文\n self._patch_text_renderer_for_chinese()\n\n def _patch_text_renderer_for_chinese(self):\n \"\"\"重写text_renderer的字体加载方法以支持中文\"\"\"\n original_load_font = self.text_renderer._load_default_font\n \n def load_chinese_font(size: int):\n try:\n return ImageFont.truetype(self.chinese_font_path, size)\n except:\n logger.warning(f\"无法加载中文字体,使用默认字体\")\n return original_load_font(size)\n \n # 替换字体加载方法\n self.text_renderer._load_default_font = load_chinese_font\n\n def generate(self,\n images: List,\n content: Optional[Dict[str, Any]] = None,\n theme_color: Optional[str] = None,\n glass_intensity: float = 1.5,\n num_variations: int = 1,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Vibrant风格海报\n\n Args:\n images (List): 主图\n content (Optional[Dict[str, Any]]): 包含所有文本信息的字典\n theme_color (Optional[str]): 预设颜色主题的名称\n glass_intensity (float): 毛玻璃效果强度\n num_variations (int): 生成海报数量\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n if content is None:\n content = self._get_default_content()\n\n self.config['glass_effect']['intensity_multiplier'] = glass_intensity\n\n main_image = images\n logger.info(f\"main_image的类型: {np.shape(main_image)}\")\n if not main_image:\n logger.error(f\"无法加载图片: \")\n return None\n\n main_image = self.image_processor.resize_image(image=main_image, target_size=self.size)\n\n estimated_height = self._estimate_content_height(content)\n gradient_start = self._detect_gradient_start_position(main_image, estimated_height)\n\n canvas = self._create_composite_image(main_image, gradient_start, theme_color)\n canvas = self._render_texts(canvas, content, gradient_start)\n\n final_image = canvas.resize((1350, 1800), Image.LANCZOS)\n return final_image\n\n def _get_default_content(self) -> Dict[str, Any]:\n \"\"\"获取默认的测试内容\"\"\"\n return {\n \"title\": \"正佳极地海洋世界\",\n \"slogan\": \"都说海洋馆是约会圣地!那锦峰夜场将是绝杀!\",\n \"price\": \"199\",\n \"ticket_type\": \"夜场票\",\n \"content_button\": \"套餐内容\",\n \"content_items\": [\n \"
"code_hash": "f12b51243f3f65b5b37c2601a17b91a7"
}
],
"imports": [
{
"type": "from_import",
"module": "ast",
"names": [
"List"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"logging"
],
"aliases": []
},
{
"type": "import",
"modules": [
"math"
],
"aliases": []
},
{
"type": "from_import",
"module": "typing",
"names": [
"Dict",
"Any",
"Optional",
"Tuple"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"numpy"
],
"aliases": [
"np"
]
},
{
"type": "from_import",
"module": "PIL",
"names": [
"Image",
"ImageDraw",
"ImageFont",
"ImageFilter"
],
"aliases": [],
"level": 0
},
{
"type": "from_import",
"module": "base_template",
"names": [
"BaseTemplate"
],
"aliases": [],
"level": 1
},
{
"type": "from_import",
"module": "utils",
"names": [
"ColorExtractor"
],
"aliases": [],
"level": 2
},
{
"type": "from_import",
"module": "psd_tools",
"names": [
"PSDImage"
],
"aliases": [],
"level": 0
},
{
"type": "from_import",
"module": "psd_tools.api.layers",
"names": [
"PixelLayer"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
},
{
"type": "import",
"modules": [
"traceback"
],
"aliases": []
}
],
"constants": [],
"docstring": "Vibrant风格活力风格海报模板",
"content_hash": "bed1b785ceb543dafa6e8f133cfa8d39"
}