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!
-
can you add more informations about this "canvas" ? Add some links in question (not im comment) As I found in Google (ie. [Image manipulation with Canvas](https://discordjs.guide/popular-topics/canvas.html) ) it seems it is used only to generate `.png` or `.jpg` images and send as `Attachment`. In Python you can use ie. [pillow](https://pillow.readthedocs.io/en/stable/) to generate images. – furas May 11 '20 at 20:28
1 Answers
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)

- 134,197
- 12
- 106
- 148
-
1Help me please once more. I need to place an avatar image inside this image. How do I do it? – Resadesker May 12 '20 at 18:18
-
1`image.paste(other_image, (x,y, x+width, y+height))` doc: [Image.paste()](https://pillow.readthedocs.io/en/3.0.x/reference/Image.html#PIL.Image.Image.paste) – furas May 12 '20 at 19:50
-
1I added code which adds avatar and it also read image from external url and use it as background. – furas May 12 '20 at 23:22
-
1I added code with transparent rectangle. and link to example how to create circle images (to create circle avatar) – furas May 13 '20 at 00:46
-
1Hello! I've got one more problem. With different users' avatar the bot works different. Sometimes it adds the avatar in the right place, sometimes 100 pixels left or right, sometimes 30 pixels up or down. How do I fix it? – Resadesker May 13 '20 at 06:51
-
first use `print()` to see values in variables - position in which you put it, vatar size, etc. You can also save avatars on disk and check them in some image Viewer. Avatars may have different sizes or transparent background and then it may looks like they are in different place. And maybe when you see different avatars then you will have idea how to change them to make them similar - maybe you have to resize them or crop them. – furas May 14 '20 at 03:29
-
I've already used `print`, but I cannot understand how to stop them all move – Resadesker May 14 '20 at 05:56
-
do you put it in the same position and with the same size ? if not then you have to resize image to the same size or better calculate its position. if you have problem then create new question on new page and put all your code. – furas May 14 '20 at 06:33
-
Please can you write me how to check and improve this cause I do not know – Resadesker May 14 '20 at 14:02
-
I don't know what code you have - I think code in my answer doesn't need any changes. – furas May 14 '20 at 16:38
-
Here is my code: https://pastebin.com/V27zU7av Could not place it here cause it's too big – Resadesker May 14 '20 at 16:55
-
create new question on new page, put minimal code which we can run and add images which shows problem - code may gives correct results for me and I will don't know what is the problem. – furas May 14 '20 at 16:58
-
-
did you use `print()` to check position and size ? Did you check `print(x,y)` to check x,y which you use to put avatar ? Did you check how you calculate `x,y` ? Did you see `x = (IMAGE_WIDTH - text_width)//2` in your code ? This value depends on text width - user name. And you use it to put avatar. If user's name is longer then avatar will be in different place. – furas May 14 '20 at 17:19