Transparency with PIL

I’d like to attribute the statusbar overlays I’ve been using in my last two posts to my mad Photoshop skillz, but my skillz aren’t mad—or even particularly skillful—and I don’t own Photoshop. I built the overlays with screenshots from my phone, some rudimentary editing in Acorn (which I’m sure can do more in better hands than mine), and an interactive session with the Python Imaging Library.

Lets start by looking at examples of the final product. Here’s the black overlay for portrait screenshots

Black statusbar overlay

and here’s the white overlay for portrait screenshots.

White statusbar overlay

The gray background is Preview’s way of telling you that an area is transparent. Around the edges of the text and graphics, the pixels are still colored black or white, but have a degree of transparency governed by an alpha channel.

Antialiasing transparency

I started with some screenshots of an app with a pure black background in its statusbar. I used Yahoo Sports, but I’m sure there are plenty of others. It’s important for the background to be pure black—an RGB triple of (0, 0, 0)—because that’s going to turn into fully transparent when we use the image as an alpha channel. One of the screenshots was taken with the battery at 100%; another was taken at 4:00 PM, the time I wanted in my overlay. I cropped out all but the top 40 pixels of each screenshot, and after some cutting, pasting, and flattening of layers in Acorn, I had a PNG that looked like this:

Mask

The edges of the text and graphics in this image are antialiased with pixels of various shades of gray. I called this image mask640.png.

From this point on, everything was easy because it required no design talent or even any expertise with an image editing program. I created a white 640×40 PNG and a black 640×40 PNG called white640.png and black640.png, respectively, and started an interactive Python session. The session went like this:

>>> import Image
>>> mask = Image.open("mask640.png")
>>> mask = mask.convert("L")
>>> bar = Image.open("black640.png")
>>> bar.putalpha(mask)
>>> bar.save("640n.png")
>>> bar = Image.open("white640.png")
>>> bar.putalpha(mask)
>>> bar.save("640w.png")

The convert("L") changes the mask from a 24-bits-per-pixel full color image1 to an 8-bits-per-pixel grayscale image (see the PIL documentation for details). The putalpha(mask) then adds the 8-bit mask as an alpha channel to whichever image it’s applied to. The pure black areas in the alpha channel become fully transparent, the fully white areas become fully opaque, and the gray areas are somewhere in between. Applying that channel to the all-black strip creates the first image shown in this post; applying it to the all-white strip creates the second.

I realize that to most people this dip into PIL must seem nuts. But to me, PIL’s algorithmic terminology is straightforward and easy to understand. I knew it would take me much less time to apply the alpha channel this way than to learn how to do it in Acorn.


  1. While the mask640.png image looks grayscale, it’s saved with red, green, and blue values for each pixel. I suppose I could’ve converted it to grayscale before saving it in Acorn, but I’m more comfortable in PIL.