[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Draw shapes with translucent edges with PIL in Python

[An ellipse with translucent edges drawing with PIL in Python]

This example shows how you can draw ellipses and rectangles that gradually fade away at their edges. The motivation is that I want to use these shapes as masks when copying one image onto another and I want to blend the overlaid image into its background. You'll see that in my next post.

draw_blurred_shape

This is actually pretty easy for standard shapes like ellipses and rectangles. The following draw_blurred_shape method draws an ellipse or rectangle with blurred edges.

from PIL import Image, ImageDraw def draw_blurred_shape(image, xmin, ymin, xmax, ymax, shape_type, radius=5, step=0.5): '''Draw a shape with blurred edges.''' # Make a Draw object to draw the shapes. dr = ImageDraw.Draw(image) # Draw the shapes. r = radius while r >= 0: fill = (0, 0, 0, int(255 * ((radius - r) / radius))) rect = (xmin - r, ymin - r, xmax + r, ymax + r) if shape_type == 'Rectangle': dr.rectangle(rect, fill=fill) else: dr.ellipse(rect, fill=fill) r -= step # Fill the final shape. fill = (0, 0, 0, 255) if shape_type == 'Rectangle': dr.rectangle(rect, fill=fill) else: dr.ellipse(rect, fill=fill)

The xmin, ymin, xmax, and ymax parameters give the shape's bounds. The shape_type parameter should be either "Rectangle" or "Ellipse." The radius parameter indicates how large the shape's blurred edge should be. The step parameter indicates how many pixels smaller each shape should be.

For example, suppose you're drawing a circle with edge = 10 and step = 1. Then the function draws 10 circles where each circle's radius is 1 pixel smaller than the one before it. If step = 0.5, then the function draws 20 circles where each radius is 0.5 less than the one before.

The function starts by creating an ImageDraw.Draw object so it can draw on the image. It then sets r = radius to draw the largest shape and enters a loop that runs while r >= 0.

The code inside the loop sets the current shape's fill color to black with opacity that increases as r decreases. It draws the shape and the loop continues drawing smaller shapes with increasing opacities.

After the loop ends, the function draws the shape one last time at its smallest size with full opacity.

Main Program

Use the Blur menu to select the shape type and the blurring radius. Then click and drag to draw a shape.

The following event handler draws the shape when you release the mouse button.

def mouse_up(self, event): '''Draw a blurred shape.''' # Clear the image. dr = ImageDraw.Draw(self.bg_image) rect = (0, 0, self.bg_image.width, self.bg_image.height) dr.rectangle(rect, fill='white') # Get the selected area's bounds. xmin = min(self.start_point[0], self.end_point[0]) xmax = max(self.start_point[0], self.end_point[0]) ymin = min(self.start_point[1], self.end_point[1]) ymax = max(self.start_point[1], self.end_point[1]) shape_type = self.shape_type_var.get() radius = self.radius_var.get() # Draw the blurred shape. draw_blurred_shape(self.bg_image, xmin, ymin, xmax, ymax, shape_type, radius) # Display the result. self.canvas.delete(tk.ALL) self.bg_photo_image = ImageTk.PhotoImage(self.bg_image) self.canvas.create_image(0, 0, image=self.bg_photo_image, anchor='nw')

This code creates an ImageDraw.Draw object to draw on the program's background image. It sets rectangle bounds rect to cover the image and then fills it with white to remove any previously drawn shape.

Next, the code gets the X and Y coordinates of the area you selected. It also gets the selected shape type and radius.

The code then calls draw_blurred_shape to draw the shape. It finishes by displaying the result on the program's Canvas widget.

Conclusion

[This technique produces noticeable Mach banding] The result is a shape with a blurry edge. You'll see how I use that in my next post.

The edges aren't perfectly smooth and it's easy to see Mach banding if you look closely at the result. See the Wikipedia article Mach bands for information on that. You could reduce the effect by using antialiasing (see my post Provide antialiasing with PIL and Python), but this will be good enough for my needs in my next post.

There are ways you can draw arbitrary shapes (like polygons) with decreasing sizes, but that's more work and I don't need it right now so I'm not going to bother. Perhaps I'll do this in a later post.

Meanwhile, download the example to experiment with it and to see additional details.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.