edesz
edesz

Reputation: 12406

Draw Ellipse in Python PIL with line thickness

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?

Upvotes: 17

Views: 14933

Answers (5)

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.

Upvotes: 3

wang wang
wang wang

Reputation: 1

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

Upvotes: -2

Håken Lid
Håken Lid

Reputation: 23064

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.

Upvotes: 18

matousc
matousc

Reputation: 3977

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)

Upvotes: 6

Mika
Mika

Reputation: 1

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.

Upvotes: 0

Related Questions