PIL supports both PNG and GIF image formats, but converting between the two while keeping transparency can be tricky. Inside is a list of tips for dealing with transparency and dithering issues when processing GIF image formats in PIL. If you are only interested in the final solution you can skip this tutorial and go directly to the code
Support for writing GIF transparency was added in PIL1.1.4. Yet the latest PIL version 1.1.6 doesn't not automatically write the transparency index for you. In order to keep the transparency, you need to explicitly specify the transparent color index as an option when saving the image in GIF format.
im = Image.open('icon.gif') transparency = im.info['transparency'] im.save('icon.gif', transparency=transparency)
im.info
is a dictionary that contains a set of properties defined by the Image.open
method.
Luckily PNG supports palette-based images. This will save us from a lot of work. The only thing you have to do is setting the transparency option.
im = Image.open('icon.gif') transparency = im.info['transparency'] im .save('icon.png', transparency=transparency)
This can be tricky for two reasons:
If the PNG image is palette-based, i.e. P mode, then the conversion is straightforward. The only thing you have to do is setting the transparency option as before:
im = Image.open('icon.png') assert im.mode == 'P' transparency = im.info['transparency'] im .save('icon.gif', transparency=transparency)
When converting form RGBA into P, we need to reduce the number of distinct colors used in the image to a maximum of 256 colors through a process called quantization.
PIL supports two types of palette quantizers, WEB
and ADAPTIVE
. The default quantizer in PIL1.1.6 is Image.WEB
which reduces the color space to web safe colors and dithers the rest. While Image.ADAPTIVE
quantizer is intended to keep the converted image as visually similar as possible to the original image.
The following table explains the difference in output between WEB
and ADAPTIVE
quantizers in PIL:
PNG: Original Image | GIF: WEB quantizer | GIF: ADAPTIVE quantizer |
---|---|---|
So to avoid dithering, we should use the ADAPTIVE
quantizer like this:
im = Image.open('mouse.png') im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE) im.save('mouse.gif')
Now that we know how to solve the first issue, we need to find a solution for the transparency issue.
Sometimes you don't need to keep the transparency; all what you want is a solid background. This will do the trick for you:
im = Image.open('mouse.png') # Create a new image with a solid color background = Image.new('RGBA', im.size, (255, 255, 255)) # Paste the image on top of the background background.paste(im, im) im = background.convert('RGB').convert('P', palette=Image.ADAPTIVE) im.save('mouse.gif')
GIF allows you to set one of the colors in the palette as fully transparent, whereas PNG images in RGBA mode allow you to have different levels of transparency through the alpha band. An alpha value of 255 means the pixel is fully opaque, 0 means fully transparent and Intermediate values indicate partially transparent pixels.
Let's try to set the black background to transparent:
im = Image.open('mouse.png') im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE) # The black index in the palette of this image is 255 im.save('mouse.gif', transparency=255)
The trick is to use the colors
keyword argument in im.convert
. This argument tells convert
how many colors it should use in the palette. The default is 256 which is the maximum possible number of colors in the palette. To reserve a color in the palette for transparency we can tell convert
to only use 255 out of 256 and then use the last index 255 to fill the transparent parts.
from PIL import Image im = Image.open('mouse.png') # Get the alpha band alpha = im.split()[3] # Convert the image into P mode but only use 255 colors in the palette out of 256 im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255) # Set all pixel values below 128 to 255, # and the rest to 0 mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0) # Paste the color of index 255 and use alpha as a mask im.paste(255, mask) # The transparency index is 255 im.save('mouse.gif', transparency=255)
And here is the final result: