修改了样式,未指定字体文件

This commit is contained in:
jinye_huang 2025-07-27 16:24:56 +08:00
parent ec8667eeea
commit 4f89cbf150
2 changed files with 130 additions and 31 deletions

View File

@ -10,7 +10,7 @@ import math
from typing import Dict, Any, Optional, Tuple from typing import Dict, Any, Optional, Tuple
import numpy as np import numpy as np
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont, ImageFilter
from .base_template import BaseTemplate from .base_template import BaseTemplate
from ..utils import ColorExtractor from ..utils import ColorExtractor
@ -206,7 +206,7 @@ class VibrantTemplate(BaseTemplate):
color_tuple = tuple(int(c) for c in color) + (alpha,) color_tuple = tuple(int(c) for c in color) + (alpha,)
draw.line([(0, y), (self.width, y)], fill=color_tuple) draw.line([(0, y), (self.width, y)], fill=color_tuple)
return self.image_processor.apply_blur(overlay, radius=enhanced_blur) return overlay.filter(ImageFilter.GaussianBlur(radius=enhanced_blur))
def _extract_glass_colors_from_image(self, image: Image.Image, gradient_start: int) -> tuple: def _extract_glass_colors_from_image(self, image: Image.Image, gradient_start: int) -> tuple:
"""从图片中提取用于毛玻璃背景的颜色""" """从图片中提取用于毛玻璃背景的颜色"""
@ -231,6 +231,84 @@ class VibrantTemplate(BaseTemplate):
return top_color, bottom_color return top_color, bottom_color
def _calculate_optimal_font_size_enhanced(self, text: str, target_width: int,
max_size: int = 120, min_size: int = 10) -> Tuple[int, int]:
"""
计算文本的最佳字体大小使其宽度接近目标宽度增强版本与demo一致
返回:
(字体大小, 实际文本宽度)
"""
# 二分查找最佳字体大小
low = min_size
high = max_size
best_size = min_size
best_width = 0
tolerance = 0.08 # 容差值,使文本宽度更接近目标值
# 首先尝试最大字体大小
try:
font = self.text_renderer._load_default_font(max_size)
max_width, _ = self.text_renderer.get_text_size(text, font)
except:
max_width = target_width * 2 # 如果出错,设置一个大值
# 如果最大字体大小下的宽度仍小于目标宽度的108%,直接使用最大字体
if max_width < target_width * (1 + tolerance):
best_size = max_size
best_width = max_width
else:
# 记录最接近目标宽度的字体大小
closest_size = min_size
closest_diff = target_width
while low <= high:
mid = (low + high) // 2
try:
font = self.text_renderer._load_default_font(mid)
width, _ = self.text_renderer.get_text_size(text, font)
except:
width = target_width * 2 # 如果出错,设置一个大值
# 计算与目标宽度的差距
diff = abs(width - target_width)
# 更新最接近的字体大小
if diff < closest_diff:
closest_diff = diff
closest_size = mid
# 如果宽度在目标宽度的允许范围内,认为找到了最佳匹配
if target_width * (1 - tolerance) <= width <= target_width * (1 + tolerance):
best_size = mid
best_width = width
break
# 如果当前宽度小于目标宽度,尝试更大的字体
if width < target_width:
if width > best_width:
best_width = width
best_size = mid
low = mid + 1
else:
# 如果当前宽度大于目标宽度,尝试更小的字体
high = mid - 1
# 如果没有找到在容差范围内的字体大小,使用最接近的字体大小
if best_width == 0:
best_size = closest_size
# 确保返回的宽度是使用最终字体计算的实际宽度
try:
best_font = self.text_renderer._load_default_font(best_size)
final_width, _ = self.text_renderer.get_text_size(text, best_font)
except:
final_width = best_width
logger.info(f"文本'{text[:min(10, len(text))]}...'的最佳字体大小: {best_size},目标宽度: {target_width},实际宽度: {final_width},差距: {abs(final_width-target_width)}")
return best_size, final_width
def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], gradient_start: int) -> Image.Image: def _render_texts(self, canvas: Image.Image, content: Dict[str, Any], gradient_start: int) -> Image.Image:
"""渲染所有文本元素,采用双栏布局""" """渲染所有文本元素,采用双栏布局"""
draw = ImageDraw.Draw(canvas) draw = ImageDraw.Draw(canvas)
@ -256,27 +334,41 @@ class VibrantTemplate(BaseTemplate):
return canvas return canvas
def _calculate_content_margins(self, content: Dict[str, Any], width: int, center_x: int) -> Tuple[int, int]: def _calculate_content_margins(self, content: Dict[str, Any], width: int, center_x: int) -> Tuple[int, int]:
"""计算内容区域的左右边距""" """计算内容区域的左右边距增强版本与demo一致"""
# 计算标题位置
title_text = content.get("title", "") title_text = content.get("title", "")
title_size=self.text_renderer.calculate_optimal_font_size(title_text,int(width * 0.95),max_size=130) title_target_width = int(width * 0.95)
title_width,title_height=self.text_renderer.get_text_size(title_text,self.text_renderer._load_default_font(title_size)) title_size, title_width = self._calculate_optimal_font_size_enhanced(
title_text, title_target_width, min_size=40, max_size=130
)
title_x = center_x - title_width // 2 title_x = center_x - title_width // 2
# 计算副标题位置
slogan_text = content.get("slogan", "") slogan_text = content.get("slogan", "")
subtitle_size=self.text_renderer.calculate_optimal_font_size(slogan_text,int(width * 0.9),max_size=50) subtitle_target_width = int(width * 0.9)
subtitle_width,subtitle_height=self.text_renderer.get_text_size(slogan_text,self.text_renderer._load_default_font(subtitle_size)) subtitle_size, subtitle_width = self._calculate_optimal_font_size_enhanced(
slogan_text, subtitle_target_width, max_size=50, min_size=20
)
subtitle_x = center_x - subtitle_width // 2 subtitle_x = center_x - subtitle_width // 2
padding = 20 # 计算内容区域边距 - 减小额外的边距,让内容区域更宽
left_margin = max(40, min(title_x, subtitle_x) - padding) padding = 20 # 从30减小到20
right_margin = min(width - 40, max(title_x + title_width, subtitle_x + subtitle_width) + padding) content_left_margin = min(title_x, subtitle_x) - padding
content_right_margin = max(title_x + title_width, subtitle_x + subtitle_width) + padding
if (right_margin - left_margin) < (min_width := int(width * 0.75)):
extra = min_width - (right_margin - left_margin) # 确保边距不超出合理范围,但允许更宽的内容区域
left_margin = max(30, left_margin - extra // 2) content_left_margin = max(40, content_left_margin)
right_margin = min(width - 30, right_margin + extra // 2) content_right_margin = min(width - 40, content_right_margin)
return left_margin, right_margin # 如果内容区域太窄,强制使用更宽的区域
min_content_width = int(width * 0.75) # 至少使用75%的宽度
current_width = content_right_margin - content_left_margin
if current_width < min_content_width:
extra_width = min_content_width - current_width
content_left_margin = max(30, content_left_margin - extra_width // 2)
content_right_margin = min(width - 30, content_right_margin + extra_width // 2)
return content_left_margin, content_right_margin
def _render_footer(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, left: int, right: int): def _render_footer(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, left: int, right: int):
"""渲染页脚文本""" """渲染页脚文本"""
@ -288,11 +380,13 @@ class VibrantTemplate(BaseTemplate):
draw.text((right - width, y), pagination, font=font, fill=(255, 255, 255)) draw.text((right - width, y), pagination, font=font, fill=(255, 255, 255))
def _render_title_subtitle(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, center_x: int, left: int, right: int) -> int: def _render_title_subtitle(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, center_x: int, left: int, right: int) -> int:
"""渲染标题和副标题""" """渲染标题和副标题增强版本与demo一致"""
# 标题 # 标题
title_text = content.get("title", "默认标题") title_text = content.get("title", "默认标题")
title_target_width = int((right - left) * 0.98) title_target_width = int((right - left) * 0.98)
title_size=self.text_renderer.calculate_optimal_font_size(title_text,title_target_width,max_size=140,min_size=40) title_size, title_actual_width = self._calculate_optimal_font_size_enhanced(
title_text, title_target_width, max_size=140, min_size=40
)
title_font = self.text_renderer._load_default_font(title_size) title_font = self.text_renderer._load_default_font(title_size)
text_w, text_h = self.text_renderer.get_text_size(title_text, title_font) text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)
@ -303,7 +397,9 @@ class VibrantTemplate(BaseTemplate):
# 副标题 (slogan) # 副标题 (slogan)
subtitle_text = content.get("slogan", "") subtitle_text = content.get("slogan", "")
subtitle_target_width = int((right - left) * 0.95) subtitle_target_width = int((right - left) * 0.95)
subtitle_size=self.text_renderer.calculate_optimal_font_size(subtitle_text,subtitle_target_width,max_size=75,min_size=20) subtitle_size, subtitle_actual_width = self._calculate_optimal_font_size_enhanced(
subtitle_text, subtitle_target_width, max_size=75, min_size=20
)
subtitle_font = self.text_renderer._load_default_font(subtitle_size) 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) sub_text_w, sub_text_h = self.text_renderer.get_text_size(subtitle_text, subtitle_font)
@ -348,31 +444,34 @@ class VibrantTemplate(BaseTemplate):
draw.text((x, item_y), " " + item, font=font, fill=(255, 255, 255)) draw.text((x, item_y), " " + item, font=font, fill=(255, 255, 255))
def _render_right_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, right_margin: int): def _render_right_column(self, draw: ImageDraw.Draw, content: Dict[str, Any], y: int, x: int, right_margin: int):
"""渲染右栏内容:价格、票种和备注""" """渲染右栏内容:价格、票种和备注增强版本与demo一致"""
price_text = content.get('price', '') price_text = content.get('price', '')
price_size=self.text_renderer.calculate_optimal_font_size(price_text,int((right_margin - x) * 0.7),max_size=120,min_size=40) price_target_width = int((right_margin - x) * 0.7)
price_width,_=self.text_renderer.get_text_size(price_text,self.text_renderer._load_default_font(price_size)) price_size, price_actual_width = self._calculate_optimal_font_size_enhanced(
price_text, price_target_width, max_size=120, min_size=40
)
price_font = self.text_renderer._load_default_font(price_size) price_font = self.text_renderer._load_default_font(price_size)
suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3)) suffix_font = self.text_renderer._load_default_font(int(price_size * 0.3))
_, price_height = self.text_renderer.get_text_size(price_text, price_font) _, price_height = self.text_renderer.get_text_size(price_text, price_font)
suffix_width, suffix_height = self.text_renderer.get_text_size("CNY起", suffix_font) suffix_width, suffix_height = self.text_renderer.get_text_size("CNY起", suffix_font)
price_x = right_margin - price_width - suffix_width price_x = right_margin - price_actual_width - suffix_width
self.text_renderer.draw_text_with_shadow(draw, (price_x, y), price_text, price_font) self.text_renderer.draw_text_with_shadow(draw, (price_x, y), price_text, price_font)
suffix_y = y + price_height - suffix_height suffix_y = y + price_height - suffix_height
draw.text((price_x + price_width, suffix_y), "CNY起", font=suffix_font, fill=(255, 255, 255)) draw.text((price_x + price_actual_width, suffix_y), "CNY起", font=suffix_font, fill=(255, 255, 255))
underline_y = y + price_height + 18 underline_y = y + price_height + 18
draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2) draw.line([(price_x - 10, underline_y), (right_margin, underline_y)], fill=(255, 255, 255, 80), width=2)
ticket_text = content.get("ticket_type", "") ticket_text = content.get("ticket_type", "")
ticket_size=self.text_renderer.calculate_optimal_font_size(ticket_text,int((right_margin - x) * 0.7),max_size=60,min_size=30) ticket_target_width = int((right_margin - x) * 0.7)
ticket_width,_=self.text_renderer.get_text_size(ticket_text,self.text_renderer._load_default_font(ticket_size)) ticket_size, ticket_actual_width = self._calculate_optimal_font_size_enhanced(
ticket_text, ticket_target_width, max_size=60, min_size=30
)
ticket_font = self.text_renderer._load_default_font(ticket_size) ticket_font = self.text_renderer._load_default_font(ticket_size)
ticket_x = right_margin - ticket_width ticket_x = right_margin - ticket_actual_width
ticket_y = y + price_height + 35 ticket_y = y + price_height + 35
self.text_renderer.draw_text_with_shadow(draw, (ticket_x, ticket_y), ticket_text, ticket_font) self.text_renderer.draw_text_with_shadow(draw, (ticket_x, ticket_y), ticket_text, ticket_font)
_, ticket_height = self.text_renderer.get_text_size(ticket_text, ticket_font) _, ticket_height = self.text_renderer.get_text_size(ticket_text, ticket_font)