Last active
February 26, 2019 00:27
-
-
Save scarletcafe/3cbf25ea665b2cebd2f9450e9ab141b9 to your computer and use it in GitHub Desktop.
discord.py rewriteで画像処理のコグの実例
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 基本的な依存関係 | |
import discord | |
from discord.ext import commands | |
# discord.pyはaiohttpに依存しているため既にインストールされているはずです。 | |
import aiohttp | |
# PILは「Python Imaging Library」で色んな画像処理ができるライブラリです | |
# `pip install -U Pillow`でインストールできます | |
from PIL import Image, ImageDraw | |
# functools.partialでrun_in_executorに渡せるpartialオブジェクトを作成できます | |
# https://docs.python.org/ja/3/library/functools.html#functools.partial | |
from functools import partial | |
# BytesIOは bytes から「file-like」のバイトストリームが作れるクラスです | |
from io import BytesIO | |
# 型ヒント用 | |
from typing import Union | |
class ImageCog(commands.Cog): | |
def __init__(self, bot: commands.Bot): | |
# あとでループを取得できるようにBotへの参照を保持します。 | |
self.bot = bot | |
# アバターの画像データを入手できるようにClientSessionをつくる | |
self.session = aiohttp.ClientSession(loop=bot.loop) | |
# コグがアンロードされたとき・・ | |
def cog_unload(self): | |
# ClientSessionを閉じます。 | |
# こうしないと「Unclosed client session」がでます | |
self.session.close() | |
# アバターの画像データをダウンロードする関数 | |
async def get_avatar(self, user: Union[discord.User, discord.Member]) -> bytes: | |
# アバターのPNG形式のURLを取得する | |
# アバターの画像は通常1024x1024ですが、保証はされていないので注意しましょう。 | |
avatar_url = user.avatar_url_as(format="png") | |
# リクエストする | |
async with self.session.get(avatar_url) as response: | |
# レスポンスの内容を読み込む | |
avatar_bytes = await response.read() | |
return avatar_bytes | |
# 画像処理の主要部分 | |
@staticmethod | |
def processing(avatar_bytes: bytes, colour: tuple) -> BytesIO: | |
# BytesIOで画像データをバイトストリームにしてPILでロードする | |
# ただのbytesを渡せばなりません | |
with Image.open(BytesIO(avatar_bytes)) as im: | |
# アバターと同じサイズで新しい画像をつくる。 | |
# colourは画像のデフォルト塗りつぶし色。この場合は、指定したユーザーのメンバーリストに表示する色です。 | |
with Image.new("RGB", im.size, colour) as background: | |
# アバター画像にアルファチャンネルが付いていないことを確かめます。 | |
rgb_avatar = im.convert("RGB") | |
# マスクで使用できる新しい画像をつくる。 | |
# 0は #000000 や (0, 0, 0) と同じく、塗りつぶし色を黒にします。 | |
# モードは前と違って、RGB じゃなくて L (グレースケール)です。 | |
with Image.new("L", im.size, 0) as mask: | |
# ImageDraw.Drawは画像に「描く」ためのPILから提供されるクラスです。 | |
# こうやって丸・四角・線などを画像に描けます | |
mask_draw = ImageDraw.Draw(mask) | |
# Draw.ellipseで(0, 0)から画像の下右に (つまり、画像のサイズに合わせる) 楕円を描きます | |
mask_draw.ellipse([(0, 0), im.size], fill=255) | |
# マスクを使用しながらアバターを背景に貼り付ける | |
background.paste(rgb_avatar, (0, 0), mask=mask) | |
# バイトストリームをつくる | |
final_buffer = BytesIO() | |
# 画像をPNG形式でストリームに保存する | |
background.save(final_buffer, "png") | |
# 読み込まれるようにストリーム位置を0に返す | |
final_buffer.seek(0) | |
return final_buffer | |
@commands.command() | |
async def maru(self, ctx, *, member: discord.Member = None): | |
"""アバターを丸にする""" | |
# ユーザーが指定されていなかった場合、メッセージを送信したユーザーを使用します。 | |
member = member or ctx.author | |
# 処理をしながら「入力中」を表示する | |
async with ctx.typing(): | |
if isinstance(member, discord.Member): | |
# サーバーにいるならユーザーの表示色を取得する | |
member_colour = member.colour.to_rgb() | |
else: | |
# DMなどにいるなら(0, 0, 0)を使用する | |
member_colour = (0, 0, 0) | |
# アバターデータを bytes として取得。 | |
avatar_bytes = await self.get_avatar(member) | |
# partialオブジェクトを作る | |
# fnが呼び出されると、avatar_bytesとmember_colorを引数として渡してself.processingを実行するのと同様の動作をします。 | |
fn = partial(self.processing, avatar_bytes, member_colour) | |
# executorを使ってfnを別スレッドで実行します。 | |
# こうやって非同期的に関数が返すまで待つことができます | |
# final_bufferはself.processingが返すバイトストリームになります。 | |
final_buffer = await self.bot.loop.run_in_executor(None, fn) | |
# ファイル名「maru.png」の指定とfinal_bufferの内部でファイルを準備して | |
file = discord.File(filename="maru.png", fp=final_buffer) | |
# 最後にファイルをアップします。 | |
await ctx.send(file=file) | |
def setup(bot: commands.Bot): | |
bot.add_cog(ImageCog(bot)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Line 5
discord.pyはaiohttpに依存しているため既にインストールされているはずです。
Line 24
あとでループを取得できるようにBotへの参照を保持します。
Line 40
アバターの画像は通常1024x1024ですが、保証はされていないので注意しましょう。
Line 62
アバター画像にアルファチャンネルが付いていないことを確かめます。
Line 70
ImageDraw.Drawは画像に「描く」ためのPILから提供されるクラスです。
Line 95
ユーザーが指定されていなかった場合、メッセージを送信したユーザーを使用します。
Line 107
アバターデータを bytes として取得。
Line 111
fnが呼び出されると、avatar_bytesとmember_colorを引数として渡してself.processingを実行するのと同様の動作をします。
Line 114
executerを使ってfnを別スレッドで実行します。
Line 116
final_bufferはself.processingが返すバイトストリームになります。
Line 119
ファイル名「maru.png」の指定とfinal_bufferの内部でファイルを準備して
Line 122
最後にファイルをアップします。