get_help添加更多特性

This commit is contained in:
KimigaiiWuyi 2023-08-20 19:23:44 +08:00
parent 721e4dbe2e
commit ebb9c225aa
42 changed files with 129 additions and 50 deletions

View File

@ -1,18 +1,50 @@
from pathlib import Path
from copy import deepcopy
from typing import Dict, List, Tuple, Callable, Optional
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw, ImageFont, ImageFilter
from gsuid_core.data_store import get_res_path
from gsuid_core.utils.image.convert import convert_img
from gsuid_core.utils.image.image_tools import (
crop_center_img,
easy_alpha_composite,
)
from gsuid_core.utils.image.image_tools import crop_center_img
from .model import PluginHelp
cache: Dict[str, int] = {}
MICON_PATH = Path(__file__).parent / 'icon'
DEFAULT_ICON = MICON_PATH / '拼图.png'
def cx(w: int, x: int) -> int:
return int((w - x) / 2)
def _get_icon(name: str, ICON_PATH: Path) -> Optional[Image.Image]:
path = ICON_PATH / f'{name}.png'
icon = None
if path.exists():
icon = Image.open(path)
else:
for i in ICON_PATH.glob('*.png'):
if i.stem in name:
icon = Image.open(i)
break
return icon
def get_icon(name: str, ICON_PATH: Optional[Path]) -> Image.Image:
if ICON_PATH is not None:
icon = _get_icon(name, ICON_PATH)
if icon is None:
icon = _get_icon(name, MICON_PATH)
else:
icon = _get_icon(name, MICON_PATH)
if icon is None:
icon = Image.open(DEFAULT_ICON)
return icon.resize((36, 36))
async def get_help(
@ -28,6 +60,12 @@ async def get_help(
is_dark: bool = True,
text_color: Tuple[int, int, int] = (250, 250, 250),
sub_color: Optional[Tuple[int, int, int]] = None,
op_color: Optional[Tuple[int, int, int]] = None,
column: int = 5,
is_gaussian: bool = False,
gaussian_blur: int = 20,
is_icon: bool = True,
ICON_PATH: Optional[Path] = None,
) -> bytes:
help_path = get_res_path('help') / f'{name}.jpg'
@ -39,73 +77,112 @@ async def get_help(
elif sub_color is None and not is_dark:
sub_color = tuple(x + 50 for x in text_color if x < 205)
title = Image.new('RGBA', (900, 600))
if op_color is None and is_dark:
op_color = tuple(x - 90 for x in text_color if x > 90)
elif op_color is None and not is_dark:
op_color = tuple(x + 90 for x in text_color if x < 160)
w, h = 50 + 260 * column, 630
button_x = 260
button_y = 103 # 80
title = Image.new('RGBA', (w, 600))
icon = icon.resize((300, 300))
title.paste(icon, (300, 89), icon)
title.paste(badge, (0, 390), badge)
title.paste(icon, (cx(w, 300), 89), icon)
title.paste(badge, (cx(w, 900), 390), badge)
badge_s = badge.resize((720, 80))
title.paste(badge_s, (90, 480), badge_s)
title.paste(badge_s, (cx(w, 720), 480), badge_s)
title_draw = ImageDraw.Draw(title)
title_draw.text((450, 440), f'{name} 帮助', text_color, font(36), 'mm')
title_draw.text((450, 520), sub_text, sub_color, font(26), 'mm')
title_draw.text((cx(w, 0), 440), f'{name} 帮助', text_color, font(36), 'mm')
title_draw.text((cx(w, 0), 520), sub_text, sub_color, font(26), 'mm')
w, h = 900, 630
if is_dark:
icon_mask = Image.new('RGBA', (36, 36), (255, 255, 255))
else:
icon_mask = Image.new('RGBA', (36, 36), (10, 10, 10))
sv_img_list: List[Image.Image] = []
for sv_name in help_data:
tr_size = len(help_data[sv_name]['data'])
y = 100 + ((tr_size + 3) // 4) * 80
y = 100 + ((tr_size + column - 1) // column) * button_y
h += y
sv_img = Image.new('RGBA', (900, y))
# 生成单个服务的背景, 依据默认column
sv_img = Image.new('RGBA', (w, y))
sv_data = help_data[sv_name]['data']
sv_desc = help_data[sv_name]['desc']
bc = deepcopy(banner)
bc_draw = ImageDraw.Draw(bc)
bc_draw.text((30, 25), sv_name, text_color, font(35), 'lm')
if hasattr(font, 'getsize'):
size, _ = font(35).getsize(sv_name)
size, _ = font(35).getsize(sv_name) # type: ignore
else:
bbox = font(35).getbbox(sv_name)
size, _ = bbox[2] - bbox[0], bbox[3] - bbox[1]
bc_draw.text((42 + size, 30), sv_desc, sub_color, font(20), 'lm')
sv_img = easy_alpha_composite(sv_img, bc, (0, 10))
# sv_img.paste(bc, (0, 10), bc)
bc_draw.text((42 + size, 30), sv_desc, sub_color, font(20), 'lm')
sv_img.paste(bc, (0, 10), bc)
# sv_img = easy_alpha_composite(sv_img, bc, (0, 10))
# 开始绘制各个按钮
for index, tr in enumerate(sv_data):
bt = deepcopy(button)
bt_draw = ImageDraw.Draw(bt)
if len(tr['name']) > 8:
# 限制长度
if is_icon and len(tr['name']) > 7:
tr_name = tr['name'][:5] + '..'
elif len(tr['name']) > 10:
tr_name = tr['name'][:8] + '..'
else:
tr_name = tr['name']
bt_draw.text((105, 28), tr_name, text_color, font(26), 'mm')
bt_draw.text((105, 51), tr['eg'], sub_color, font(17), 'mm')
offset_x = 210 * (index % 4)
offset_y = 80 * (index // 4)
sv_img = easy_alpha_composite(
sv_img, bt, (26 + offset_x, 83 + offset_y)
)
# sv_img.paste(bt, (26 + offset_x, 83 + offset_y), bt)
if is_icon:
f = 38
icon = get_icon(tr['name'], ICON_PATH)
bt.paste(icon_mask, (13, 17), icon)
else:
f = 0
# 标题
bt_draw.text((20 + f, 28), tr_name, text_color, font(26), 'lm')
# 使用范例
bt_draw.text((20 + f, 50), tr['eg'], sub_color, font(17), 'lm')
# 简单介绍
bt_draw.text((20, 78), tr['desc'], op_color, font(16), 'lm')
offset_x = button_x * (index % column)
offset_y = button_y * (index // column)
sv_img.paste(bt, (25 + offset_x, 83 + offset_y), bt)
sv_img_list.append(sv_img)
img = crop_center_img(bg, w, h)
if is_gaussian:
img = img.filter(ImageFilter.GaussianBlur(gaussian_blur))
img.paste(title, (0, 0), title)
temp = 0
for _sm in sv_img_list:
img.paste(_sm, (0, 600 + temp), _sm)
temp += _sm.size[1]
img = img.convert('RGBA')
all_white = Image.new('RGBA', img.size, (255, 255, 255))
img = Image.alpha_composite(all_white, img)
img = img.convert('RGB')
help_path = get_res_path('help') / f'{name}.jpg'
img.save(
help_path,
'JPEG',
quality=85,
quality=89,
subsampling=0,
)
cache[name] = 1
return await convert_img(img)

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

View File

@ -100,7 +100,7 @@ def get_str_size(
line += i
if hasattr(font, 'getsize'):
size, _ = font.getsize(line)
size, _ = font.getsize(line) # type: ignore
else:
bbox = font.getbbox(line)
size, _ = bbox[2] - bbox[0], bbox[3] - bbox[1]

View File

@ -44,7 +44,7 @@ def draw_center_text_by_line(
x, y = pos
if hasattr(font, 'getsize'):
_, h = font.getsize('X')
_, h = font.getsize('X') # type: ignore
else:
bbox = font.getbbox('X')
_, h = 0, bbox[3] - bbox[1]
@ -54,7 +54,8 @@ def draw_center_text_by_line(
anchor = 'la' if not_center else 'mm'
for char in text:
if hasattr(font, 'getsize'):
size, _ = font.getsize(char) # 获取当前字符的宽度
# 获取当前字符的宽度
size, _ = font.getsize(char) # type: ignore
else:
bbox = font.getbbox(char)
size, _ = bbox[2] - bbox[0], bbox[3] - bbox[1]
@ -89,7 +90,7 @@ def draw_text_by_line(
x, y = pos
if hasattr(font, 'getsize'):
_, h = font.getsize('X')
_, h = font.getsize('X') # type: ignore
else:
bbox = font.getbbox('X')
_, h = 0, bbox[3] - bbox[1]
@ -102,8 +103,9 @@ def draw_text_by_line(
row = "" # 存储本行文字
length = 0 # 记录本行长度
for character in text:
# 获取当前字符的宽度
if hasattr(font, 'getsize'):
w, h = font.getsize(character) # 获取当前字符的宽度
w, h = font.getsize(character) # type: ignore
else:
bbox = font.getbbox('X')
w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
@ -115,7 +117,7 @@ def draw_text_by_line(
row += character
if center:
if hasattr(font, 'getsize'):
font_size = font.getsize(row)
font_size = font.getsize(row) # type: ignore
else:
bbox = font.getbbox(character)
font_size = bbox[2] - bbox[0], bbox[3] - bbox[1]
@ -127,7 +129,7 @@ def draw_text_by_line(
if row != "":
if center:
if hasattr(font, 'getsize'):
font_size = font.getsize(row)
font_size = font.getsize(row) # type: ignore
else:
bbox = font.getbbox(row)
font_size = bbox[2] - bbox[0], bbox[3] - bbox[1]

22
poetry.lock generated
View File

@ -196,13 +196,13 @@ reference = "mirrors"
[[package]]
name = "apscheduler"
version = "3.10.3"
version = "3.10.4"
description = "In-process task scheduler with Cron-like capabilities"
optional = false
python-versions = ">=3.6"
files = [
{file = "APScheduler-3.10.3-py3-none-any.whl", hash = "sha256:1611d207b095ff52d97884e90736c192bdd94dbd44ce27eb92c3f1da24a400d3"},
{file = "APScheduler-3.10.3.tar.gz", hash = "sha256:3f17fd3915f14f4bfa597f552130a14a9d1cc74a002fa1d95dd671cb48dba70e"},
{file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"},
{file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"},
]
[package.dependencies]
@ -605,13 +605,13 @@ reference = "mirrors"
[[package]]
name = "click"
version = "8.1.6"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
@ -1996,17 +1996,17 @@ reference = "mirrors"
[[package]]
name = "setuptools"
version = "68.1.0"
version = "68.1.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"},
{file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"},
{file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"},
{file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
@ -2708,4 +2708,4 @@ reference = "mirrors"
[metadata]
lock-version = "2.0"
python-versions = "^3.8.1"
content-hash = "cf1907f9dba50e810dcf9bc81a81c79309a080aac345358c68a34b1dbd82d44e"
content-hash = "a57a5ae92fffd7da50ee98c76df4945223b2bf5636f1f6a84b2e38f0de05975f"

View File

@ -24,8 +24,8 @@ aiosqlite = ">=0.17.0"
aiofiles = ">=0.8.0"
sqlmodel = ">=0.0.8"
gitpython = ">=3.1.27"
fastapi-amis-admin = ">=0.5.0"
fastapi-user-auth = ">=0.5.0"
fastapi-amis-admin = "^0.5.8"
fastapi-user-auth = "^0.5.0"
qrcode = {extras = ["pil"], version = "^7.3.1"}
msgspec = ">= 0.13.1"
uvicorn = ">=0.20.0"

View File

@ -5,7 +5,7 @@ aiohttp==3.8.5 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0
aiosignal==1.3.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
aiosqlite==0.19.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
anyio==3.7.1 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
apscheduler==3.10.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
apscheduler==3.10.4 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
async-timeout==4.0.3 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
attrs==23.1.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
backports-zoneinfo==0.2.1 ; python_full_version >= "3.8.1" and python_version < "3.9"
@ -14,7 +14,7 @@ beautifulsoup4==4.12.2 ; python_full_version >= "3.8.1" and python_full_version
certifi==2023.7.22 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
cffi==1.15.1 ; os_name == "nt" and implementation_name != "pypy" and python_full_version >= "3.8.1" and python_full_version < "4.0.0"
charset-normalizer==3.2.0 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
click==8.1.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
click==8.1.7 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"
colorama==0.4.6 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0" and (platform_system == "Windows" or sys_platform == "win32")
dnspython==2.4.2 ; python_full_version >= "3.8.1" and python_version < "4.0"
email-validator==2.0.0.post2 ; python_full_version >= "3.8.1" and python_full_version < "4.0.0"