8

I would like to convert a PNG32 image (with transparency) to PNG8 with Python Image Library. So far I have succeeded converting to PNG8 with a solid background.

Below is what I am doing:

from PIL import Image
im = Image.open("logo_256.png")
im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
im.save("logo_py.png", colors=255)
Emil H
  • 39,840
  • 10
  • 78
  • 97
montonero
  • 772
  • 6
  • 16
  • 2
    PNG32 has 8 bits of transparency, PNG8 has only 1, so it's impossible to convert faithfully. Can you live with that? – Mark Ransom May 24 '11 at 17:46
  • Yes, since it's only a background that is transparent. – montonero May 24 '11 at 17:51
  • Please post your solution as an answer; This fits the format of the site better. I'll be happy to give it an upvote if you do. – Mark Ransom May 24 '11 at 18:18
  • @MarkRansom PNG8 has 256 levels of transparency per palette entry, try yourself: [pngquant](http://pngquant.org) or [tinypng](http://tinypng.org). – Kornel Feb 24 '14 at 01:35
  • @porneL, you're right of course, but in practice you'll find the number of transparency levels limited because each combination of color and transparency takes an entry in the palette. GIF has the limitation I spoke of, and the software I worked on many years ago treated them both the same so sometimes even today I get confused. I believe PIL also works this way. There was also a problem with Internet Explorer incorrectly displaying PNG8 with levels of transparency but I think it's been fixed for a while. – Mark Ransom Feb 24 '14 at 03:51

4 Answers4

16

After much searching on the net, here is the code to accomplish what I asked for:

from PIL import Image

im = Image.open("logo_256.png")

# PIL complains if you don't load explicitly
im.load()

# Get the alpha band
alpha = im.split()[-1]

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("logo_py.png", transparency=255)

Source: http://nadiana.com/pil-tips-converting-png-gif Although the code there does not call im.load(), and thus crashes on my version of os/python/pil. (It looks like that is the bug in PIL).

montonero
  • 772
  • 6
  • 16
1

This is an old question so perhaps older answers are tuned to older version of PIL?

But for anyone coming to this with Pillow>=6.0.0 then the following answer is many magnitudes faster and simpler.

im = Image.open('png32_or_png64_with_alpha.png')
im = im.quantize()
im.save('png8_with_alpha_channel_preserved.png')
gbtimmon
  • 4,238
  • 1
  • 21
  • 36
1

As mentioned by Mark Ransom, your paletized image will only have one transparency level.

When saving your paletized image, you'll have to specify which color index you want to be the transparent color like this :

im.save("logo_py.png", transparency=0) 

to save the image as a paletized colors and using the first color as a transparent color.

Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
  • I've just tried this, but no transparent colours have appeared in a newly created image. I guess I must find a way to find out what is the colour that replaces a transparent background in this step: im.convert('RGB') – montonero May 24 '11 at 17:58
  • @montonero: It's possible that more than one color has been mapped to the transparent color since you're greatly reducing the number of colors possible. – martineau May 25 '11 at 00:26
  • @martineau The code that I found on the net extracts alpha _before_ applying convert() command, thus everything works perfect in my case (with only transparent backgrounds, although I assume that it should satisfy other situations as well.) – montonero May 25 '11 at 10:56
  • @montonero: Oh, OK, I understand -- in that case you might as well accept your own answer (which I think is acceptable here). – martineau May 25 '11 at 14:54
0

Don't use PIL to generate the palette, as it can't handle RGBA properly and has quite limited quantization algorithm.

Use pngquant instead.

Kornel
  • 97,764
  • 37
  • 219
  • 309