如何用Pillow绘制进度条?

How to draw a progress bar with Pillow?

我目前正在尝试根据计算出的百分比绘制进度条。

但是,我无法以正确的格式显示它。

我将自己定位于此站点的另一个答案 ( & Is it possible to add a blue bar using PIL or Pillow?)

但要么是进度条太长,width限制不起作用,要么进度条没有显示进度。

示例 1:

async def rank(self, ctx, member: discord.Member):
    member = ctx.author

    data = await database.find_user(collection_user, ctx.guild.id, ctx.author.id)
    already_earned = data["exp"]

    to_reach= ((50 * (data['lvl'] ** 2)) + (50 * (data['lvl'] - 1)))
    
    percentage = ((data["exp"] / next_level_xp ) * 100) # Get the percentage

    ## Rank card
    img = Image.open("leveling/rank.png")
    draw = ImageDraw.Draw(img)

    font = ImageFont.truetype("settings/myfont.otf", 35)
    font1 = ImageFont.truetype("settings/myfont.otf", 24)
    async with aiohttp.ClientSession() as session:
        async with session.get(str(ctx.author.avatar)) as response:
            image = await response.read()
    icon = Image.open(BytesIO(image)).convert("RGBA")
    img.paste(icon.resize((156, 156)), (50, 60))

    # Some text drawn, this works

    ### From Whosebug ###
    def drawProgressBar(d, x, y, w, h, progress, bg=(129, 66, 97), fg=(211,211,211)):
        # draw background
        draw.ellipse((x+w, y, x+h+w, y+h), fill=bg)
        draw.ellipse((x, y, x+h, y+h), fill=bg)
        draw.rectangle((x+(h/2), y, x+w+(h/2), y+h), fill=bg, width=10)

        # draw progress bar
        progress = ((already_earned / to_reach ) * 100)
        w *= progress 
        draw.ellipse((x+w, y, x+h+w, y+h),fill=fg)
        draw.ellipse((x, y, x+h, y+h),fill=fg)
        draw.rectangle((x+(h/2), y, x+w+(h/2), y+h),fill=fg, width=10)

        return d

    drawProgressBar(img, 10, 10, 100, 25, 0.5)
    ### From Whosebug ###

    img.save('leveling/infoimg2.png') # Save it and send it out

看起来像这样:

第二个例子:

async def rank(self, ctx, member: discord.Member):

    member = ctx.author

    data = await database.find_user(collection_user, ctx.guild.id, ctx.author.id)
    already_earned = data["exp"]

    to_reach = ((50 * (data['lvl'] ** 2)) + (50 * (data['lvl'] - 1)))
    
    percentage = ((already_earned / to_reach) * 100) # Get the percentage 

    img = Image.open("leveling/rank.png")
    draw = ImageDraw.Draw(img)

    ### From Whosebug ###
    color=(129, 66, 97)
    x, y, diam = percentage, 8, 34
    draw.ellipse([x,y,x+diam,y+diam], fill=color)
    ImageDraw.floodfill(img, xy=(14,24), value=color, thresh=40)
    ### From Whosebug ###

    font = ImageFont.truetype("settings/myfont.otf", 35)
    font1 = ImageFont.truetype("settings/myfont.otf", 24)
    async with aiohttp.ClientSession() as session:
        async with session.get(str(ctx.author.avatar)) as response:
            image = await response.read()
    icon = Image.open(BytesIO(image)).convert("RGBA")
    img.paste(icon.resize((156, 156)), (50, 60))
    # Draw some other text here, this works though
    img.save('leveling/infoimg2.png') # Save the file and output it

看起来像这样:

两个结果都不符合答案和问题中显示的图片。

有人能告诉我哪里做错了吗? 我还尝试在第二个示例中增加 x 或设置 img = Image.open("pic.png").format('RGB') 但似乎没有任何效果。进度条太长或太短。

我试图实现我的进度条被限制在一定的大小,总是匹配 100% 并且我定义的 progress 会适应它。

您的代码存在的问题是背景和进度条部分的颜色相同,因此您看不到它。这可以通过不同的颜色来解决。

progress = ((already_earned / to_reach ) * 100) 还将 progress 设置为百分比 [0, 100]。然后将 width 乘以这个。对于 100 的输入,例如 50% 的填充量会使椭圆变为 5000 像素 - 远离屏幕并覆盖已经在那里绘制的所有内容。

@client.command(name='rank')
async def rank(ctx, progress: float):

    # Open the image and do stuff
    # I tested this with a blank 800x400 RGBA

    def new_bar(x, y, width, height, progress, bg=(129, 66, 97), fg=(211,211,211), fg2=(15,15,15)):
        # Draw the background
        draw.rectangle((x+(height/2), y, x+width+(height/2), y+height), fill=fg2, width=10)
        draw.ellipse((x+width, y, x+height+width, y+height), fill=fg2)
        draw.ellipse((x, y, x+height, y+height), fill=fg2)
        width = int(width*progress)
        # Draw the part of the progress bar that is actually filled
        draw.rectangle((x+(height/2), y, x+width+(height/2), y+height), fill=fg, width=10)
        draw.ellipse((x+width, y, x+height+width, y+height), fill=fg)
        draw.ellipse((x, y, x+height, y+height), fill=fg)

    new_bar(10, 10, 100, 25, progress)


    # send

示例:

progress = 0.25

progress = 0.9


至于第二个代码片段,它使用 floodfill() 不正确。 thresh 是“像素值与‘背景’的最大容忍差异,以便替换它。”这意味着 floodfill 实际上什么都不做,你只画了椭圆(而不是进度条前面的部分)。

    # this draws a circle with bounding box `x,y` and `x+diam,y+diam`
    # note that `x` is dependent on the progress value: higher progress means larger x, which means the circle is drawn more to the right
    draw.ellipse([x,y, x+diam,y+diam], fill=color)
    # If you look at the post where the code was given, you can see the error.
    # In that post, the entirety of the progress bar already exists, and is a very different color (allowing the use of the threshold value).
    # In this code, no progress bar exists yet, meaning everything else is just one solid color, and then the floodfill cannot do anything.
    # Also, xy is specified as arbitrary coordinates, which you would need to change to fit your bar.
    # This does nothing.
    ImageDraw.floodfill(img, xy=(14,24), value=color, thresh=40)

如果你想解决这个问题,你需要在进度条的左边填充颜色。上面的第一个函数已经这样做了。如果需要,您可以删除前 3 行以避免绘制背景,从而产生相同的结果。