Difference Between Images in 'P' and 'L' Mode in Pil

What is the difference between images in 'P' and 'L' mode in PIL?

  • Normally, images are RGB, which means they have 3 channels, one for red, one for green and one for blue. That normally means that each pixel takes 3 bytes of storage, one for red, one for green and one for blue.

  • If you have a P mode image, that means it is palettised. That means there is a palette with up to 256 different colours in it, and instead of storing 3 bytes for R, G and B for each pixel, you store 1 byte which is the index into the palette. This confers both advantages and disadvantages. The advantage is that your image requires 1/3 of the space in memory and on disk. The disadvantage is that it can only represent 256 unique colours - so you may get banding or artefacts.

  • If you have an L mode image, that means it is a single channel image - normally interpreted as greyscale. The L means that is just stores the Luminance. It is very compact, but only stores a greyscale, not colour.


You can tell which mode your image has by looking at:

image.mode

If your image is palettised it will be P, or PA if palettised with an alpha channel as well. If your image is greyscale it will be L, or LA if greyscale with alpha channel.

You convert between them using the convert(mode) function, e.g. to go to RGB mode, use:

image.convert('RGB')

I used the word "normally" quite a lot! Why? Because you can do abnormal things!

  • You can store a grey-looking image in an RGB format. All you do, is make the red component equal to the green component equal to the blue component (R=G=B) and it will appear grey but be stored in an inefficient RGB format that takes 3x the space it might otherwise need to.

  • You can store a grey-looking image in a P format, you just make sure all the palette entries have the R=G=B.


Here's the kicker... if you want and expect an RGB image, you should just convert to RGB on opening:

im = Image.open("image.jpg").convert('RGB')

that way you will never have problems with GIF files (which are always palettised) nor with PNG files which can be palettised and can be greyscale or RGB. You will not normally get problems with JPEG images because they are pretty much always RGB anyway.


Here's an example to demonstrate. Start with this red-blue gradient image:

Sample Image

Let's use IPython to look at in RGB space. First, look at the Red channel:

In [21]: im = Image.open('a.png').convert('RGB')

In [22]: np.array(im.getchannel(0))
Out[22]:
array([[255, 255, 255, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255],
[254, 254, 254, ..., 254, 254, 254],
...,
[ 1, 1, 1, ..., 1, 1, 1],
[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 0, 0, ..., 0, 0, 0]], dtype=uint8)

Notice it has 255 at the top because it is red, and 0 at the bottom because there is no red there.

Now let's look at the Green channel, it is 0 everywhere because there is no green.

In [23]: np.array(im.getchannel(1))
Out[23]:
array([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]], dtype=uint8)

And finally, let's look at the Blue channel. It is 0 at the top where the image is pure Red and 255 at the bottom where the image is pure Blue.

In [24]: np.array(im.getchannel(2))
Out[24]:
array([[ 0, 0, 0, ..., 0, 0, 0],
[ 0, 0, 0, ..., 0, 0, 0],
[ 1, 1, 1, ..., 1, 1, 1],
...,
[254, 254, 254, ..., 254, 254, 254],
[255, 255, 255, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255]], dtype=uint8)

Now let's look at the same image in palette mode.

# Convert to palette mode
im = Image.open('a.png').convert('P')

# Extract the palette and reshape as 256 entries of 3 RGB bytes each
In [27]: np.array(im.getpalette()).reshape(256,3)
Out[27]:
array([[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 0, 0, 0],
[ 51, 0, 0],
[102, 0, 0],
[153, 0, 0],
[204, 0, 0],
[255, 0, 0], <--- entry 15 = rgb(255,0,0) = Red
[ 0, 51, 0],
[ 51, 51, 0],
[102, 51, 0],
[153, 51, 0],
[204, 51, 0],
[255, 51, 0],
[ 0, 102, 0],
[ 51, 102, 0],
[102, 102, 0],
[153, 102, 0],
[204, 102, 0],
[255, 102, 0],
[ 0, 153, 0],
[ 51, 153, 0],
[102, 153, 0],
[153, 153, 0],
[204, 153, 0],
[255, 153, 0],
[ 0, 204, 0],
[ 51, 204, 0],
[102, 204, 0],
[153, 204, 0],
[204, 204, 0],
[255, 204, 0],
[ 0, 255, 0],
[ 51, 255, 0],
[102, 255, 0],
[153, 255, 0],
[204, 255, 0],
[255, 255, 0],
...
... up to 256 entries

Now get the indices into the palette:

In [28]: np.array(im.getchannel(0))
Out[28]:
array([[ 15, 15, 15, ..., 15, 15, 15],
[ 15, 15, 15, ..., 15, 15, 15],
[ 15, 15, 15, ..., 15, 15, 15],
...,
[190, 190, 190, ..., 190, 190, 190],
[190, 190, 190, ..., 190, 190, 190],
[190, 190, 190, ..., 190, 190, 190]], dtype=uint8)

Now you can see that the top row of the image has palette index 15, which, if you look it up in the preceding palette, you will see is Red.

Now let's look at the same image in L mode - remember L means "Luminance" which is just a fancy way of saying "brightness" on a scale of black to white, i.e. greyscale :

# Open into greyscale, or L mode
In [1]: im = Image.open('a.png').convert('L')

# Dump the pixels
In [2]: np.array(im.getchannel(0))
Out[2]:
array([[76, 76, 76, ..., 76, 76, 76],
[76, 76, 76, ..., 76, 76, 76],
[76, 76, 76, ..., 76, 76, 76],
...,
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29],
[29, 29, 29, ..., 29, 29, 29]], dtype=uint8)

So, now the top row of the image is 76 and the bottom row is 29. What are those? Well, the formula for converting RGB to L is:

L = R * 299/1000 + G * 587/1000 + B * 114/1000

So, in the top row, R=255, G=0, B=0, so the Luminance has become:

L = 255 * 299/1000 + 0 + 0 
L = 76

And on the bottom row, R=0, G=0, B=255, so the Luminance has become:

L = 0 + 0 + 255 * 114/1000
L = 29

Keywords: Python, PIL, Pillow, palette, image processing, prime.

PIL Image convert from I mode to P mode

I think the issue is that your values are scaled to the range 0..65535 rather than 0..255.

If you do this, you will see the values are larger than you expected:

i = Image.open('depth.png') 
n = np.array(i)

print(n.max(),n.mean())
# prints 32257, 6437.173

So, I quickly tried:

n = (n/256).astype(np.uint8)
r = Image.fromarray(n)
r=r.convert('P')
r.putpalette(custom_palette) # I grabbed this from your pastebin

Sample Image

PIL Image mode P -> RGBA

This issue was reported here:

https://bitbucket.org/effbot/pil-2009-raclette/issue/8/corrupting-images-in-palette-mode

In March 2012, a comment says it's now fixed in development version of PIL. The most recent released version is 1.1.7, so the fix won't be available until 1.2 comes out. PIL updates very slowly, so don't expect this to come out soon.

List of Image modes

There are two distinct concepts in Pillow, with confusingly similar names:

"Modes"

These are listed at https://pillow.readthedocs.io/en/latest/handbook/concepts.html#modes.

Per those docs:

The mode of an image defines the type and depth of a pixel in the image.

This kind of "mode" is what is exposed through an Image's .mode attribute, can be changed through the .convert() method, and can be passed to methods that take a mode parameter. They are not the same as "raw modes".

"Raw modes"

These are used internally by the raw decoder, which converts uncompressed data from an image file into a format that a PIL Image object can understand. There are several times more "raw modes" than "modes", and they convey information about not only the type (colored or grayscale) and bit depth of pixels in an image, but also their layout in the file. For example, raw mode RGB;L is documented as meaning "24-bit true colour, line interleaved (first all red pixels, then all green pixels, finally all blue pixels)."

As noted in the docs linked above (and also in the old PIL documentation), a list of raw modes can be found in Unpack.c. You'll find the list near the end of the file.

Unpack.c from the current main branch of Pillow: https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Unpack.c

Unpack.c from the final release of PIL: http://svn.effbot.org/public/tags/pil-1.1.7/libImaging/Unpack.c

PIL Image mode I is grayscale?

Is there a way of specifying a coloured image based on a 32-bit integer?

Yes, use the RGB format for that, but instead use an integer instead of "red" as the color argument:

from PIL import Image

r, g, b = 255, 240, 227
intcolor = (b << 16 ) | (g << 8 ) | r
print intcolor # 14938367
ImgRGB = Image.new("RGB", (200, 200), intcolor)
ImgRGB.show()

Mapping values of pixels in an image with Python

As I mentioned in the comments, for loops are slow, inefficient and error-prone for processing images in Python.

Here's a more efficient way of doing what you ask. Your colours of 0, 1, 2 and 255 are hard to see on StackOverflow's background and hard to differentiate from each other because 3 of them are essentially black, so I am transforming 64 and 150 into 192 and 32 but you can adapt to your own numbers:

Sample Image

from PIL import Image

# Load image and ensure greyscale, i.e. 'L'
im = Image.open('image.png').convert('L')

# Remap colours 150 -> 32, 64 -> 192
res = im.point((lambda p: 32 if p==150 else (192 if p==64 else 0)))

res.save('result.png')

Sample Image


If you want to use Numpy instead, which is also vectorised and optimised, it might look like this (other techniques are also possible):

from PIL import Image
import numpy as np

# Load image and ensure greyscale, i.e. 'L'
im = Image.open('image.png').convert('L')

# Make into Numpy array
na = np.array(im)

# Anywhere pixels are 150, make them 32
na[na==150] = 32

# Anywhere pixels are 64, make them 192
na[na==64] = 192

# Convert back to PIL Image and save
Image.fromarray(na).save('result.png')

What does the 'L' mean when someone is explaining code?

From the docs for PIL:

The default method of converting a greyscale (“L”)



Related Topics



Leave a reply



Submit