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.infois a dictionary that contains a set of properties defined by the
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,
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
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() # 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: