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

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
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from .base_template import BaseTemplate
from ..utils import ColorExtractor
@ -206,7 +206,7 @@ class VibrantTemplate(BaseTemplate):
color_tuple = tuple(int(c) for c in color) + (alpha,)
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:
"""从图片中提取用于毛玻璃背景的颜色"""
@ -231,6 +231,84 @@ class VibrantTemplate(BaseTemplate):
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:
"""渲染所有文本元素,采用双栏布局"""
draw = ImageDraw.Draw(canvas)
@ -256,27 +334,41 @@ class VibrantTemplate(BaseTemplate):
return canvas
def _calculate_content_margins(self, content: Dict[str, Any], width: int, center_x: int) -> Tuple[int, int]:
"""计算内容区域的左右边距"""
"""计算内容区域的左右边距增强版本与demo一致"""
# 计算标题位置
title_text = content.get("title", "")
title_size=self.text_renderer.calculate_optimal_font_size(title_text,int(width * 0.95),max_size=130)
title_width,title_height=self.text_renderer.get_text_size(title_text,self.text_renderer._load_default_font(title_size))
title_target_width = int(width * 0.95)
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
# 计算副标题位置
slogan_text = content.get("slogan", "")
subtitle_size=self.text_renderer.calculate_optimal_font_size(slogan_text,int(width * 0.9),max_size=50)
subtitle_width,subtitle_height=self.text_renderer.get_text_size(slogan_text,self.text_renderer._load_default_font(subtitle_size))
subtitle_target_width = int(width * 0.9)
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
padding = 20
left_margin = max(40, min(title_x, subtitle_x) - padding)
right_margin = min(width - 40, 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)
right_margin = min(width - 30, right_margin + extra // 2)
return left_margin, right_margin
# 计算内容区域边距 - 减小额外的边距,让内容区域更宽
padding = 20 # 从30减小到20
content_left_margin = min(title_x, subtitle_x) - padding
content_right_margin = max(title_x + title_width, subtitle_x + subtitle_width) + padding
# 确保边距不超出合理范围,但允许更宽的内容区域
content_left_margin = max(40, content_left_margin)
content_right_margin = min(width - 40, content_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):
"""渲染页脚文本"""
@ -288,11 +380,13 @@ class VibrantTemplate(BaseTemplate):
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:
"""渲染标题和副标题"""
"""渲染标题和副标题增强版本与demo一致"""
# 标题
title_text = content.get("title", "默认标题")
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)
text_w, text_h = self.text_renderer.get_text_size(title_text, title_font)
@ -303,7 +397,9 @@ class VibrantTemplate(BaseTemplate):
# 副标题 (slogan)
subtitle_text = content.get("slogan", "")
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)
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))
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_size=self.text_renderer.calculate_optimal_font_size(price_text,int((right_margin - x) * 0.7),max_size=120,min_size=40)
price_width,_=self.text_renderer.get_text_size(price_text,self.text_renderer._load_default_font(price_size))
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
)
price_font = self.text_renderer._load_default_font(price_size)
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)
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)
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
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_size=self.text_renderer.calculate_optimal_font_size(ticket_text,int((right_margin - x) * 0.7),max_size=60,min_size=30)
ticket_width,_=self.text_renderer.get_text_size(ticket_text,self.text_renderer._load_default_font(ticket_size))
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
)
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
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)