Compare commits
2 Commits
91ac7ca65a
...
4f89cbf150
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f89cbf150 | |||
| ec8667eeea |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user