xjcl
xjcl

Reputation: 15329

Transparency is turned to an olive green

This code:

import numpy
import PIL.Image

base = PIL.Image.open('grin-emoji-by-twitter.png').convert('RGBA')
base2 = numpy.array(base)
print(base2.shape)
print(base2)

produces the following output:

(512, 512, 4)        # 512 px by 512 px with RGBA channels
[[[ 71 112  76   0]  # list of all the RGBA pixels...
  [ 71 112  76   0]  # ...visible as an olive green shade when saved
  [ 71 112  76   0]
                     # ...and so on...

which is showing the pixels of the top-left corner: While they should be transparent ([0 0 0 0]) they are instead of some weird olive green shade ([71 112 76 0]) (left image: original, right image: "processed"):

enter image description here

The bug is strange as this is the "proper" way to open RGBA images and to convert them to NumPy. Using numpy.asarray instead did not help either.

The original file for reproduction is here, and while it is colormapped (a palette image), which could be the source of my problem. However I use .convert('RGBA') which converts it from a colormapped to an RGBA image. PIL version used is 6.1.0.

Upvotes: 1

Views: 572

Answers (2)

HansHirse
HansHirse

Reputation: 18925

There's no bug, but maybe a misunderstanding how the different modes in Pillow work.

As you found out, the image in question is a palletised image. But, by explicitly converting the image to RGBA mode, all the information from the original palette and transparency are "processed", such that when convertig to some NumPy array, you will only see the colors taken from the palette, and the alpha channel extracted.

If you open the image without any converting, mode P (or maybe PA) will be taken automatically, and some extracted NumPy array will only have one channel. Let's see the following example:

from matplotlib import pyplot as plt
import numpy as np
from PIL import Image

plt.figure(1, figsize=(10, 9))

# Read image with Pillow, explicit RGBA mode
image_pil = Image.open('grin-emoji-by-twitter.png').convert('RGBA')
plt.subplot(2, 2, 1), plt.imshow(image_pil), plt.title('Pillow image; explicit RGBA mode')
image_np = np.array(image_pil)
plt.subplot(2, 2, 2), plt.imshow(image_np), plt.title('NumPy array')

print("Image.open(...).convert('RGBA'):")
print(image_np.shape, image_np.dtype, '\n')

# Palette of Pillow image
print("Palette:")
print(image_pil.getpalette(), '\n')

# Image info of Pillow image
print("Information:")
print(image_pil.info, '\n')

# Read image with Pillow, no mode set, P mode is taken implicitly
image_pil = Image.open('grin-emoji-by-twitter.png')
plt.subplot(2, 2, 3), plt.imshow(image_pil), plt.title('Pillow image; implicit P mode')
image_np = np.array(image_pil)
plt.subplot(2, 2, 4), plt.imshow(image_np), plt.title('NumPy array')

print("Image.open(...):")
print(image_np.shape, image_np.dtype, '\n')

# Palette of Pillow image
print("Palette:")
print(image_pil.getpalette(), '\n')

# Image info of Pillow image
print("Information:")
print(image_pil.info)

plt.tight_layout()
plt.show()

That's the image output:

Output

As you can see, the Pillow image is the same for both modes, but the extracted NumPy arrays differ (four channel RGBA vs. single channel).

Let's have a further look at the print outputs:

Image.open(...).convert('RGBA'):
(512, 512, 4) uint8 

Palette:
None 

Information:
{} 

Image.open(...):
(512, 512) uint8 

Palette:
[71, 112, 76, 255, 202, 78, ... ] 

Information:
{'transparency': b"\x00\x0e\x02\..."}

We again see the difference in the NumPy arrays. But, you can also see, that the palette and transparency information are no longer stored as meta data for the explicitly RGBA converted Pillow image, but encoded into the pixel values themselves, whereas they're maintained when loaded with mode P. Also, you see that [71, 112, 76] (olive green) is used for all 0 pixel values, which is the background. (Why that color was chosen, is another question.)

So, depending on what you want to achieve with the extracted NumPy array, use mode P when loading the image with Pillow.

Hope that helps!

----------------------------------------
System information
----------------------------------------
Platform:    Windows-10-10.0.16299-SP0
Python:      3.8.1
Matplotlib:  3.2.0rc1
NumPy:       1.18.1
Pillow:      7.0.0
----------------------------------------

Upvotes: 0

AKX
AKX

Reputation: 169338

Right, I misunderstood in the comments...

The point is that the RGB color of a fully transparent pixel doesn't matter – [0 0 0 0] is just as transparent as [71 112 76 0] – some software has chosen that shade of olive-ish green for transparent pixels for that particular image.

Whatever viewer – MatPlotLib maybe? – you're using to view those matrices isn't apparently able to correctly show the alpha channel, so it opts to not interpret it at all.

If you need to view an image with a saner background color, you can use .paste(im, mask=im) to have Pillow use the image's alpha channel as a transparency mask.

I've used #FF00FF, affectionately known as Magic Pink to show the effect off better, but you might want white instead.

import PIL.Image

background_color = (255, 0, 255)

base = PIL.Image.open('grin-emoji-by-twitter.png').convert('RGBA')

matte = PIL.Image.new('RGBA', base.size)
matte.paste(background_color, box=(0, 0) + base.size)
matte.paste(base, mask=base)
matte.save('matte.png')

Upvotes: 1

Related Questions