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:
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:
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?
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.
The white ellipse is 20 pixels wide, and the black ellipse is 0.5 pixels wide.
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.
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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With