HEXI/hexi/plugins/battlefield2042status/picture_tools.py

375 lines
14 KiB
Python
Raw Normal View History

2023-11-02 11:57:54 +08:00
import hashlib
import json
2023-11-08 18:44:35 +08:00
from nonebot.log import logger as sv
2023-11-02 11:57:54 +08:00
import os
import random
import time
from io import BytesIO
import aiohttp
import cairosvg
import qrcode
import requests
import requests.exceptions
from PIL import Image, ImageDraw, ImageFont
filepath = os.path.dirname(__file__).replace("\\", "/")
# 记录URL
bf_ban_url = "https://api.gametools.network/bfban/checkban"
# 圆角遮罩处理
def draw_rect(img, pos, radius, **kwargs):
transp = Image.new('RGBA', img.size, (0, 0, 0, 0))
alpha_draw = ImageDraw.Draw(transp, "RGBA")
alpha_draw.rounded_rectangle(pos, radius, **kwargs)
img.paste(Image.alpha_composite(img, transp))
return img
# 圆角处理
def circle_corner(img, radii):
"""
半透明圆角处理
:param img: 要修改的文件
:param radii: 圆角弧度
:return: 返回修改过的文件
"""
circle = Image.new('L', (radii * 2, radii * 2), 0) # 创建黑色方形
draw = ImageDraw.Draw(circle)
draw.ellipse((0, 0, radii * 2, radii * 2), fill=255) # 黑色方形内切白色圆形
img = img.convert("RGBA")
w, h = img.size
# 创建一个alpha层存放四个圆角使用透明度切除圆角外的图片
alpha = Image.new('L', img.size, 255)
alpha.paste(circle.crop((0, 0, radii, radii)), (0, 0)) # 左上角
alpha.paste(circle.crop((radii, 0, radii * 2, radii)),
(w - radii, 0)) # 右上角
alpha.paste(circle.crop((radii, radii, radii * 2, radii * 2)),
(w - radii, h - radii)) # 右下角
alpha.paste(circle.crop((0, radii, radii, radii * 2)),
(0, h - radii)) # 左下角
img.putalpha(alpha) # 白色区域透明可见,黑色区域不可见
# 添加圆角边框
draw = ImageDraw.Draw(img)
draw.rounded_rectangle(img.getbbox(), outline="white", width=3, radius=radii)
return img
# 获取专家类型图片
def get_class_type(class_type):
"""
获取专家类型
:param class_type: 专家类型
:return: 返回对应的图标路径
"""
icon_path = filepath + "/img/class_icon/No-Pats.png"
if class_type == 'Assault':
icon_path = filepath + "/img/class_icon/ui/Assault_Icon_small.png"
elif class_type == 'Support':
icon_path = filepath + "/img/class_icon/ui/Support_Icon_small.png"
elif class_type == 'Recon':
icon_path = filepath + "/img/class_icon/ui/Recon_Icon_small.png"
elif class_type == 'Engineer':
icon_path = filepath + "/img/class_icon/ui/Engineer_Icon_small.png"
return icon_path
# PNG重绘大小
def png_resize(source_file, new_width=0, new_height=0, resample="LANCZOS", ref_file=''):
"""
PNG缩放透明度处理
:param source_file: 源文件Image.open()
:param new_width: 设置的宽度
:param new_height: 设置的高度
:param resample: 抗锯齿
:param ref_file: 参考文件
:return:
"""
img = source_file
img = img.convert("RGBA")
width, height = img.size
if ref_file != '':
imgRef = Image.open(ref_file)
new_width, new_height = imgRef.size
else:
if new_height == 0:
new_height = new_width * width / height
bands = img.split()
resample_map = {
"NEAREST": Image.NEAREST,
"BILINEAR": Image.BILINEAR,
"BICUBIC": Image.BICUBIC,
"LANCZOS": Image.LANCZOS
}
resample_method = resample_map.get(resample, Image.LANCZOS) # 默认使用 LANCZOS
bands = [b.resize((new_width, new_height), resample=resample_method) for b in bands]
resized_file = Image.merge('RGBA', bands)
return resized_file
# 获取物品图片
def get_top_object_img(object_data):
"""
获取对应物品图标
:param object_data: 物品数据
:return: 图标
"""
img_url = object_data["image"]
img = Image.open(filepath + "/img/object_icon/default.png").convert('RGBA')
# object_name = "default"
path = filepath + "/img/object_icon/"
try:
obj_name = os.listdir(path)
if "weaponName" in object_data:
object_name = object_data["weaponName"]
if object_name in str(obj_name):
sv.info(f"本地已存在{object_name}物品图标")
img = Image.open(f"{path}{object_name}.png").convert('RGBA')
else:
sv.info(f"未检测到{object_name}物品图标,缓存至本地")
img = Image.open(BytesIO(requests.get(img_url).content)).convert('RGBA')
img.save(filepath + f"/img/object_icon/{object_name}.png")
elif "vehicleName" in object_data:
object_name = object_data["vehicleName"]
if object_name in str(obj_name):
sv.info(f"本地已存在{object_name}物品图标")
img = Image.open(f"{path}{object_name}.png").convert('RGBA')
else:
sv.info(f"未检测到{object_name}物品图标,缓存至本地")
img = Image.open(BytesIO(requests.get(img_url).content)).convert('RGBA')
img.save(filepath + f"/img/object_icon/{object_name}.png")
except Exception as err:
print(err)
return img
# 二维码生成
def qr_code_gen(player, platform):
"""
version QR code 的版次可以设置 1 40 的版次
error_correction 容错率可选 7%15%25%30%参数如下
qrcode.constants.ERROR_CORRECT_L 7%
qrcode.constants.ERROR_CORRECT_M 15%预设
qrcode.constants.ERROR_CORRECT_Q 25%
qrcode.constants.ERROR_CORRECT_H 30%
box_size 每个模块的像素个数
border 边框区的厚度预设是 4
image_factory 图片格式默认是 PIL
mask_pattern mask_pattern 参数是 0 7如果省略会自行使用最适当的方法
"""
if "pc" == platform:
platform = "origin"
bf_tracker_link = f"https://battlefieldtracker.com/bf2042/profile/{platform}/{player}/overview"
qr = qrcode.QRCode(version=1,
error_correction=qrcode.constants.ERROR_CORRECT_M,
box_size=5,
border=2)
qr.add_data(bf_tracker_link)
img = qr.make_image(fill_color='white', back_color="black")
return img
# 图片粘贴
def image_paste(paste_image, under_image, pos):
"""
:param paste_image: 需要粘贴的图片
:param under_image: 底图
:param pos: 位置x,y坐标
:return: 返回图片
"""
# 获取需要贴入图片的透明通道
r, g, b, alpha = paste_image.split()
# 粘贴时将alpha值传递至mask属性
under_image.paste(paste_image, pos, alpha)
return under_image
# 获取专属图库
def get_favorite_image(uid):
bg_path = filepath + f"/img/bg/user/{uid}/"
if os.listdir(bg_path):
bg_name = os.listdir(bg_path)
index = random.randint(0, len(bg_name) - 1)
img = Image.open(bg_path + f"{bg_name[index]}").convert('RGBA').resize((1920, 1080))
else:
common_bg_name = os.listdir(filepath + "/img/bg/common/")
index = random.randint(0, len(common_bg_name) - 1)
img = Image.open(filepath + f"/img/bg/common/{common_bg_name[index]}").convert('RGBA').resize((1920, 1080))
return img
# 获取bot管理员专属图库
def get_user_avatar(user):
avatar = download_avatar(user)
avatar = Image.open(BytesIO(avatar)).convert('RGBA').resize((1920, 1080))
return avatar
# 下载QQ头像
def download_avatar(user_id: str) -> bytes:
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=640"
data = download_url(url)
if not data or hashlib.md5(data).hexdigest() == "acef72340ac0e914090bd35799f5594e":
url = f"http://q1.qlogo.cn/g?b=qq&nk={user_id}&s=100"
data = download_url(url)
return data
# 根据URL下载文件
def download_url(url: str) -> bytes:
for i in range(3):
try:
resp = requests.get(url)
if resp.status_code != 200:
continue
return resp.content
except Exception as e:
print(f"Error downloading {url}, retry {i}/3: {str(e)}")
# 保存用户的图片
async def user_img_save(pic_data: bytes, uid: int):
bg_path = filepath + f"/img/bg/user/{uid}/"
try:
# 裁剪图片
pic_data = cut_image(pic_data, 16 / 9)
# 保存图片
time_now = int(time.time())
pic_data = pic_data.convert('RGB')
pic_data = pic_data.resize((1920, 1080))
pic_data.save(bg_path + str(time_now) + ".jpeg", quality=95)
except Exception as e:
raise Exception("图片保存失败")
# 图片裁剪
def cut_image(pic_data: bytes, target_ratio: float):
try:
pic_data = Image.open(BytesIO(pic_data))
w, h = pic_data.size
pic_ratio = w / h
if pic_ratio > target_ratio:
# 宽高比大于目标比例,按高度缩放。保持宽度不变
new_h = w / target_ratio
h_delta = (h - new_h) / 2
w_delta = 0
else:
# 宽高比小于目标比例,按宽度缩放。保持高度不变
new_w = h * target_ratio
w_delta = (w - new_w) / 2
h_delta = 0
cropped = pic_data.crop((w_delta, h_delta, w - w_delta, h - h_delta))
return cropped
except Exception as e:
raise Exception("图片剪裁失败")
# 加logo
def paste_ic_logo(img):
# 载入logo
ic_logo = filepath + "/img/dev_logo/IC.png"
# 载入字体
en_text_font = ImageFont.truetype(filepath + '/font/BF_Modernista-Bold.ttf', 18)
# 载入字体
ch_text_font = ImageFont.truetype(filepath + '/font/NotoSansSCMedium-4.ttf', 18)
logo_file = Image.open(ic_logo).convert("RGBA").resize((20, 20))
draw = ImageDraw.Draw(img)
img = draw_rect(img, (25, 1058, 1895, 1078), 1, fill=(0, 0, 0, 150))
draw.text((30, 1056), "Data Source From : GAMETOOLS.NETWORK", fill="white", font=en_text_font)
draw.text((700, 1056), "BF2042 Players Status Plugin Designed By", fill="white", font=en_text_font)
img = image_paste(logo_file, img, (1040, 1058))
draw.text((1065, 1056), "SANSENHOSHI", fill="skyblue", font=en_text_font)
draw.text((1400, 1058), "友情合作:", fill="white", font=ch_text_font)
draw.text((1500, 1058), "铁幕重工224077009", fill="#99CC00", font=ch_text_font)
draw.text((1700, 1058), "贴吧官群559190861", fill="#99CC00", font=ch_text_font)
return img
# 获取EA头像
async def get_avatar(platform_id, persona_id, nucleus_id):
default_avatar_path = filepath + "/img/class_icon/No-Pats.png"
try:
url = f"https://api.gametools.network/bf2042/feslid/?platformid={platform_id}&personaid={persona_id}&nucleusid={nucleus_id}"
async with aiohttp.ClientSession() as session:
async with session.get(url, headers={'accept': 'application/json'}) as response:
response.raise_for_status()
data = json.loads(await response.text())
avatar_url = data.get('avatar')
sv.info("头像URL处理" + avatar_url)
if avatar_url:
try:
# 添加10s超时判断如果超时直接使用默认头像
res = BytesIO(requests.get(avatar_url, timeout=10).content)
avatar = Image.open(res).convert('RGBA')
return avatar
except requests.exceptions.RequestException as e:
sv.warning(f"请求异常:{e}")
except requests.exceptions.RequestException as e:
sv.warning(f"请求异常:{e}")
# 使用默认头像
avatar = Image.open(default_avatar_path).convert('RGBA')
return avatar
async def get_special_icon(special):
"""
获取专家头像
:return: 专家头像
"""
"""
获取对应物品图标
:param object_data: 物品数据
:return: 图标
"""
img_url = special["image"]
img = Image.open(filepath + "/img/specialist_icon/No-Pats.png").convert('RGBA')
# object_name = "default"
path = filepath + "/img/specialist_icon/"
try:
icons_name = os.listdir(path)
character_name = special["characterName"]
if character_name in str(icons_name):
sv.info(f"本地已存在{character_name}专家图标")
img = Image.open(f"{path}{character_name}.png").convert('RGBA')
else:
sv.info(f"未检测到{character_name}专家图标,缓存至本地")
out_put = filepath + f"/img/specialist_icon/{character_name}.png"
img = await svg_to_png(img_url, out_put)
except Exception as err:
sv.error(f"获取专家图标失败:{str(err)}")
return img
async def svg_to_png(svg_file, png_file, width=500, height=500):
try:
# 使用 cairosvg 将 SVG 转换为 PNG并指定输出大小
cairosvg.svg2png(url=svg_file,
write_to=png_file,
output_width=width,
output_height=height)
character_icon = Image.open(png_file)
return character_icon
except Exception as err:
sv.error(f"获取图标失败:{str(err)}")
async def draw_point_line(image, start_point, end_point, line_spacing=5, line_length=10, line_width=2,
line_color='white'):
# 创建一个绘图对象
draw = ImageDraw.Draw(image)
# 绘制虚线
for x in range(start_point[0], end_point[0], line_length + line_spacing):
draw.line([(x, start_point[1]), (x + line_length, start_point[1])], fill=line_color, width=line_width)
return image