如何使用Pillow绘制进度条?

7

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

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

我参考了这个网站上的另一个答案(如何制作进度条并将其放在图像上?是否可能使用PIL或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 StackOverflow ###
    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 StackOverflow ###

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

这看起来像这样:
enter image description here

第二个例子:

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 StackOverflow ###
    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 StackOverflow ###

    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

这是它的样子:
enter image description here

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

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

我试图实现我的进度条被限制在某些大小范围内,始终匹配100%,并且我的定义的 progress 将会适应它。


请点击 [编辑] 并确保您的代码完整且可运行。谢谢。 - Mark Setchell
@MarkSetchell 我编辑了我的文本以提供更多信息。 - Leon Coller
仅供视觉比较和灵感参考,以下是使用标准库、tqdm、alive-progress等实现的示例。Python进度条 - smci
1个回答

4
你的代码问题在于背景和进度条部分颜色相同,因此你看不到进度条。这可以通过不同的着色来解决。
代码行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是“允许的最大像素值与‘背景’之间的差异,以便替换它。” 这意味着洪水填充根本不起作用,您只绘制了椭圆(而不是进度条前面的部分)。

    # 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)

如果您想要解决这个问题,需要填写进度条左侧的颜色。上面的第一个函数已经实现了这一点。如果您愿意,可以删除前三行代码以避免绘制背景,并得到相同的结果。

1
请注意,选择这种颜色(黑色背景上的深灰色条形中的椭圆)对于对比度和可访问性来说是不好的。 - smci
谢谢您详细的回答,非常有帮助。画黑色和灰色条都可以了。然而,在这个问题上我仍然遇到一些问题:我的百分比显示为 0.97,而 width = int(width*progress) 计算出来是 97,所以进度条填充了近 1%。我该如何解决这个问题,例如让进度条更长,避免这样的计算错误? - Leon Coller
也许我还需要添加这个:我删除了 progress: float 并只是使用了 new_bar(10, 10, 100, 25) - 如果这是原因,我将不得不找到一种方法来同时使用 float,因为我不想将其作为命令的要求。 - Leon Coller
1
没关系,那只是一个占位符。我写的函数中传递给它的progress值在[0, 1]范围内,其中1表示完全填充,0表示空。因此,您需要除以100。如果您想说0.97%,则必须传递值0.0097。我不会说这是进度条,因为它会四舍五入到最近的像素,但如果您想要,可以增加其大小。 - Eric Jin
@EricJin 谢谢你的解释,这样改进进度在这种情况下很有意义。非常有用的答案! - Leon Coller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接