修复了中文显示
This commit is contained in:
parent
4f89cbf150
commit
88e23501b2
Binary file not shown.
@ -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))
|
||||
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
|
||||
Loading…
x
Reference in New Issue
Block a user