PIL Tutorial: Converting Between PNG and GIF

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

Saving GIF with transparency

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.

Converting from GIF into PNG

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)

Converting from PNG into GIF

This can be tricky for two reasons:

PNG image is palette-based

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)

PNG image is in RGBA mode

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
mouse mouse bad mouse

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.

Solid background workaround

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')

mouse with white background

Solution for keeping transparency

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)

bad transparent mouse
In addition to the background, black is also used in other places in the image, such as the eyes. That's why the result is bad. A solution to this is to fill the transparent parts with a unique color that doesn't exist in other opaque parts.
The solution

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)

We used the paste function to fill the transparent parts with the last reserved index. For more examples on using the paste function please refer to PIL Tutorial: How to Create a Button Generator.

And here is the final result:
background mouse

Source

http://nadiana.com/pil-tips-converting-png-gif