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
import logging as sv
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