问题
I need help with discord.py. I've been creating a bot on python, so I wanted to make a canvas profile card with this bot. The problem is, I did not find anything in google about it, only node.js. I do not want to rewrite my bot and I'd like to make a profile card like example: juniperbot, mee6. Help me with it please!
回答1:
I don't know jupiterbot
nor mee6
but if canvas
means Image manipulation with Canvas in documentation for discord.js
then it is used only to generate image and simply send()
as normal file .png
or .jpg
.
Python usually uses module pillow to generate or modify image. Image, ImageDraw, ImageFont
from discord.ext import commands
from discord import File
from PIL import Image, ImageDraw, ImageFont
import io
TOKEN = 'MY-TOKEN'
bot = commands.Bot(command_prefix='!')
@bot.command(name='canvas')
async def canvas(ctx, text=None):
IMAGE_WIDTH = 600
IMAGE_HEIGHT = 300
# create empty image 600x300
image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT)) # RGB, RGBA (with alpha), L (grayscale), 1 (black & white)
# or load existing image
#image = Image.open('/home/furas/images/lenna.png')
# create object for drawing
draw = ImageDraw.Draw(image)
# draw red rectangle with green outline from point (50,50) to point (550,250) #(600-50, 300-50)
draw.rectangle([50, 50, IMAGE_WIDTH-50, IMAGE_HEIGHT-50], fill=(255,0,0), outline=(0,255,0))
# draw text in center
text = f'Hello {ctx.author.name}'
font = ImageFont.truetype('Arial.ttf', 30)
text_width, text_height = draw.textsize(text, font=font)
x = (IMAGE_WIDTH - text_width)//2
y = (IMAGE_HEIGHT - text_height)//2
draw.text( (x, y), text, fill=(0,0,255), font=font)
# create buffer
buffer = io.BytesIO()
# save PNG in buffer
image.save(buffer, format='PNG')
# move to beginning of buffer so `send()` it will read from beginning
buffer.seek(0)
# send image
await ctx.send(file=File(buffer, 'myimage.png'))
if __name__ == '__main__':
bot.run(TOKEN)
Result:
EDIT: Version which add user's avatar.
I also shows how to read image from url and use it as background. But it could read it only once - at start.
@bot.command(name='canvas')
async def canvas(ctx, text=None):
#print('\n'.join(dir(ctx)))
#print('\n'.join(dir(ctx.author)))
# --- create empty image ---
#IMAGE_WIDTH = 600
#IMAGE_HEIGHT = 300
# create empty image 600x300
#image = Image.new('RGB', (IMAGE_WIDTH, IMAGE_HEIGHT)) # RGB, RGBA (with alpha), L (grayscale), 1 (black & white)
# --- load image from local file ---
# or load existing image
#image = Image.open('/home/furas/Obrazy/images/lenna.png')
# --- load image from url ---
import urllib.request
url = 'https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png?download'
response = urllib.request.urlopen(url)
image = Image.open(response) # it doesn't need `io.Bytes` because it `response` has method `read()`
print('size:', image.size)
#IMAGE_WIDTH, IMAGE_HEIGHT = image.size
IMAGE_WIDTH = image.size[0]
# --- draw on image ---
# create object for drawing
draw = ImageDraw.Draw(image)
# draw red rectangle with green outline from point (50,50) to point (550,250) #(600-50, 300-50)
draw.rectangle([50, 50, IMAGE_WIDTH-50, IMAGE_HEIGHT-50], fill=(255,0,0, 128), outline=(0,255,0))
# draw text in center
text = f'Hello {ctx.author.name}'
font = ImageFont.truetype('Arial.ttf', 30)
text_width, text_height = draw.textsize(text, font=font)
x = (IMAGE_WIDTH - text_width)//2
y = (IMAGE_HEIGHT - text_height)//2
draw.text( (x, y), text, fill=(0,0,255), font=font)
# --- avatar ---
#print('avatar:', ctx.author.avatar_url)
#print('avatar:', ctx.author.avatar_url_as(format='jpg'))
#print('avatar:', ctx.author.avatar_url_as(format='png'))
AVATAR_SIZE = 128
# get URL to avatar
# sometimes `size=` doesn't gives me image in expected size so later I use `resize()`
avatar_asset = ctx.author.avatar_url_as(format='jpg', size=AVATAR_SIZE)
# read JPG from server to buffer (file-like object)
buffer_avatar = io.BytesIO()
await avatar_asset.save(buffer_avatar)
buffer_avatar.seek(0)
# read JPG from buffer to Image
avatar_image = Image.open(buffer_avatar)
# resize it
avatar_image = avatar_image.resize((AVATAR_SIZE, AVATAR_SIZE)) #
x = 50 + 5
y = (IMAGE_HEIGHT-AVATAR_SIZE)//2 # center vertically
image.paste(avatar_image, (x, y))
# --- sending image ---
# create buffer
buffer_output = io.BytesIO()
# save PNG in buffer
image.save(buffer_output, format='PNG')
# move to beginning of buffer so `send()` it will read from beginning
buffer_output.seek(0)
# send image
await ctx.send(file=File(buffer_output, 'myimage.png'))
Result:
EDIT: Example which draws transparent rectangle using new Image and Image.alpha_composite()
Pillow doc: Example: Draw Partial Opacity Text
from discord.ext import commands
from discord import File
from PIL import Image, ImageDraw, ImageFont
import io
import urllib.request
TOKEN = 'MY-TOKEN'
bot = commands.Bot(command_prefix='!')
# read background image only once
url = 'https://upload.wikimedia.org/wikipedia/en/7/7d/Lenna_%28test_image%29.png?download'
response = urllib.request.urlopen(url)
background_image = Image.open(response) # it doesn't need `io.Bytes` because it `response` has method `read()`
background_image = background_image.convert('RGBA') # add channel ALPHA to draw transparent rectangle
@bot.command(name='canvas')
async def canvas(ctx, text=None):
AVATAR_SIZE = 128
# --- duplicate image ----
image = background_image.copy()
image_width, image_height = image.size
# --- draw on image ---
# create object for drawing
#draw = ImageDraw.Draw(image)
# draw red rectangle with alpha channel on new image (with the same size as original image)
rect_x0 = 20 # left marign
rect_y0 = 20 # top marign
rect_x1 = image_width - 20 # right margin
rect_y1 = 20 + AVATAR_SIZE - 1 # top margin + size of avatar
rect_width = rect_x1 - rect_x0
rect_height = rect_y1 - rect_y0
rectangle_image = Image.new('RGBA', (image_width, image_height))
rectangle_draw = ImageDraw.Draw(rectangle_image)
rectangle_draw.rectangle((rect_x0, rect_y0, rect_x1, rect_y1), fill=(255,0,0, 128))
# put rectangle on original image
image = Image.alpha_composite(image, rectangle_image)
# create object for drawing
draw = ImageDraw.Draw(image) # create new object for drawing after changing original `image`
# draw text in center
text = f'Hello {ctx.author.name}'
font = ImageFont.truetype('Arial.ttf', 30)
text_width, text_height = draw.textsize(text, font=font)
x = (rect_width - text_width - AVATAR_SIZE)//2 # skip avatar when center text
y = (rect_height - text_height)//2
x += rect_x0 + AVATAR_SIZE # skip avatar when center text
y += rect_y0
draw.text((x, y), text, fill=(0,0,255,255), font=font)
# --- avatar ---
# get URL to avatar
# sometimes `size=` doesn't gives me image in expected size so later I use `resize()`
avatar_asset = ctx.author.avatar_url_as(format='jpg', size=AVATAR_SIZE)
# read JPG from server to buffer (file-like object)
buffer_avatar = io.BytesIO()
await avatar_asset.save(buffer_avatar)
buffer_avatar.seek(0)
# read JPG from buffer to Image
avatar_image = Image.open(buffer_avatar)
# resize it
avatar_image = avatar_image.resize((AVATAR_SIZE, AVATAR_SIZE)) #
image.paste(avatar_image, (rect_x0, rect_y0))
# --- sending image ---
# create buffer
buffer_output = io.BytesIO()
# save PNG in buffer
image.save(buffer_output, format='PNG')
# move to beginning of buffer so `send()` it will read from beginning
buffer_output.seek(0)
# send image
await ctx.send(file=File(buffer_output, 'myimage.png'))
if __name__ == '__main__':
print('Running ... https://discord.com/channels/709507681441808385/709507681441808388')
bot.run(TOKEN)
Result:
BTW: Example how to use alpha channel to create circle image (to create circle avatar):
What's the most simple way to crop a circle thumbnail from an image?
EDIT: Version which use mask to display circle avatar
# --- avatar ---
# get URL to avatar
# sometimes `size=` doesn't gives me image in expected size so later I use `resize()`
avatar_asset = ctx.author.avatar_url_as(format='jpg', size=AVATAR_SIZE)
# read JPG from server to buffer (file-like object)
buffer_avatar = io.BytesIO(await avatar_asset.read())
# buffer_avatar = io.BytesIO()
# await avatar_asset.save(buffer_avatar)
# buffer_avatar.seek(0)
# read JPG from buffer to Image
avatar_image = Image.open(buffer_avatar)
# resize it
avatar_image = avatar_image.resize((AVATAR_SIZE, AVATAR_SIZE)) #
circle_image = Image.new('L', (AVATAR_SIZE, AVATAR_SIZE))
circle_draw = ImageDraw.Draw(circle_image)
circle_draw.ellipse((0, 0, AVATAR_SIZE, AVATAR_SIZE), fill=255)
#avatar_image.putalpha(circle_image)
#avatar_image.show()
image.paste(avatar_image, (rect_x0, rect_y0), circle_image)
来源:https://stackoverflow.com/questions/61724373/how-to-make-a-canvas-profile-card-in-discord-python-bot