Reputation: 15329
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"):
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
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:
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
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