Files
HeXi/hexi/plugins/nonebot_plugin_bf_bot/image_builder_2.py
T
sansenhoshiandClaude b61d09f09f Add HeXi bot codebase: custom plugins, web frontends, tests
- hexi core: message handling, rate limiting, cooldown, plugin manager
- Custom plugins: BF stats, daily check-in, quotes, persona cards, etc.
- Community plugins vendored under hexi/plugins with local fixes
- Web admin frontends (learning-chat, persona-admin), unified hexi/web
- Tests for rate_limit/cooldown/memes/persona; poetry.lock

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-01 13:13:40 +08:00

201 lines
8.6 KiB
Python

import datetime
import io
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from . import add_commas_with_padding, add_commas
from .data_utils import *
from .img_utils import *
from .param import *
# from img_utils import *
# from data_utils import *
# from param import *
filepath = os.path.dirname(__file__).replace("\\", "/")
# 字体
font_XXL = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 72)
font_XL = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 64)
font_L = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 48)
font_ML = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 42)
font_M = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 36)
font_MS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 32)
font_S = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 28)
font_XS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 24)
font_XXS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 18)
font_XXXS = ImageFont.truetype(f"{filepath}/font/bf-sub-headline-bold 等宽数字.ttf", 16)
font_XXL_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 72)
font_XL_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 64)
font_L_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 48)
font_ML_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 42)
font_M_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 36)
font_MS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 32)
font_S_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 28)
font_XS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 24)
font_XXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 18)
font_XXXS_CH = ImageFont.truetype(f"{filepath}/font/演示创黑FLY.ttf", 16)
async def build_bf6_horizon_stat(game="bfv",
qq_id=2931,
player="Player001",
pid=1145141919,
rank=114,
kd=1.25,
kpm=2.3,
spm=500,
acc=25.6,
head_shots=10,
time_play=100,
kills=100,
kill_assists=150,
revives=114,
repairs=1000,
captured=300,
score=145674,
wins=10,
loses=10,
destroyed=514,
best_weapon="M1907 SF",
best_vehicle="坦克",
best_class="Support", ):
# 获取当前事件并且格式化
now = datetime.datetime.now()
formatted_time = now.strftime("%Y-%m-%d %H:%M:%S")
# ---------------- OpenCV 部分 ----------------
# 载入模板
img = cv2.imread(f"{filepath}/template/horizon/{game}.png", cv2.IMREAD_COLOR)
# ---------------- 头像 ----------------
avatar = cv2.imread(f"{filepath}/avatar_cache/0.png", cv2.IMREAD_UNCHANGED)
avatar = cv2.resize(avatar, (134, 134), interpolation=cv2.INTER_AREA)
# 粘贴头像(保持原有坐标)
y, x = 65, 105
h, w = avatar.shape[:2]
img[y:y + h, x:x + w] = avatar
# ---------------- 勋章 ----------------
# ---------------- PIL 部分 ----------------
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
# 玩家信息 105+160
draw.text((265, 80), f"{player}", fill="white", font=font_XL)
draw.text((265, 140), f"UID: {pid}", fill="#CCCCCC", font=font_L)
# 等级
pil_img = paste_rank_icon(int(rank), pil_img)
draw_centered_text(draw=draw, text=f"{rank}", x_left=950, x_right=1075, y=205,
font=font_M,
fill="white")
# draw.text((740, 212), f"{rank}", fill="white", font=font_M)
# 游玩时长
draw.text((2300, 350), f"{time_play:.1f} H", fill="white", font=font_L)
# 场次
draw.text((2300, 450), f"{wins + loses}", fill="white", font=font_L)
# 生涯总览
# 命中率
acc_format = round(float(acc.replace("%", "")), 1)
draw_centered_text(draw=draw, text=f"{acc_format}%", x_left=1210, x_right=1303, y=363,
font=font_M,
fill="white")
# 爆头率
if head_shots:
head_shots = head_shots.replace("%", "")
else:
head_shots = 0
draw_centered_text(draw=draw, text=f"{round(float(head_shots), 1)}%", x_left=1210, x_right=1303, y=458,
font=font_M,
fill="white")
# kd
draw.text((1350, 355), text=str(kd), fill="white", font=font_XXL)
# kpm
draw.text((1610, 355), text=f"{kpm:.2f}", fill="white", font=font_XXL)
# spm
draw.text((1870, 355), text=f"{int(spm)}", fill="white", font=font_XXL)
# ---------------- 数据 ----------------
# 击杀
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=805,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(kills)), x_left=1165, x_right=1455, y=805,
font=font_L, fill="white")
# 急救
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=1080,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(revives)), x_left=1165, x_right=1455, y=1080,
font=font_L, fill="white")
# 摧毁
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1165, x_right=1455, y=1350,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(destroyed)), x_left=1165, x_right=1455, y=1350,
font=font_L, fill="white")
# 右侧
# 占领
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=745,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(captured)), x_left=1485, x_right=1780, y=745,
font=font_L, fill="white")
# 得分
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=955,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(score)), x_left=1485, x_right=1780, y=955,
font=font_L, fill="white")
# 助攻
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=1160,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(kill_assists)), x_left=1485, x_right=1780, y=1160,
font=font_L, fill="white")
# 修理
draw_right_aligned_text(draw=draw, text=add_commas_with_padding("0", 10), x_left=1485, x_right=1780, y=1370,
font=font_L,
fill="#444445")
draw_right_aligned_text(draw=draw, text=add_commas(str(repairs)), x_left=1485, x_right=1780, y=1370,
font=font_L, fill="white")
# 底部栏
# 最佳⭐
pil_img = build_best_bf6_horizon(draw, pil_img, best_weapon, best_vehicle, best_class, game)
# 生成时间
draw.text((1750, 10), f"{formatted_time}/战地中文社区/维护 SANSENHOSHI", fill=(154, 132, 149), font=font_XS_CH)
# ---------------- 转回 OpenCV ----------------
img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
# ---------------- 雷达图 ----------------
# 保存
success, buffer = cv2.imencode(".png", img)
if success:
b_io = io.BytesIO(buffer)
return b_io # 就可以直接 return 二进制流
else:
return None
def build_best_bf6_horizon(draw, pil_img, best_weapon, best_vehicle, best_class, game):
return 1