How to convert this indexed PNG to grayscale and k

2019-04-16 04:29发布

问题:

I am trying to convert images to grayscale using Python/Pillow. I had no difficulty in most images, but then, while testing with different images, I found this logo from the BeeWare project, that I know that has been further edited with some image editor and recompressed using ImageOptim.

The image has some kind of transparency (in the whole white area around the bee), but black color gets messed up. Here is the code:

#/usr/bin/env python3

import os
from PIL import Image, ImageFile

src_path = os.path.expanduser("~/Desktop/prob.png")
img = Image.open(src_path)
folder, filename = os.path.split(src_path)
temp_file_path = os.path.join(folder + "/~temp~" + filename)


if 'transparency' in img.info:
    transparency = img.info['transparency']
else:
    transparency = None

if img.mode == "P":
    img = img.convert("LA").convert("P")

try:
    img.save(temp_file_path, optimize=True, format="PNG", transparency=transparency)
except IOError:
    ImageFile.MAXBLOCK = img.size[0] * img.size[1]
    img.save(temp_file_path, optimize=True, format="PNG", transparency=transparency)

I also tried this:

png_info = img.info

if img.mode == "P":
    img = img.convert("LA").convert("P")

try:
    img.save(temp_file_path, optimize=True, format="PNG", **png_info)
except IOError:
    ImageFile.MAXBLOCK = img.size[0] * img.size[1]
    img.save(temp_file_path, optimize=True, format="PNG", **png_info)

Using either approach, all the black in the image becomes transparent.

I am trying to understand what I am missing here, or if this is some bug or limitation in Pillow. Digging a little through the image palette, I would say that transparency is in fact assigned to the black color in the palette. For instance, if I convert it to RGBA mode, the outside becomes black. So there must be something else that makes the outside area transparent.

Any tips?

回答1:

Digging a little through the image palette, I would say that transparency is in fact assigned to the black color in the palette.

pngcheck tells me that is not the case:

...
chunk PLTE at offset 0x00025, length 48: 16 palette entries
chunk tRNS at offset 0x00061, length 1: 1 transparency entry

Each actual color has an index in PLTE, including black, and there is an additional entry that is designated "transparent". The black surroundings are probably an artefact of one of the previous conversions, where alpha=0 got translated to RGBA (0,0,0,0).

It seems Pillow's immediate conversion to Lab ("L" and "LA") cannot handle indexed color conversions.
You can solve this by converting the image to RGBA first, then converting each pixel quadruplet of RGBA to gray using the Lab conversion formula from the documentation, and then converting it back to palettized:

for i in range(img.size[0]): # for every pixel:
    for j in range(img.size[1]):
        g = (pixels[i,j][0]*299 + pixels[i,j][1]*587 + pixels[i,j][2]*114)//1000
        pixels[i,j] = (g,g,g,pixels[i,j][3])

but then I realized that since you start out with a palettized image and want to end up with one again, converting only the palette is much easier ...

#/usr/bin/env python3

from PIL import Image, ImageFile

img = Image.open('bee.png')

palette = img.getpalette()
for i in range(len(palette)//3):
    gray = (palette[3*i]*299 + palette[3*i+1]*587 + palette[3*i+2]*114)//1000
    palette[3*i:3*i+3] = [gray,gray,gray]

img.putpalette(palette)
img.save('bee2a.png', optimize=True, format="PNG")

print ('done')

(Hardcoded to assume your input image is indeed an indexed file. Add checks if you want to make sure.)

Result, wrapped inside a comment block so you can see the transparency: