Draw Ellipse in Python PIL with line thickness
Asked Answered
S

5

17

I am trying to draw a circle on an image, using Python. I tried this using PIL but I would like to specify a linewidth. Currently, PIL draws a circle but the border is too thin.

Here is what I have done.

For a test image: I created a 1632 X 1200 image in MS Paint and filled it green. I called it test_1.jpg. Here is the input file: Input

from PIL import Image, ImageDraw

im = Image.open('test_1.jpg')

width, height = im.size
eX, eY = 816,816 #Size of Bounding Box for ellipse

bbox =  (width/2 - eX/2, height/2 - eY/2, width/2 + eX/2, height/2 + eY/2)

draw = ImageDraw.Draw(im)
bbox_L = []
for j in range(0,5):
    bbox_L.append([element+j for element in bbox])
    draw.ellipse(tuple(bbox_L[j]), outline ='white')

im.show()

Basically, I tried to draw multiple circles that would be centered at the same spot but with a different radius. My thinking was that this would create the effect of a thicker line.

However, this is producing the output shown in the attached file below: Output

Problem: As you can see, the bottom-left and top-right are too thin. Also, there are gaps between the various circles (see top left and bottom right).

The circle has a varying thickness. I am looking a circle with a uniform thickness.

Question: Is there a way to do draw a circle in Python, on an image like test_1.jpg, using PIL, NumPy, etc. and to specify line thickness?

Scarlet answered 10/9, 2015 at 14:11 Comment(2)
Any thoughts here about suggested approaches to this? If any additional information is required, please let me know and I will update the original post.Scarlet
I'd wish to know the answer too! Alas, I cannot help you.Femmine
N
18

I had the same problem, and decided to write a helper function, similar to yours. This function draws two concentric ellipses in black and white on a mask layer, and the intended outline colour is stamped onto the original image through the mask. To get smoother results (antialias), the ellipses and mask is drawn in higher resolution.

Output with and without antialias

ellipses with PIL

The white ellipse is 20 pixels wide, and the black ellipse is 0.5 pixels wide.

Code

from PIL import Image, ImageDraw

def draw_ellipse(image, bounds, width=1, outline='white', antialias=4):
    """Improved ellipse drawing function, based on PIL.ImageDraw."""

    # Use a single channel image (mode='L') as mask.
    # The size of the mask can be increased relative to the imput image
    # to get smoother looking results. 
    mask = Image.new(
        size=[int(dim * antialias) for dim in image.size],
        mode='L', color='black')
    draw = ImageDraw.Draw(mask)

    # draw outer shape in white (color) and inner shape in black (transparent)
    for offset, fill in (width/-2.0, 'white'), (width/2.0, 'black'):
        left, top = [(value + offset) * antialias for value in bounds[:2]]
        right, bottom = [(value - offset) * antialias for value in bounds[2:]]
        draw.ellipse([left, top, right, bottom], fill=fill)

    # downsample the mask using PIL.Image.LANCZOS 
    # (a high-quality downsampling filter).
    mask = mask.resize(image.size, Image.LANCZOS)
    # paste outline color to input image through the mask
    image.paste(outline, mask=mask)

# green background image
image = Image.new(mode='RGB', size=(700, 300), color='green')

ellipse_box = [50, 50, 300, 250]

# draw a thick white ellipse and a thin black ellipse
draw_ellipse(image, ellipse_box, width=20)

# draw a thin black line, using higher antialias to preserve finer detail
draw_ellipse(image, ellipse_box, outline='black', width=.5, antialias=8)

# Lets try without antialiasing
ellipse_box[0] += 350 
ellipse_box[2] += 350 

draw_ellipse(image, ellipse_box, width=20, antialias=1)
draw_ellipse(image, ellipse_box, outline='black', width=1, antialias=1)

image.show()

I've only tested this code in python 3.4, but I think it should work with 2.7 without major modification.

Needleful answered 21/1, 2016 at 14:8 Comment(8)
This answers my question.Scarlet
For width > 1, I found the following change necessary to make the ellipse actually fit completely in the border: for offset, fill in ( 0, 'white' ), ( width, 'black' ):.Hod
How would this work if I want to draw a circle with fill color?Prow
@Stefan: Just draw the filled ellipsis first. Then draw the outline ellipsis.Aeri
@Håken Lid: I tried that but it looks good only if I increase the thicknes of the second elipse so as to completely owerwrite the first. That means I have to have some lines thicker than others ... also a second thing that I noticed is that now my drawings take up more memory, around three times more, than before ...Prow
I'm using the above method for a cleaner looking ellipse and it's perfect. However my PIL drawing includes rounded corners and I'm sure I can see the tiniest bit of colour (blue my case) showing through, assuming it's the ellipse mask behind showing through here's a screenshot pasteboard.co/JxtnY6K.png but it's very hard to see but very annoying to me is there a way round this?Appellee
@Appellee I can't tell what causes the blue colour there from just the image. You can post a new question and include your code, making it a minimal reproducible example.Aeri
Hi @HåkenLid I've opened a question here Kind regards.Appellee
S
6

Simple (but not nice) solution is to draw two circles (the smaller one with color of background):

outline = 10 # line thickness
draw.ellipse((x1-outline, y1-outline, x2+outline, y2+outline), fill=outline_color)
draw.ellipse((x1, y1, x2, y2), fill=background_color)
Stainless answered 16/1, 2016 at 20:34 Comment(0)
S
3

From version 5.3.0 onwards, released on 18 Oct 2018, Pillow has supported width for ImageDraw.ellipse. I doubt many people are using PIL nowadays.

Satyr answered 21/1, 2021 at 8:41 Comment(2)
This answer should be way, way higher.Cappuccino
is there a more modern alternative?Gildagildas
G
0

I don't think there's a way to specify ellipse thickness, but you probably can draw lines at each pixel where ellipse pass, with the argument width=...

NB: I'm foreign, so sorry if my english is wrong.

Gilead answered 24/11, 2015 at 7:17 Comment(0)
G
-2

You can use the Image.core.draw method like this:

zero_array = np.zeros((224,224))
im = Image.fromarray(np.uint8(zero_array))
draw = ImageDraw.Draw(im)
dr_im = Image.core.draw(im.getdata(), 0)
dr_im.draw_rectangle((22,33, 150,100),220,2)
dr_im.draw_rectangle((22,33, 150,100),125,0)
#draw.rectangle((22,33, 150,100), fill=220,outline = 125)
print(np.array(im)[33][23])
im.show()
Godred answered 26/2, 2019 at 11:3 Comment(1)
This only draws a couple of rectangles — so it is unclear what it has to do with drawing thick ellipses.Dewees

© 2022 - 2024 — McMap. All rights reserved.