Short answer

Check with img.mode. The common ones: RGB (colour photos), RGBA (colour with transparency), L (greyscale), P (palette: up to 256 colours, like GIFs), 1 (black and white). Count channels with len(img.getbands()). Convert with img.convert(mode), watching for the cases below that throw information away.

Every mode at a glance

Generated by Pillow itself, so the table matches the version you are reading about:

modes_table.py
from PIL import Image

print(f"{'mode':<6} {'bands':<14} {'getpixel returns':<26} NumPy dtype")
for mode in ["1", "L", "LA", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "I;16", "F"]:
    img = Image.new(mode, (2, 2))
    px = img.getpixel((0, 0))
    try:
        import numpy as np
        dtype = str(np.asarray(img).dtype)
    except Exception as e:
        dtype = type(e).__name__
    print(f"{mode:<6} {','.join(img.getbands()):<14} {type(px).__name__ + ' ' + repr(px):<26} {dtype}")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
mode   bands          getpixel returns           NumPy dtype
1      1              int 0                      bool
L      L              int 0                      uint8
LA     L,A            tuple (0, 0)               uint8
P      P              int 0                      uint8
RGB    R,G,B          tuple (0, 0, 0)            uint8
RGBA   R,G,B,A        tuple (0, 0, 0, 0)         uint8
CMYK   C,M,Y,K        tuple (0, 0, 0, 0)         uint8
YCbCr  Y,Cb,Cr        tuple (0, 0, 0)            uint8
LAB    L,A,B          tuple (0, 0, 0)            uint8
HSV    H,S,V          tuple (0, 0, 0)            uint8
I      I              int 0                      int32
I;16   I              int 0                      uint16
F      F              float 0.0                  float32

Notes on the less obvious ones:

Which mode will a file open as?

file_modes.py
from PIL import Image

for name in ["photo.jpg", "photo.png", "logo.png", "spinner.gif", "rings.png"]:
    with Image.open(name) as img:
        print(f"{name:<12} {img.format:<5} {img.mode}")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
photo.jpg    JPEG  RGB
photo.png    PNG   RGB
logo.png     PNG   RGBA
spinner.gif  GIF   P
rings.png    PNG   L

The mode depends on the file, not just the format: a PNG can be RGB, RGBA, P, L, LA, 1 or I;16. Code that assumes every PNG is RGBA, or every image RGB, fails on the first file that is not. Normalise at the start: img = img.convert("RGB") (or "RGBA" if you need transparency).

P vs L: palette vs greyscale

Both store one number per pixel, which is why they get confused. In L the number is the brightness. In P it is an index into a palette, which can hold any colours:

p_vs_l.py
from PIL import Image

photo = Image.open("photo.jpg").resize((400, 267))
p = photo.quantize(colors=16)                  # P: up to 256 colours, chosen for this image
l = photo.convert("L")                         # L: 256 shades of grey

x, y = 330, 170                                # orange sky near the horizon
print("P pixel:", p.getpixel((x, y)), " palette entry:", tuple(p.getpalette()[p.getpixel((x, y)) * 3:][:3]))
print("L pixel:", l.getpixel((x, y)))
print("colours used: P", len(p.getcolors()), " L", len(l.getcolors()))

sheet = Image.new("RGB", (810, 267), "white")
sheet.paste(p.convert("RGB"), (0, 0))
sheet.paste(l.convert("RGB"), (410, 0))
sheet.save("p_vs_l.png")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
P pixel: 2  palette entry: (216, 178, 112)
L pixel: 206
colours used: P 16  L 248
The sunset photo in 16 palette colours on the left, still in colour with visible banding, and in greyscale on the right
Real output. Left: mode P with a 16-colour palette. Right: mode L.

Arithmetic on P pixels (averaging, thresholding, resizing with smoothing) is meaningless, because neighbouring indexes can be completely different colours. Pillow handles this for its own operations, but if you process the numbers yourself, convert to RGB or L first.

How many channels does an image have?

channels.py
from PIL import Image

for name in ["photo.jpg", "logo.png", "spinner.gif"]:
    img = Image.open(name)
    print(f"{name:<12} mode {img.mode:<5} channels: {len(img.getbands())}  {img.getbands()}")
print("RGBA has", Image.getmodebands("RGBA"), "bands")      # without opening an image
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
photo.jpg    mode RGB   channels: 3  ('R', 'G', 'B')
logo.png     mode RGBA  channels: 4  ('R', 'G', 'B', 'A')
spinner.gif  mode P     channels: 1  ('P',)
RGBA has 4 bands

Conversions that lose information

RGBA to RGB drops the alpha channel. Transparent pixels show whatever colour they happen to store, often black:

rgba_to_rgb.py
from PIL import Image

logo = Image.open("logo.png")                                   # RGBA, transparent corners
naive = logo.convert("RGB")
print("convert('RGB') corner:", naive.getpixel((5, 5)))           # alpha dropped: stored colour shows

flat = Image.new("RGB", logo.size, "white")
flat.paste(logo, mask=logo.getchannel("A"))
print("flattened on white:   ", flat.getpixel((5, 5)))
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
convert('RGB') corner: (0, 0, 0)
flattened on white:    (255, 255, 255)

Composite onto a background instead, as above. The RGBA to JPEG tool covers this in detail.

P with transparency to RGB has the same problem, with the twist that transparency is not a channel but an entry in img.info:

palette_transparency.py
from PIL import Image

# A palette image with one transparent palette entry, as GIFs and small PNGs often have
img = Image.new("P", (4, 1))
img.putpalette([255, 0, 0, 0, 255, 0] + [0] * 762)              # entry 0 red, entry 1 green
img.putdata([0, 1, 0, 1])
img.info["transparency"] = 0                                    # entry 0 is see-through
img.save("tiny.png")

reopened = Image.open("tiny.png")
print("mode", reopened.mode, "transparency", reopened.info.get("transparency"))
print("to RGB: ", reopened.convert("RGB").getpixel((0, 0)))     # transparency ignored: solid red
print("to RGBA:", reopened.convert("RGBA").getpixel((0, 0)))    # alpha 0: really transparent
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
mode P transparency 0
to RGB:  (255, 0, 0)
to RGBA: (255, 0, 0, 0)

Go through RGBA whenever a palette image may be transparent. Other lossy conversions: anything to 1 (see black and white), I;16/I/F to L, which clips instead of scaling (see cannot write mode), and CMYK to RGB without a colour profile.

The outputs and images on this page are the real results of running every example with Pillow 12.3.0 on Python 3.13 (Linux) on 2026-10-15. Every example is also re-run automatically on macOS (Darwin 25.6.0, Pillow 12.3.0), Linux (Linux 6.17.0-1022-azure, Pillow 12.3.0), Windows (Windows 2025Server, Pillow 12.3.0); the line under each example shows the result per operating system. The DejaVu Sans font files sit next to each script so font examples behave the same everywhere. Sample photos: NASA/Sam Lott, public domain. Spotted something wrong? Tell us.