From 88e23501b2c5d847b57b4b90dce09bab5a587fc8 Mon Sep 17 00:00:00 2001 From: jinye_huang Date: Sun, 27 Jul 2025 16:43:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vibrant_template.cpython-312.pyc | Bin 26017 -> 47347 bytes poster/templates/vibrant_template.py | 468 +++++++++++++++++- 2 files changed, 467 insertions(+), 1 deletion(-) diff --git a/poster/templates/__pycache__/vibrant_template.cpython-312.pyc b/poster/templates/__pycache__/vibrant_template.cpython-312.pyc index 2d6aab3e725fcc43d2bd4e531831b7640f513e20..de627e75fe8c8f292f9690e40a6ad14ca5e4dc52 100644 GIT binary patch delta 21920 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 F{l7k5u%rM0 delta 3632 zcmai0dvp|68J|1*$Uc&gkYq`g1U3)|!{#B60-ly6ZFr?X3M8?RXt$f4*=2EG+?^Fj zOM@O_TLmkA5)?(NAXeT4{c9vAXqDiwLYNq`|gki!a;8` zzrFWQG>fLVd9*4efm_;2&)UJB-QBFhN4xQAiDuUv^cS^>$Vr6q5srb|&BOY}c>P{ThZM;>NR=BP&cB)XFjy9E}Ho;qM zYSD(Gn}KWOs|xB@{a^i=BAy`XPI^q*%(^FcNJ=AYbNzdzyxakUo;;FbE3YpGGIu(t_U{iiItAQ8um$58d&DZTay_Y4!R=n8G6lg1Ja+%IV zIu_G7>?k|sUxZuAml3oP+)J>WUJ&`laaf()FUINvFjYlEW7R+fg&%H=Er2P+plg`;)w zm&$sUf{LoYd6$yOeFR~nWYSr-Vk`Ddhn~wMG}E4lXLbHC&DgTJq-jfrH!EH)d|Ng` zc~e}~*PG7uT0FL1H(e1erE5I2nfJo%z^APxyJ2HsE*pTufpg9W(TMjNP~9Rs*HFI$ z{xEe#k??BCX!2gTqh`5R#M^`8zFJ@d<-4*E3h$mSN#j5^#lU#aU`8cbJLT|CG>bE z5!W~$L`fEJFewmd;Y+{_u4O-jIdu!zQb^Q2HuWe!BN{_yXRsw`BktvZhVz7~oY z8tZ4UU%=Y>mab<=bv%I^!L%>p;Z6KW8n+UNaL6m~B(WpRd3X!oNqu`Vj0D>v>8vSd z;v#0+n-rE7{I$N?qRt|K?0aES!vpLDY;9;@d*E0@1^X_%-q2<3!~v*ooXw6IosCx* zmeJ~2Lo(X|3+8UEB&N6s;0M^dIUo4d+-2-t@Za&;gx`|dZ8V0UX11dN|0M5h0SW>)HfuAUhFwKI@Xnje&Keu$&!?;pE%<_+^+CnLFtiobG#A7)J)~=! z^-wn6r zxc_dGRc<0B13__%o#!vn1Qmkmhd;LYA=av}qp+)WR*g6V5fY0_LS+6E8d~X7Z#N9J zK4qPY&*HeR3U;n6cT(`#1@OY6e^{qM=p@WYOAKJ%XqzS>I;5?sJEQEBV zEI>IacI%W7kGzaje@b8{5tOW{>!;lid&j+GIfaI_9fEoOCiY_A+%H;l%-Y?H&5C{a z_oNae3jySre-#0l6Qt2rjI1q_kYJc%#exuJK%m%UJ_CNVW~zV829I)m0j{p8WT(O3 zQPm)dcMQ$D$@wVP1tNWdaiDd~^%rB;EXd}1^|+qU#1Z5rBrXM`;~wUNe|Gd*wepb! zq0X7?5#uYJd9qWywca-NekNk;Ptg+V2?WERRP0E8HRx~n*Ceke&(jEKo0L3G!J01d zLW!qS*4=!H<~-4OA`uer1g?<5OaehnHT9+tq*1edxTq3;c!ogSb$#%7cUAu=LxMUg zMAN6@g&*SSTuRl{5Z5AlI2B81T$C!^FPzSdI6bCYOXZRp59^xZvo4)6#J)|c;zYGz zET*?Nhu2{$6iuge9Kf`mit$v${7VR?17DcQEWZS+dNx~hUX$RLJ+D*pG6J@U8iZd9EVEv?dg$GZhA629)Wiv zole2W04!7wJHJfk85mZpSQq4JE!L}1f|Z)WP8*xF6SAF33`z!ToFbn992lJAKT2lz z5ZDNa15Rg^KLQsA%T{aDT}`l#Kzx$*Q11W%eQEHg3B)Bymp8vc;3jy9;3~mK1lI^8 zIC#9QWifWeXCQus&}{Jt5R=(zjO1V2rAxBS;kx81ad`H19h~#L?cjp_p${Y+UTiS_ zd;F?g_n}nlXqCA*IOIX8;5nkl7RE0;%Erv2kJWf8#$SeOCu>Iokdis`y5nO1WNt%+|#J^YulE8y8vcbBZnW3BSn6CX&TBP>1!=ErhE diff --git a/poster/templates/vibrant_template.py b/poster/templates/vibrant_template.py index ba4c550..a5ec2d0 100644 --- a/poster/templates/vibrant_template.py +++ b/poster/templates/vibrant_template.py @@ -43,6 +43,25 @@ class VibrantTemplate(BaseTemplate): 'intensity_multiplier': 1.5 }, } + # 设置中文字体路径 + self.chinese_font_path = "/root/TravelContentCreator/assets/font/兰亭粗黑简.TTF" + + # 重写text_renderer的字体加载方法以支持中文 + self._patch_text_renderer_for_chinese() + + def _patch_text_renderer_for_chinese(self): + """重写text_renderer的字体加载方法以支持中文""" + original_load_font = self.text_renderer._load_default_font + + def load_chinese_font(size: int): + try: + return ImageFont.truetype(self.chinese_font_path, size) + except: + logger.warning(f"无法加载中文字体,使用默认字体") + return original_load_font(size) + + # 替换字体加载方法 + self.text_renderer._load_default_font = load_chinese_font def generate(self, images: List, @@ -482,4 +501,451 @@ class VibrantTemplate(BaseTemplate): remarks_y = ticket_y + ticket_height + 30 for i, remark in enumerate(remarks): remark_width, _ = self.text_renderer.get_text_size(remark, remarks_font) - draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255, 200)) \ No newline at end of file + draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255, 200)) + + def generate_layered_psd(self, + images, + content: Optional[Dict[str, Any]] = None, + theme_color: Optional[str] = None, + glass_intensity: float = 1.5, + output_path: str = "layered_poster.psd", + **kwargs) -> str: + """ + 生成分层的PSD文件,方便后续修改 + + Args: + images: 主图 + content: 包含所有文本信息的字典 + theme_color: 预设颜色主题的名称 + glass_intensity: 毛玻璃效果强度 + output_path: PSD文件输出路径 + + Returns: + str: 生成的PSD文件路径 + """ + try: + from psd_tools import PSDImage + from psd_tools.api.layers import PixelLayer + except ImportError: + logger.error("需要安装psd-tools库: pip install psd-tools") + return None + + logger.info("开始生成分层PSD文件...") + + if content is None: + content = self._get_default_content() + + self.config['glass_effect']['intensity_multiplier'] = glass_intensity + + main_image = images + if not main_image: + logger.error("无法加载图片") + return None + + main_image = self.image_processor.resize_image(image=main_image, target_size=self.size) + estimated_height = self._estimate_content_height(content) + gradient_start = self._detect_gradient_start_position(main_image, estimated_height) + + # 创建新的PSD文档 + psd = PSDImage.new("RGB", self.size, color=(255, 255, 255)) + logger.info(f"创建PSD文档,尺寸: {self.size}") + + # 1. 添加背景图层 + background_layer = PixelLayer.frompil(main_image, psd, "Background") + psd.append(background_layer) + logger.info("✓ 添加背景图层") + + # 2. 添加毛玻璃效果层 + glass_overlay = self._create_glass_overlay_layer(main_image, gradient_start, theme_color) + if glass_overlay: + glass_layer = PixelLayer.frompil(glass_overlay, psd, "Glass Effect") + psd.append(glass_layer) + logger.info("✓ 添加毛玻璃效果层") + + # 3-7. 添加文字图层 + text_layers = self._create_text_layers(content, gradient_start) + for layer_name, layer_image in text_layers.items(): + if layer_image: + text_layer = PixelLayer.frompil(layer_image, psd, layer_name) + psd.append(text_layer) + logger.info(f"✓ Added {layer_name} layer") + + # 注意:PSD文件保持原始尺寸,最终调整在导出时进行 + + # 保存PSD文件 + psd.save(output_path) + logger.info(f"✓ PSD文件已保存: {output_path}") + + return output_path + + def _create_glass_overlay_layer(self, main_image: Image.Image, gradient_start: int, theme_color: Optional[str]) -> Optional[Image.Image]: + """创建毛玻璃效果的独立图层""" + try: + if theme_color and theme_color in self.config['colors']: + top_color, bottom_color = self.config['colors'][theme_color] + else: + top_color, bottom_color = self._extract_glass_colors_from_image(main_image, gradient_start) + + # 创建透明背景的毛玻璃层 + overlay = self._create_frosted_glass_overlay(top_color, bottom_color, gradient_start) + return overlay + except Exception as e: + logger.error(f"创建毛玻璃层失败: {e}") + return None + + def _create_text_layers(self, content: Dict[str, Any], gradient_start: int) -> Dict[str, Optional[Image.Image]]: + """创建各个文字图层""" + layers = {} + + try: + # 创建透明画布 + canvas_size = self.size + + # 计算布局参数 + width, height = canvas_size + center_x = width // 2 + left_margin, right_margin = self._calculate_content_margins(content, width, center_x) + + # 1. 标题层 + layers["Title Text"] = self._create_title_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size) + + # 2. 副标题层 + layers["Subtitle Text"] = self._create_subtitle_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size) + + # 3. 装饰线层 + layers["Decorations"] = self._create_decoration_layer(content, gradient_start, center_x, left_margin, right_margin, canvas_size) + + # 4. 左栏内容层 + title_y = gradient_start + 40 + subtitle_height = 80 + 30 # 预估副标题高度 + content_start_y = title_y + subtitle_height + 30 + content_area_width = right_margin - left_margin + left_column_width = int(content_area_width * 0.5) + + layers["Left Content"] = self._create_left_column_layer(content, content_start_y, left_margin, left_column_width, canvas_size) + + # 5. 右栏内容层 + right_column_x = left_margin + left_column_width + layers["Right Content"] = self._create_right_column_layer(content, content_start_y, right_column_x, right_margin, canvas_size) + + # 6. 页脚层 + footer_y = height - 30 + layers["Footer Info"] = self._create_footer_layer(content, footer_y, left_margin, right_margin, canvas_size) + + except Exception as e: + logger.error(f"创建文字图层失败: {e}") + + return layers + + def _draw_text_with_outline_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int], + text: str, font: ImageFont.FreeTypeFont, + text_color: Tuple[int, int, int, int] = (255, 255, 255, 255), + outline_color: Tuple[int, int, int, int] = (0, 0, 0, 255), + outline_width: int = 2): + """简单的文本描边绘制方法""" + x, y = position + + # 绘制描边 + for dx in range(-outline_width, outline_width + 1): + for dy in range(-outline_width, outline_width + 1): + if dx == 0 and dy == 0: + continue + draw.text((x + dx, y + dy), text, font=font, fill=outline_color) + + # 绘制主文本 + draw.text((x, y), text, font=font, fill=text_color) + + def _draw_text_with_shadow_simple(self, draw: ImageDraw.Draw, position: Tuple[int, int], + text: str, font: ImageFont.FreeTypeFont, + text_color: Tuple[int, int, int, int] = (255, 255, 255, 255), + shadow_color: Tuple[int, int, int, int] = (0, 0, 0, 128), + shadow_offset: Tuple[int, int] = (2, 2)): + """简单的文本阴影绘制方法""" + x, y = position + shadow_x, shadow_y = shadow_offset + + # 绘制阴影 + draw.text((x + shadow_x, y + shadow_y), text, font=font, fill=shadow_color) + + # 绘制主文本 + draw.text((x, y), text, font=font, fill=text_color) + + 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]: + """创建标题图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + 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) + + # 重新计算实际尺寸 + bbox = title_font.getbbox(title_text) + text_w = bbox[2] - bbox[0] + text_h = bbox[3] - bbox[1] + title_x = center_x - text_w // 2 + title_y = gradient_start + 40 + + # 绘制带描边的标题 + self._draw_text_with_outline_simple( + draw, (title_x, title_y), title_text, title_font, + text_color=(255, 255, 255, 255), + outline_color=(0, 30, 80, 200), + outline_width=4 + ) + + return canvas + except Exception as e: + logger.error(f"创建标题层失败: {e}") + import traceback + traceback.print_exc() + return None + + 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]: + """创建副标题图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + subtitle_text = content.get("slogan", "") + if not subtitle_text: + return None + + 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) + + bbox = subtitle_font.getbbox(subtitle_text) + sub_text_w = bbox[2] - bbox[0] + sub_text_h = bbox[3] - bbox[1] + subtitle_x = center_x - sub_text_w // 2 + subtitle_y = gradient_start + 40 + 100 + 30 # title_y + title_height + spacing + + # 绘制带阴影的副标题 + self._draw_text_with_shadow_simple( + 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) + ) + + return canvas + except Exception as e: + logger.error(f"创建副标题层失败: {e}") + import traceback + traceback.print_exc() + return None + + 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]: + """创建装饰元素图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + # 获取标题信息用于装饰线定位 + 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) + + bbox = title_font.getbbox(title_text) + text_w = bbox[2] - bbox[0] + text_h = bbox[3] - bbox[1] + title_x = center_x - text_w // 2 + title_y = gradient_start + 40 + + # 在标题下方添加装饰线 + line_y = title_y + text_h + 5 + line_start_x = title_x - text_w * 0.025 + line_end_x = title_x + text_w * 1.025 + draw.line([(line_start_x, line_y), (line_end_x, line_y)], fill=(215, 215, 215, 80), width=3) + + return canvas + except Exception as e: + logger.error(f"创建装饰层失败: {e}") + import traceback + traceback.print_exc() + return None + + def _create_left_column_layer(self, content: Dict[str, Any], y: int, x: int, width: int, canvas_size: Tuple[int, int]) -> Optional[Image.Image]: + """创建左栏内容图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + # 使用中文字体 + + # 按钮 + try: + button_font = ImageFont.truetype(self.chinese_font_path, 30) + except: + button_font = self.text_renderer._load_default_font(30) + + button_text = content.get("content_button", "套餐内容") + bbox = button_font.getbbox(button_text) + button_text_width = bbox[2] - bbox[0] + button_width = button_text_width + 40 + button_height = 50 + + # 绘制简单的矩形按钮 + draw.rounded_rectangle([x, y, x + button_width, y + button_height], + radius=20, fill=(0, 140, 210, 180), + outline=(255, 255, 255, 255), width=1) + draw.text((x + 20, y + (button_height - 30) // 2), button_text, font=button_font, fill=(255, 255, 255)) + + # 项目列表 + items = content.get("content_items", []) + if items: + try: + list_font = ImageFont.truetype(self.chinese_font_path, 28) + except: + list_font = self.text_renderer._load_default_font(28) + + list_y = y + button_height + 20 + line_spacing = 36 + + for i, item in enumerate(items): + item_y = list_y + i * line_spacing + draw.text((x, item_y), "• " + item, font=list_font, fill=(255, 255, 255)) + + return canvas + except Exception as e: + logger.error(f"创建左栏内容层失败: {e}") + import traceback + traceback.print_exc() + return None + + 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]: + """创建右栏内容图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + # 使用中文字体 + + # 价格 + price_text = content.get('price', '') + if price_text: + price_target_width = int((right_margin - x) * 0.7) + price_size, price_actual_width = self._calculate_optimal_font_size_enhanced( + price_text, price_target_width, max_size=120, min_size=40 + ) + + try: + price_font = ImageFont.truetype(self.chinese_font_path, price_size) + suffix_font = ImageFont.truetype(self.chinese_font_path, int(price_size * 0.3)) + except: + price_font = self.text_renderer._load_default_font(price_size) + suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3)) + + price_bbox = price_font.getbbox(price_text) + price_height = price_bbox[3] - price_bbox[1] + suffix_bbox = suffix_font.getbbox("CNY起") + suffix_width = suffix_bbox[2] - suffix_bbox[0] + suffix_height = suffix_bbox[3] - suffix_bbox[1] + + price_x = right_margin - price_actual_width - suffix_width + self._draw_text_with_shadow_simple(draw, (price_x, y), price_text, price_font) + + suffix_y = y + price_height - suffix_height + draw.text((price_x + price_actual_width, suffix_y), "CNY起", font=suffix_font, fill=(255, 255, 255)) + + # 下划线 + underline_y = y + price_height + 18 + draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2) + + # 票种 + ticket_text = content.get("ticket_type", "") + if ticket_text: + ticket_target_width = int((right_margin - x) * 0.7) + ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced( + ticket_text, ticket_target_width, max_size=60, min_size=30 + ) + + try: + ticket_font = ImageFont.truetype(self.chinese_font_path, ticket_size) + except: + ticket_font = self.text_renderer._load_default_font(ticket_size) + + ticket_x = right_margin - ticket_actual_width + ticket_y = y + price_height + 35 + self._draw_text_with_shadow_simple(draw, (ticket_x, ticket_y), ticket_text, ticket_font) + + ticket_bbox = ticket_font.getbbox(ticket_text) + ticket_height = ticket_bbox[3] - ticket_bbox[1] + + # 备注 + remarks = content.get("remarks", []) + if remarks: + try: + remarks_font = ImageFont.truetype(self.chinese_font_path, 16) + except: + remarks_font = self.text_renderer._load_default_font(16) + + remarks_y = ticket_y + ticket_height + 30 + for i, remark in enumerate(remarks): + remark_bbox = remarks_font.getbbox(remark) + remark_width = remark_bbox[2] - remark_bbox[0] + draw.text((right_margin - remark_width, remarks_y + i * 21), remark, font=remarks_font, fill=(255, 255, 255, 200)) + + return canvas + except Exception as e: + logger.error(f"创建右栏内容层失败: {e}") + import traceback + traceback.print_exc() + return None + + 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]: + """创建页脚图层""" + try: + canvas = Image.new('RGBA', canvas_size, (0, 0, 0, 0)) + draw = ImageDraw.Draw(canvas) + + # 使用中文字体 + try: + font = ImageFont.truetype(self.chinese_font_path, 18) + except: + font = self.text_renderer._load_default_font(18) + + # 标签(左下角) + tag_text = content.get("tag", "") + if tag_text: + draw.text((left_margin, footer_y), tag_text, font=font, fill=(255, 255, 255)) + + # 分页信息(右下角) + pagination_text = content.get("pagination", "") + if pagination_text: + pagination_bbox = font.getbbox(pagination_text) + pagination_width = pagination_bbox[2] - pagination_bbox[0] + draw.text((right_margin - pagination_width, footer_y), pagination_text, font=font, fill=(255, 255, 255)) + + return canvas + except Exception as e: + logger.error(f"创建页脚层失败: {e}") + import traceback + traceback.print_exc() + return None \ No newline at end of file