[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 glassy button images in Python

[This example lets you make glassy button images in Python]

My previous post Use gradient brushes to make button images in Python shows how to build a program that lets you generate button images and save them into files. This post uses the same general technique but it draws the buttons in a different style. The main differences are in the make_button_image method that draws the buttons and the make_highlight_mask helper function that it uses to create the highlights.

make_button_image

The make_button_image method is pretty long, so I'll show it in three pieces. Here's the first piece.

def make_button_image(self, num, face_color, border_color, text_color, highlight_color, width, border, font): '''Draw a button for this number.''' wid = width * self.SCALE image = Image.new('RGBA', (wid, wid), color=None) dr = ImageDraw.Draw(image) # Draw the button. # Border. radius = cx = cy = wid / 2 bounds = [cx - radius, cy - radius, cx + radius - 1, cy + radius - 1] dr.ellipse(bounds, fill=border_color) # Face. face_radius = radius - border * self.SCALE bounds = [cx - face_radius, cy - face_radius, cx + face_radius - 1, cy + face_radius - 1] dr.ellipse(bounds, fill=face_color) # Text. dr.text(xy=(cx, cy), text=f'{num}', fill=text_color, font=font, anchor='mm')

This piece is fairly similar to the code used in the previous example. It creates an image scaled for antialiasing, fills the button with the border color, then fills the button's face with its face color, and draws the button's text.

All that's left is to draw the button's two highlights. Here's the code that draws the top highlight.

################# # Top highlight # ################# x_radius = face_radius * 0.7 y_radius = face_radius * 0.45 xmin = cx - x_radius ymin = (1.5 * border) * self.SCALE # Make the highlight mask. wid = 2 * x_radius hgt = 2 * y_radius start_color = [255, 255, 255, 200] end_color = [255, 255, 255, 0] highlight_mask = make_highlight_mask(wid, hgt, start_color, end_color) highlight_mask.save('highlight_mask.png') # Paste the highlight image onto the main image. highlight_image = Image.new('RGBA', (highlight_mask.width, highlight_mask.height), color=highlight_color) image.paste(highlight_image, (int(xmin), int(ymin)), highlight_mask)

[The highlight mask indicates which highlight pixels are more or less opaque] The code calculates the highlight's size (picking good values just took a bunch of fiddling). It then calls the make_highlight_mask function to make a highlight mask like the one on the right. That image shows what the mask looks like in MS Paint, which draws a checkboard behind tanslucent pixels so you can see how opaque they are. I'll describe the make_highlight_mask function a bit later.

The mask's alpha component indicates how much of the highlight color should be placed on top of the button's image. The mask is transparent (alpha = 0) around the edges so the highlight doesn't appear there. The middle of the mask holds an ellipse that shades from mostly opaque (alpha = 200) at the top to fully transparent (alpha = 0) at the bottom.

Having created the mask, the code makes a highlight_image of the same size and filled with the highlight color. It then pastes the highlight image ontp the button's image. The mask determines how opaque the highlight image's pixels are as they are drawn on top of the button image.

The following code shows how the make_button_image method draws the lower highlight.

######################### # Lower right highlight # ######################### x_radius = face_radius * 0.6 y_radius = face_radius * 0.25 xmin = cx - x_radius ymin = (1.5 * border) * self.SCALE # Make the highlight mask. wid = 2 * x_radius hgt = 2 * y_radius start_color = [255, 255, 255, 0] end_color = [255, 255, 255, 164] highlight_mask = make_highlight_mask(wid, hgt, start_color, end_color) # Rotate the highlight. highlight_mask = highlight_mask.rotate(45, expand=True, fillcolor=None) # Paste the highlight image onto the main image. highlight_image = Image.new('RGBA', (highlight_mask.width, highlight_mask.height), color=highlight_color) highlight_cx = cx + face_radius * 0.5 highlight_cy = cy + face_radius * 0.5 x = highlight_cx - highlight_image.width / 2 y = highlight_cy - highlight_image.height / 2 image.paste(highlight_image, (int(x), int(y)), highlight_mask) # Scale to desired size. image = image.resize((width, width), resample=Image.LANCZOS) # Convet to PhotoImage and return. photo_image = ImageTk.PhotoImage(image) return image, photo_image

This part of the method is similar to the earlier part with two twists. First, the highlight mask shades from transparent at the top to opaque at the bottom instead of vice versa. Second, after it creates the mask, the code rotates it 45°. The code makes a few calculations to position the highlight properly (again, a bit of fiddling to find the right spot) and pastes the highlight onto the button.

The method finishes as in the earlier post, resizing the button image to reduce aliasing and returning the image and a PhotoImage.

make_highlight_mask

The make_highlight_mask function creates the highlight mask image. The idea is to create a fully transparent image and then draw an ellipse on it that shades from one color to another. (In make_button_image, the top highlight shades from mostly opaque to mostly transparent.)

Without further ado, here's the function.

def make_highlight_mask(wid, hgt, start_color, end_color): '''Make an elliptical mask for drawing a highlight.''' # Make a trasparent image. wid = int(wid) hgt = int(hgt) mask = Image.new('RGBA', (wid, hgt), color=None) # Make a linear gradient brush shading from start_color to end_color. start_point = (0, 0) end_point = (0, hgt) brush = LinearGradientBrush(start_point, end_point, start_color, end_color) # Use the brush to draw the ellipse. bounds = [0, 0, wid, hgt] pil_fill_ellipse(mask, brush, bounds) return mask

The code first makes a transparent image sized to fit the highlight. It then creates a linear gradient brush that shades from the start color to the end color. (For information on linear gradient brushes, see my post Make a LinearGradientBrush class for use with PIL in Python.) Finally, it uses the brush to fill the highlight's ellipse and returns the resulting image.

Conclusion

This makes building fancy-looking buttons easy. It lets you change the button's colors, size, text size, and numbers. Click Save to save button images into files.

The following picture shows ten button images as displays in File Explorer (in Windows 11).

[Ten buttons displayed in File Explorer]

Use the Highlight Color button to set the highlight color. For example, the following button uses a pink highlight. This is similar to what you might get if a pink light were shining on the button.

[This button has pink highlights]

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

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