From 29551abc2e623c9624b7f72252e1b6a3b0c2a7ac Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Sun, 27 Jul 2025 17:33:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=A4=9A=E5=9B=BE?= =?UTF-8?q?=E5=B1=82=E5=88=86=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vibrant_template.cpython-312.pyc | Bin 50153 -> 54972 bytes poster/templates/vibrant_template.py | 115 ++++++++++++++++-- 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/poster/templates/__pycache__/vibrant_template.cpython-312.pyc b/poster/templates/__pycache__/vibrant_template.cpython-312.pyc index 9c105e3320b051e8c15929b4255a93be766445c2..1e70c5db39a75d4a5e03fc2636f65ab3d5e42a6e 100644 GIT binary patch delta 5369 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 VzLOJL6O6Pi*1ssyvTJbe{{bEFab5rb diff --git a/poster/templates/vibrant_template.py b/poster/templates/vibrant_template.py index 7c5a7f9..2c41243 100644 --- a/poster/templates/vibrant_template.py +++ b/poster/templates/vibrant_template.py @@ -562,24 +562,40 @@ class VibrantTemplate(BaseTemplate): psd.append(composite_layer) logger.info("✓ 添加完美参考图层") - # === 第三步:按合成模式创建可编辑图层 === + # === 第三步:提供丰富的可编辑图层 === - # 图层2:背景+毛玻璃的复合底层(模拟canvas状态) - base_composite = self._create_composite_image(main_image, gradient_start, theme_color) - base_composite_scaled = base_composite.resize(final_size, Image.LANCZOS) - base_layer = PixelLayer.frompil(base_composite_scaled, psd, "Background + Glass") - psd.append(base_layer) - logger.info("✓ 添加背景+毛玻璃复合图层") + # 图层2:纯背景图层(无任何效果) + background_only = main_image.resize(final_size, Image.LANCZOS) + bg_layer = PixelLayer.frompil(background_only, psd, "Background Image") + psd.append(bg_layer) + logger.info("✓ 添加纯背景图层") - # 图层3:纯文字图层(在透明背景上,模拟_render_texts的结果) + # 图层3:毛玻璃效果图层 + glass_overlay = self._create_glass_overlay_layer(main_image, gradient_start, theme_color) + if glass_overlay: + glass_scaled = glass_overlay.resize(final_size, Image.LANCZOS) + glass_layer = PixelLayer.frompil(glass_scaled, psd, "Glass Overlay") + psd.append(glass_layer) + logger.info("✓ 添加毛玻璃效果图层") + + # 图层4-9:分离的文字图层 + detailed_text_layers = self._create_detailed_text_layers(content, gradient_start, self.size) + for layer_name, layer_image in detailed_text_layers.items(): + if layer_image: + text_scaled = layer_image.resize(final_size, Image.LANCZOS) + text_layer = PixelLayer.frompil(text_scaled, psd, layer_name) + psd.append(text_layer) + logger.info(f"✓ 添加文字图层: {layer_name}") + + # 图层10:整体文字图层(备用) text_only_canvas = self._create_text_only_layer(content, gradient_start, self.size) if text_only_canvas: text_only_scaled = text_only_canvas.resize(final_size, Image.LANCZOS) - text_layer = PixelLayer.frompil(text_only_scaled, psd, "All Text Content") - psd.append(text_layer) - logger.info("✓ 添加纯文字图层") + all_text_layer = PixelLayer.frompil(text_only_scaled, psd, "All Text (Backup)") + psd.append(all_text_layer) + logger.info("✓ 添加整体文字备用图层") - logger.info(f"PSD包含{len(list(psd))}个图层,遵循原始合成逻辑") + logger.info(f"PSD包含{len(list(psd))}个图层:1个参考层+{len(list(psd))-1}个编辑层") # 保存PSD文件 psd.save(output_path) @@ -718,7 +734,80 @@ class VibrantTemplate(BaseTemplate): logger.error(f"创建纯文字图层失败: {e}") return None - + def _create_detailed_text_layers(self, content: Dict[str, Any], gradient_start: int, canvas_size: tuple) -> Dict[str, Optional[Image.Image]]: + """创建详细的分离文字图层,每个文字元素独立可编辑""" + layers = {} + + try: + width, height = canvas_size + center_x = width // 2 + left_margin, right_margin = self._calculate_content_margins(content, width, center_x) + + # 1. 标题图层 + title_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + title_draw = ImageDraw.Draw(title_canvas) + title_y = gradient_start + 40 + + title_text = content.get("title", "默认标题") + title_target_width = int((right_margin - left_margin) * 0.98) + title_size, title_actual_width = self._calculate_optimal_font_size_enhanced( + title_text, title_target_width, max_size=140, min_size=40 + ) + try: + title_font = ImageFont.truetype(self.chinese_font_path, title_size) + except: + title_font = self.text_renderer._load_default_font(title_size) + text_w, text_h = self.text_renderer.get_text_size(title_text, title_font) + title_x = center_x - text_w // 2 + + self.text_renderer.draw_text_with_outline(title_draw, (title_x, title_y), title_text, title_font, + text_color=(255, 255, 255, 255), outline_color=(0, 30, 80, 200), outline_width=4) + layers["Title Text"] = title_canvas + + # 2. 副标题图层 + subtitle_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + subtitle_draw = ImageDraw.Draw(subtitle_canvas) + + subtitle_text = content.get("slogan", "") + if subtitle_text: + subtitle_target_width = int((right_margin - left_margin) * 0.95) + subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced( + subtitle_text, subtitle_target_width, max_size=75, min_size=20 + ) + try: + subtitle_font = ImageFont.truetype(self.chinese_font_path, subtitle_size) + except: + subtitle_font = self.text_renderer._load_default_font(subtitle_size) + sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font) + subtitle_x = center_x - sub_text_w // 2 + subtitle_y = title_y + text_h + 30 + + self.text_renderer.draw_text_with_shadow(subtitle_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)) + layers["Subtitle Text"] = subtitle_canvas + + # 3. 页脚图层 + footer_canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + footer_draw = ImageDraw.Draw(footer_canvas) + + footer_y = height - 30 + try: + footer_font = ImageFont.truetype(self.chinese_font_path, 18) + except: + footer_font = self.text_renderer._load_default_font(18) + + if tag := content.get("tag"): + footer_draw.text((left_margin, footer_y), tag, font=footer_font, fill=(255, 255, 255)) + if pagination := content.get("pagination"): + width_pg, _ = self.text_renderer.get_text_size(pagination, footer_font) + footer_draw.text((right_margin - width_pg, footer_y), pagination, font=footer_font, fill=(255, 255, 255)) + + layers["Footer"] = footer_canvas + + except Exception as e: + logger.error(f"创建详细文字图层失败: {e}") + + return {k: v for k, v in layers.items() if v is not None} def _draw_text_with_outline_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int], text: str, font: ImageFont.FreeTypeFont,