715 lines
31 KiB
Python
715 lines
31 KiB
Python
import cv2
|
||
import os
|
||
import numpy as np
|
||
from pathlib import Path
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
import json
|
||
import random
|
||
import traceback
|
||
|
||
class PosterInfo:
|
||
def __init__(self, index, main_title, texts):
|
||
self.index = index
|
||
self.main_title = main_title
|
||
self.texts = texts
|
||
|
||
class PosterConfig:
|
||
def __init__(self, config_path):
|
||
self.config_path = config_path
|
||
try:
|
||
if isinstance(config_path, str) and os.path.exists(config_path):
|
||
# 如果是文件路径,从文件读取
|
||
print(f"从文件加载配置: {config_path}")
|
||
self.config = json.load(open(config_path, "r", encoding="utf-8"))
|
||
else:
|
||
# 如果是字符串但不是文件路径,尝试直接解析
|
||
print("尝试直接解析配置字符串")
|
||
self.config = json.loads(config_path)
|
||
|
||
self.img_list = []
|
||
for item in self.config:
|
||
print(item)
|
||
self.img_list.append([item['index'], item["main_title"], item["texts"]])
|
||
# self.img_list.append(PosterInfo(item['img_url'], item['main_title'], item['texts']))
|
||
except json.JSONDecodeError as e:
|
||
print(f"JSON解析错误: {e}")
|
||
print(f"尝试解析的内容: {config_path[:100]}...") # 只打印前100个字符
|
||
# 创建一个默认配置
|
||
self.config = [{"index": 0, "main_title": "景点风光", "texts": ["自然美景", "人文体验"]}]
|
||
self.img_list = [[0, "景点风光", ["自然美景", "人文体验"]]]
|
||
print("使用默认配置")
|
||
except Exception as e:
|
||
print(f"加载配置时出错: {e}")
|
||
# 创建一个默认配置
|
||
self.config = [{"index": 0, "main_title": "景点风光", "texts": ["自然美景", "人文体验"]}]
|
||
self.img_list = [[0, "景点风光", ["自然美景", "人文体验"]]]
|
||
print("使用默认配置")
|
||
|
||
def get_config(self):
|
||
return self.config
|
||
|
||
def get_config_by_index(self, index):
|
||
if index >= len(self.config):
|
||
print(f"警告: 索引 {index} 超出配置范围,使用默认配置")
|
||
return self.config[0]
|
||
return self.config[index]
|
||
|
||
class PosterGenerator:
|
||
def __init__(self, base_dir, output_dir=None):
|
||
# 基础路径设置
|
||
if not base_dir or not os.path.isdir(base_dir):
|
||
# Handle error: Base directory is essential
|
||
raise ValueError(f"Poster assets base directory '{base_dir}' is invalid or not provided.")
|
||
self.base_dir = base_dir
|
||
print(f"Initializing PosterGenerator with asset base: {self.base_dir}") # Log the used base_dir
|
||
self.font_dir = os.path.join(self.base_dir, "font")
|
||
self.frame_dir = os.path.join(self.base_dir, "frames") # 边框素材目录
|
||
self.sticker_dir = os.path.join(self.base_dir, "stickers") # 贴纸素材目录
|
||
self.text_bg_dir = os.path.join(self.base_dir, "text_backgrounds") # 文本框底图目录
|
||
|
||
self.img_frame_posbility = 0.7
|
||
self.text_bg_posbility = 0.7
|
||
# 设置输出目录
|
||
if output_dir:
|
||
self.output_dir = output_dir
|
||
else:
|
||
self.output_dir = os.path.join(self.base_dir, "output")
|
||
|
||
# 初始化资源
|
||
self.fonts = self._initialize_fonts()
|
||
self.frames = self._initialize_frames()
|
||
self.stickers = self._initialize_stickers()
|
||
self.text_bgs = self._initialize_text_backgrounds()
|
||
|
||
# 创建输出目录
|
||
os.makedirs(self.output_dir, exist_ok=True)
|
||
|
||
# 固定使用白色字体蓝色描边效果
|
||
self.selected_effect = "白色字体蓝色描边"
|
||
|
||
# 海报计数器,用于控制添加边框的频率
|
||
self.poster_count = 0
|
||
|
||
def _initialize_text_backgrounds(self):
|
||
"""初始化文本框底图"""
|
||
try:
|
||
text_bg_files = [f for f in os.listdir(self.text_bg_dir) if f.endswith(('.png'))]
|
||
print(f"找到文本框底图: {text_bg_files}")
|
||
return text_bg_files
|
||
except Exception as e:
|
||
print(f"初始化文本框底图失败: {e}")
|
||
return []
|
||
|
||
def _initialize_fonts(self):
|
||
"""初始化字体"""
|
||
fonts = []
|
||
try:
|
||
font_files = [f for f in os.listdir(self.font_dir) if f.endswith(('.ttf', '.otf'))]
|
||
print(f"找到字体文件: {font_files}")
|
||
return font_files
|
||
except Exception as e:
|
||
print(f"初始化字体失败: {e}")
|
||
return []
|
||
|
||
def _initialize_frames(self):
|
||
"""初始化边框素材"""
|
||
frames = []
|
||
try:
|
||
frame_files = [f for f in os.listdir(self.frame_dir) if f.endswith(('.png', '.jpg'))]
|
||
print(f"找到边框素材: {len(frame_files)}个")
|
||
return frame_files
|
||
except Exception as e:
|
||
print(f"初始化边框失败: {e}")
|
||
return []
|
||
|
||
def _initialize_stickers(self):
|
||
"""初始化贴纸素材"""
|
||
stickers = []
|
||
try:
|
||
sticker_files = [f for f in os.listdir(self.sticker_dir) if f.endswith(('.png'))]
|
||
print(f"找到贴纸素材: {sticker_files}")
|
||
return sticker_files
|
||
except Exception as e:
|
||
print(f"初始化贴纸失败: {e}")
|
||
return []
|
||
|
||
def get_random_font(self):
|
||
"""获取随机字体"""
|
||
try:
|
||
# 使用正确的字体目录
|
||
font_dir = "/root/autodl-tmp/poster_baseboard_0403/font"
|
||
|
||
# 获取所有支持的字体文件
|
||
font_files = [f for f in os.listdir(font_dir)
|
||
if f.lower().endswith(('.ttf', '.otf', '.ttc', '.TTC', '.TTF', '.OTF'))]
|
||
|
||
if not font_files:
|
||
print("警告: 字体目录为空,使用默认字体")
|
||
return os.path.join(font_dir, "华康海报体简.ttc")
|
||
|
||
# 随机选择一个字体文件
|
||
font_file = random.choice(font_files)
|
||
font_path = os.path.join(font_dir, font_file)
|
||
|
||
# 验证字体文件是否存在且可读
|
||
if not os.path.isfile(font_path):
|
||
print(f"警告: 字体文件 {font_file} 不存在,使用默认字体")
|
||
return os.path.join(font_dir, "华康海报体简.ttc")
|
||
|
||
print(f"使用字体: {font_file}")
|
||
return font_path
|
||
|
||
except Exception as e:
|
||
print(f"获取字体时出错: {str(e)}")
|
||
return os.path.join(font_dir, "华康海报体简.ttc")
|
||
|
||
def create_base_layer(self, image_path, target_size):
|
||
"""创建底层(图片层)"""
|
||
try:
|
||
base_image = Image.open(image_path).convert('RGBA')
|
||
base_image = base_image.resize(target_size, Image.Resampling.LANCZOS)
|
||
return base_image
|
||
except Exception as e:
|
||
print(f"创建底层失败: {e}")
|
||
return Image.new('RGBA', target_size, (255, 255, 255, 255))
|
||
|
||
def add_frame(self, image, target_size):
|
||
"""添加边框"""
|
||
if not self.frames:
|
||
print("没有可用的边框素材")
|
||
return image
|
||
|
||
try:
|
||
# 随机选择一个边框
|
||
frame_file = random.choice(self.frames)
|
||
frame_path = os.path.join(self.frame_dir, frame_file)
|
||
|
||
# 加载边框图像
|
||
frame_image = Image.open(frame_path).convert('RGBA')
|
||
|
||
# 调整边框大小以匹配目标尺寸
|
||
frame_image = frame_image.resize(target_size, Image.Resampling.LANCZOS)
|
||
|
||
# 创建一个新图层用于合成
|
||
framed_image = Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
|
||
# 先放置基础图像
|
||
framed_image.paste(image, (0, 0))
|
||
|
||
# 再合成边框图像
|
||
framed_image.alpha_composite(frame_image)
|
||
|
||
print(f"添加边框: {frame_file}")
|
||
return framed_image
|
||
except Exception as e:
|
||
print(f"添加边框失败: {e}")
|
||
return image
|
||
|
||
def create_middle_layer(self, target_size):
|
||
"""创建中间层(文本框底图)"""
|
||
try:
|
||
middle_layer = Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
|
||
# 1. 首先确定固定的文本区域
|
||
width, height = target_size
|
||
|
||
# 定义文本区域大小
|
||
header_height = int(height * 0.2) # 占顶部20%
|
||
header_width = int(width * 0.9) # 占宽度的90%
|
||
|
||
# 标题区域的位置(居中)
|
||
title_x = (width - header_width) // 2
|
||
title_y = int(height * 0.16) # 从原来的0.05(5%)改为0.12(12%),向下移动
|
||
|
||
# 保存标题区域信息
|
||
self.title_area = {
|
||
'x': title_x,
|
||
'y': title_y,
|
||
'width': header_width,
|
||
'height': header_height
|
||
}
|
||
|
||
# 计算额外文本的区域(位于底部区域)
|
||
additional_text_y = int(height * 0.65) # 位于底部35%的位置
|
||
additional_text_height = int(height * 0.1) # 占高度的10%
|
||
|
||
# 保存额外文本区域信息
|
||
self.additional_text_area = {
|
||
'x': int(width * 0.1), # 左边距10%
|
||
'y': additional_text_y,
|
||
'width': int(width * 0.8), # 占宽度的80%
|
||
'height': additional_text_height
|
||
}
|
||
|
||
# 输出文本区域信息
|
||
print("文本区域信息:")
|
||
print(f"主标题区域: x={self.title_area['x']}, y={self.title_area['y']}, "
|
||
f"宽={self.title_area['width']}, 高={self.title_area['height']}")
|
||
|
||
# 2. 检查是否使用文本框底图
|
||
use_text_bg = random.random() < self.text_bg_posbility
|
||
print(f"环境变量USE_TEXT_BG设置为: {os.environ.get('USE_TEXT_BG', 'True')}, 是否使用文本框底图: {use_text_bg}")
|
||
|
||
if use_text_bg and self.text_bgs:
|
||
print("根据环境变量设置,使用文本框底图")
|
||
# 随机选择文本背景
|
||
text_bg_path = os.path.join(self.text_bg_dir, random.choice(self.text_bgs))
|
||
text_bg = Image.open(text_bg_path).convert('RGBA')
|
||
|
||
# 计算文本框底图的尺寸,适度放大
|
||
bg_width = int(width * 1.9) # 宽度为画布宽度的130%
|
||
bg_height = int(height * 0.85) # 高度为画布高度的50%
|
||
|
||
# 计算主标题的中心点
|
||
title_center_x = self.title_area['x'] + self.title_area['width'] // 2
|
||
title_center_y = self.title_area['y'] + self.title_area['height'] // 2
|
||
|
||
# 根据主标题中心点计算底图位置,确保完全对齐
|
||
bg_x = title_center_x - (bg_width // 2) # 从标题中心点向两侧扩展
|
||
bg_y = title_center_y - (bg_height // 2) # 从标题中心点向上下扩展
|
||
|
||
# 调整文本背景大小
|
||
text_bg = text_bg.resize((bg_width, bg_height), Image.Resampling.LANCZOS)
|
||
|
||
# 创建临时图层并合并
|
||
temp = Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
temp.paste(text_bg, (bg_x, bg_y), text_bg)
|
||
middle_layer.alpha_composite(temp)
|
||
|
||
print(f"文本背景: 宽={bg_width}px (画布宽度的{bg_width/width:.1f}倍), "
|
||
f"高={bg_height}px (画布高度的{bg_height/height:.1f}倍)")
|
||
else:
|
||
print("根据环境变量设置,不使用文本框底图,仅使用固定文本区域")
|
||
|
||
return middle_layer
|
||
except Exception as e:
|
||
print(f"创建中间层时出错: {str(e)}")
|
||
traceback.print_exc() # 打印详细错误信息
|
||
return Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
|
||
def create_text_layer(self, target_size, text_data=None):
|
||
"""创建文字层"""
|
||
try:
|
||
text_layer = Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
draw = ImageDraw.Draw(text_layer)
|
||
|
||
# 使用固定效果:字体蓝色立体效果
|
||
self.selected_effect = "文字蓝色立体效果"
|
||
print(f"使用文字效果: {self.selected_effect}")
|
||
|
||
# 如果没有文字数据,使用默认值
|
||
if text_data is None:
|
||
text_data = {'title': '泰宁县 甘露岩寺'}
|
||
|
||
# 1. 处理主标题
|
||
if hasattr(self, 'title_area') and 'title' in text_data:
|
||
font_path = self._get_font_path()
|
||
title = text_data['title']
|
||
|
||
# 使用柠檬黄色作为主标题颜色
|
||
lemon_yellow = (255, 250, 55, 255) # 柠檬黄色
|
||
|
||
# 使用统一定义的标题区域
|
||
font, text_width, text_height, x, y = self._calculate_text_layout(
|
||
draw, font_path, title, self.title_area, size_factor=0.75)
|
||
|
||
# 绘制主标题文字
|
||
self._draw_text_with_effects(draw, title, font, x, y,
|
||
shadow_offset=8,
|
||
color=lemon_yellow)
|
||
|
||
# 打印调试信息
|
||
self._print_text_debug_info("主标题", font, text_width, x, y, font_path)
|
||
print(f"- 主标题颜色: 柠檬黄色 RGB(255, 250, 55)")
|
||
|
||
# 2. 处理副标题(如果有)
|
||
if hasattr(self, 'title_area') and 'subtitle' in text_data and text_data['subtitle']:
|
||
subtitle = text_data['subtitle']
|
||
# 计算副标题区域(在主标题下方)
|
||
subtitle_area = {
|
||
'x': self.title_area['x'],
|
||
'y': self.title_area['y'] + self.title_area['height'],
|
||
'width': self.title_area['width'],
|
||
'height': int(self.title_area['height'] * 0.5) # 副标题高度为主标题的一半
|
||
}
|
||
|
||
# 获取副标题字体和布局
|
||
font, text_width, text_height, x, y = self._calculate_text_layout(
|
||
draw, font_path, subtitle, subtitle_area, size_factor=0.6)
|
||
|
||
# 副标题保持白色
|
||
white_color = (255, 255, 255, 255)
|
||
|
||
# 绘制副标题
|
||
self._draw_text_with_effects(draw, subtitle, font, x, y,
|
||
shadow_offset=8,
|
||
color=white_color)
|
||
|
||
# 打印调试信息
|
||
self._print_text_debug_info("副标题", font, text_width, x, y, font_path)
|
||
|
||
# 3. 处理额外文本(如果有)
|
||
if 'additional_texts' in text_data and text_data['additional_texts']:
|
||
# 过滤掉空文本项
|
||
additional_texts = [item for item in text_data['additional_texts'] if item.get('text')]
|
||
|
||
if additional_texts and hasattr(self, 'title_area'):
|
||
# 获取主标题的字体大小
|
||
main_title_font_size = font.size
|
||
|
||
# 使用固定字体
|
||
specific_font_path = os.path.join("/root/autodl-tmp/poster_baseboard_0403/font", "华康海报体简.ttc")
|
||
if not os.path.isfile(specific_font_path):
|
||
specific_font_path = font_path
|
||
|
||
# 计算额外文本在屏幕上的位置
|
||
height = target_size[1]
|
||
width = target_size[0]
|
||
|
||
# 将垂直位置调整到主标题下方
|
||
title_bottom = self.title_area['y'] + self.title_area['height']
|
||
extra_text_y_start = title_bottom + int(height * 0.01) # 从原来的0.05(5%)减小到0.02(2%)
|
||
extra_text_height = int(height * 0.2)
|
||
|
||
# 安全边距
|
||
safe_margin_x = int(width * 0.05)
|
||
max_text_width = width - (safe_margin_x * 2)
|
||
|
||
# 总文本行数
|
||
total_lines = len(additional_texts)
|
||
line_height = extra_text_height // total_lines if total_lines > 0 else 0
|
||
|
||
print(f"额外文本区域: y={extra_text_y_start}, 高度={extra_text_height}, 每行高度={line_height}")
|
||
print(f"文本安全宽度: {max_text_width}px (留出两侧各{safe_margin_x}px安全边距)")
|
||
print(f"文本颜色: 统一白色")
|
||
|
||
# 渲染每一行文本
|
||
for i, text_item in enumerate(additional_texts):
|
||
item_text = text_item['text']
|
||
|
||
# 设置字体大小为主标题的0.8倍
|
||
size_factor = 0.8
|
||
font_size = int(main_title_font_size * size_factor)
|
||
text_font = ImageFont.truetype(specific_font_path, font_size)
|
||
|
||
# 测量文本宽度并调整
|
||
text_width = draw.textlength(item_text, font=text_font)
|
||
|
||
# 保护措施:如果文本宽度超出安全区域,逐步缩小字体直到适合
|
||
while text_width > max_text_width and font_size > int(main_title_font_size * 0.4):
|
||
size_factor *= 0.95
|
||
font_size = int(main_title_font_size * size_factor)
|
||
text_font = ImageFont.truetype(specific_font_path, font_size)
|
||
text_width = draw.textlength(item_text, font=text_font)
|
||
|
||
# 再次测量调整后的文本尺寸
|
||
text_bbox = draw.textbbox((0, 0), item_text, font=text_font)
|
||
text_height = text_bbox[3] - text_bbox[1]
|
||
|
||
# 计算垂直位置 - 在分配的空间内居中
|
||
line_y = extra_text_y_start + (i * line_height) + ((line_height - text_height) // 2)
|
||
|
||
# 水平居中位置
|
||
line_x = (width - text_width) // 2
|
||
|
||
# 统一使用白色文本
|
||
text_color = (255, 255, 255, 255) # 白色
|
||
|
||
# 绘制文本
|
||
self._draw_text_with_effects(draw, item_text, text_font, line_x, line_y,
|
||
shadow_offset=3,
|
||
color=text_color)
|
||
|
||
# 打印调试信息
|
||
print(f"额外文本{i+1}: '{item_text}'")
|
||
print(f"- 文本颜色: 白色")
|
||
print(f"- 字体大小: {font_size}px (主标题的{size_factor:.2f}倍)")
|
||
print(f"- 位置: x={line_x}, y={line_y}")
|
||
|
||
return text_layer
|
||
except Exception as e:
|
||
print(f"创建文字层时出错: {str(e)}")
|
||
traceback.print_exc()
|
||
return Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
|
||
def _get_font_path(self):
|
||
"""获取并验证字体路径"""
|
||
# 获取随机字体
|
||
font_path = self.get_random_font()
|
||
try:
|
||
# 尝试加载字体
|
||
ImageFont.truetype(font_path, size=1) # 先用小尺寸测试字体是否可用
|
||
except Exception as e:
|
||
print(f"加载字体失败: {str(e)},使用默认字体")
|
||
font_path = os.path.join("/root/autodl-tmp/poster_baseboard_0403/font", "华康海报体简.ttc")
|
||
|
||
return font_path
|
||
|
||
def _get_text_area(self, text_item, target_size, index):
|
||
"""获取文本区域"""
|
||
width, height = target_size
|
||
|
||
# 根据指定位置创建文本区域
|
||
position = text_item.get('position', 'bottom')
|
||
|
||
if position == 'custom' and 'x' in text_item and 'y' in text_item:
|
||
# 自定义位置
|
||
text_area = {
|
||
'x': text_item['x'],
|
||
'y': text_item['y'],
|
||
'width': text_item.get('width', width // 2),
|
||
'height': text_item.get('height', height // 10)
|
||
}
|
||
elif position == 'top':
|
||
# 顶部区域
|
||
text_area = {
|
||
'x': int(width * 0.1),
|
||
'y': int(height * 0.05),
|
||
'width': int(width * 0.8),
|
||
'height': int(height * 0.1)
|
||
}
|
||
elif position == 'middle':
|
||
# 中部区域
|
||
text_area = {
|
||
'x': int(width * 0.1),
|
||
'y': int(height * 0.45),
|
||
'width': int(width * 0.8),
|
||
'height': int(height * 0.1)
|
||
}
|
||
else: # 默认为底部
|
||
# 底部区域,增加随机性
|
||
# 从图像的一半高开始(height * 0.5)到底部的0.9位置之间随机选择
|
||
random_y_position = random.uniform(0.5, 0.85)
|
||
# 水平位置也增加随机性,在10%到30%之间
|
||
random_x_position = random.uniform(0.05, 0.1)
|
||
|
||
# 多个底部文本时的偏移
|
||
offset = index * 0.08
|
||
|
||
text_area = {
|
||
'x': int(width * random_x_position),
|
||
'y': int(height * (random_y_position + offset)),
|
||
'width': int(width * 0.8),
|
||
'height': int(height * 0.1)
|
||
}
|
||
|
||
return text_area
|
||
|
||
def _calculate_text_layout(self, draw, font_path, text, text_area, size_factor=0.85, align_left=False):
|
||
"""计算文本布局、字体大小和位置
|
||
|
||
Args:
|
||
draw: ImageDraw对象
|
||
font_path: 字体路径
|
||
text: 文本内容
|
||
text_area: 文本区域字典
|
||
size_factor: 字体大小因子(相对于区域高度)
|
||
align_left: 是否左对齐
|
||
"""
|
||
# 设置初始字体大小
|
||
initial_font_size = int(text_area['height'] * size_factor)
|
||
font = ImageFont.truetype(font_path, initial_font_size)
|
||
|
||
# 调整字体大小以适应宽度(目标是占据95%的宽度)
|
||
target_width = text_area['width'] * 0.98 # 增加宽度利用率到98%
|
||
text_width = draw.textlength(text, font=font)
|
||
|
||
# 字体大小调整逻辑优化:先尝试增大字体以更好地填充宽度
|
||
if text_width < target_width * 0.9 and initial_font_size < int(text_area['height'] * 0.98):
|
||
# 如果文本宽度小于目标宽度的90%,尝试增大字体
|
||
while text_width < target_width * 0.9 and initial_font_size < int(text_area['height'] * 0.98):
|
||
initial_font_size += 2
|
||
font = ImageFont.truetype(font_path, initial_font_size)
|
||
text_width = draw.textlength(text, font=font)
|
||
|
||
# 如果字体太大,再缩小字体以适应宽度
|
||
while text_width > target_width and initial_font_size > 10:
|
||
initial_font_size -= 2
|
||
font = ImageFont.truetype(font_path, initial_font_size)
|
||
text_width = draw.textlength(text, font=font)
|
||
|
||
# 获取文字高度
|
||
text_bbox = draw.textbbox((0, 0), text, font=font)
|
||
text_height = text_bbox[3] - text_bbox[1]
|
||
|
||
# 计算位置
|
||
if align_left:
|
||
# 左对齐
|
||
x = text_area['x'] + int(text_area['width'] * 0.05) # 5% 的左边距
|
||
y = text_area['y'] + (text_area['height'] - text_height) // 2
|
||
else:
|
||
# 居中对齐
|
||
x = text_area['x'] + (text_area['width'] - text_width) // 2
|
||
y = text_area['y'] + (text_area['height'] - text_height) // 2
|
||
|
||
return font, text_width, text_height, x, y
|
||
|
||
def _draw_text_with_effects(self, draw, text, font, x, y, shadow_offset=8, color=(255, 255, 255, 255)):
|
||
"""绘制带立体效果的文字
|
||
|
||
Args:
|
||
draw: ImageDraw对象
|
||
text: 文本内容
|
||
font: 字体对象
|
||
x, y: 文本坐标
|
||
shadow_offset: 立体深度
|
||
color: 文本表面颜色
|
||
"""
|
||
# 文字颜色使用传入的color参数
|
||
text_color = color
|
||
|
||
# 蓝色立体效果 - 只用一种深蓝色
|
||
blue_color = (29, 60, 171, 255) # 深蓝色
|
||
|
||
# 立体深度 - 使用更大的深度
|
||
depth = 8 # 8像素的立体深度
|
||
|
||
# 1. 首先绘制深蓝色立体部分
|
||
for i in range(1, depth + 1):
|
||
# 下方
|
||
draw.text((x, y + i), text, font=font, fill=blue_color)
|
||
# 右侧
|
||
draw.text((x + i, y), text, font=font, fill=blue_color)
|
||
# 右下角
|
||
draw.text((x + i, y + i), text, font=font, fill=blue_color)
|
||
|
||
# 2. 绘制蓝色描边 - 增强文字的边缘清晰度
|
||
stroke_thickness = 2
|
||
|
||
for dx in range(-stroke_thickness, stroke_thickness+1):
|
||
for dy in range(-stroke_thickness, stroke_thickness+1):
|
||
if dx == 0 and dy == 0:
|
||
continue
|
||
# 计算当前点到中心的距离
|
||
distance = (dx**2 + dy**2)**0.5
|
||
# 如果距离在描边范围内,绘制蓝色描边
|
||
if distance <= stroke_thickness:
|
||
draw.text((x + dx, y + dy), text, font=font, fill=blue_color)
|
||
|
||
# 3. 最后绘制主文字在最上层,使用传入的颜色
|
||
draw.text((x, y), text, font=font, fill=text_color)
|
||
|
||
def _print_text_debug_info(self, text_type, font, text_width, x, y, font_path):
|
||
"""打印文字调试信息"""
|
||
print(f"{text_type}信息:")
|
||
print(f"- 字体大小: {font.size}")
|
||
print(f"- 文字宽度: {text_width}")
|
||
print(f"- 文字位置: x={x}, y={y}")
|
||
print(f"- 使用字体: {os.path.basename(font_path)}")
|
||
|
||
def create_poster(self, image_path, text_data, output_name):
|
||
"""生成海报"""
|
||
try:
|
||
# 增加计数器
|
||
self.poster_count += 1
|
||
|
||
# 设置目标尺寸为3:4比例
|
||
target_size = (900, 1200) # 3:4比例
|
||
|
||
# 创建三个图层
|
||
base_layer = self.create_base_layer(image_path, target_size)
|
||
middle_layer = self.create_middle_layer(target_size)
|
||
|
||
# 先合成底层和中间层
|
||
final_image = Image.new('RGBA', target_size, (0, 0, 0, 0))
|
||
final_image.paste(base_layer, (0, 0))
|
||
final_image.alpha_composite(middle_layer)
|
||
|
||
# 创建并添加文字层
|
||
text_layer = self.create_text_layer(target_size, text_data)
|
||
final_image.alpha_composite(text_layer) # 确保文字层在最上面
|
||
|
||
# 使用模10的余数决定是否添加边框
|
||
# 每十张中的第1、5、9张添加边框(余数为1,5,9)
|
||
add_frame_flag = random.random() < self.img_frame_posbility
|
||
|
||
if add_frame_flag:
|
||
final_image = self.add_frame(final_image, target_size)
|
||
|
||
# 保存结果
|
||
# 检查output_name是否已经是完整路径
|
||
if os.path.dirname(output_name):
|
||
output_path = output_name
|
||
# 确保目录存在
|
||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||
else:
|
||
# 如果只是文件名,拼接输出目录
|
||
output_path = os.path.join(self.output_dir, output_name)
|
||
|
||
# 如果没有扩展名,添加.jpg
|
||
if not output_path.lower().endswith(('.jpg', '.jpeg', '.png')):
|
||
output_path += '.jpg'
|
||
|
||
final_image.convert('RGB').save(output_path)
|
||
print(f"海报已保存: {output_path}")
|
||
print(f"图片尺寸: {target_size[0]}x{target_size[1]} (3:4比例)")
|
||
print(f"使用的文字特效: {self.selected_effect}")
|
||
|
||
return output_path
|
||
|
||
except Exception as e:
|
||
print(f"生成海报失败: {e}")
|
||
return None
|
||
|
||
def process_directory(self, input_dir, text_data=None):
|
||
pass
|
||
"""遍历处理目录中的所有图片"""
|
||
# 支持的图片格式
|
||
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp')
|
||
|
||
try:
|
||
# 获取目录中的所有文件
|
||
files = os.listdir(input_dir)
|
||
image_files = [f for f in files if f.lower().endswith(image_extensions)]
|
||
|
||
if not image_files:
|
||
print(f"在目录 {input_dir} 中未找到图片文件")
|
||
return
|
||
|
||
print(f"找到 {len(image_files)} 个图片文件")
|
||
|
||
# 处理每个图片文件
|
||
for i, image_file in enumerate(image_files, 1):
|
||
image_path = os.path.join(input_dir, image_file)
|
||
print(f"\n处理第 {i}/{len(image_files)} 个图片: {image_file}")
|
||
|
||
try:
|
||
# 构建输出文件名
|
||
output_name = os.path.splitext(image_file)[0]
|
||
# 生成海报
|
||
self.create_poster(image_path, text_data, output_name)
|
||
print(f"完成处理: {image_file}")
|
||
|
||
except Exception as e:
|
||
print(f"处理图片 {image_file} 时出错: {e}")
|
||
continue
|
||
|
||
except Exception as e:
|
||
print(f"处理目录时出错: {e}")
|
||
|
||
def main():
|
||
# 设置是否使用文本框底图(True为使用,False为不使用)
|
||
os.environ['USE_TEXT_BG'] = 'False' # 或 'True'
|
||
print(f"设置环境变量USE_TEXT_BG为: {os.environ['USE_TEXT_BG']}")
|
||
|
||
# 创建生成器实例
|
||
generator = PosterGenerator("/root/autodl-tmp/poster_baseboard_0403")
|
||
|
||
poster_config_path = "/root/autodl-tmp/poster_generate_result/2025-04-16_20-49-32.json"
|
||
poster_config = PosterConfig(poster_config_path)
|
||
for item in poster_config.get_config():
|
||
text_data = {
|
||
"title": f"{item['main_title']}",
|
||
"subtitle": "",
|
||
"additional_texts": [
|
||
{"text": f"{item['texts'][0]}", "position": "bottom", "size_factor": 0.5},
|
||
{"text": f"{item['texts'][1]}", "position": "bottom", "size_factor": 0.5}
|
||
]
|
||
}
|
||
# 处理目录中的所有图片
|
||
img_path = "/root/autodl-tmp/poster_baseboard_0403/output_collage/random_collage_1_collage.png"
|
||
generator.create_poster(img_path, text_data, f"{item['index']}.jpg")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|