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
| Mode | Bands | Range | Use for |
|---|---|---|---|
1 | 1 | 0 or 255 | Bilevel line art |
L | 1 | 0 to 255 | Greyscale |
LA | 2 | Greyscale plus alpha | Greyscale with transparency |
RGB | 3 | 0 to 255 each | Ordinary colour images |
RGBA | 4 | RGB plus alpha | Colour with transparency |
P | 1 | Index 0 to 255 | Palette images, GIF frames |
CMYK | 4 | Ink coverage | Print pipelines |
I / F | 1 | 32-bit int or float | Scientific 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
Pimage 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
RGBAtoLdiscards 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.Related
Opening, inspecting and saving images Drawing and converting formats
Last refreshed 2026-09-18.