[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: Make elliptical images with smoother blended edges in Python

[This program uses a Gaussian filter to make elliptical images with smoother blended edges]

My post Make elliptical images in Python explains how you can make an elliptical image with sharp edges. My follow-up post Make elliptical images with blended edges in Python explains how you can make the edges softer so the transition from the background to the image is smoother.

This post describes a small problem with that technique and explains how you can fix it.

Mach Banding

[A linearly blended mask produces Mach bands] If you look closely at the blended image from the previous post and shown on the right, you can see that the image's edges are smooth but the border where the edge is blended seems to start and stop somewhat abruptly. This is caused by Mach banding (named after its discoverer physicist Ernst Mach), an optical illusion that makes the human eye exaggerate contrast where different shades of color meet.

The effect occurs at the outer edge of the blended area because the background image is moving along smoothly and then suddenly starts changing with the foreground image. Another Mach bands appears similarly at the inner edge of the blended area because the foreground image is moving along smoothly and then suddenly starts changing with the background image.

The effect is similar to the jerk you feel if you're driving a car and you apply the brakes evenly to slow in a linear fashion. When you go from moving slowly to a speed of zero, you feel a jerk. To avoid that, you can slow the braking as you approach zero speed to smooth out your speed's rate of change. (The second derivative of your position.)

You get a similar (though opposite) jerk if you start with linear acceleration and you can prevent that with the same technique: start accelerating very slowly and then gradually increase your acceleration.

The following graph shows the idea when a color's brightness changes smoothly from 80% to 20%. When the gradient starts and stops, Mach bands appear.

[Mach bands are an optical illusion that makes the human eye exaggerate contrast where different shades of color meet]

Reducing Mach Bands

The solution is the same one you use when you're starting or stopping your car: begin the change gradually instead of linearly. The following graph shows this approach.
[You can reduce Mach banding by starting and stopping the mask edges gradually]
You can write some Python code to make the mask's edges begin and end more gradually if you like, but PIL's Image.filter method can do this for you more easily and quickly. It even simplifies the masking code.

The GaussianBlur filter is a low-pass filter meaning it allows low-frequency features (think large, gradual changes) to remain in an image while is blurs high-frequency features (places where the image changes quickly). In this case, parts of the elliptical mask that are solid white or solid black are not changing quickly so the filter leaves them alone. In contrast, the edges of the mask change abruptly from black to white or vice versa so there the image changes quickly and the filter blurs the result. (For more information on how this filter works, see my post Use image filters to blur and sharpen images, and to detect edges in Python. See my other filter posts for information about other filters and filters in general.)

The result is a mask that uses nicely smoothed edges to reduce Mach banding.

Implementing Gaussian Smoothing

The following code shows the new elliptical_image_gaussian function, which uses a smoothed filter to create an elliptical image.

def elliptical_image_gaussian(image, blur_width=50): '''Return an elliptical copy of the image with smoothly blended edges.''' # Make sure the blur_width is less than half the width and height. blur_width = min(blur_width, image.width // 2, image.height // 2) # Make an elliptical mask image with transparent background. mask = Image.new('L', (image.width, image.height), 0) dr = ImageDraw.Draw(mask) rect = (blur_width, blur_width, image.width - 1 - blur_width, image.height - 1 - blur_width) dr.ellipse(rect, fill='white', outline=None) # Use a Gaussian filter to blur the mask's edges. radius = int(blur_width / 2) mask = mask.filter(ImageFilter.GaussianBlur(radius=radius)) # Copy the image and set its transparency. image = image.copy() image.putalpha(mask) return image

This code creates a mask as before. This time, instead of creating a sequence of nested ellipses with different opacities, it creates a single ellipse the size of the image.

The statement mask.filter(ImageFilter.GaussianBlur(radius=radius)) applies a Gaussian filter to the mask to smooth its edges.

The code then finishes as before, calling putalpha to copy the mask's opacity onto the image.

Conclusion

The following picture shows the three masks created by the three elliptical image examples.
[The three elliptical image examples use these masks]
Here are the results.
[The three elliptical image examples produce these results]
The left image has sharp edges. The middle image has blended edges and you can see some Mach banding, particularly on the sides and bottom of the image where the webpage's green background blends into the darker parts of the image. The image on the right uses a Gaussian filter to reduce Mach banding.

Download the example to experiment with it and to see additional details.

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