Bands, channels and colour modes in depth

Inspect and manipulate individual channels, convert between modes deliberately, and understand what a palette image really stores.

The mode table

ModeBandsRangeUse for
110 or 255Bilevel line art
L10 to 255Greyscale
LA2Greyscale plus alphaGreyscale with transparency
RGB30 to 255 eachOrdinary colour images
RGBA4RGB plus alphaColour with transparency
P1Index 0 to 255Palette images, GIF frames
CMYK4Ink coveragePrint pipelines
I / F132-bit int or floatScientific data, not display
from PIL import Image

im = Image.open("photo.png")
print(im.mode, im.getbands(), im.getextrema())

# split a colour image into channels and reassemble
r, g, b = im.convert("RGB").split()
red_only = Image.merge("RGB", (r, Image.new("L", im.size, 0), Image.new("L", im.size, 0)))

# work on one channel without touching the others
g = g.point(lambda v: min(255, int(v * 1.2)))
im2 = Image.merge("RGB", (r, g, b))

split() returns one image per band, each in the corresponding single-band mode. merge() requires exactly matching sizes and the right number of bands for the target mode.

Converting between modes

# RGB to greyscale using perceived luminance, not a plain average
grey = im.convert("L")

# to a palette image, choosing the number of colours
pal = im.convert("RGB").convert("P", palette=Image.Palette.ADAPTIVE, colors=64)
print(len(pal.getpalette()) // 3)          # palette entries

# palette back to RGB, expanding the indices through the palette
back = pal.convert("RGB")

# preserve transparency when expanding a palette image
rgba = pal.convert("RGBA")

# numeric arrays round-trip without losing range
import numpy as np
arr = np.asarray(im.convert("L"))
deep = Image.fromarray(arr.astype("float32"), mode="F")
  • A P image stores indices plus a palette of up to 768 bytes, not colours per pixel. Resizing it works on the indices unless you convert first.
  • Converting RGBA to L discards alpha entirely; the transparent area becomes whatever colour is behind it in the RGB channels.
  • palette=Image.ADAPTIVE is the modern spelling; the older integer palette constants still work but are deprecated.

Mode and format must agree

# JPEG cannot store alpha: flatten onto a background first
if im.mode in ("RGBA", "LA", "P"):
    im = im.convert("RGBA")
    bg = Image.new("RGB", im.size, "white")
    bg.paste(im, mask=im.split()[-1])
    im = bg

im.save("out.jpg", quality=85, optimize=True)
⚠️
Saving an RGBA image as JPEG raises OSError: cannot write mode RGBA as JPEG. Composite onto an opaque background rather than calling convert("RGB"), which keeps the colour but replaces transparency with black.

FAQ

Why does my transparent PNG look black after conversion?
The alpha channel was dropped and the underlying RGB values in transparent areas were zero. Composite onto a background colour before converting, so transparent pixels take the colour you want.
How do I keep only the red channel as a greyscale image?
Split the image and take the band you want: im.split()[0] returns an L image. Convert to L afterwards if you want a different band arrangement merged back.

Opening, inspecting and saving images Drawing and converting formats

Last refreshed 2026-09-18.