|Ned Batchelder : Blog | Code | Text | Site|
Truly transparent text with PIL
» Home : Blog : January 2008
I needed a transparent PNG image of some text to overlay text on an image. My first try looked OK, but the edges of the text seemed to be the wrong color. After some finagling, I came up with PIL code that did the right thing.
Here was the first code I used:
import Image, ImageFont, ImageDraw
Here's the image it produces: (If you are viewing this in IE6, you won't see the transparency)
You can see that the edges of the letters are grimy. The white text should not be visible at all against the white background, but you can see the edges.
This is because when PIL draws a partially-transparent pixel at the edge of a letter, it uses the partial coverage of the shape to blend the background and foreground pixels. If the background were fully opaque, this would be the right thing to do, but with a fully transparent background like we are using, this gives the wrong color. We specified the background as fully transparent black, so for a pixel half-covered with white, PIL computes a color of half-transparent gray. It should be half-transparent white, so that the final image will be able to blend properly with any color underneath it.
Look at it another way: if I specify the background as completely transparent (alpha of 0), then it shouldn't matter what color I provide for the RGB channels. I should get the same final result if I specify (0,0,0,0) or (255,255,255,0): the background is completely transparent, it has no color at all, those values are merely placeholders. But PIL will use the color channels to assign color to the edges of the type, so the placeholder "background color" will bleed into the result.
To get the proper result, I draw each string onto a separate gray channel, then add those gray pixels into an accumulated alpha channel. Then I use the gray text to compute full-color pixels for any pixels with even a slight trace of the text on it. When combined, the alpha channel will dilute down the color of the edge pixels down to give the proper appearance.
import Image, ImageFont, ImageDraw, ImageChops
This is more work, but gives the correct results. Here's the alpha channel, the color channels, and the final result:
And the result on various backgrounds: