Compare commits

...

2 Commits

71 changed files with 158 additions and 63 deletions

Binary file not shown.

View File

@ -141,14 +141,14 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute(
"SELECT description FROM contentStyle WHERE styleName = %s",
(styleName,)
)
result = cursor.fetchone()
if result:
logger.info(f"从数据库获取风格提示词: {styleName}")
return result['description']
cursor.execute(
"SELECT description FROM contentStyle WHERE styleName = %s",
(styleName,)
)
result = cursor.fetchone()
if result:
logger.info(f"从数据库获取风格提示词: {styleName}")
return result['description']
except Exception as e:
logger.error(f"从数据库获取风格提示词失败: {e}")
@ -178,14 +178,14 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute(
"SELECT description FROM targetAudience WHERE audienceName = %s",
(audience_name,)
)
result = cursor.fetchone()
if result:
logger.info(f"从数据库获取受众提示词: {audience_name}")
return result["description"]
cursor.execute(
"SELECT description FROM targetAudience WHERE audienceName = %s",
(audience_name,)
)
result = cursor.fetchone()
if result:
logger.info(f"从数据库获取受众提示词: {audience_name}")
return result["description"]
except Exception as e:
logger.error(f"从数据库获取受众提示词失败: {e}")
@ -233,7 +233,7 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute(
cursor.execute(
"SELECT description FROM scenicSpot WHERE name = %s",
(spot_name,)
)
@ -276,7 +276,7 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute(
cursor.execute(
"SELECT detailedDescription FROM product WHERE productName = %s",
(product_name,)
)
@ -390,8 +390,8 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT styleName as name, description FROM contentStyle")
results = cursor.fetchall()
cursor.execute("SELECT styleName as name, description FROM contentStyle")
results = cursor.fetchall()
if results:
logger.info(f"从数据库获取所有风格: {len(results)}")
return results
@ -434,7 +434,7 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT audienceName as name, description FROM targetAudience")
cursor.execute("SELECT audienceName as name, description FROM targetAudience")
results = cursor.fetchall()
if results:
logger.info(f"从数据库获取所有受众: {len(results)}")
@ -478,7 +478,7 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT name as name, description FROM scenicSpot")
cursor.execute("SELECT name as name, description FROM scenicSpot")
results = cursor.fetchall()
if results:
logger.info(f"从数据库获取所有景区: {len(results)}")
@ -524,9 +524,9 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor() as cursor:
# 检查是否存在
cursor.execute(
cursor.execute(
"SELECT COUNT(*) FROM contentStyle WHERE styleName = %s",
(name,)
)
@ -588,9 +588,9 @@ class PromptService:
try:
with self.db_pool.get_connection() as conn:
with conn.cursor() as cursor:
# 检查是否存在
cursor.execute(
cursor.execute(
"SELECT COUNT(*) FROM targetAudience WHERE audienceName = %s",
(name,)
)
@ -634,4 +634,5 @@ class PromptService:
return True
except Exception as e:
logger.error(f"受众保存到文件系统失败: {e}")
return False
return False

View File

@ -1,13 +1,8 @@
{
"host": "localhost",
"user": "root",
<<<<<<< HEAD
"password": "Kj#9mP2$",
"database": "travel_content",
=======
"password": "mysql2025.",
"database": "bangbang",
>>>>>>> poster_update
"port": 3306,
"charset": "utf8mb4"
}

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)