2022-08-29 22:14:42 +08:00

211 lines
11 KiB
Python

import asyncio
import datetime
import math
from typing import Tuple, List, Optional
from LittlePaimon.config import RESOURCE_BASE_PATH
from LittlePaimon.utils.files import load_image
from LittlePaimon.utils.message import MessageBuild
from LittlePaimon.utils.image import PMImage, get_qq_avatar, font_manager as fm
from .models import GachaLogInfo, FiveStarItem, FourStarItem
avatar_point = [69, 156, 259, 358, 456, 558, 645, 746, 840, 945]
line_point = [88, 182, 282, 378, 477, 574, 673, 769, 864, 967]
bar_color = [('#b6d6f2', '#3d6e99'), ('#c8b6f2', '#593d99'), ('#abede0', '#3a9382')]
name_level_color = ['#ff3600', '#ff7800', '#ffb400', 'black']
small_avatar_cache = {}
async def get_avatar(qid: str, size: Tuple[int, int] = (146, 146)) -> PMImage:
avatar = await get_qq_avatar(qid)
await avatar.resize(size)
await avatar.to_circle('circle')
await avatar.add_border(6, '#ddcdba', 'circle')
return avatar
async def small_avatar(info: FiveStarItem):
if info.name in small_avatar_cache:
return small_avatar_cache[info.name]
bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'small_circle.png'))
img = PMImage(
await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png',
size=(42, 42)))
await img.to_circle('circle')
await bg.paste(img.image, (2, 2))
small_avatar_cache[info.name] = bg
return bg
async def detail_avatar(info: FiveStarItem):
bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'item_avatar_5.png'))
img = PMImage(
await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png',
size=(123, 123)))
await img.to_circle('circle')
await bg.paste(img.image, (14, 26))
await bg.text(info.name, (0, bg.width), 162, fm.get('hywh', 24),
'#ff3600' if info.name not in {'迪卢克', '刻晴', '莫娜', '七七', ''} and info.type == '角色' else '#33231a', 'center')
if info.count < (20 if info.type == '角色' else 15):
color = name_level_color[0]
elif (20 if info.type == '角色' else 15) <= info.count < (40 if info.type == '角色' else 30):
color = name_level_color[1]
elif (40 if info.type == '角色' else 30) <= info.count < (70 if info.type == '角色' else 60):
color = name_level_color[2]
else:
color = name_level_color[3]
await bg.text(str(info.count), 144, 6, fm.get('bahnschrift_regular', 48, 'Bold'), color, 'right')
return bg
async def draw_pool_detail(pool_name: str, data: List[FiveStarItem], total_count: int, not_out: int) -> Optional[
PMImage]:
if not data:
return None
total_height = 181 + (446 if len(data) > 3 else 0) + 47 + 204 * math.ceil(len(data) / 6) + 20 + 60
img = PMImage(size=(1009, total_height), mode='RGBA', color=(255, 255, 255, 0))
# 橙线
await img.paste(await load_image(RESOURCE_BASE_PATH / 'general' / 'line.png'), (0, 0))
await img.text(f'{pool_name[:2]}卡池', 25, 11, fm.get('hywh', 30), 'white')
# 数据
await img.text('平均出货', 174, 142, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(round((total_count - not_out) / len(data), 2)), (176, 270), 84,
fm.get('bahnschrift_regular', 48, 'Regular'),
'#252525', 'center')
await img.text('总抽卡数', 471, 142, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(total_count), (472, 567), 84, fm.get('bahnschrift_regular', 48, 'Regular'),
'#252525', 'center')
await img.text('未出五星', 753, 142, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(not_out), (755, 848), 84, fm.get('bahnschrift_regular', 48, 'Regular'),
'#252525', 'center')
# 折线图
if len(data) > 3:
last_point = None
await img.paste(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'broken_line_bg.png'), (1, 181))
i = 0
for chara in data[-10:]:
height = int(583 - (chara.count / 90) * 340)
if last_point:
await img.draw_line(last_point, (line_point[i], height), '#ff6f30', 4)
last_point = (line_point[i], height)
i += 1
i = 0
for chara in data[-10:]:
height = int(583 - (chara.count / 90) * 340)
point = avatar_point[i]
await img.paste(await small_avatar(chara), (point, height - 23))
await img.text(str(chara.count), (point, point + 44), height - 48, fm.get('bahnschrift_regular', 30,
'Regular'), '#040404', 'center')
i += 1
# 详细数据统计
chara_bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'detail_bg.png'))
await chara_bg.stretch((47, chara_bg.height - 20), 204 + 204 * (len(data) // 6), 'height')
await img.paste(chara_bg, (1, 655 if len(data) > 3 else 181))
await asyncio.gather(
*[img.paste(await detail_avatar(data[i]),
(18 + i % 6 * 163, (708 if len(data) > 3 else 234) + i // 6 * 204))
for i in range(len(data))])
return img
async def draw_four_star(info: FourStarItem) -> PMImage:
bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'item_avatar_4.png'))
img = PMImage(
await load_image(RESOURCE_BASE_PATH / ('avatar' if info.type == '角色' else 'weapon') / f'{info.icon}.png',
size=(123, 123)))
await img.to_circle('circle')
await bg.paste(img, (34, 26))
await bg.text(info.name, (0, bg.width), 163, fm.get('hywh', 24), '#221a33', 'center')
await bg.text(str(info.num['角色祈愿']), (4, 64), 209, fm.get('bahnschrift_regular', 36, 'Bold'), '#3d6e99',
'center')
await bg.text(str(info.num['武器祈愿']), (65, 125), 209, fm.get('bahnschrift_regular', 36, 'Bold'), '#593d99',
'center')
await bg.text(str(info.num['常驻祈愿'] + info.num['新手祈愿']), (126, 186), 209, fm.get('bahnschrift_regular', 36, 'Bold'),
'#3a9381',
'center')
return bg
async def draw_four_star_detail(data: List[FourStarItem]):
bar = await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'four_star_bar.png')
total_height = 105 + 260 * math.ceil(len(data) / 5)
bg = PMImage(size=(1008, total_height), mode='RGBA', color=(255, 255, 255, 0))
await bg.paste(bar, (0, 0))
await asyncio.gather(*[bg.paste(await draw_four_star(data[i]), (i % 5 * 204, 105 + i // 5 * 260)) for i in range(len(data))])
return bg
async def draw_gacha_log(user_id: str, uid: str, nickname: Optional[str], signature: Optional[str], gacha_log: GachaLogInfo):
img = PMImage(size=(1080, 8000), mode='RGBA', color=(255, 255, 255, 0))
bg = PMImage(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'bg.png'))
line = await load_image(RESOURCE_BASE_PATH / 'general' / 'line.png')
# 头像
avatar = await get_avatar(user_id, (108, 108))
await img.paste(avatar, (38, 45))
# 昵称
await img.text(nickname, 166, 49, fm.get('hywh', 48), '#252525')
# 签名和uid
if signature:
await img.text(signature, 165, 116, fm.get('hywh', 32), '#252525')
nickname_length = img.text_length(nickname, fm.get('hywh', 40))
await img.text(f'UID{uid}', 166 + nickname_length + 36, 58,
fm.get('bahnschrift_regular', 48, 'Regular'),
'#252525')
else:
await img.text(f'UID{uid}', 165, 103, fm.get('bahnschrift_regular', 48, 'Regular'), '#252525')
data5, data4, data_not = gacha_log.get_statistics()
# 数据总览
await img.paste(line, (36, 181))
await img.text('数据总览', 60, 192, fm.get('hywh', 30), 'white')
await img.text(f'CREATED BY LITTLEPAIMON AT {datetime.datetime.now().strftime("%m-%d %H:%M")}', 1025, 196,
fm.get('bahnschrift_regular', 30), '#252525', 'right')
total_gacha_count = sum(len(pool) for pool in gacha_log.item_list.values())
out_gacha_count = total_gacha_count - sum(data_not.values())
total_five_star_count = sum(len(pool) for pool in data5.values())
five_star_average = round(out_gacha_count / total_five_star_count, 2) if total_five_star_count else 0
await img.text('平均出货', 209, 335, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(five_star_average), (211, 305), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center')
await img.text('总抽卡数', 506, 335, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(total_gacha_count), (507, 602), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center')
await img.text('总计出金', 788, 335, fm.get('hywh', 24), (24, 24, 24, 102))
await img.text(str(total_five_star_count), (789, 884), 286, fm.get('bahnschrift_regular', 48), '#040404', 'center')
four_star_detail = await draw_four_star_detail(list(data4.values()))
if total_five_star_count:
chara_pool_per = round(len(data5['角色祈愿']) / total_five_star_count, 3)
weapon_pool_per = round(len(data5['武器祈愿']) / total_five_star_count, 3)
new_pool_per = round((len(data5['常驻祈愿']) + len(data5['新手祈愿'])) / total_five_star_count, 3)
now_used_width = 56
pers = [chara_pool_per, weapon_pool_per, new_pool_per]
i = 0
for per in pers:
if per >= 0.03:
await img.draw_rectangle((now_used_width, 399, now_used_width + int(per * 967), 446),
bar_color[i][0])
if per >= 0.1:
await img.text(f'{per * 100}%', now_used_width + 18, 410, fm.get('bahnschrift_regular', 30, 'Bold'),
bar_color[i][1])
now_used_width += int(per * 967)
i += 1
await img.paste(await load_image(RESOURCE_BASE_PATH / 'gacha_log' / 'text.png'), (484, 464))
now_height = 525
for pool_name, data in data5.items():
pool_detail_img = await draw_pool_detail(pool_name, data, len(gacha_log.item_list[pool_name]),
data_not[pool_name])
if pool_detail_img:
await img.paste(pool_detail_img, (36, now_height))
now_height += pool_detail_img.height
now_height += 10
await img.paste(four_star_detail, (36, now_height))
now_height += four_star_detail.height + 30
await img.crop((0, 0, 1080, now_height))
await bg.stretch((50, bg.height - 50), now_height - 100, 'height')
else:
await img.paste(four_star_detail, (36, 410))
await img.crop((0, 0, 1080, 410 + four_star_detail.height + 30))
await bg.stretch((50, bg.height - 50), img.height - 100, 'height')
await bg.paste(img, (0, 0))
return MessageBuild.Image(bg, mode='RGB', quality=80)