PIL Tutorial: How to Create a Button Generator

Python Imaging Library (PIL) is an external library for Python. It supports opening, manipulating, and saving different image file formats. PIL is well documented and easy to install.

In this tutorial I'm focusing on the paste function. At the end of the tutorial, I'll explain a useful script that converts any icon into a web 2.0 style button, just like these:

delicious button feed button blogger button

The paste function

im.paste(im, box=None, mask=None)

The first argument can either be an Image object or a color. If it is an Image, the image will be pasted into this image. If it is a color, the region specified by the box argument will be filled with a single color.
The box argument defines the size and position of the pasting region. It can be a 2-tuple that defines the left upper corner, or a 4-tuple that defines the left, upper, right and lower pixel coordinate. It defaults to None, which translates to (0, 0, self.width, self.height)PIL's documentation says the default is (0, 0), but I checked the source code of version 1.1.6 and found this not to be the case..
When providing a mask argument, it only pastes the regions indicated by the mask. You can either use "1", "L" or "RGBA" images. In the latter case, the alpha band is used as mask. Where the mask is 255, the given source is copied as is. Where the mask is 0, the current value is preserved. Intermediate values can be used for transparency effects.

Examples

Please note I'm using transparency as a background for illustrative purposes only; it's not actually a part of the image itself.Those unfortunate to use IE6 (or older) won't be able to view the transparent parts, but I guess this won't affect the readability of the tutorial.

We have two images, a flower and a butterfly.The flower and butterfly icons were downloaded from iconspedia

flower    butterfly

We want to paste the butterfly image on top of the flower. Since the butterfly image is smaller in size, we need to set the box argument. Otherwise, PIL will complain saying "Images do not match". I will set the box to (40, 10), which is roughly the top edge of the flower.

flower = Image.open('flower.png')
butterfly = Image.open('butterfly.png')
flower.paste(butterfly, (40, 10))

Now let's see how the result looks:

butterfly on flower bad

Oops, transparency was pasted on the flower along with the butterfly's image, definitely not what we want!
But that is to be expected; we asked to paste the image as it is. We should have specified a mask argument to tell PIL what to paste and what to leave.
If we specify a mask that is the same as the source then we will get the effect we want.

flower = Image.open('flower.png')
butterfly = Image.open('butterfly.png')
flower.paste(butterfly, (40, 10), butterfly)
butterfly on flower

Where the mask's alpha is 255 (opaque), the source is copied as it is. Where the alpha is 0 (transparent), the source is not copied.

Very simple button effect

Now let's say we want to create a very simple button effect. For this purpose we need an image with a transparent border effect.I created the border image with GIMP.

border effect
border = Image.open('border.png')
flower.paste(border, mask=border)
flower with border bad

No, this is not what we want. Part of the transparency is being copied along with the border, but why?
Because as we learned in the previous example, transparency is not ignored by the paste function. In fact transparency in the source image is being treated as any other color band.This is actually a personal conclusion from numerous tests. Therefore, even when using the same image as the source and mask, the intermediate transparency levels will be copied from the source into the result. To work around this, we should get rid of the alpha band in the source image, while keeping it in the mask. And what is easier to achieve this than converting the image into 'RGB'.

border = Image.open('border.png')
source = border.convert('RGB')
flower.paste(source, mask=border)
flower with border

Now this worked!

Putting it all together

We'll put everything we learned together to create a web 2.0 style button.
First we need a mask image to crop the icon in the shape and size we want. Then, we need a highlight image to create the button effect.Both the mask and the round button effect were created with GIMP.

round mask    round highlight

The mask and highlight images need to be of the same size for our example to work.
Let's convert this Blogger icon into a button

blogger icon
highlight = Image.open('round.png')
mask = Image.open('round-mask.png')
# Read the icon and convert it into 'RGBA' in case it wasn't
icon = Image.open('blogger.png').convert('RGBA')
button = Image.new('RGBA', mask.size)
 
# Resize Icon
icon = ImageOps.fit(
  icon, highlight.size, method=Image.ANTIALIAS, centering=(0.5, 0.5)
)
 
# Create a helper image that will hold the icon after the reshape
helper = button.copy()
# Cut the icon by the shape of the mask
helper.paste(icon, mask=mask)
 
# Fill with a solid color by the mask's shape
button.paste((255, 255, 255), mask=mask)
# Get rid of the icon's alpha band
icon = icon.convert('RGB')
# Paste the icon on the solid background
# Note we are using the reshaped icon as a mask
button.paste(icon, mask=helper)
 
# Get a copy of the highlight image without the alpha band
overlay = highlight.copy().convert('RGB')
button.paste(overlay, mask=highlight)
 
button.save('button.png')

And the result is:

blogger button

You may wonder why ImageOps.fit was used instead of Image.resize. Image.resize will work perfectly fine with images that have the same aspect ratio as the new size, but will distort the image otherwise. ImageOps.fit will resize and crop the image to match the requested aspect ratio and size. You have the option to control the cropping position, which is the center (0.5, 0.5) in our case.

You have to keep in mind that if we want our script to handle icons with transparent backgrounds, we need to specify a background color that fills the transparent spaces, or we'll end up with buttons that look like this:

button bad

This is why I fill the button's background with a solid color before pasting the icon.

Web 2.0 style buttons generator

Now here is the complete script with command line arguments support using getopt module
Example of usage:

./make-button.py feed.png
./make-button.py delicious.gif blogger.png butterfly.png --shape round
./make-button.py butterfly.png --shape heart --color 200 120 22

I've also attached the full script with helper images in a zip file for convenience. The helper images include round, heart and square shaped button effects.

#!/usr/bin/python
 
# Button generator script
# Author: Nadia Alramli http://nadiana.com
 
from PIL import Image
import ImageOps
 
from optparse import OptionParser
import os.path
 
def create_button(iconPath, shape, bgColor):
  highlight = Image.open('helper/%s.png' % shape)
  mask = Image.open('helper/%s-mask.png' % shape)
  # Read the icon and convert it into 'RGBA' in case it wasn't
  icon = Image.open(iconPath).convert('RGBA')
  button = Image.new('RGBA', mask.size)
 
  # Resize Icon
  icon = ImageOps.fit(
    icon, highlight.size, method=Image.ANTIALIAS, centering=(0.5, 0.5)
  )
 
  # Create a helper image that will hold the icon after the reshape
  helper = button.copy()
  # Cut the icon by the shape of the mask
  helper.paste(icon, mask=mask)
 
  # Fill with a solid color by the mask's shape
  button.paste((255, 255, 255), mask=mask)
  # Get rid of the icon's alpha band
  icon = icon.convert('RGB')
  # Paste the icon on the solid background
  # Note we are using the reshaped icon as a mask
  button.paste(icon, mask=helper)
 
  # Get a copy of the highlight image without the alpha band
  overlay = highlight.copy().convert('RGB')
  button.paste(overlay, mask=highlight)
 
  iconPath, ext = os.path.splitext(iconPath)
  button.save('%s-%s.png' % (iconPath, shape))
 
 
def main():
  usage = 'usage: %prog file [file] [option]'
  parser = OptionParser(usage)
 
  parser.add_option('-s', '--shape', dest='shape', help='button shape')
  parser.add_option(
    '-c', '--color', dest='color', help='background color', 
    nargs=3, type= int
  )
  parser.set_defaults(shape='square', color=(255, 255, 255))
  options, args = parser.parse_args()
 
  if len(args) < 1:
    parser.error('Missing file operand')
 
  shape = options.shape
  bgColor = options.color
 
  for arg in args:
    iconPath = arg
    create_button(iconPath, shape, bgColor)
 
 
if __name__ == '__main__':
    main()

With this script you can add as many button effects as you want. All you need is two images: a mask and a highlight.
Let's say the style you want is a "star" then name the highlight image as star.png and the mask image as star-mask.png and place both of them in the helper folder.
Then run:

./make-button.py icon.png --shape star
AttachmentSize
button_generator.zip60.1 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Great posts!

Thanks a lot for making these. Discovering ImageOps.fit by reading this post has sure made my life a lot easier. I've just hacked about 10 lines of code out of several scripts!

very informative

excel tutorial
was looking for a function that returns the picture taken date, can you help?
thanks

Thanks

Thank you, Nadia.

I've been having some trouble with the alpha layer being copied along until I read your explanation. Thanks =)

Thank you, Nadia.

Thank you, Nadia.

Awesome would be great to

Awesome
would be great to extend this to create the needed image files from code, instead of needing an editor :)

Nadia, The last bit of the

Nadia,
The last bit of the script looks like an typo to me:
"if name == 'main':"

Very interesting tutorial overall, though.

Ah, that's because a newly

Ah, that's because a newly installed filter is breaking up existing code. I'll remove the filter. Thanks for the note!

hi! i would like to use this

hi! i would like to use this script, with some modifications, for a desktop widget.

can i change it? Obviously I will write your name as the original author, with a link to your site. :)

Sure, you can change it. No

Sure, you can change it. No problem :)

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options