2025-07-31 15:35:23 +08:00

1085 lines
74 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"file_path": "poster/templates/business_template.py",
"file_size": 15585,
"line_count": 349,
"functions": [
{
"name": "__init__",
"line_start": 29,
"line_end": 52,
"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 'total_parts': 4.0,\n 'transparent_ratio': 0.5,\n 'dynamic_spacing': {\n 'min_line_spacing': 8,\n 'max_line_spacing': 25,\n 'content_margin': 20,\n 'section_spacing': 35,\n 'bottom_reserve': 40\n }\n }\n self.color_themes = {\n \"modern_blue\": [(25, 52, 85), (65, 120, 180)],\n \"warm_sunset\": [(45, 25, 20), (180, 100, 60)],\n \"fresh_green\": [(15, 45, 25), (90, 140, 80)],\n \"deep_ocean\": [(20, 40, 70), (70, 140, 200)],\n \"elegant_purple\": [(35, 25, 55), (120, 90, 160)],\n \"classic_gray\": [(30, 35, 40), (120, 130, 140)],\n \"premium_gold\": [(60, 50, 30), (160, 140, 100)],\n \"tech_gradient\": [(20, 30, 50), (80, 100, 140)]\n }\n self._current_background_colors = None",
"code_hash": "322475812b4e75fe9b40b44c5e01d6fd"
},
{
"name": "generate",
"line_start": 54,
"line_end": 104,
"args": [
{
"name": "self"
},
{
"name": "top_image_path",
"type_hint": "str"
},
{
"name": "bottom_image_path",
"type_hint": "str"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "small_image_paths",
"type_hint": "Optional[List[str]]"
},
{
"name": "color_theme",
"type_hint": "Optional[str]"
}
],
"return_type": "Image.Image",
"docstring": "生成Business风格海报\n\nArgs:\n top_image_path (str): 顶部图像路径\n bottom_image_path (str): 底部图像路径\n content (Dict[str, Any]): 包含所有文本信息的字典\n small_image_paths (Optional[List[str]]): 中间小图路径列表 (最多3张)\n color_theme (Optional[str]): 预设颜色主题名称\n\nReturns:\n Image.Image: 生成的海报图像",
"is_async": false,
"decorators": [],
"code": " def generate(self,\n top_image_path: str,\n bottom_image_path: str,\n content: Dict[str, Any],\n small_image_paths: Optional[List[str]] = None,\n color_theme: Optional[str] = None,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Business风格海报\n\n Args:\n top_image_path (str): 顶部图像路径\n bottom_image_path (str): 底部图像路径\n content (Dict[str, Any]): 包含所有文本信息的字典\n small_image_paths (Optional[List[str]]): 中间小图路径列表 (最多3张)\n color_theme (Optional[str]): 预设颜色主题名称\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n top_img = self.image_processor.load_image(top_image_path)\n bottom_img = self.image_processor.load_image(bottom_image_path)\n if not top_img or not bottom_img:\n return None\n\n top_img = self.image_processor.resize_and_crop(top_img, (self.width, top_img.height))\n bottom_img = self.image_processor.resize_and_crop(bottom_img, (self.width, bottom_img.height))\n\n if color_theme and color_theme in self.color_themes:\n top_color, bottom_color = self.color_themes[color_theme]\n else:\n top_color = self._extract_dominant_color_edge(top_img)\n bottom_color = self._extract_dominant_color_edge(bottom_img)\n top_color, bottom_color = self._ensure_colors_harmony(top_color, bottom_color)\n \n self._current_background_colors = (top_color, bottom_color)\n canvas = self.create_gradient_background(top_color, bottom_color)\n\n top_img_trans = self._apply_top_transparency(top_img)\n bottom_img_trans = self._apply_bottom_transparency(bottom_img)\n\n section_heights = self._calculate_section_heights()\n canvas = self._compose_images(canvas, top_img_trans, bottom_img_trans)\n \n if small_image_paths:\n canvas = self._add_small_images(canvas, small_image_paths, section_heights)\n\n canvas = self._add_decorative_elements(canvas)\n canvas = self._render_texts(canvas, content, section_heights)\n \n return canvas",
"code_hash": "42f5f2868d6a50c919cae13902b787eb"
},
{
"name": "_calculate_section_heights",
"line_start": 106,
"line_end": 118,
"args": [
{
"name": "self"
}
],
"return_type": "Dict[str, int]",
"docstring": "计算各区域高度1:2:1的比例",
"is_async": false,
"decorators": [],
"code": " def _calculate_section_heights(self) -> Dict[str, int]:\n \"\"\"计算各区域高度1:2:1的比例\"\"\"\n total_height = self.height\n total_parts = self.config['total_parts']\n \n top_h = int(total_height / total_parts)\n middle_h = int(total_height * 2 / total_parts)\n bottom_h = total_height - top_h - middle_h\n \n return {\n 'top_height': top_h, 'middle_height': middle_h, 'bottom_height': bottom_h,\n 'middle_start': top_h, 'middle_end': top_h + middle_h\n }",
"code_hash": "2abdd7700e7ed722432ed82c10a5394d"
},
{
"name": "_apply_top_transparency",
"line_start": 120,
"line_end": 131,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "对上部图像应用从不透明到透明的渐变",
"is_async": false,
"decorators": [],
"code": " def _apply_top_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对上部图像应用从不透明到透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n \n start_y = int(h * (1 - self.config['transparent_ratio']))\n for y in range(start_y, h):\n ratio = ((y - start_y) / (h - start_y)) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)",
"code_hash": "a7fd60a4794368431e9e7859ff219782"
},
{
"name": "_apply_bottom_transparency",
"line_start": 133,
"line_end": 144,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "对下部图像应用从透明到不透明的渐变",
"is_async": false,
"decorators": [],
"code": " def _apply_bottom_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对下部图像应用从透明到不透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n\n end_y = int(h * self.config['transparent_ratio'])\n for y in range(end_y):\n ratio = (1 - y / end_y) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)",
"code_hash": "67f7bb79063786ddb1f5282bcddf58ed"
},
{
"name": "_compose_images",
"line_start": 146,
"line_end": 150,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "top_img",
"type_hint": "Image.Image"
},
{
"name": "bottom_img",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "合成背景和上下图像",
"is_async": false,
"decorators": [],
"code": " def _compose_images(self, canvas: Image.Image, top_img: Image.Image, bottom_img: Image.Image) -> Image.Image:\n \"\"\"合成背景和上下图像\"\"\"\n canvas.paste(top_img, ((self.width - top_img.width) // 2, 0), top_img)\n canvas.paste(bottom_img, ((self.width - bottom_img.width) // 2, self.height - bottom_img.height), bottom_img)\n return canvas",
"code_hash": "102240df3fd7b621825ee93fabea25e6"
},
{
"name": "_render_texts",
"line_start": 152,
"line_end": 171,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "heights",
"type_hint": "Dict[str, int]"
}
],
"return_type": "Image.Image",
"docstring": "渲染所有文本内容",
"is_async": false,
"decorators": [],
"code": " def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], heights: Dict[str, int]) -> Image.Image:\n \"\"\"渲染所有文本内容\"\"\"\n draw = ImageDraw.Draw(canvas)\n center_x = self.width // 2\n \n text_start_y, text_end_y = heights['middle_start'], heights['middle_end']\n available_height = text_end_y - text_start_y - self.config['dynamic_spacing']['content_margin'] * 2\n estimated_height = self._estimate_content_height(content, self.width)\n \n layout_params = self._calculate_dynamic_layout_params(estimated_height, available_height)\n \n current_y = text_start_y + self.config['dynamic_spacing']['content_margin']\n available_width = int(self.width * 0.9)\n margin_x = (self.width - available_width) // 2\n\n current_y = self._render_title(draw, content.get(\"name\", \"\"), current_y, center_x, self.width, layout_params)\n current_y = self._render_feature(draw, content.get(\"feature\", \"\"), current_y, center_x, available_width, layout_params)\n self._render_info_section(draw, content, current_y, margin_x, available_width, layout_params, text_end_y)\n \n return canvas",
"code_hash": "d18dca5fb0cea9e6c4ade11a20b59cb8"
},
{
"name": "_add_small_images",
"line_start": 173,
"line_end": 191,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "image_paths",
"type_hint": "List[str]"
},
{
"name": "heights",
"type_hint": "Dict[str, int]"
}
],
"return_type": "Image.Image",
"docstring": "在中间区域添加最多三张小图",
"is_async": false,
"decorators": [],
"code": " def _add_small_images(self, canvas: Image.Image, image_paths: List[str], heights: Dict[str, int]) -> Image.Image:\n \"\"\"在中间区域添加最多三张小图\"\"\"\n if not image_paths: return canvas\n \n num_images = min(len(image_paths), 3)\n padding = 10\n total_img_width = int(self.width * 0.8)\n img_width = (total_img_width - (num_images - 1) * padding) // num_images\n img_height = int(img_width * 0.75)\n \n start_x = (self.width - total_img_width) // 2\n y_pos = heights['middle_start'] + (heights['middle_height'] - img_height) // 2\n\n for i, path in enumerate(image_paths[:num_images]):\n if img := self.image_processor.load_image(path):\n img = self.image_processor.resize_and_crop(img, (img_width, img_height))\n x_pos = start_x + i * (img_width + padding)\n canvas.paste(img, (x_pos, y_pos))\n return canvas",
"code_hash": "5b26f5936ce38d97154b52b35ca8212b"
},
{
"name": "_add_decorative_elements",
"line_start": 193,
"line_end": 209,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "添加装饰性元素增强设计感",
"is_async": false,
"decorators": [],
"code": " def _add_decorative_elements(self, canvas: Image.Image) -> Image.Image:\n \"\"\"添加装饰性元素增强设计感\"\"\"\n overlay = Image.new('RGBA', self.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(overlay)\n \n # 顶部装饰线条\n line_y = self.height // 4 - 20\n for i in range(w := self.width // 3):\n alpha = int(255 * (1 - abs(i - w//2) / (w//2)) * 0.3)\n draw.line([((self.width-w)//2 + i, line_y), ((self.width-w)//2 + i, line_y + 2)], fill=(255,255,255,alpha), width=1)\n\n # 角落装饰\n s, a = 80, 40\n draw.arc([20, 20, 20+s, 20+s], 180, 270, fill=(255,255,255,a), width=3)\n draw.arc([self.width-s-20, self.height-s-20, self.width-20, self.height-20], 0, 90, fill=(255,255,255,a), width=3)\n\n return Image.alpha_composite(canvas, overlay)",
"code_hash": "521c31157d9c9e066a73afdeec3e2d2c"
},
{
"name": "_estimate_content_height",
"line_start": 211,
"line_end": 218,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "width",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "预估内容总高度",
"is_async": false,
"decorators": [],
"code": " def _estimate_content_height(self, content: Dict[str, Any], width: int) -> int:\n \"\"\"预估内容总高度\"\"\"\n spacing = self.config['dynamic_spacing']['section_spacing']\n title_h = int(self.text_renderer.load_font(60).getbbox(\"T\")[3] * 1.2)\n feature_h = int(self.text_renderer.load_font(30).getbbox(\"T\")[3] * 1.5) + 50\n info_h = len(content.get(\"info_list\", [])) * 35\n price_h = 80\n return 20 + title_h + spacing + feature_h + spacing + info_h + price_h + self.config['dynamic_spacing']['bottom_reserve']",
"code_hash": "2592787b26f2d175a36ac1c30366e2a3"
},
{
"name": "_calculate_dynamic_layout_params",
"line_start": 220,
"line_end": 228,
"args": [
{
"name": "self"
},
{
"name": "estimated_height",
"type_hint": "int"
},
{
"name": "available_height",
"type_hint": "int"
}
],
"return_type": "Dict[str, Any]",
"docstring": "根据空间利用率计算动态布局参数",
"is_async": false,
"decorators": [],
"code": " def _calculate_dynamic_layout_params(self, estimated_height: int, available_height: int) -> Dict[str, Any]:\n \"\"\"根据空间利用率计算动态布局参数\"\"\"\n ratio = estimated_height / available_height if available_height > 0 else 1.0\n factor = 1.2 if ratio <= 0.8 else (0.8 if ratio > 1.0 else 1.0)\n spacing = self.config['dynamic_spacing']\n return {\n 'line_spacing': int(spacing['min_line_spacing'] + (spacing['max_line_spacing'] - spacing['min_line_spacing']) * factor),\n 'section_spacing': int(spacing['section_spacing'] * factor)\n }",
"code_hash": "ae6e5d22b2c4e482778efca5e0aa498e"
},
{
"name": "_render_title",
"line_start": 230,
"line_end": 236,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "params",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "渲染主标题",
"is_async": false,
"decorators": [],
"code": " def _render_title(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染主标题\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.85), max_size=80)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n self.text_renderer.draw_text_with_outline(draw, (center_x - text_w // 2, y), text, font, text_color=(255, 240, 200), outline_width=2)\n return y + text_h + params['section_spacing']",
"code_hash": "75d772c4eff10fad822e67bd3afd4c4b"
},
{
"name": "_render_feature",
"line_start": 238,
"line_end": 251,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "params",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "渲染特色描述及其背景",
"is_async": false,
"decorators": [],
"code": " def _render_feature(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染特色描述及其背景\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.8), max_size=40)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n \n bg_h = 50\n bg_w = min(text_w + 30, width)\n bg_x = center_x - bg_w // 2\n \n self.text_renderer.draw_rounded_rectangle(draw, (bg_x, y-5), (bg_w, bg_h), 20, fill=(50, 50, 50, 180))\n color = self._get_smart_feature_color()\n draw.text((center_x - text_w // 2, y), text, font=font, fill=color)\n return y + bg_h + params['section_spacing']",
"code_hash": "3c3f9cde607593040b5e3ea15ede89a5"
},
{
"name": "_render_info_section",
"line_start": 253,
"line_end": 275,
"args": [
{
"name": "self"
},
{
"name": "draw"
},
{
"name": "content"
},
{
"name": "y"
},
{
"name": "x"
},
{
"name": "width"
},
{
"name": "params"
},
{
"name": "max_y"
}
],
"return_type": null,
"docstring": "渲染信息列表和价格区域",
"is_async": false,
"decorators": [],
"code": " def _render_info_section(self, draw, content, y, x, width, params, max_y):\n \"\"\"渲染信息列表和价格区域\"\"\"\n info_texts = content.get(\"info_list\", [])\n if not info_texts: return y\n\n # 为信息列表找到合适的字体\n longest_info_line = max([f\"{item.get('title', '')}: {item.get('desc', '')}\" for item in info_texts], key=len) if info_texts else \"\"\n info_size, _ = self.text_renderer.calculate_font_size_and_width(longest_info_line, int(width*0.55), max_size=30)\n info_font = self.text_renderer.load_font(info_size)\n _, line_h = self.text_renderer.get_text_size(\"T\", info_font)\n\n # 为价格找到合适的字体\n price_text = f\"¥{content.get('price', '')}\"\n price_size, price_w = self.text_renderer.calculate_font_size_and_width(price_text, int(width*0.35), max_size=200, min_size=60)\n price_font = self.text_renderer.load_font(price_size)\n\n # 渲染信息列表\n for i, info in enumerate(info_texts):\n info_str = f\"{info.get('title', '')}: {info.get('desc', '')}\" if isinstance(info, dict) else str(info)\n draw.text((x, y + i * (line_h + params['line_spacing'])), info_str, font=info_font, fill=(255, 255, 255))\n \n # 渲染价格\n self.text_renderer.draw_text_with_shadow(draw, (x + width - price_w, y), price_text, price_font)",
"code_hash": "7f845fa572fcc19f7375d625fdf1f70c"
},
{
"name": "_extract_dominant_color_edge",
"line_start": 278,
"line_end": 296,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "从图像边缘提取主要颜色",
"is_async": false,
"decorators": [],
"code": " def _extract_dominant_color_edge(self, image: Image.Image) -> Tuple[int, int, int]:\n \"\"\"从图像边缘提取主要颜色\"\"\"\n if image.mode != 'RGB': image = image.convert('RGB')\n w, h = image.size\n pixels = []\n edge_w = min(w, h) // 4\n \n points = [(x,y) for y in range(h) for x in range(w) if x<edge_w or x>w-edge_w or y<edge_w or y>h-edge_w]\n sample_points = random.sample(points, min(len(points), 200))\n \n for p in sample_points:\n pixel = image.getpixel(p)\n if sum(pixel) > 50 and sum(pixel) < 700:\n pixels.append(pixel)\n \n if not pixels: return (80, 120, 160)\n \n best_color = self._select_best_color(Counter(pixels).most_common(5))\n return self._adjust_color_for_background(best_color)",
"code_hash": "276f39dc81fd9020ddd0dfaccfc398ac"
},
{
"name": "_select_best_color",
"line_start": 298,
"line_end": 312,
"args": [
{
"name": "self"
},
{
"name": "candidates",
"type_hint": "List[Tuple[Tuple[int, int, int], int]]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "从候选颜色中选择最合适的",
"is_async": false,
"decorators": [],
"code": " def _select_best_color(self, candidates: List[Tuple[Tuple[int, int, int], int]]) -> Tuple[int, int, int]:\n \"\"\"从候选颜色中选择最合适的\"\"\"\n best_score, best_color = -1, None\n for color, count in candidates:\n r,g,b = color\n brightness = (r*299 + g*587 + b*114) / 1000\n saturation = (max(r,g,b) - min(r,g,b)) / max(r,g,b) if max(r,g,b)>0 else 0\n \n b_score = 1.0 - abs((brightness - 130) / 130)\n s_score = 1.0 - abs(saturation - 0.5) / 0.5\n score = b_score * 0.4 + s_score * 0.6\n \n if score > best_score:\n best_score, best_color = score, color\n return best_color or candidates[0][0]",
"code_hash": "c6f8d99a56b8fdac123e439044e74db0"
},
{
"name": "_adjust_color_for_background",
"line_start": 314,
"line_end": 321,
"args": [
{
"name": "self"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "调整颜色使其更适合作为背景",
"is_async": false,
"decorators": [],
"code": " def _adjust_color_for_background(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"调整颜色使其更适合作为背景\"\"\"\n r, g, b = color\n h, s, v = colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0)\n v = max(0.3, min(0.7, v)) # 限制亮度\n s = max(0.4, min(0.8, s)) # 限制饱和度\n r,g,b = [int(c*255) for c in colorsys.hsv_to_rgb(h,s,v)]\n return (r, g, b)",
"code_hash": "c52004e292c6e22dd63bc8c2a8bf2598"
},
{
"name": "_ensure_colors_harmony",
"line_start": 323,
"line_end": 333,
"args": [
{
"name": "self"
},
{
"name": "top",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "bottom",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[Tuple[int, int, int], Tuple[int, int, int]]",
"docstring": "确保颜色和谐",
"is_async": false,
"decorators": [],
"code": " def _ensure_colors_harmony(self, top: Tuple[int, int, int], bottom: Tuple[int, int, int]) -> Tuple[Tuple[int, int, int], Tuple[int, int, int]]:\n \"\"\"确保颜色和谐\"\"\"\n dist = sum(abs(a-b) for a,b in zip(top,bottom))\n if dist < 30:\n factor = 1.1 if sum(top)/3 > 128 else 0.9\n top = tuple(max(0,min(255,int(c*factor))) for c in top)\n elif dist > 150:\n mid = tuple((a+b)//2 for a,b in zip(top,bottom))\n top = tuple(int(c*0.8+m*0.2) for c,m in zip(top,mid))\n bottom = tuple(int(c*0.8+m*0.2) for c,m in zip(bottom,mid))\n return top, bottom",
"code_hash": "02784e554bf3893db1397d3f052b4bc0"
},
{
"name": "_get_smart_feature_color",
"line_start": 335,
"line_end": 350,
"args": [
{
"name": "self"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "为feature文本智能选择颜色",
"is_async": false,
"decorators": [],
"code": " def _get_smart_feature_color(self) -> Tuple[int, int, int]:\n \"\"\"为feature文本智能选择颜色\"\"\"\n if not self._current_background_colors: return (255, 255, 255)\n top, bottom = self._current_background_colors\n avg_bright = (sum(top)+sum(bottom))/6\n \n def get_tone(c):\n r,g,b=c\n return 'w' if r > g and r > b else ('f' if g > r and g > b else 'c')\n \n tone = get_tone(tuple((a+b)//2 for a,b in zip(top,bottom)))\n \n if avg_bright > 120:\n return {'c':(65,105,155), 'w':(155,85,65)}.get(tone, (85,125,85))\n else:\n return {'c':(135,185,235), 'w':(255,195,135)}.get(tone, (155,215,155)) ",
"code_hash": "82d5c3e7b9ca6e1245378fc1bb425c0b"
},
{
"name": "get_tone",
"line_start": 341,
"line_end": 343,
"args": [
{
"name": "c"
}
],
"return_type": null,
"docstring": "",
"is_async": false,
"decorators": [],
"code": " def get_tone(c):\n r,g,b=c\n return 'w' if r > g and r > b else ('f' if g > r and g > b else 'c')",
"code_hash": "ebc9c8b08b38d2c55f1df5632a124ce4"
}
],
"classes": [
{
"name": "BusinessTemplate",
"line_start": 23,
"line_end": 350,
"bases": [
"BaseTemplate"
],
"methods": [
{
"name": "__init__",
"line_start": 29,
"line_end": 52,
"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 'total_parts': 4.0,\n 'transparent_ratio': 0.5,\n 'dynamic_spacing': {\n 'min_line_spacing': 8,\n 'max_line_spacing': 25,\n 'content_margin': 20,\n 'section_spacing': 35,\n 'bottom_reserve': 40\n }\n }\n self.color_themes = {\n \"modern_blue\": [(25, 52, 85), (65, 120, 180)],\n \"warm_sunset\": [(45, 25, 20), (180, 100, 60)],\n \"fresh_green\": [(15, 45, 25), (90, 140, 80)],\n \"deep_ocean\": [(20, 40, 70), (70, 140, 200)],\n \"elegant_purple\": [(35, 25, 55), (120, 90, 160)],\n \"classic_gray\": [(30, 35, 40), (120, 130, 140)],\n \"premium_gold\": [(60, 50, 30), (160, 140, 100)],\n \"tech_gradient\": [(20, 30, 50), (80, 100, 140)]\n }\n self._current_background_colors = None",
"code_hash": "322475812b4e75fe9b40b44c5e01d6fd"
},
{
"name": "generate",
"line_start": 54,
"line_end": 104,
"args": [
{
"name": "self"
},
{
"name": "top_image_path",
"type_hint": "str"
},
{
"name": "bottom_image_path",
"type_hint": "str"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "small_image_paths",
"type_hint": "Optional[List[str]]"
},
{
"name": "color_theme",
"type_hint": "Optional[str]"
}
],
"return_type": "Image.Image",
"docstring": "生成Business风格海报\n\nArgs:\n top_image_path (str): 顶部图像路径\n bottom_image_path (str): 底部图像路径\n content (Dict[str, Any]): 包含所有文本信息的字典\n small_image_paths (Optional[List[str]]): 中间小图路径列表 (最多3张)\n color_theme (Optional[str]): 预设颜色主题名称\n\nReturns:\n Image.Image: 生成的海报图像",
"is_async": false,
"decorators": [],
"code": " def generate(self,\n top_image_path: str,\n bottom_image_path: str,\n content: Dict[str, Any],\n small_image_paths: Optional[List[str]] = None,\n color_theme: Optional[str] = None,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Business风格海报\n\n Args:\n top_image_path (str): 顶部图像路径\n bottom_image_path (str): 底部图像路径\n content (Dict[str, Any]): 包含所有文本信息的字典\n small_image_paths (Optional[List[str]]): 中间小图路径列表 (最多3张)\n color_theme (Optional[str]): 预设颜色主题名称\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n top_img = self.image_processor.load_image(top_image_path)\n bottom_img = self.image_processor.load_image(bottom_image_path)\n if not top_img or not bottom_img:\n return None\n\n top_img = self.image_processor.resize_and_crop(top_img, (self.width, top_img.height))\n bottom_img = self.image_processor.resize_and_crop(bottom_img, (self.width, bottom_img.height))\n\n if color_theme and color_theme in self.color_themes:\n top_color, bottom_color = self.color_themes[color_theme]\n else:\n top_color = self._extract_dominant_color_edge(top_img)\n bottom_color = self._extract_dominant_color_edge(bottom_img)\n top_color, bottom_color = self._ensure_colors_harmony(top_color, bottom_color)\n \n self._current_background_colors = (top_color, bottom_color)\n canvas = self.create_gradient_background(top_color, bottom_color)\n\n top_img_trans = self._apply_top_transparency(top_img)\n bottom_img_trans = self._apply_bottom_transparency(bottom_img)\n\n section_heights = self._calculate_section_heights()\n canvas = self._compose_images(canvas, top_img_trans, bottom_img_trans)\n \n if small_image_paths:\n canvas = self._add_small_images(canvas, small_image_paths, section_heights)\n\n canvas = self._add_decorative_elements(canvas)\n canvas = self._render_texts(canvas, content, section_heights)\n \n return canvas",
"code_hash": "42f5f2868d6a50c919cae13902b787eb"
},
{
"name": "_calculate_section_heights",
"line_start": 106,
"line_end": 118,
"args": [
{
"name": "self"
}
],
"return_type": "Dict[str, int]",
"docstring": "计算各区域高度1:2:1的比例",
"is_async": false,
"decorators": [],
"code": " def _calculate_section_heights(self) -> Dict[str, int]:\n \"\"\"计算各区域高度1:2:1的比例\"\"\"\n total_height = self.height\n total_parts = self.config['total_parts']\n \n top_h = int(total_height / total_parts)\n middle_h = int(total_height * 2 / total_parts)\n bottom_h = total_height - top_h - middle_h\n \n return {\n 'top_height': top_h, 'middle_height': middle_h, 'bottom_height': bottom_h,\n 'middle_start': top_h, 'middle_end': top_h + middle_h\n }",
"code_hash": "2abdd7700e7ed722432ed82c10a5394d"
},
{
"name": "_apply_top_transparency",
"line_start": 120,
"line_end": 131,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "对上部图像应用从不透明到透明的渐变",
"is_async": false,
"decorators": [],
"code": " def _apply_top_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对上部图像应用从不透明到透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n \n start_y = int(h * (1 - self.config['transparent_ratio']))\n for y in range(start_y, h):\n ratio = ((y - start_y) / (h - start_y)) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)",
"code_hash": "a7fd60a4794368431e9e7859ff219782"
},
{
"name": "_apply_bottom_transparency",
"line_start": 133,
"line_end": 144,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "对下部图像应用从透明到不透明的渐变",
"is_async": false,
"decorators": [],
"code": " def _apply_bottom_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对下部图像应用从透明到不透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n\n end_y = int(h * self.config['transparent_ratio'])\n for y in range(end_y):\n ratio = (1 - y / end_y) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)",
"code_hash": "67f7bb79063786ddb1f5282bcddf58ed"
},
{
"name": "_compose_images",
"line_start": 146,
"line_end": 150,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "top_img",
"type_hint": "Image.Image"
},
{
"name": "bottom_img",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "合成背景和上下图像",
"is_async": false,
"decorators": [],
"code": " def _compose_images(self, canvas: Image.Image, top_img: Image.Image, bottom_img: Image.Image) -> Image.Image:\n \"\"\"合成背景和上下图像\"\"\"\n canvas.paste(top_img, ((self.width - top_img.width) // 2, 0), top_img)\n canvas.paste(bottom_img, ((self.width - bottom_img.width) // 2, self.height - bottom_img.height), bottom_img)\n return canvas",
"code_hash": "102240df3fd7b621825ee93fabea25e6"
},
{
"name": "_render_texts",
"line_start": 152,
"line_end": 171,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "heights",
"type_hint": "Dict[str, int]"
}
],
"return_type": "Image.Image",
"docstring": "渲染所有文本内容",
"is_async": false,
"decorators": [],
"code": " def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], heights: Dict[str, int]) -> Image.Image:\n \"\"\"渲染所有文本内容\"\"\"\n draw = ImageDraw.Draw(canvas)\n center_x = self.width // 2\n \n text_start_y, text_end_y = heights['middle_start'], heights['middle_end']\n available_height = text_end_y - text_start_y - self.config['dynamic_spacing']['content_margin'] * 2\n estimated_height = self._estimate_content_height(content, self.width)\n \n layout_params = self._calculate_dynamic_layout_params(estimated_height, available_height)\n \n current_y = text_start_y + self.config['dynamic_spacing']['content_margin']\n available_width = int(self.width * 0.9)\n margin_x = (self.width - available_width) // 2\n\n current_y = self._render_title(draw, content.get(\"name\", \"\"), current_y, center_x, self.width, layout_params)\n current_y = self._render_feature(draw, content.get(\"feature\", \"\"), current_y, center_x, available_width, layout_params)\n self._render_info_section(draw, content, current_y, margin_x, available_width, layout_params, text_end_y)\n \n return canvas",
"code_hash": "d18dca5fb0cea9e6c4ade11a20b59cb8"
},
{
"name": "_add_small_images",
"line_start": 173,
"line_end": 191,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
},
{
"name": "image_paths",
"type_hint": "List[str]"
},
{
"name": "heights",
"type_hint": "Dict[str, int]"
}
],
"return_type": "Image.Image",
"docstring": "在中间区域添加最多三张小图",
"is_async": false,
"decorators": [],
"code": " def _add_small_images(self, canvas: Image.Image, image_paths: List[str], heights: Dict[str, int]) -> Image.Image:\n \"\"\"在中间区域添加最多三张小图\"\"\"\n if not image_paths: return canvas\n \n num_images = min(len(image_paths), 3)\n padding = 10\n total_img_width = int(self.width * 0.8)\n img_width = (total_img_width - (num_images - 1) * padding) // num_images\n img_height = int(img_width * 0.75)\n \n start_x = (self.width - total_img_width) // 2\n y_pos = heights['middle_start'] + (heights['middle_height'] - img_height) // 2\n\n for i, path in enumerate(image_paths[:num_images]):\n if img := self.image_processor.load_image(path):\n img = self.image_processor.resize_and_crop(img, (img_width, img_height))\n x_pos = start_x + i * (img_width + padding)\n canvas.paste(img, (x_pos, y_pos))\n return canvas",
"code_hash": "5b26f5936ce38d97154b52b35ca8212b"
},
{
"name": "_add_decorative_elements",
"line_start": 193,
"line_end": 209,
"args": [
{
"name": "self"
},
{
"name": "canvas",
"type_hint": "Image.Image"
}
],
"return_type": "Image.Image",
"docstring": "添加装饰性元素增强设计感",
"is_async": false,
"decorators": [],
"code": " def _add_decorative_elements(self, canvas: Image.Image) -> Image.Image:\n \"\"\"添加装饰性元素增强设计感\"\"\"\n overlay = Image.new('RGBA', self.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(overlay)\n \n # 顶部装饰线条\n line_y = self.height // 4 - 20\n for i in range(w := self.width // 3):\n alpha = int(255 * (1 - abs(i - w//2) / (w//2)) * 0.3)\n draw.line([((self.width-w)//2 + i, line_y), ((self.width-w)//2 + i, line_y + 2)], fill=(255,255,255,alpha), width=1)\n\n # 角落装饰\n s, a = 80, 40\n draw.arc([20, 20, 20+s, 20+s], 180, 270, fill=(255,255,255,a), width=3)\n draw.arc([self.width-s-20, self.height-s-20, self.width-20, self.height-20], 0, 90, fill=(255,255,255,a), width=3)\n\n return Image.alpha_composite(canvas, overlay)",
"code_hash": "521c31157d9c9e066a73afdeec3e2d2c"
},
{
"name": "_estimate_content_height",
"line_start": 211,
"line_end": 218,
"args": [
{
"name": "self"
},
{
"name": "content",
"type_hint": "Dict[str, Any]"
},
{
"name": "width",
"type_hint": "int"
}
],
"return_type": "int",
"docstring": "预估内容总高度",
"is_async": false,
"decorators": [],
"code": " def _estimate_content_height(self, content: Dict[str, Any], width: int) -> int:\n \"\"\"预估内容总高度\"\"\"\n spacing = self.config['dynamic_spacing']['section_spacing']\n title_h = int(self.text_renderer.load_font(60).getbbox(\"T\")[3] * 1.2)\n feature_h = int(self.text_renderer.load_font(30).getbbox(\"T\")[3] * 1.5) + 50\n info_h = len(content.get(\"info_list\", [])) * 35\n price_h = 80\n return 20 + title_h + spacing + feature_h + spacing + info_h + price_h + self.config['dynamic_spacing']['bottom_reserve']",
"code_hash": "2592787b26f2d175a36ac1c30366e2a3"
},
{
"name": "_calculate_dynamic_layout_params",
"line_start": 220,
"line_end": 228,
"args": [
{
"name": "self"
},
{
"name": "estimated_height",
"type_hint": "int"
},
{
"name": "available_height",
"type_hint": "int"
}
],
"return_type": "Dict[str, Any]",
"docstring": "根据空间利用率计算动态布局参数",
"is_async": false,
"decorators": [],
"code": " def _calculate_dynamic_layout_params(self, estimated_height: int, available_height: int) -> Dict[str, Any]:\n \"\"\"根据空间利用率计算动态布局参数\"\"\"\n ratio = estimated_height / available_height if available_height > 0 else 1.0\n factor = 1.2 if ratio <= 0.8 else (0.8 if ratio > 1.0 else 1.0)\n spacing = self.config['dynamic_spacing']\n return {\n 'line_spacing': int(spacing['min_line_spacing'] + (spacing['max_line_spacing'] - spacing['min_line_spacing']) * factor),\n 'section_spacing': int(spacing['section_spacing'] * factor)\n }",
"code_hash": "ae6e5d22b2c4e482778efca5e0aa498e"
},
{
"name": "_render_title",
"line_start": 230,
"line_end": 236,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "params",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "渲染主标题",
"is_async": false,
"decorators": [],
"code": " def _render_title(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染主标题\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.85), max_size=80)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n self.text_renderer.draw_text_with_outline(draw, (center_x - text_w // 2, y), text, font, text_color=(255, 240, 200), outline_width=2)\n return y + text_h + params['section_spacing']",
"code_hash": "75d772c4eff10fad822e67bd3afd4c4b"
},
{
"name": "_render_feature",
"line_start": 238,
"line_end": 251,
"args": [
{
"name": "self"
},
{
"name": "draw",
"type_hint": "ImageDraw.Draw"
},
{
"name": "text",
"type_hint": "str"
},
{
"name": "y",
"type_hint": "int"
},
{
"name": "center_x",
"type_hint": "int"
},
{
"name": "width",
"type_hint": "int"
},
{
"name": "params",
"type_hint": "Dict[str, Any]"
}
],
"return_type": "int",
"docstring": "渲染特色描述及其背景",
"is_async": false,
"decorators": [],
"code": " def _render_feature(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染特色描述及其背景\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.8), max_size=40)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n \n bg_h = 50\n bg_w = min(text_w + 30, width)\n bg_x = center_x - bg_w // 2\n \n self.text_renderer.draw_rounded_rectangle(draw, (bg_x, y-5), (bg_w, bg_h), 20, fill=(50, 50, 50, 180))\n color = self._get_smart_feature_color()\n draw.text((center_x - text_w // 2, y), text, font=font, fill=color)\n return y + bg_h + params['section_spacing']",
"code_hash": "3c3f9cde607593040b5e3ea15ede89a5"
},
{
"name": "_render_info_section",
"line_start": 253,
"line_end": 275,
"args": [
{
"name": "self"
},
{
"name": "draw"
},
{
"name": "content"
},
{
"name": "y"
},
{
"name": "x"
},
{
"name": "width"
},
{
"name": "params"
},
{
"name": "max_y"
}
],
"return_type": null,
"docstring": "渲染信息列表和价格区域",
"is_async": false,
"decorators": [],
"code": " def _render_info_section(self, draw, content, y, x, width, params, max_y):\n \"\"\"渲染信息列表和价格区域\"\"\"\n info_texts = content.get(\"info_list\", [])\n if not info_texts: return y\n\n # 为信息列表找到合适的字体\n longest_info_line = max([f\"{item.get('title', '')}: {item.get('desc', '')}\" for item in info_texts], key=len) if info_texts else \"\"\n info_size, _ = self.text_renderer.calculate_font_size_and_width(longest_info_line, int(width*0.55), max_size=30)\n info_font = self.text_renderer.load_font(info_size)\n _, line_h = self.text_renderer.get_text_size(\"T\", info_font)\n\n # 为价格找到合适的字体\n price_text = f\"¥{content.get('price', '')}\"\n price_size, price_w = self.text_renderer.calculate_font_size_and_width(price_text, int(width*0.35), max_size=200, min_size=60)\n price_font = self.text_renderer.load_font(price_size)\n\n # 渲染信息列表\n for i, info in enumerate(info_texts):\n info_str = f\"{info.get('title', '')}: {info.get('desc', '')}\" if isinstance(info, dict) else str(info)\n draw.text((x, y + i * (line_h + params['line_spacing'])), info_str, font=info_font, fill=(255, 255, 255))\n \n # 渲染价格\n self.text_renderer.draw_text_with_shadow(draw, (x + width - price_w, y), price_text, price_font)",
"code_hash": "7f845fa572fcc19f7375d625fdf1f70c"
},
{
"name": "_extract_dominant_color_edge",
"line_start": 278,
"line_end": 296,
"args": [
{
"name": "self"
},
{
"name": "image",
"type_hint": "Image.Image"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "从图像边缘提取主要颜色",
"is_async": false,
"decorators": [],
"code": " def _extract_dominant_color_edge(self, image: Image.Image) -> Tuple[int, int, int]:\n \"\"\"从图像边缘提取主要颜色\"\"\"\n if image.mode != 'RGB': image = image.convert('RGB')\n w, h = image.size\n pixels = []\n edge_w = min(w, h) // 4\n \n points = [(x,y) for y in range(h) for x in range(w) if x<edge_w or x>w-edge_w or y<edge_w or y>h-edge_w]\n sample_points = random.sample(points, min(len(points), 200))\n \n for p in sample_points:\n pixel = image.getpixel(p)\n if sum(pixel) > 50 and sum(pixel) < 700:\n pixels.append(pixel)\n \n if not pixels: return (80, 120, 160)\n \n best_color = self._select_best_color(Counter(pixels).most_common(5))\n return self._adjust_color_for_background(best_color)",
"code_hash": "276f39dc81fd9020ddd0dfaccfc398ac"
},
{
"name": "_select_best_color",
"line_start": 298,
"line_end": 312,
"args": [
{
"name": "self"
},
{
"name": "candidates",
"type_hint": "List[Tuple[Tuple[int, int, int], int]]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "从候选颜色中选择最合适的",
"is_async": false,
"decorators": [],
"code": " def _select_best_color(self, candidates: List[Tuple[Tuple[int, int, int], int]]) -> Tuple[int, int, int]:\n \"\"\"从候选颜色中选择最合适的\"\"\"\n best_score, best_color = -1, None\n for color, count in candidates:\n r,g,b = color\n brightness = (r*299 + g*587 + b*114) / 1000\n saturation = (max(r,g,b) - min(r,g,b)) / max(r,g,b) if max(r,g,b)>0 else 0\n \n b_score = 1.0 - abs((brightness - 130) / 130)\n s_score = 1.0 - abs(saturation - 0.5) / 0.5\n score = b_score * 0.4 + s_score * 0.6\n \n if score > best_score:\n best_score, best_color = score, color\n return best_color or candidates[0][0]",
"code_hash": "c6f8d99a56b8fdac123e439044e74db0"
},
{
"name": "_adjust_color_for_background",
"line_start": 314,
"line_end": 321,
"args": [
{
"name": "self"
},
{
"name": "color",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "调整颜色使其更适合作为背景",
"is_async": false,
"decorators": [],
"code": " def _adjust_color_for_background(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"调整颜色使其更适合作为背景\"\"\"\n r, g, b = color\n h, s, v = colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0)\n v = max(0.3, min(0.7, v)) # 限制亮度\n s = max(0.4, min(0.8, s)) # 限制饱和度\n r,g,b = [int(c*255) for c in colorsys.hsv_to_rgb(h,s,v)]\n return (r, g, b)",
"code_hash": "c52004e292c6e22dd63bc8c2a8bf2598"
},
{
"name": "_ensure_colors_harmony",
"line_start": 323,
"line_end": 333,
"args": [
{
"name": "self"
},
{
"name": "top",
"type_hint": "Tuple[int, int, int]"
},
{
"name": "bottom",
"type_hint": "Tuple[int, int, int]"
}
],
"return_type": "Tuple[Tuple[int, int, int], Tuple[int, int, int]]",
"docstring": "确保颜色和谐",
"is_async": false,
"decorators": [],
"code": " def _ensure_colors_harmony(self, top: Tuple[int, int, int], bottom: Tuple[int, int, int]) -> Tuple[Tuple[int, int, int], Tuple[int, int, int]]:\n \"\"\"确保颜色和谐\"\"\"\n dist = sum(abs(a-b) for a,b in zip(top,bottom))\n if dist < 30:\n factor = 1.1 if sum(top)/3 > 128 else 0.9\n top = tuple(max(0,min(255,int(c*factor))) for c in top)\n elif dist > 150:\n mid = tuple((a+b)//2 for a,b in zip(top,bottom))\n top = tuple(int(c*0.8+m*0.2) for c,m in zip(top,mid))\n bottom = tuple(int(c*0.8+m*0.2) for c,m in zip(bottom,mid))\n return top, bottom",
"code_hash": "02784e554bf3893db1397d3f052b4bc0"
},
{
"name": "_get_smart_feature_color",
"line_start": 335,
"line_end": 350,
"args": [
{
"name": "self"
}
],
"return_type": "Tuple[int, int, int]",
"docstring": "为feature文本智能选择颜色",
"is_async": false,
"decorators": [],
"code": " def _get_smart_feature_color(self) -> Tuple[int, int, int]:\n \"\"\"为feature文本智能选择颜色\"\"\"\n if not self._current_background_colors: return (255, 255, 255)\n top, bottom = self._current_background_colors\n avg_bright = (sum(top)+sum(bottom))/6\n \n def get_tone(c):\n r,g,b=c\n return 'w' if r > g and r > b else ('f' if g > r and g > b else 'c')\n \n tone = get_tone(tuple((a+b)//2 for a,b in zip(top,bottom)))\n \n if avg_bright > 120:\n return {'c':(65,105,155), 'w':(155,85,65)}.get(tone, (85,125,85))\n else:\n return {'c':(135,185,235), 'w':(255,195,135)}.get(tone, (155,215,155)) ",
"code_hash": "82d5c3e7b9ca6e1245378fc1bb425c0b"
}
],
"docstring": "商务风格模板,适用于酒店、房地产、高端服务等场景。\n特点是上下图片融合中间为信息展示区整体风格沉稳、专业。",
"decorators": [],
"code": "class BusinessTemplate(BaseTemplate):\n \"\"\"\n 商务风格模板,适用于酒店、房地产、高端服务等场景。\n 特点是上下图片融合,中间为信息展示区,整体风格沉稳、专业。\n \"\"\"\n\n def __init__(self, size: Tuple[int, int] = (900, 1200)):\n super().__init__(size)\n self.config = {\n 'total_parts': 4.0,\n 'transparent_ratio': 0.5,\n 'dynamic_spacing': {\n 'min_line_spacing': 8,\n 'max_line_spacing': 25,\n 'content_margin': 20,\n 'section_spacing': 35,\n 'bottom_reserve': 40\n }\n }\n self.color_themes = {\n \"modern_blue\": [(25, 52, 85), (65, 120, 180)],\n \"warm_sunset\": [(45, 25, 20), (180, 100, 60)],\n \"fresh_green\": [(15, 45, 25), (90, 140, 80)],\n \"deep_ocean\": [(20, 40, 70), (70, 140, 200)],\n \"elegant_purple\": [(35, 25, 55), (120, 90, 160)],\n \"classic_gray\": [(30, 35, 40), (120, 130, 140)],\n \"premium_gold\": [(60, 50, 30), (160, 140, 100)],\n \"tech_gradient\": [(20, 30, 50), (80, 100, 140)]\n }\n self._current_background_colors = None\n\n def generate(self,\n top_image_path: str,\n bottom_image_path: str,\n content: Dict[str, Any],\n small_image_paths: Optional[List[str]] = None,\n color_theme: Optional[str] = None,\n **kwargs) -> Image.Image:\n \"\"\"\n 生成Business风格海报\n\n Args:\n top_image_path (str): 顶部图像路径\n bottom_image_path (str): 底部图像路径\n content (Dict[str, Any]): 包含所有文本信息的字典\n small_image_paths (Optional[List[str]]): 中间小图路径列表 (最多3张)\n color_theme (Optional[str]): 预设颜色主题名称\n\n Returns:\n Image.Image: 生成的海报图像\n \"\"\"\n top_img = self.image_processor.load_image(top_image_path)\n bottom_img = self.image_processor.load_image(bottom_image_path)\n if not top_img or not bottom_img:\n return None\n\n top_img = self.image_processor.resize_and_crop(top_img, (self.width, top_img.height))\n bottom_img = self.image_processor.resize_and_crop(bottom_img, (self.width, bottom_img.height))\n\n if color_theme and color_theme in self.color_themes:\n top_color, bottom_color = self.color_themes[color_theme]\n else:\n top_color = self._extract_dominant_color_edge(top_img)\n bottom_color = self._extract_dominant_color_edge(bottom_img)\n top_color, bottom_color = self._ensure_colors_harmony(top_color, bottom_color)\n \n self._current_background_colors = (top_color, bottom_color)\n canvas = self.create_gradient_background(top_color, bottom_color)\n\n top_img_trans = self._apply_top_transparency(top_img)\n bottom_img_trans = self._apply_bottom_transparency(bottom_img)\n\n section_heights = self._calculate_section_heights()\n canvas = self._compose_images(canvas, top_img_trans, bottom_img_trans)\n \n if small_image_paths:\n canvas = self._add_small_images(canvas, small_image_paths, section_heights)\n\n canvas = self._add_decorative_elements(canvas)\n canvas = self._render_texts(canvas, content, section_heights)\n \n return canvas\n\n def _calculate_section_heights(self) -> Dict[str, int]:\n \"\"\"计算各区域高度1:2:1的比例\"\"\"\n total_height = self.height\n total_parts = self.config['total_parts']\n \n top_h = int(total_height / total_parts)\n middle_h = int(total_height * 2 / total_parts)\n bottom_h = total_height - top_h - middle_h\n \n return {\n 'top_height': top_h, 'middle_height': middle_h, 'bottom_height': bottom_h,\n 'middle_start': top_h, 'middle_end': top_h + middle_h\n }\n\n def _apply_top_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对上部图像应用从不透明到透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n \n start_y = int(h * (1 - self.config['transparent_ratio']))\n for y in range(start_y, h):\n ratio = ((y - start_y) / (h - start_y)) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)\n\n def _apply_bottom_transparency(self, image: Image.Image) -> Image.Image:\n \"\"\"对下部图像应用从透明到不透明的渐变\"\"\"\n img_rgba = self.image_processor.ensure_rgba(image)\n img_array = np.array(img_rgba)\n h = img_array.shape[0]\n\n end_y = int(h * self.config['transparent_ratio'])\n for y in range(end_y):\n ratio = (1 - y / end_y) ** 2\n img_array[y, :, 3] = np.clip(img_array[y, :, 3] * (1 - ratio), 0, 255)\n \n return Image.fromarray(img_array)\n\n def _compose_images(self, canvas: Image.Image, top_img: Image.Image, bottom_img: Image.Image) -> Image.Image:\n \"\"\"合成背景和上下图像\"\"\"\n canvas.paste(top_img, ((self.width - top_img.width) // 2, 0), top_img)\n canvas.paste(bottom_img, ((self.width - bottom_img.width) // 2, self.height - bottom_img.height), bottom_img)\n return canvas\n\n def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], heights: Dict[str, int]) -> Image.Image:\n \"\"\"渲染所有文本内容\"\"\"\n draw = ImageDraw.Draw(canvas)\n center_x = self.width // 2\n \n text_start_y, text_end_y = heights['middle_start'], heights['middle_end']\n available_height = text_end_y - text_start_y - self.config['dynamic_spacing']['content_margin'] * 2\n estimated_height = self._estimate_content_height(content, self.width)\n \n layout_params = self._calculate_dynamic_layout_params(estimated_height, available_height)\n \n current_y = text_start_y + self.config['dynamic_spacing']['content_margin']\n available_width = int(self.width * 0.9)\n margin_x = (self.width - available_width) // 2\n\n current_y = self._render_title(draw, content.get(\"name\", \"\"), current_y, center_x, self.width, layout_params)\n current_y = self._render_feature(draw, content.get(\"feature\", \"\"), current_y, center_x, available_width, layout_params)\n self._render_info_section(draw, content, current_y, margin_x, available_width, layout_params, text_end_y)\n \n return canvas\n\n def _add_small_images(self, canvas: Image.Image, image_paths: List[str], heights: Dict[str, int]) -> Image.Image:\n \"\"\"在中间区域添加最多三张小图\"\"\"\n if not image_paths: return canvas\n \n num_images = min(len(image_paths), 3)\n padding = 10\n total_img_width = int(self.width * 0.8)\n img_width = (total_img_width - (num_images - 1) * padding) // num_images\n img_height = int(img_width * 0.75)\n \n start_x = (self.width - total_img_width) // 2\n y_pos = heights['middle_start'] + (heights['middle_height'] - img_height) // 2\n\n for i, path in enumerate(image_paths[:num_images]):\n if img := self.image_processor.load_image(path):\n img = self.image_processor.resize_and_crop(img, (img_width, img_height))\n x_pos = start_x + i * (img_width + padding)\n canvas.paste(img, (x_pos, y_pos))\n return canvas\n\n def _add_decorative_elements(self, canvas: Image.Image) -> Image.Image:\n \"\"\"添加装饰性元素增强设计感\"\"\"\n overlay = Image.new('RGBA', self.size, (0, 0, 0, 0))\n draw = ImageDraw.Draw(overlay)\n \n # 顶部装饰线条\n line_y = self.height // 4 - 20\n for i in range(w := self.width // 3):\n alpha = int(255 * (1 - abs(i - w//2) / (w//2)) * 0.3)\n draw.line([((self.width-w)//2 + i, line_y), ((self.width-w)//2 + i, line_y + 2)], fill=(255,255,255,alpha), width=1)\n\n # 角落装饰\n s, a = 80, 40\n draw.arc([20, 20, 20+s, 20+s], 180, 270, fill=(255,255,255,a), width=3)\n draw.arc([self.width-s-20, self.height-s-20, self.width-20, self.height-20], 0, 90, fill=(255,255,255,a), width=3)\n\n return Image.alpha_composite(canvas, overlay)\n\n def _estimate_content_height(self, content: Dict[str, Any], width: int) -> int:\n \"\"\"预估内容总高度\"\"\"\n spacing = self.config['dynamic_spacing']['section_spacing']\n title_h = int(self.text_renderer.load_font(60).getbbox(\"T\")[3] * 1.2)\n feature_h = int(self.text_renderer.load_font(30).getbbox(\"T\")[3] * 1.5) + 50\n info_h = len(content.get(\"info_list\", [])) * 35\n price_h = 80\n return 20 + title_h + spacing + feature_h + spacing + info_h + price_h + self.config['dynamic_spacing']['bottom_reserve']\n\n def _calculate_dynamic_layout_params(self, estimated_height: int, available_height: int) -> Dict[str, Any]:\n \"\"\"根据空间利用率计算动态布局参数\"\"\"\n ratio = estimated_height / available_height if available_height > 0 else 1.0\n factor = 1.2 if ratio <= 0.8 else (0.8 if ratio > 1.0 else 1.0)\n spacing = self.config['dynamic_spacing']\n return {\n 'line_spacing': int(spacing['min_line_spacing'] + (spacing['max_line_spacing'] - spacing['min_line_spacing']) * factor),\n 'section_spacing': int(spacing['section_spacing'] * factor)\n }\n\n def _render_title(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染主标题\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.85), max_size=80)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n self.text_renderer.draw_text_with_outline(draw, (center_x - text_w // 2, y), text, font, text_color=(255, 240, 200), outline_width=2)\n return y + text_h + params['section_spacing']\n\n def _render_feature(self, draw: ImageDraw.Draw, text: str, y: int, center_x: int, width: int, params: Dict[str, Any]) -> int:\n \"\"\"渲染特色描述及其背景\"\"\"\n size, calculated_width = self.text_renderer.calculate_font_size_and_width(text, int(width * 0.8), max_size=40)\n font = self.text_renderer.load_font(size)\n text_w, text_h = self.text_renderer.get_text_size(text, font)\n \n bg_h = 50\n bg_w = min(text_w + 30, width)\n bg_x = center_x - bg_w // 2\n \n self.text_renderer.draw_rounded_rectangle(draw, (bg_x, y-5), (bg_w, bg_h), 20, fill=(50, 50, 50, 180))\n color = self._get_smart_feature_color()\n draw.text((center_x - text_w // 2, y), text, font=font, fill=color)\n return y + bg_h + params['section_spacing']\n\n def _render_info_section(self, draw, content, y, x, width, params, max_y):\n \"\"\"渲染信息列表和价格区域\"\"\"\n info_texts = content.get(\"info_list\", [])\n if not info_texts: return y\n\n # 为信息列表找到合适的字体\n longest_info_line = max([f\"{item.get('title', '')}: {item.get('desc', '')}\" for item in info_texts], key=len) if info_texts else \"\"\n info_size, _ = self.text_renderer.calculate_font_size_and_width(longest_info_line, int(width*0.55), max_size=30)\n info_font = self.text_renderer.load_font(info_size)\n _, line_h = self.text_renderer.get_text_size(\"T\", info_font)\n\n # 为价格找到合适的字体\n price_text = f\"¥{content.get('price', '')}\"\n price_size, price_w = self.text_renderer.calculate_font_size_and_width(price_text, int(width*0.35), max_size=200, min_size=60)\n price_font = self.text_renderer.load_font(price_size)\n\n # 渲染信息列表\n for i, info in enumerate(info_texts):\n info_str = f\"{info.get('title', '')}: {info.get('desc', '')}\" if isinstance(info, dict) else str(info)\n draw.text((x, y + i * (line_h + params['line_spacing'])), info_str, font=info_font, fill=(255, 255, 255))\n \n # 渲染价格\n self.text_renderer.draw_text_with_shadow(draw, (x + width - price_w, y), price_text, price_font)\n\n # --- Color Extraction Logic (from demo) ---\n def _extract_dominant_color_edge(self, image: Image.Image) -> Tuple[int, int, int]:\n \"\"\"从图像边缘提取主要颜色\"\"\"\n if image.mode != 'RGB': image = image.convert('RGB')\n w, h = image.size\n pixels = []\n edge_w = min(w, h) // 4\n \n points = [(x,y) for y in range(h) for x in range(w) if x<edge_w or x>w-edge_w or y<edge_w or y>h-edge_w]\n sample_points = random.sample(points, min(len(points), 200))\n \n for p in sample_points:\n pixel = image.getpixel(p)\n if sum(pixel) > 50 and sum(pixel) < 700:\n pixels.append(pixel)\n \n if not pixels: return (80, 120, 160)\n \n best_color = self._select_best_color(Counter(pixels).most_common(5))\n return self._adjust_color_for_background(best_color)\n\n def _select_best_color(self, candidates: List[Tuple[Tuple[int, int, int], int]]) -> Tuple[int, int, int]:\n \"\"\"从候选颜色中选择最合适的\"\"\"\n best_score, best_color = -1, None\n for color, count in candidates:\n r,g,b = color\n brightness = (r*299 + g*587 + b*114) / 1000\n saturation = (max(r,g,b) - min(r,g,b)) / max(r,g,b) if max(r,g,b)>0 else 0\n \n b_score = 1.0 - abs((brightness - 130) / 130)\n s_score = 1.0 - abs(saturation - 0.5) / 0.5\n score = b_score * 0.4 + s_score * 0.6\n \n if score > best_score:\n best_score, best_color = score, color\n return best_color or candidates[0][0]\n\n def _adjust_color_for_background(self, color: Tuple[int, int, int]) -> Tuple[int, int, int]:\n \"\"\"调整颜色使其更适合作为背景\"\"\"\n r, g, b = color\n h, s, v = colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0)\n v = max(0.3, min(0.7, v)) # 限制亮度\n s = max(0.4, min(0.8, s)) # 限制饱和度\n r,g,b = [int(c*255) for c in colorsys.hsv_to_rgb(h,s,v)]\n return (r, g, b)\n\n def _ensure_colors_harmony(self, top: Tuple[int, int, int], bottom: Tuple[int, int, int]) -> Tuple[Tuple[int, int, int], Tuple[int, int, int]]:\n \"\"\"确保颜色和谐\"\"\"\n dist = sum(abs(a-b) for a,b in zip(top,bottom))\n if dist < 30:\n factor = 1.1 if sum(top)/3 > 128 else 0.9\n top = tuple(max(0,min(255,int(c*factor))) for c in top)\n elif dist > 150:\n mid = tuple((a+b)//2 for a,b in zip(top,bottom))\n top = tuple(int(c*0.8+m*0.2) for c,m in zip(top,mid))\n bottom = tuple(int(c*0.8+m*0.2) for c,m in zip(bottom,mid))\n return top, bottom\n\n def _get_smart_feature_color(self) -> Tuple[int, int, int]:\n \"\"\"为feature文本智能选择颜色\"\"\"\n if not self._current_background_colors: return (255, 255, 255)\n top, bottom = self._current_background_colors\n avg_bright = (sum(top)+sum(bottom))/6\n \n def get_tone(c):\n r,g,b=c\n return 'w' if r > g and r > b else ('f' if g > r and g > b else 'c')\n \n tone = get_tone(tuple((a+b)//2 for a,b in zip(top,bottom)))\n \n if avg_bright > 120:\n return {'c':(65,105,155), 'w':(155,85,65)}.get(tone, (85,125,85))\n else:\n return {'c':(135,185,235), 'w':(255,195,135)}.get(tone, (155,215,155)) ",
"code_hash": "f1af3a7202fb120866f2e4337e6aae7e"
}
],
"imports": [
{
"type": "import",
"modules": [
"logging"
],
"aliases": []
},
{
"type": "import",
"modules": [
"math"
],
"aliases": []
},
{
"type": "import",
"modules": [
"random"
],
"aliases": []
},
{
"type": "from_import",
"module": "collections",
"names": [
"Counter"
],
"aliases": [],
"level": 0
},
{
"type": "from_import",
"module": "typing",
"names": [
"Dict",
"Any",
"Optional",
"List",
"Tuple"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"numpy"
],
"aliases": [
"np"
]
},
{
"type": "from_import",
"module": "PIL",
"names": [
"Image",
"ImageDraw",
"ImageFont"
],
"aliases": [],
"level": 0
},
{
"type": "import",
"modules": [
"colorsys"
],
"aliases": []
},
{
"type": "from_import",
"module": "base_template",
"names": [
"BaseTemplate"
],
"aliases": [],
"level": 1
}
],
"constants": [],
"docstring": "Business风格商务风格海报模板",
"content_hash": "06936a833501e450934d06b15f40aabc"
}