Reputation: 3602
I am currently trying to draw a progress bar, based on a calculated percentage.
However, I am not able to display it in the right format.
I orientated myself on another answer from this site here (How do you make a progress bar and put it on an image? & Is it possible to add a blue bar using PIL or Pillow?)
But either the case is that the progress bar is too long and the width
restriction does not work or the bar shows no progress.
Example 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
Second example:
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
Both results do not match the pictures shown in the answers and questions.
Is somebody able to tell me where I went wrong?
I also tried to increase x
in the second example or set img = Image.open("pic.png").format('RGB')
but nothing seems to work. The progress bar is either too long or too short.
I tried to achieve that my progress bar is restricted to some size, always matches 100% and that my defined progress
will adapt to it.
Upvotes: 7
Views: 1815
Reputation: 3924
The issue with your code was that the background and progress bar sections were of the same color, and as such, you couldn't see it. This can be fixed by coloring them differently.
The line progress = ((already_earned / to_reach ) * 100)
also sets progress
to a percentage [0, 100]
. You then multiply width
by this. For the input of 100
, a fill amount of 50% for example makes the ellipse go 5000
pixels - way off the screen and overwriting everything that was already drawn there.
@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
Example:
As to the second code snippet, it's using floodfill()
incorrectly. thresh
is the "maximum tolerable difference of a pixel value from the ‘background’ in order for it to be replaced." This means that the floodfill does literally nothing, and you only drew the ellipse (and not the part of the progress bar that goes before it).
# 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)
If you want to fix this, you need to fill in the color to the left of the progress bar. The first function above already does this. If you want, you can remove the first 3 lines to avoid drawing a background, producing the same results.
Upvotes: 4