修复网络IO堵塞导致图片丢失问题(fix #152)

This commit is contained in:
MingxuanGame 2022-03-30 13:07:12 +08:00
parent b80464a8ca
commit 4e326368a2
No known key found for this signature in database
GPG Key ID: 90C7EFA11DC3C2FF
21 changed files with 158 additions and 95 deletions

View File

@ -1,17 +1,16 @@
import asyncio,os,sys
import base64,re
import base64
import traceback
from aiocqhttp.exceptions import ActionFailed
from aiohttp import ClientConnectorError
from nonebot import get_bot, MessageSegment
from hoshino import Service
from hoshino.typing import CQEvent, HoshinoBot
from nonebot import get_bot, logger, MessageSegment
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
from mihoyo_libs.get_data import *
from mihoyo_libs.get_image import *
from mihoyo_libs.get_mihoyo_bbs_data import *
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
sv = Service('genshinuid')
hoshino_bot = get_bot()
@ -19,6 +18,7 @@ FILE_PATH = os.path.join(os.path.join(os.path.dirname(__file__), 'mihoyo_libs'),
INDEX_PATH = os.path.join(FILE_PATH, 'index')
Texture_PATH = os.path.join(FILE_PATH, 'texture2d')
@sv.on_rex('[\u4e00-\u9fa5]+(用什么|能用啥|怎么养)')
async def send_char_adv(bot: HoshinoBot, ev: CQEvent):
try:
@ -28,6 +28,7 @@ async def send_char_adv(bot: HoshinoBot, ev: CQEvent):
except Exception as e:
logger.exception("获取建议失败。")
@sv.on_rex('[\u4e00-\u9fa5]+(能给谁|给谁用|要给谁|谁能用)')
async def send_weapon_adv(bot: HoshinoBot, ev: CQEvent):
try:
@ -37,6 +38,7 @@ async def send_weapon_adv(bot: HoshinoBot, ev: CQEvent):
except Exception as e:
logger.exception("获取建议失败。")
@sv.on_prefix('语音')
async def send_audio(bot: HoshinoBot, ev: CQEvent):
try:
@ -240,6 +242,7 @@ async def clean_cache():
async def draw_event():
await draw_event_pic()
@sv.on_fullmatch('开始获取米游币')
async def send_mihoyo_coin(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, "开始操作……", at_sender=True)
@ -260,6 +263,7 @@ async def send_mihoyo_coin(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, "机器人发送消息失败:{}".format(e.info['wording']))
logger.exception("发送签到信息失败")
@sv.on_fullmatch('全部重签')
async def _(bot: HoshinoBot, ev: CQEvent):
try:
@ -273,6 +277,7 @@ async def _(bot: HoshinoBot, ev: CQEvent):
traceback.print_exc()
await bot.send(ev, "发生错误 {},请检查后台输出。".format(e))
@sv.on_fullmatch('全部重获取')
async def bbscoin_resign(bot: HoshinoBot, ev: CQEvent):
try:
@ -286,6 +291,7 @@ async def bbscoin_resign(bot: HoshinoBot, ev: CQEvent):
traceback.print_exc()
await bot.send(ev, "发生错误 {},请检查后台输出。".format(e))
# 每隔半小时检测树脂是否超过设定值
@sv.scheduled_job('cron', minute="*/30")
async def push():
@ -366,11 +372,13 @@ async def daily_sign():
conn.close()
return
# 每日零点五十进行米游币获取
@sv.scheduled_job('cron', hour='0', minute="50")
async def sign_at_night():
await daily_mihoyo_bbs_sign()
async def daily_mihoyo_bbs_sign():
conn = sqlite3.connect('ID_DATA.db')
c = conn.cursor()
@ -391,6 +399,7 @@ async def daily_mihoyo_bbs_sign():
logger.exception(f"{im} Error")
logger.info("已结束。")
# 私聊事件
@hoshino_bot.on_message('private')
async def setting(ctx):
@ -481,6 +490,7 @@ async def setting(ctx):
await hoshino_bot.send_msg(self_id=sid, user_id=userid, group_id=gid, message="未找到uid绑定记录。")
logger.exception("关闭自动签到失败")
# 群聊开启 自动签到 和 推送树脂提醒 功能
@sv.on_prefix('gs开启')
async def open_switch_func(bot: HoshinoBot, ev: CQEvent):
@ -589,10 +599,10 @@ async def close_switch_func(bot: HoshinoBot, ev: CQEvent):
else:
return
except ActionFailed as e:
await bot.send("机器人发送消息失败:{}".format(e))
await bot.send(ev, "机器人发送消息失败:{}".format(e))
logger.exception("发送设置成功信息失败")
except Exception as e:
await bot.send("发生错误 {},请检查后台输出。".format(e))
await bot.send(ev, "发生错误 {},请检查后台输出。".format(e))
logger.exception("设置简洁签到报告失败")
except Exception as e:
await bot.send(ev, "发生错误 {},请检查后台输出。".format(e))
@ -670,6 +680,7 @@ async def send_daily_data(bot: HoshinoBot, ev: CQEvent):
await bot.send(ev, "机器人发送消息失败:{}".format(e))
logger.exception("发送当前状态信息失败")
# 图片版信息
@sv.on_fullmatch('当前信息')
async def send_genshin_info(bot: HoshinoBot, ev: CQEvent):
@ -894,6 +905,9 @@ async def get_info(bot, ev):
except TypeError:
await bot.send(ev, "获取失败可能是Cookies失效或者未打开米游社角色详情开关。")
logger.exception("数据获取失败Cookie失效/不公开信息)")
except ClientConnectorError:
await bot.send(ev, "获取失败:连接超时")
logger.exception("连接超时")
except Exception as e:
await bot.send(ev, "获取失败,有可能是数据状态有问题,\n{}\n请检查后台输出。".format(e))
logger.exception("数据获取失败(数据状态问题)")

View File

@ -10,6 +10,7 @@ import time
from shutil import copyfile
import requests
from aiohttp import ClientSession
from httpx import AsyncClient
from nonebot import logger
@ -726,11 +727,10 @@ def get_character(uid, character_ids, ck, server_id="cn_gf01"):
logger.info(e.with_traceback)
async def get_calculate_info(uid, char_id, ck, server_id="cn_gf01"):
async def get_calculate_info(client: ClientSession, uid, char_id, ck, name, server_id="cn_gf01"):
if uid[0] == '5':
server_id = "cn_qd01"
url = "https://api-takumi.mihoyo.com/event/e20200928calculate/v1/sync/avatar/detail"
async with AsyncClient() as client:
req = await client.get(
url=url,
headers={
@ -747,7 +747,9 @@ async def get_calculate_info(uid, char_id, ck, server_id="cn_gf01"):
"region": server_id
}
)
data = json.loads(req.text)
data = await req.json()
data.update({"name": name})
logger.debug(name + "=" + str(char_id) + "===" + str(data)) # DEBUG
return data

View File

@ -14,8 +14,9 @@ from wordcloud import WordCloud
from .get_data import *
STATUS = []
FILE_PATH = os.path.dirname(__file__)
FILE2_PATH = os.path.join(FILE_PATH, 'mihoyo_libs/mihoyo_bbs')
FILE2_PATH = os.path.join(FILE_PATH, 'mihoyo_bbs')
CHAR_PATH = os.path.join(FILE2_PATH, 'chars')
CHAR_DONE_PATH = os.path.join(FILE2_PATH, 'char_done')
CHAR_IMG_PATH = os.path.join(FILE2_PATH, 'char_img')
@ -27,7 +28,7 @@ WEAPON_PATH = os.path.join(FILE2_PATH, 'weapon')
BG_PATH = os.path.join(FILE2_PATH, 'bg')
class customize_image:
class CustomizeImage:
def __init__(self, image: Match, based_w: int, based_h: int) -> None:
self.bg_img = self.get_image(image, based_w, based_h)
@ -192,7 +193,7 @@ def get_rel_pic(url: str):
f.write(get(url).content)
class get_cookies:
class GetCookies:
def __init__(self) -> None:
self.useable_cookies: Optional[str] = None
self.uid: Optional[str] = None
@ -545,7 +546,7 @@ async def draw_word_cloud(uid: str, image: Optional[Match] = None, mode: int = 2
async def draw_abyss0_pic(uid, nickname, image=None, mode=2, date="1"):
# 获取Cookies
data_def = get_cookies()
data_def = GetCookies()
retcode = await data_def.get_useable_cookies(uid, mode, date)
if not retcode:
return retcode
@ -565,7 +566,7 @@ async def draw_abyss0_pic(uid, nickname, image=None, mode=2, date="1"):
# 获取背景图片各项参数
based_w = 900
based_h = 660 + levels_num * 315
image_def = customize_image(image, based_w, based_h)
image_def = CustomizeImage(image, based_w, based_h)
bg_img = image_def.bg_img
bg_color = image_def.bg_color
text_color = image_def.text_color
@ -794,13 +795,13 @@ async def draw_abyss0_pic(uid, nickname, image=None, mode=2, date="1"):
abyss2.paste(abyss_star1, (730, 155), abyss_star1)
abyss2_text_draw = ImageDraw.Draw(abyss2)
abyss2_text_draw.text((87, 30), f"{j + 1}", text_color, genshin_font(21))
timeStamp1 = int(floors_data['levels'][j]['battles'][0]['timestamp'])
timeStamp2 = int(floors_data['levels'][j]['battles'][1]['timestamp'])
timeArray1 = time.localtime(timeStamp1)
timeArray2 = time.localtime(timeStamp2)
otherStyleTime1 = time.strftime("%Y--%m--%d %H:%M:%S", timeArray1)
otherStyleTime2 = time.strftime("%Y--%m--%d %H:%M:%S", timeArray2)
abyss2_text_draw.text((167, 33), f"{otherStyleTime1}/{otherStyleTime2}", text_color, genshin_font(19))
timestamp1 = int(floors_data['levels'][j]['battles'][0]['timestamp'])
timestamp2 = int(floors_data['levels'][j]['battles'][1]['timestamp'])
time_array1 = time.localtime(timestamp1)
time_array2 = time.localtime(timestamp2)
other_style_time1 = time.strftime("%Y--%m--%d %H:%M:%S", time_array1)
other_style_time2 = time.strftime("%Y--%m--%d %H:%M:%S", time_array2)
abyss2_text_draw.text((167, 33), f"{other_style_time1}/{other_style_time2}", text_color, genshin_font(19))
bg_img.paste(abyss2, (0, 605 + j * 315), abyss2)
bg_img.paste(abyss3, (0, len(floors_data["levels"]) * 315 + 610), abyss3)
@ -826,7 +827,7 @@ async def draw_abyss0_pic(uid, nickname, image=None, mode=2, date="1"):
async def draw_abyss_pic(uid: str, nickname: str, floor_num: int, image: Optional[Match] = None, mode: int = 2,
date: str = "1"):
# 获取Cookies
data_def = get_cookies()
data_def = GetCookies()
retcode = await data_def.get_useable_cookies(uid, mode, date)
if not retcode:
return retcode
@ -848,7 +849,7 @@ async def draw_abyss_pic(uid: str, nickname: str, floor_num: int, image: Optiona
# 获取背景图片各项参数
based_w = 900
based_h = 440 + levels_num * 340
image_def = customize_image(image, based_w, based_h)
image_def = CustomizeImage(image, based_w, based_h)
bg_img = image_def.bg_img
bg_color = image_def.bg_color
text_color = image_def.text_color
@ -952,13 +953,13 @@ async def draw_abyss_pic(uid: str, nickname: str, floor_num: int, image: Optiona
abyss2.paste(abyss_star1, (730, 155), abyss_star1)
abyss2_text_draw = ImageDraw.Draw(abyss2)
abyss2_text_draw.text((87, 30), f"{j + 1}", text_color, genshin_font(21))
timeStamp1 = int(based_data['levels'][j]['battles'][0]['timestamp'])
timeStamp2 = int(based_data['levels'][j]['battles'][1]['timestamp'])
timeArray1 = time.localtime(timeStamp1)
timeArray2 = time.localtime(timeStamp2)
otherStyleTime1 = time.strftime("%Y--%m--%d %H:%M:%S", timeArray1)
otherStyleTime2 = time.strftime("%Y--%m--%d %H:%M:%S", timeArray2)
abyss2_text_draw.text((167, 33), f"{otherStyleTime1}/{otherStyleTime2}", text_color, genshin_font(19))
timestamp1 = int(based_data['levels'][j]['battles'][0]['timestamp'])
timestamp2 = int(based_data['levels'][j]['battles'][1]['timestamp'])
time_array1 = time.localtime(timestamp1)
time_array2 = time.localtime(timestamp2)
other_style_time1 = time.strftime("%Y--%m--%d %H:%M:%S", time_array1)
other_style_time2 = time.strftime("%Y--%m--%d %H:%M:%S", time_array2)
abyss2_text_draw.text((167, 33), f"{other_style_time1}/{other_style_time2}", text_color, genshin_font(19))
bg_img.paste(abyss2, (0, 350 + j * 340), abyss2)
bg_img.paste(abyss3, (0, len(based_data['levels']) * 340 + 400), abyss3)
@ -979,9 +980,20 @@ async def draw_abyss_pic(uid: str, nickname: str, floor_num: int, image: Optiona
return resultmes
async def get_all_calculate_info(client: ClientSession, uid: str, char_id: list[str], ck: str, name: list):
tasks = []
for id_, name_ in zip(char_id, name):
tasks.append(get_calculate_info(client, uid, id_, ck, name_))
data = []
repos = await asyncio.wait(tasks)
for i in repos[0]:
data.append(i.result())
return data
async def draw_char_pic(img: Image, char_data: dict, index: int, bg_color: tuple[int, int, int],
text_color: tuple[int, int, int], bg_detail_color: tuple[int, int, int],
char_high_color: tuple[int, int, int], uid, use_cookies):
char_high_color: tuple[int, int, int], char_talent_data: dict):
char_mingzuo = 0
for k in char_data['constellations']:
if k['is_actived']:
@ -1006,8 +1018,7 @@ async def draw_char_pic(img: Image, char_data: dict, index: int, bg_color: tuple
char_3.putalpha(alpha)
"""
char_1_mask = Image.open(os.path.join(TEXT_PATH, "char_1_mask.png"))
char_talent_data = await get_calculate_info(uid, str(char_data["id"]), use_cookies)
STATUS.append(char_data['name'])
if not os.path.exists(os.path.join(WEAPON_PATH, str(char_data['weapon']['icon'].split('/')[-1]))):
get_weapon_pic(char_data['weapon']['icon'])
if not os.path.exists(os.path.join(CHAR_PATH, str(char_data['id']) + ".png")):
@ -1055,13 +1066,14 @@ async def draw_char_pic(img: Image, char_data: dict, index: int, bg_color: tuple
anchor="mm")
char_crop = (75 + 190 * (index % 4), 800 + 100 * (index // 4))
STATUS.remove(char_data['name'])
img.paste(char_0, char_crop, char_0)
async def draw_pic(uid: str, nickname: str, image: Optional[Match] = None, mode: int = 2,
role_level: Optional[int] = None):
# 获取Cookies
data_def = get_cookies()
data_def = GetCookies()
retcode = await data_def.get_useable_cookies(uid, mode)
if not retcode:
return retcode
@ -1089,7 +1101,7 @@ async def draw_pic(uid: str, nickname: str, image: Optional[Match] = None, mode:
# 获取背景图片各项参数
based_w = 900
based_h = 870 + char_hang * 100 if char_num > 8 else 890 + char_hang * 110
image_def = customize_image(image, based_w, based_h)
image_def = CustomizeImage(image, based_w, based_h)
bg_img = image_def.bg_img
bg_color = image_def.bg_color
text_color = image_def.text_color
@ -1154,34 +1166,46 @@ async def draw_pic(uid: str, nickname: str, image: Optional[Match] = None, mode:
text_draw.text((258, 625.4), str(raw_data['stats']['way_point_number']), text_color, genshin_font(24))
text_draw.text((258, 675.4), str(raw_data['stats']['domain_number']), text_color, genshin_font(24))
# 蒙德
text_draw.text((490, 370), str(raw_data['world_explorations'][4]['exploration_percentage'] / 10) + '%', text_color,
mondstadt = liyue = dragonspine = inazuma = offering = dict()
for i in raw_data['world_explorations']:
if i["name"] == "蒙德":
mondstadt = i
elif i["name"] == "璃月":
liyue = i
elif i["name"] == "龙脊雪山":
dragonspine = i
elif i["name"] == "稻妻":
inazuma = i
elif i["name"] == "渊下宫":
offering = i
text_draw.text((490, 370), str(mondstadt['exploration_percentage'] / 10) + '%', text_color,
genshin_font(22))
text_draw.text((490, 400), 'lv.' + str(raw_data['world_explorations'][4]['level']), text_color, genshin_font(22))
text_draw.text((490, 400), 'lv.' + str(mondstadt['level']), text_color, genshin_font(22))
text_draw.text((513, 430), str(raw_data['stats']['anemoculus_number']), text_color, genshin_font(22))
# 璃月
text_draw.text((490, 490), str(raw_data['world_explorations'][3]['exploration_percentage'] / 10) + '%', text_color,
text_draw.text((490, 490), str(liyue['exploration_percentage'] / 10) + '%', text_color,
genshin_font(22))
text_draw.text((490, 520), 'lv.' + str(raw_data['world_explorations'][3]['level']), text_color, genshin_font(22))
text_draw.text((490, 520), 'lv.' + str(liyue['level']), text_color, genshin_font(22))
text_draw.text((513, 550), str(raw_data['stats']['geoculus_number']), text_color, genshin_font(22))
# 雪山
text_draw.text((745, 373.5), str(raw_data['world_explorations'][2]['exploration_percentage'] / 10) + '%',
text_draw.text((745, 373.5), str(dragonspine['exploration_percentage'] / 10) + '%',
text_color,
genshin_font(22))
text_draw.text((745, 407.1), 'lv.' + str(raw_data['world_explorations'][2]['level']), text_color, genshin_font(22))
text_draw.text((745, 407.1), 'lv.' + str(dragonspine['level']), text_color, genshin_font(22))
# 稻妻
text_draw.text((490, 608), str(raw_data['world_explorations'][1]['exploration_percentage'] / 10) + '%', text_color,
text_draw.text((490, 608), str(inazuma['exploration_percentage'] / 10) + '%', text_color,
genshin_font(22))
text_draw.text((490, 635), 'lv.' + str(raw_data['world_explorations'][1]['level']), text_color, genshin_font(22))
text_draw.text((490, 662), 'lv.' + str(raw_data['world_explorations'][1]['offerings'][0]['level']), text_color,
text_draw.text((490, 635), 'lv.' + str(inazuma['level']), text_color, genshin_font(22))
text_draw.text((490, 662), 'lv.' + str(inazuma['offerings'][0]['level']), text_color,
genshin_font(22))
text_draw.text((513, 689), str(raw_data['stats']['electroculus_number']), text_color, genshin_font(22))
# 渊下宫
text_draw.text((745, 480), str(raw_data['world_explorations'][0]['exploration_percentage'] / 10) + '%', text_color,
text_draw.text((745, 480), str(offering['exploration_percentage'] / 10) + '%', text_color,
genshin_font(22))
# 家园
@ -1231,10 +1255,30 @@ async def draw_pic(uid: str, nickname: str, image: Optional[Match] = None, mode:
char_datas.sort(key=lambda x: (-x['rarity'], -x['level'], -x['fetter']))
if char_num > 8:
char_names = []
client = ClientSession()
for i in char_datas:
char_names.append(i['name'])
talent_data = await get_all_calculate_info(client, uid, char_ids,
use_cookies, char_names)
await client.close()
tasks = []
for index, i in enumerate(char_datas):
tasks.append(draw_char_pic(bg_img, i, index, char_color, text_color, bg_detail_color, char_high_color, uid,
use_cookies))
for j in talent_data:
if j["name"] == i['name']:
tasks.append(
draw_char_pic(
bg_img,
i,
index,
char_color,
text_color,
bg_detail_color,
char_high_color,
j
)
)
await asyncio.wait(tasks)
"""
char_mingzuo = 0
@ -1428,7 +1472,7 @@ async def draw_info_pic(uid: str, image: Optional[Match] = None) -> str:
return "%02d:%02d:%02d" % (h, m, s)
# 获取Cookies
data_def = get_cookies()
data_def = GetCookies()
retcode = await data_def.get_useable_cookies(uid)
if not retcode:
return retcode
@ -1443,7 +1487,7 @@ async def draw_info_pic(uid: str, image: Optional[Match] = None) -> str:
# 获取背景图片各项参数
based_w = 900
based_h = 1380
image_def = customize_image(image, based_w, based_h)
image_def = CustomizeImage(image, based_w, based_h)
bg_img = image_def.bg_img
bg_color = image_def.bg_color
text_color = image_def.text_color
@ -1492,7 +1536,8 @@ async def draw_info_pic(uid: str, image: Optional[Match] = None) -> str:
# 本日原石/摩拉
text_draw.text((675, 148),
f"{award_data['data']['day_data']['current_primogems']}/{award_data['data']['day_data']['last_primogems']}",
f"{award_data['data']['day_data']['current_primogems']}/"
f"{award_data['data']['day_data']['last_primogems']}",
text_color, genshin_font(28), anchor="lm")
text_draw.text((675, 212),
f"{award_data['data']['day_data']['current_mora']}\n{award_data['data']['day_data']['last_mora']}",
@ -1523,7 +1568,8 @@ async def draw_info_pic(uid: str, image: Optional[Match] = None) -> str:
text_draw.text((390, 503), f"{daily_data['finished_task_num']}/{daily_data['total_task_num']}", text_color,
genshin_font(26), anchor="lm")
text_draw.text((390, 597),
f"{str(daily_data['resin_discount_num_limit'] - daily_data['remain_resin_discount_num'])}/{daily_data['resin_discount_num_limit']}",
f"{str(daily_data['resin_discount_num_limit'] - daily_data['remain_resin_discount_num'])}/"
f"{daily_data['resin_discount_num_limit']}",
text_color, genshin_font(26), anchor="lm")
# 树脂恢复时间计算
@ -1576,10 +1622,11 @@ async def draw_info_pic(uid: str, image: Optional[Match] = None) -> str:
if not os.path.exists(
os.path.join(CHAR_IMG_PATH, f"UI_AvatarIcon_{i['avatar_side_icon'].split('_')[-1][:-4]}@2x.png")):
get_char_img_pic(
f"https://upload-bbs.mihoyo.com/game_record/genshin/character_image/UI_AvatarIcon_{i['avatar_side_icon'].split('_')[-1][:-4]}@2x.png")
# char_stand_img = os.path.join(CHAR_IMG_PATH, f"UI_AvatarIcon_{i['avatar_side_icon'].split('_')[-1][:-4]}@2x.png")
# char_stand = Image.open(char_stand_img)
# char_stand_mask = Image.open(os.path.join(TEXT_PATH, "stand_mask.png"))
f"https://upload-bbs.mihoyo.com/game_record/genshin/character_image"
f"/UI_AvatarIcon_{i['avatar_side_icon'].split('_')[-1][:-4]}@2x.png")
# char_stand_img = os.path.join(CHAR_IMG_PATH, f"UI_AvatarIcon_{i['avatar_side_icon'].split('_')[-1][
# :-4]}@2x.png") char_stand = Image.open(char_stand_img) char_stand_mask = Image.open(os.path.join(TEXT_PATH,
# "stand_mask.png"))
# charpic_temp = Image.new("RGBA", (900, 130))
# charpic_temp.paste(char_stand, (395, -99), char_stand_mask)

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB