11import io
22import random
33from pathlib import Path
4- from typing import Optional
4+ from typing import Optional , Tuple
55
66from nonebot import logger
77from PIL import Image , ImageDraw
1010from typing import Union
1111from io import BytesIO
1212
13+ from .config import ImageStyleConfig
14+
1315try :
1416 import emoji
1517 EMOJI_AVAILABLE = True
2325FONT_PATH : Path = RESOURCE_DIR / "可爱字体.ttf"
2426EMOJI_FONT_PATH : Path = RESOURCE_DIR / "NotoColorEmoji.ttf"
2527
26- # 样式配置
27- FONT_SIZE = 35 # 字体大小
28- TEXT_PADDING = 10 # 文本与边框的间距
29- AVATAR_SIZE = None # 头像大小(None 表示与文本高度一致)
30- BORDER_THICKNESS = 10 # 边框厚度
31- BORDER_COLOR = (38 , 38 , 38 ) # 边框颜色 #262626
32- CORNER_RADIUS = 30 # 圆角大小
33-
3428# 加载字体
3529def load_font (font_path : Path , size : int ) -> Union [ImageFont .ImageFont , FreeTypeFont ]:
3630 """加载字体,如果失败则使用默认字体"""
@@ -44,36 +38,42 @@ def load_font(font_path: Path, size: int) -> Union[ImageFont.ImageFont, FreeType
4438RESOURCE_DIR .mkdir (parents = True , exist_ok = True )
4539
4640# 加载主字体
47- if FONT_PATH .exists ():
48- cute_font = load_font (FONT_PATH , FONT_SIZE )
49- logger .debug (f"[QQDetail] 主字体加载: { FONT_PATH } " )
50- else :
51- logger .warning (f"[QQDetail] 字体文件不存在: { FONT_PATH } ,使用默认字体" )
52- cute_font = ImageFont .load_default ()
41+ def get_cute_font (size : int ) -> Union [ImageFont .ImageFont , FreeTypeFont ]:
42+ """获取主字体"""
43+ if FONT_PATH .exists ():
44+ return load_font (FONT_PATH , size )
45+ else :
46+ logger .warning (f"[QQDetail] 字体文件不存在: { FONT_PATH } ,使用默认字体" )
47+ return ImageFont .load_default ()
5348
5449# 加载 Emoji 字体
55- emoji_font = None
56- if EMOJI_FONT_PATH .exists ():
57- emoji_font = load_font (EMOJI_FONT_PATH , FONT_SIZE )
58- logger .debug (f"[QQDetail] Emoji 字体加载: { EMOJI_FONT_PATH } " )
59- else :
60- logger .warning (f"[QQDetail] Emoji 字体文件不存在: { EMOJI_FONT_PATH } " )
50+ def get_emoji_font (size : int ) -> Optional [Union [ImageFont .ImageFont , FreeTypeFont ]]:
51+ """获取Emoji字体"""
52+ if EMOJI_FONT_PATH .exists ():
53+ return load_font (EMOJI_FONT_PATH , size )
54+ else :
55+ logger .warning (f"[QQDetail] Emoji 字体文件不存在: { EMOJI_FONT_PATH } " )
56+ return None
6157
6258
63- def create_image (avatar : bytes , reply : list ) -> bytes :
59+ def create_image (avatar : bytes , reply : list , style_config : ImageStyleConfig ) -> bytes :
6460 """创建用户资料图片"""
6561 reply_str = "\n " .join (reply )
66-
62+
63+ # 获取字体
64+ cute_font = get_cute_font (style_config .font_size )
65+ emoji_font = get_emoji_font (style_config .font_size )
66+
6767 # 创建临时图片计算文本的宽高
6868 temp_img = Image .new ("RGBA" , (1 , 1 ))
6969 temp_draw = ImageDraw .Draw (temp_img )
70-
70+
7171 # 将 Emoji 替换为中文字符以计算宽度
7272 if EMOJI_AVAILABLE :
7373 no_emoji_reply = "" .join ("一" if emoji .is_emoji (c ) else c for c in reply_str ) # type: ignore[union-attr]
7474 else :
7575 no_emoji_reply = reply_str
76-
76+
7777 # 计算每行文本的高度
7878 lines = no_emoji_reply .split ("\n " )
7979 line_heights = []
@@ -84,14 +84,14 @@ def create_image(avatar: bytes, reply: list) -> bytes:
8484 line_height = int (line_bbox [3 ] - line_bbox [1 ])
8585 line_heights .append (line_height )
8686 except :
87- line_heights .append (FONT_SIZE + 5 )
87+ line_heights .append (style_config . font_size + 5 )
8888 else :
89- line_heights .append (FONT_SIZE + 5 )
90-
89+ line_heights .append (style_config . font_size + 5 )
90+
9191 # 计算整体文本高度
9292 text_height = sum (line_heights ) + (len (lines ) - 1 ) * 5
93- text_height += TEXT_PADDING # 底部额外边距
94-
93+ text_height += style_config . text_padding # 底部额外边距
94+
9595 # 计算最大文本宽度
9696 max_line_width = 0
9797 for line in lines :
@@ -101,40 +101,48 @@ def create_image(avatar: bytes, reply: list) -> bytes:
101101 line_width = int (line_bbox [2 ] - line_bbox [0 ])
102102 max_line_width = max (max_line_width , line_width + 15 )
103103 except :
104- max_line_width = max (max_line_width , len (line ) * FONT_SIZE + 15 )
105-
104+ max_line_width = max (max_line_width , len (line ) * style_config . font_size + 15 )
105+
106106 text_width = max_line_width
107- img_height = text_height + 2 * TEXT_PADDING
108-
107+ img_height = text_height + 2 * style_config . text_padding
108+
109109 # 调整头像大小
110110 avatar_img = Image .open (BytesIO (avatar ))
111- avatar_size = AVATAR_SIZE if AVATAR_SIZE else text_height
111+ avatar_size = style_config . avatar_size if style_config . avatar_size else text_height
112112 avatar_img = avatar_img .resize ((avatar_size , avatar_size ))
113- img_width = avatar_img .width + text_width + 2 * TEXT_PADDING
114-
113+ img_width = avatar_img .width + text_width + 2 * style_config . text_padding
114+
115115 # 创建主图
116116 img = Image .new ("RGBA" , (img_width , img_height ), color = (255 , 255 , 255 , 255 ))
117117 img .paste (avatar_img , (0 , (img_height - avatar_size ) // 2 ))
118-
118+
119119 # 绘制文本
120- _draw_multi (img , reply_str , avatar_img .width + TEXT_PADDING , TEXT_PADDING )
121-
120+ _draw_multi (img , reply_str , avatar_img .width + style_config . text_padding , style_config . text_padding , cute_font , emoji_font , style_config )
121+
122122 # 添加边框
123123 border_img = Image .new (
124124 mode = "RGBA" ,
125- size = (img_width + BORDER_THICKNESS * 2 , img_height + BORDER_THICKNESS * 2 ),
126- color = BORDER_COLOR ,
125+ size = (img_width + style_config . border_thickness * 2 , img_height + style_config . border_thickness * 2 ),
126+ color = style_config . border_color ,
127127 )
128- border_img .paste (img , (BORDER_THICKNESS , BORDER_THICKNESS ))
129-
128+ border_img .paste (img , (style_config . border_thickness , style_config . border_thickness ))
129+
130130 # 转换为字节
131131 img_byte_arr = io .BytesIO ()
132132 border_img .save (img_byte_arr , format = "PNG" )
133133 return img_byte_arr .getvalue ()
134134
135135
136- def _draw_multi (img : Image .Image , text : str , text_x : int = 10 , text_y : int = 10 ):
136+ def _draw_multi (img : Image .Image , text : str , text_x : int = 10 , text_y : int = 10 , cute_font = None , emoji_font = None , style_config : Optional [ ImageStyleConfig ] = None ):
137137 """在图片上绘制多语言文本"""
138+ if cute_font is None :
139+ cute_font = get_cute_font (35 ) # 默认值
140+ if emoji_font is None :
141+ emoji_font = get_emoji_font (35 )
142+ if style_config is None :
143+ from .config import ImageStyleConfig
144+ style_config = ImageStyleConfig ()
145+
138146 lines = text .split ("\n " )
139147 current_y = text_y
140148 draw = ImageDraw .Draw (img )
@@ -147,10 +155,10 @@ def _draw_multi(img: Image.Image, text: str, text_x: int = 10, text_y: int = 10)
147155 random .randint (240 , 255 ),
148156 )
149157 current_x = text_x
150-
158+
151159 # 跳过空行
152160 if not line .strip ():
153- current_y += FONT_SIZE + 5
161+ current_y += style_config . font_size + 5
154162 continue
155163
156164 # 计算当前行的实际高度
@@ -161,7 +169,7 @@ def _draw_multi(img: Image.Image, text: str, text_x: int = 10, text_y: int = 10)
161169
162170 try :
163171 bbox = cute_font .getbbox (no_emoji_line )
164- line_height = int (bbox [3 ] - bbox [1 ]) if bbox else FONT_SIZE
172+ line_height = int (bbox [3 ] - bbox [1 ]) if bbox else style_config . font_size
165173
166174 for char in line :
167175 # 判断是否为 Emoji
@@ -175,12 +183,12 @@ def _draw_multi(img: Image.Image, text: str, text_x: int = 10, text_y: int = 10)
175183 char_bbox = cute_font .getbbox (char )
176184
177185 # 计算字符宽度
178- char_width = char_bbox [2 ] - char_bbox [0 ] if char_bbox else FONT_SIZE // 2
179-
186+ char_width = char_bbox [2 ] - char_bbox [0 ] if char_bbox else style_config . font_size // 2
187+
180188 # 检查是否超出边界
181- if current_x + char_width > img .width - TEXT_PADDING :
189+ if current_x + char_width > img .width - style_config . text_padding :
182190 current_x = text_x
183- current_y += max (line_height , FONT_SIZE ) + 5
191+ current_y += max (line_height , style_config . font_size ) + 5
184192
185193 # 重新绘制当前字符
186194 if is_emoji_char and emoji_font :
@@ -191,13 +199,13 @@ def _draw_multi(img: Image.Image, text: str, text_x: int = 10, text_y: int = 10)
191199 current_x += char_width
192200
193201 # 移动到下一行
194- next_y = current_y + max (line_height , FONT_SIZE ) + 5
195- if next_y > img .height - TEXT_PADDING :
196- current_y = img .height - TEXT_PADDING - max (line_height , FONT_SIZE )
202+ next_y = current_y + max (line_height , style_config . font_size ) + 5
203+ if next_y > img .height - style_config . text_padding :
204+ current_y = img .height - style_config . text_padding - max (line_height , style_config . font_size )
197205 else :
198206 current_y = next_y
199207 except Exception as e :
200208 logger .error (f"[QQDetail] 绘制文本时出错: { e } " )
201- current_y += FONT_SIZE + 5
209+ current_y += style_config . font_size + 5
202210
203211 return img
0 commit comments