Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 950
edesz Avatar asked Sep 10 '15 14:09

edesz


3 Answers

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.

like image 55
Håken Lid Avatar answered Nov 20 '22 20:11

Håken Lid


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)
like image 29
matousc Avatar answered Nov 20 '22 21:11

matousc


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.