Skip to content

ImageDraw.Draw.rectangle is ignoring fill alpha #2496

Description

@crobertsbmw

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

Activity

  1. wiredfool commented on Apr 26, 2017

    @wiredfool
    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.

  2. crobertsbmw commented on Apr 26, 2017

    @crobertsbmw
    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.

  3. MyNameIsFu commented on Oct 5, 2023

    @MyNameIsFu

    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?

  4. radarhere commented on Oct 6, 2023

    @radarhere
    Member

    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?

  5. MyNameIsFu commented on Nov 16, 2023

    @MyNameIsFu

    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.

  6. radarhere commented on Nov 16, 2023

    @radarhere
    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.

  7. Aphexus commented on May 14, 2024

    @Aphexus

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

  8. Aphexus commented on May 14, 2024

    @Aphexus

    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')
  9. radarhere commented on May 14, 2024

    @radarhere
    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.

  10. Sharpz7 commented on Oct 15, 2024

    @Sharpz7

    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

  11. radarhere commented on Oct 15, 2024

    @radarhere
    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.

  12. saluk commented on Feb 13, 2025

    @saluk

    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.

  13. radarhere commented on Feb 14, 2025

    @radarhere
    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.

    Edit: Looking at this now, this code is actually drawing RGBA on top of RGB. I think different people might have different expectations about how the alpha values should be combined. Here is one possibility to consider.

    from PIL import Image, ImageDraw
    im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
    im2 = Image.new("RGBA", (1, 1))
    d = ImageDraw.Draw(im2)
    d.rectangle((0, 0, 1, 1), (0, 255, 0, 127))
    im.paste(im2.convert("RGB"), mask=im2)
    assert im.getpixel((0, 0)) == (128, 127, 0, 255)
  14. Rossolson commented on Sep 8, 2025

    @Rossolson

    I was also hit by the 'base image should be in RGB, but the drawn object should be in RGBA' roadblock for an hour today. The documentation fixes or a change to the example would have been a big help.

  15. radarhere commented on Sep 8, 2025

    @radarhere
    Member

    Which example are you referring to?

    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.

  16. added a commit that references this issue on Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions