Skip to content

Instantly share code, notes, and snippets.

@Gorialis
Last active February 26, 2019 00:27
Show Gist options
  • Save Gorialis/3cbf25ea665b2cebd2f9450e9ab141b9 to your computer and use it in GitHub Desktop.
Save Gorialis/3cbf25ea665b2cebd2f9450e9ab141b9 to your computer and use it in GitHub Desktop.
discord.py rewriteで画像処理のコグの実例
# 基本的な依存関係
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))
@Lazialize
Copy link

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
最後にファイルをアップします。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment