2

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!

Resadesker
  • 83
  • 1
  • 6
  • 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 Answers1

8

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:

enter image description here


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:

enter image description here


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:

enter image description here


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)

enter image description here

furas
  • 134,197
  • 12
  • 106
  • 148
  • 1
    Help 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
  • 1
    I 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
  • 1
    I 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
  • 1
    Hello! 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
  • Ok, I will create a new question. – Resadesker May 14 '20 at 17:08
  • 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