Skip to content

ImageDraw.Draw.rectangle is ignoring fill alpha #2496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
crobertsbmw opened this issue Apr 25, 2017 · 13 comments
Closed

ImageDraw.Draw.rectangle is ignoring fill alpha #2496

crobertsbmw opened this issue Apr 25, 2017 · 13 comments

Comments

@crobertsbmw
Copy link

crobertsbmw commented Apr 25, 2017

What did you do?

from PIL import Image
from PIL import ImageDraw 
from io import BytesIO
from urllib.request import urlopen

url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((0, 00), (img.size[0], img.size[1])), fill=(0,0,0,127))
img.save('dark-cat.jpg')

What did you expect to happen?

I expected to see this:
image

What actually happened?

I got a black square:
dark-cat

What versions of Pillow and Python are you using?

Python 3.5.2
Pillow==4.1.0

@wiredfool
Copy link
Member

It appears to be filling the rectangle with (0,0,0,127).

I'm not clear, but you either want to alpha composite a black half transparent rectangle over the cat, or put a half transparent alpha channel over the cat. Look at Image.alpha_composite or Image.putalpha.

@crobertsbmw
Copy link
Author

According to my SO question, it's not a bug, but rather I misunderstood what Draw is intented to do.
http://stackoverflow.com/questions/43618910/python-pil-drawing-a-semi-transparent-square-overlay-on-image#43620169
Closing this issue.

@MyNameIsFu
Copy link

MyNameIsFu commented Oct 5, 2023

More than 6 Years later and I'm running into the same confusion. Version 10.x comments in ImageDraw:

def __init__(self, im, mode=None):
        """
        Create a drawing instance.

        :param im: The image to draw in.
        :param mode: Optional mode to use for color values.  For RGB
           images, this argument can be RGB or RGBA (to blend the
           drawing into the image).  For all other modes, this argument
           must be the same as the image mode.  If omitted, the mode
           defaults to the mode of the image.
        """

[...] or RGBA (to blend the drawing into the image)

It's still replacing the original values of the image with the drawing (eg. polygon) resulting in black Images with Alpha instead of 'blending' anything.
I'd recommend to alter the Comment and maybe reference the alpha_composite option?

@radarhere
Copy link
Member

radarhere commented Oct 6, 2023

The docstring states

For RGB images, this argument can be RGB or RGBA (to blend the drawing into the image).

In my evaluation, the problem in the code posted earlier is that it wasn't an RGB image.

img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")

You can see, the image is converted to RGBA before being given to ImageDraw.

If the original image

maxresdefault

is left as an RGB image instead,

from PIL import Image
from PIL import ImageDraw 
from io import BytesIO
from urllib.request import urlopen

url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
# Commenting out this line to keep it as an RGB image
#img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((0, 00), (img.size[0], img.size[1])), fill=(0,0,0,127))
img.save('dark-cat.jpg')

the result is fine.

dark-cat

As far as I can see, RGB images with RGBA ImageDraw instances blend perfectly well?

@MyNameIsFu
Copy link

MyNameIsFu commented Nov 16, 2023

Your example seems even less intuitive. Using 'RGB' mode on the Image and 'RGBA' mode on ImageDraw works perfectly fine.

rgb_image_rgba_draw

On the other side, using RGBA mode on both results in overwriting the original image.

rgba_image_rgba_draw

I would expect both compositions to behave the same way. Is there a reason why it should not? Is this the intended behavior?
Keep the Docs in mind:
"[...] this argument can be RGB or RGBA (to blend the drawing into the image)."
This should at least emphasise, that the original image needs to be opened in 'RGB' mode.

@radarhere
Copy link
Member

It has been this way since the fork from PIL. Pillow values backwards compatibility, so I'm reluctant to change the behaviour, as it might break the code of existing users.

For RGB images, this argument can be RGB or RGBA (to blend the drawing into the image)

That is the only mention of blending in the docstring, and so seems to clearly state that blending requires an RGB image. If have an alternate suggestion for what the docstring should look like, feel free to suggest it.

@Aphexus
Copy link

Aphexus commented May 14, 2024

This is really annoying and seems to be a bug.

# Fails for very large images
def save_image(fn, data, dpi=100):
    shape=np.shape(data)[0:2][::-1]
    size = [float(i)/dpi for i in shape]

    fig = plt.figure()
    fig.set_size_inches(size)
    ax = plt.Axes(fig,[0,0,1,1])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.imshow(data)
    fig.savefig(fn, dpi=dpi) #, bbox_inches='tight', format='svg')
    #plt.show()

# fails to handle alpha correctly
def save_as_png(canvas, fileName, type):    
    canvas.save(fileName + '.' + type, type) 

I am drawing a rectangle to set the background color then I draw row rectangles to make a grid. Using the first save the alpha channel works correctly and the rows blend in with the background. But here the image is cropped at a width of around 19000. Using the second no matter what I try the alpha channel is not correctly blended(it seems to just ignore the alpha). I don't know how PIL works but if it is some "retained mode" drawing then the canvas.save seems to ignore the alpha completely. I've tried both RGB and RGBA and it's the same problem.

It seems to be an issue with the save rather than anything else given that using the matplotlib saves the alpha correctly(but which I can't use since it is cropping the image for some reason).

@Aphexus
Copy link

Aphexus commented May 14, 2024

https://stackoverflow.com/questions/43618910/pil-drawing-a-semi-transparent-square-overlay-on-image#43620169

It fails when, as a commenter says, when the image has an alpha channel.

It seems that the code that combines alphas is flawed and does not properly merge pixels with alpha.

url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((280, 10), (1010, 706)), fill=(200, 100, 0, 127))
draw.rectangle(((280, 10), (1010, 706)), outline=(0, 0, 0, 127), width=3)
img.save('Z:/orange-cat.png')

@radarhere
Copy link
Member

Without knowing what variables you are passing into the functions, the code from your first comment doesn't seem to use Pillow at all.

If you do not convert the image to RGBA in your second comment,

from PIL import Image, ImageDraw
from io import BytesIO
from urllib.request import urlopen
url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
#img = img.convert("RGBA")
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((280, 10), (1010, 706)), fill=(200, 100, 0, 127))
draw.rectangle(((280, 10), (1010, 706)), outline=(0, 0, 0, 127), width=3)
img.save('orange-cat.png')

then the orange square over the cat becomes translucent. See #2496 (comment) for my prior explanation of this behaviour.

@Sharpz7
Copy link

Sharpz7 commented Oct 15, 2024

Your example seems even less intuitive. Using 'RGB' mode on the Image and 'RGBA' mode on ImageDraw works perfectly fine.

rgb_image_rgba_draw

On the other side, using RGBA mode on both results in overwriting the original image.

rgba_image_rgba_draw

I would expect both compositions to behave the same way. Is there a reason why it should not? Is this the intended behavior? Keep the Docs in mind: "[...] this argument can be RGB or RGBA (to blend the drawing into the image)." This should at least emphasise, that the original image needs to be opened in 'RGB' mode.

First, 7 years later from the tickets creation, thank you for saving me hours of pain to finally get the result I wanted. @MyNameIsFu

Is there an existing ticket for improving the documentation on this? I even feel like an embedded warning might be appropriate... this is a real head-sore for new users. Happy to do those doc-changes myself if they would be appreciated.

cc @radarhere

@radarhere
Copy link
Member

https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.Draw

mode – Optional mode to use for color values. For RGB images, this argument can be RGB or RGBA (to blend the drawing into the image)

If you think this doesn't sufficiently explain that an RGB image with an RGBA mode is needed to blend, then feel free to suggest other wording.

@saluk
Copy link

saluk commented Feb 13, 2025

I get that backwards compatibility is important somewhat, but not being able to draw in RGBA on top of RGBA is a serious feature gap which has had me looking around for some other more complete, more modern drawing library. (There's not a lot of great options... python is pretty weak in this area) But I figured out I can remedy with this simple fix, run this once in whichever file first imports ImageDraw:

image_draw_init = ImageDraw.__init__
def image_draw_init_force_blend(self, *args, **kwargs):
    image_draw_init(self, *args, **kwargs)
    self.draw = Image.core.draw(self.im, 1)
ImageDraw.__init__ = image_draw_init_force_blend

I'm still thinking of trying something else like SKIA though.

@radarhere
Copy link
Member

not being able to draw in RGBA on top of RGBA is a serious feature gap

It is possible to do so. Taking code from #2496 (comment)

from PIL import Image, ImageDraw
from io import BytesIO
from urllib.request import urlopen
url = "https://i.ytimg.com/vi/W4qijIdAPZA/maxresdefault.jpg"
file = BytesIO(urlopen(url).read())
img = Image.open(file)
draw = ImageDraw.Draw(img, "RGBA")
draw.rectangle(((280, 10), (1010, 706)), fill=(200, 100, 0, 127))
draw.rectangle(((280, 10), (1010, 706)), outline=(0, 0, 0, 127), width=3)
img.save('orange-cat.png')

I can draw a translucent orange rectangle on top of the image.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants