全新的帮助图绘制函数, 用于统一管理插件帮助

This commit is contained in:
KimigaiiWuyi 2024-09-18 02:45:34 +08:00
parent 6b70fca38a
commit 8c61c83b83
18 changed files with 265 additions and 250 deletions

BIN
ICON.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -1,16 +1,13 @@
import asyncio
import inspect import inspect
from gsuid_core.aps import scheduler from gsuid_core.aps import scheduler
from gsuid_core.logger import logger from gsuid_core.logger import logger
from gsuid_core.server import GsServer from gsuid_core.server import GsServer
from gsuid_core.help.draw_help import get_help_img
gss = GsServer() gss = GsServer()
if not gss.is_load: if not gss.is_load:
gss.is_load = True gss.is_load = True
gss.load_plugins() gss.load_plugins()
asyncio.run(get_help_img())
repeat_jobs = {} repeat_jobs = {}
for i in scheduler.get_jobs(): for i in scheduler.get_jobs():

View File

@ -1,232 +0,0 @@
from pathlib import Path
from typing import Dict, List, Tuple, Union, Optional
from PIL import Image, ImageDraw
from gsuid_core.sv import SV, Plugins
# from gsuid_core.utils.image.image_tools import get_color_bg
from gsuid_core.utils.fonts.fonts import core_font
from gsuid_core.utils.plugins_config.gs_config import pic_gen_config
pic_quality: int = pic_gen_config.get_config('PicQuality').data
TEXT_PATH = Path(__file__).parent / 'texture2d'
CORE_HELP_IMG = Path(__file__).parent / 'core_help.jpg'
plugin_title = 92
sv_title = 67
tag_color = {
'prefix': (137, 228, 124),
'suffix': (124, 180, 228),
'file': (190, 228, 217),
'keyword': (217, 228, 254),
'fullmatch': (228, 124, 124),
'regex': (225, 228, 124),
'command': (228, 124, 124),
'message': (176, 150, 198),
'other': (228, 190, 191),
}
tag_text: Dict[str, str] = {
'prefix': '前缀',
'suffix': '后缀',
'file': '文件',
'keyword': '包含',
'fullmatch': '完全',
'regex': '正则',
'command': '命令',
'message': '消息',
'other': '其他',
}
tags: Dict[str, Optional[Image.Image]] = {
'prefix': None,
'suffix': None,
'file': None,
'keyword': None,
'fullmatch': None,
'regex': None,
'command': None,
'other': None,
'message': None,
}
def get_tag(tag_type: str) -> Image.Image:
cache = tags[tag_type]
if cache is not None:
return cache
text = tag_text[tag_type]
tag = Image.new('RGBA', (60, 40))
tag_draw = ImageDraw.Draw(tag)
tag_draw.rounded_rectangle((7, 5, 53, 35), 10, tag_color[tag_type])
tag_draw.text((30, 20), text, (62, 62, 62), core_font(22), 'mm')
tags[tag_type] = tag
return tag
def get_command_bg(command: str, tag_type: str):
img = Image.new('RGBA', (220, 40))
img_draw = ImageDraw.Draw(img)
img_draw.rounded_rectangle((6, 5, 160, 35), 10, (230, 202, 167))
img_draw.text((83, 20), command, (62, 62, 62), core_font(20), 'mm')
tag = get_tag(tag_type)
img.paste(tag, (160, 0), tag)
return img
def _c(data: Union[int, str, bool]) -> Tuple[int, int, int]:
gray_color = (184, 184, 184)
if isinstance(data, bool):
color = tag_color['prefix'] if data else gray_color
elif isinstance(data, str):
color = (
tag_color['prefix']
if data == 'ALL'
else tag_color['command'] if data == 'GROUP' else tag_color['file']
)
else:
colors = list(tag_color.values())
if data <= len(colors) and data >= 0:
color = colors[data]
else:
color = tag_color['other']
return color
def _t(data: Union[int, str, bool]) -> str:
if isinstance(data, bool):
text = '开启' if data else '关闭'
elif isinstance(data, str):
text = (
'不限' if data == 'ALL' else '群聊' if data == 'GROUP' else '私聊'
)
else:
texts = [
'主人',
'超管',
'群主',
'管理',
'频管',
'子管',
'正常',
'',
'',
]
if data <= len(texts) and data >= 0:
text = [
'主人',
'超管',
'群主',
'管理',
'频管',
'子管',
'正常',
'',
'',
][data]
else:
text = '最低'
return text
def get_plugin_bg(plugin: Plugins, sv_list: List[SV]):
plugin_name = plugin.name
img_list: List[Image.Image] = []
for sv in sv_list:
sv_img = Image.new(
'RGBA',
(
900,
sv_title + ((len(sv.TL) + 3) // 4) * 40,
),
)
sv_img_draw = ImageDraw.Draw(sv_img)
index = 0
for type in sv.TL:
for trigger_name in sv.TL[type]:
tg_img = get_command_bg(
trigger_name, sv.TL[type][trigger_name].type
)
sv_img.paste(
tg_img,
(6 + 220 * (index % 4), 67 + 40 * (index // 4)),
tg_img,
)
index += 1
index = 0
sv_img_draw.rounded_rectangle((15, 19, 25, 50), 10, (62, 62, 62))
sv_img_draw.text((45, 31), sv.name, (62, 62, 62), core_font(36), 'lm')
sv_img_draw.rounded_rectangle((710, 15, 760, 50), 10, _c(sv.enabled))
sv_img_draw.rounded_rectangle((770, 15, 820, 50), 10, _c(sv.pm))
sv_img_draw.rounded_rectangle((830, 15, 880, 50), 10, _c(sv.area))
sv_img_draw.text(
(735, 32), _t(sv.enabled), (62, 62, 62), core_font(22), 'mm'
)
sv_img_draw.text(
(795, 32), _t(sv.pm), (62, 62, 62), core_font(22), 'mm'
)
sv_img_draw.text(
(855, 32), _t(sv.area), (62, 62, 62), core_font(22), 'mm'
)
img_list.append(sv_img)
img = Image.new(
'RGBA',
(
900,
plugin_title + sum([i.size[1] for i in img_list]),
),
)
img_draw = ImageDraw.Draw(img)
img_draw.rounded_rectangle((10, 26, 890, 76), 10, (230, 202, 167))
img_draw.text((450, 51), plugin_name, (62, 62, 62), core_font(42), 'mm')
temp = 0
for _img in img_list:
img.paste(_img, (0, 92 + temp), _img)
temp += _img.size[1]
return img
async def get_help_img() -> Image.Image:
from gsuid_core.sv import SL
content = SL.detail_lst
img_list: List[Image.Image] = []
for plugin in content:
plugin_img = get_plugin_bg(plugin, content[plugin])
img_list.append(plugin_img)
x = 900
y = 200 + sum([i.size[1] for i in img_list])
# img = await get_color_bg(x, y)
img = Image.new('RGBA', (x, y), (255, 255, 255))
title = Image.open(TEXT_PATH / 'title.png')
# white = Image.new('RGBA', img.size, (255, 255, 255, 120))
# img.paste(white, (0, 0), white)
img.paste(title, (0, 50), title)
temp = 0
for _img in img_list:
img.paste(_img, (0, 340 + temp), _img)
temp += _img.size[1]
img = img.convert('RGB')
img.save(
CORE_HELP_IMG,
format='JPEG',
quality=pic_quality,
subsampling=0,
)
return img

View File

@ -0,0 +1,223 @@
import random
from pathlib import Path
from copy import deepcopy
from typing import Dict, Literal, Optional
from PIL import Image, ImageDraw
from gsuid_core.help.model import PluginHelp
from gsuid_core.data_store import get_res_path
from gsuid_core.utils.fonts.fonts import core_font
from gsuid_core.utils.image.convert import convert_img
from gsuid_core.utils.plugins_config.gs_config import pic_gen_config
from gsuid_core.utils.image.image_tools import (
crop_center_img,
draw_color_badge,
)
cache: Dict[str, int] = {}
ICON_PATH = Path(__file__).parent / 'new_icon'
TEXT_PATH = Path(__file__).parent / 'texture2d'
pic_quality: int = pic_gen_config.get_config('PicQuality').data
def find_icon(name: str, icon_path: Path = ICON_PATH):
for icon in icon_path.glob('*.png'):
if icon.stem == name:
_r = icon
break
else:
for icon in icon_path.glob('*.png'):
if icon.stem in name:
_r = icon
break
else:
if (icon_path / '通用.png').exists():
_r = icon_path / '通用.png'
else:
_r = random.choice(list(icon_path.iterdir()))
return Image.open(_r)
async def get_new_help(
plugin_name: str,
plugin_info: Dict[str, str],
plugin_icon: Image.Image,
plugin_help: Dict[str, PluginHelp],
plugin_prefix: str = '',
help_mode: Literal['dark', 'light'] = 'dark',
banner_bg: Optional[Image.Image] = None,
banner_sub_text: str = '💖且听风吟。',
help_bg: Optional[Image.Image] = None,
cag_bg: Optional[Image.Image] = None,
item_bg: Optional[Image.Image] = None,
icon_path: Path = ICON_PATH,
footer: Optional[Image.Image] = None,
enable_cache: bool = True,
):
help_path = get_res_path('help') / f'{plugin_name}.jpg'
if (
help_path.exists()
and plugin_name in cache
and cache[plugin_name]
and enable_cache
):
return await convert_img(Image.open(help_path))
if banner_bg is None:
banner_bg = Image.open(TEXT_PATH / 'banner_bg.jpg')
if help_bg is None:
help_bg = Image.open(TEXT_PATH / 'help_bg.jpg')
if cag_bg is None:
cag_bg = Image.open(TEXT_PATH / 'cag_bg.png')
if footer is None:
footer = Image.open(TEXT_PATH / f'footer_{help_mode}.png')
if item_bg is None:
item_bg = Image.open(TEXT_PATH / f'item_{help_mode}.png')
if help_mode == 'dark':
main_color = (255, 255, 255)
sub_color = (206, 206, 206)
else:
main_color = (0, 0, 0)
sub_color = (102, 102, 102)
banner_bg = banner_bg.convert('RGBA')
help_bg = help_bg.convert('RGBA')
cag_bg = cag_bg.convert('RGBA')
item_bg = item_bg.convert('RGBA')
footer = footer.convert('RGBA')
plugin_icon = plugin_icon.resize((128, 128))
# 准备计算整体帮助图大小
w, h = 1545, 300 + footer.height
cag_num = len(plugin_help)
h += cag_num * 100
for cag in plugin_help:
cag_data = plugin_help[cag]['data']
sv_num = len(cag_data)
h += (((sv_num - 1) // 3) + 1) * 175
# 基准图
img = crop_center_img(help_bg, w, h)
# 绘制banner
banner_bg.paste(plugin_icon, (89, 88), plugin_icon)
banner_draw = ImageDraw.Draw(banner_bg)
_banner_name = plugin_name + '帮助'
banner_draw.text(
(262, 128),
_banner_name,
main_color,
font=core_font(50),
anchor='lm',
)
banner_draw.text(
(262, 183),
banner_sub_text,
sub_color,
font=core_font(30),
anchor='lm',
)
x1, y1, x2, y2 = core_font(50).getbbox(_banner_name)
plugin_name_len = int(x2 - x1)
for key, value in plugin_info.items():
if value == 'any' or not value:
value = (252, 69, 69)
badge = draw_color_badge(
key,
value,
core_font(30),
(255, 255, 255),
)
banner_bg.paste(
badge,
(262 + plugin_name_len + 10, 128 - badge.height // 2),
badge,
)
plugin_name_len += badge.width + 10
img.paste(banner_bg, (0, 0), banner_bg)
# 开始粘贴服务
hs = 0
for cag in plugin_help:
sv = plugin_help[cag]
cag_bar = deepcopy(cag_bg)
cag_desc = sv['desc']
cag_data = sv['data']
cag_draw = ImageDraw.Draw(cag_bar)
cag_draw.text(
(136, 50),
cag,
main_color,
font=core_font(45),
anchor='lm',
)
bbox = core_font(45).getbbox(cag)
cag_name_len = int(bbox[2] - bbox[0])
cag_draw.text(
(136 + cag_name_len + 15, 55),
cag_desc,
sub_color,
font=core_font(30),
anchor='lm',
)
img.paste(cag_bar, (0, 280 + hs), cag_bar)
for i, command in enumerate(cag_data):
command_name = command['name']
# command_desc = command['desc']
command_eg = command['eg']
command_bg = deepcopy(item_bg)
icon = find_icon(command_name, icon_path)
command_bg.paste(icon, (6, 12), icon)
command_draw = ImageDraw.Draw(command_bg)
command_draw.text(
(160, 67),
plugin_prefix + command_name,
main_color,
font=core_font(40),
anchor='lm',
)
command_draw.text(
(160, 116),
plugin_prefix + command_eg,
sub_color,
font=core_font(26),
anchor='lm',
)
x, y = 45 + (i % 3) * 490, 370 + (i // 3) * 175 + hs
img.paste(command_bg, (x, y), command_bg)
hs += (((len(cag_data) - 1) // 3) + 1) * 175 + 100
img.paste(
footer,
((w - footer.width) // 2, h - footer.height - 20),
footer,
)
img = img.convert('RGB')
img.save(
help_path,
'JPEG',
quality=pic_quality,
subsampling=0,
)
cache[plugin_name] = 1
return await convert_img(img)

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -2,8 +2,6 @@ from gsuid_core.sv import SV
from gsuid_core.bot import Bot from gsuid_core.bot import Bot
from gsuid_core.models import Event from gsuid_core.models import Event
from gsuid_core.logger import logger from gsuid_core.logger import logger
from gsuid_core.utils.image.convert import convert_img
from gsuid_core.help.draw_help import CORE_HELP_IMG, get_help_img
sv_core_help_img = SV('Core帮助') sv_core_help_img = SV('Core帮助')
@ -11,10 +9,4 @@ sv_core_help_img = SV('Core帮助')
@sv_core_help_img.on_fullmatch(('core帮助', 'Core帮助')) @sv_core_help_img.on_fullmatch(('core帮助', 'Core帮助'))
async def send_core_htlp_msg(bot: Bot, ev: Event): async def send_core_htlp_msg(bot: Bot, ev: Event):
logger.info('[早柚核心] 开始执行[帮助图]') logger.info('[早柚核心] 开始执行[帮助图]')
if CORE_HELP_IMG.exists(): await bot.send('该功能已删除...')
img = await convert_img(CORE_HELP_IMG)
else:
img = await get_help_img()
img = await convert_img(img)
logger.info('[早柚核心] 帮助图获取成功!')
await bot.send(img)

View File

@ -11,5 +11,5 @@ AMBR_GCG_DETAIL = AMBR_BASE_URL + '/v2/chs/gcg/{}?vh=37F4'
AMBR_MONSTER_LIST = AMBR_BASE_URL + '/v2/chs/monster?vh=37F4' AMBR_MONSTER_LIST = AMBR_BASE_URL + '/v2/chs/monster?vh=37F4'
AMBR_ICON_URL = AMBR_BASE_URL + '/assets/UI' AMBR_ICON_URL = AMBR_BASE_URL + '/assets/UI'
AMBR_MONSTER_ICON_URL = f'{AMBR_ICON_URL}/monster/' AMBR_MONSTER_ICON_URL = f'{AMBR_ICON_URL}/monster/'
AMBR_DAILY_URL = AMBR_BASE_URL + '/v2/chs/dailyDungeon?vh=37F4' AMBR_DAILY_URL = AMBR_BASE_URL + '/api/v2/chs/dailyDungeon?vh=37F4'
AMBR_UPGRADE_URL = AMBR_BASE_URL + '/v2/chs/upgrade?vh=40F3' AMBR_UPGRADE_URL = AMBR_BASE_URL + '/api/v2/chs/upgrade?vh=40F3'

Binary file not shown.

View File

@ -2,7 +2,7 @@ from pathlib import Path
from PIL import ImageFont from PIL import ImageFont
FONT_ORIGIN_PATH = Path(__file__).parent / 'yuanshen_origin.ttf' FONT_ORIGIN_PATH = Path(__file__).parent / 'MiSans-Bold.ttf'
def core_font(size: int) -> ImageFont.FreeTypeFont: def core_font(size: int) -> ImageFont.FreeTypeFont:

View File

@ -6,6 +6,7 @@ from typing import Tuple, Union, Optional
import httpx import httpx
from httpx import get from httpx import get
from utils.fonts.fonts import core_font
from PIL import Image, ImageDraw, ImageFont, ImageFilter from PIL import Image, ImageDraw, ImageFont, ImageFilter
from gsuid_core.models import Event from gsuid_core.models import Event
@ -20,6 +21,31 @@ def get_div():
return Image.open(TEXT_PATH / 'div.png') return Image.open(TEXT_PATH / 'div.png')
def draw_color_badge(
text: str,
color: Union[Tuple[int, int, int], str] = (252, 69, 69),
font: ImageFont.FreeTypeFont = core_font(30),
font_color: Tuple[int, int, int] = (255, 255, 255),
):
x1, y1, x2, y2 = font.getbbox(text)
offset_x = int((x2 - x1) * 0.3)
offset_y = int((y2 - y1) * 0.5)
x3, y3, x4, y4 = x1 - offset_x, y1 - offset_y, x2 + offset_x, y2 + offset_y
w, h = int(x4 - x3), int(y4 - y3)
center_x, center_y = int(w // 2), int(h // 2)
img = Image.new('RGBA', (w, h), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
draw.rounded_rectangle((0, 0, w, h), fill=color, radius=20)
draw.text(
(center_x, center_y),
text,
font=font,
fill=font_color,
anchor='mm',
)
return img
def get_status_icon(status: Union[int, bool]) -> Image.Image: def get_status_icon(status: Union[int, bool]) -> Image.Image:
if status: if status:
img = Image.open(TEXT_PATH / 'yes.png') img = Image.open(TEXT_PATH / 'yes.png')
@ -89,9 +115,9 @@ async def shift_image_hue(img: Image.Image, angle: float = 30) -> Image.Image:
for y in range(img.height): for y in range(img.height):
for x in range(img.width): for x in range(img.width):
h, s, v = pixels[x, y] h, s, v = pixels[x, y] # type: ignore
h = (h + hue_shift) % 360 h = (h + hue_shift) % 360
pixels[x, y] = (h, s, v) pixels[x, y] = (h, s, v) # type: ignore
img = img.convert('RGBA') img = img.convert('RGBA')
img.putalpha(alpha) img.putalpha(alpha)
@ -394,7 +420,16 @@ class CustomizeImage:
img = img.convert("RGBA") img = img.convert("RGBA")
img = img.resize((1, 1), resample=0) img = img.resize((1, 1), resample=0)
dominant_color = img.getpixel((0, 0)) dominant_color = img.getpixel((0, 0))
return dominant_color if isinstance(dominant_color, float):
_dominant_color = tuple(
[int(dominant_color * 255) for _ in range(3)]
) # type: ignore
elif dominant_color is None or isinstance(dominant_color, int):
_dominant_color: Tuple[int, int, int] = (255, 255, 255)
else:
_dominant_color = dominant_color # type: ignore
return _dominant_color
@staticmethod @staticmethod
def get_bg_color( def get_bg_color(