Without colour management, wide-gamut phone photos and print CMYK files look dull or shifted after processing.
The same RGB numbers mean different colours in different colour spaces. Most of the web assumes sRGB, but iPhones shoot in Display P3, photographers use Adobe RGB, and print uses CMYK with device-specific profiles. The space is described by an ICC profile embedded in the file, found in im.info.get("icc_profile").
Pillow's convert("RGB") on a CMYK image, or simply ignoring the profile, does a naive conversion. Results look wrong: CMYK JPEGs come out oversaturated or muddy, and P3 photos look washed out once the profile is lost.
`ImageCms` (a wrapper around LittleCMS) does proper conversions:
ImageCms.ImageCmsProfile(io.BytesIO(icc_bytes))loads the embedded profileImageCms.createProfile("sRGB")creates a standard targetImageCms.profileToProfile(im, src, dst, outputMode="RGB")converts pixelsbuildTransform(...)once plusapplyTransform(im, t)per image is faster for batches
The rendering intent decides how out-of-gamut colours are handled: perceptual compresses the whole gamut smoothly, relative colorimetric clips only what doesn't fit.
A robust pipeline for web output: if the image has a profile, convert it to sRGB with ImageCms; then either embed an sRGB profile with save(..., icc_profile=ImageCms.ImageCmsProfile(srgb).tobytes()) or omit it, since browsers assume sRGB.
Example
import io
from PIL import Image, ImageCms
srgb = ImageCms.createProfile("sRGB")
lab = ImageCms.createProfile("LAB")
im = Image.new("RGB", (2, 1), (255, 0, 0))
to_lab = ImageCms.buildTransform(srgb, lab, "RGB", "LAB")
lab_im = ImageCms.applyTransform(im, to_lab)
print(lab_im.mode, lab_im.getpixel((0, 0))[0] > 100) # LAB True (L of red ~54%)
icc = ImageCms.ImageCmsProfile(srgb).tobytes() # embed a profile
im.save("tagged.png", icc_profile=icc)
src = Image.open("tagged.png")
prof = ImageCms.ImageCmsProfile(io.BytesIO(src.info["icc_profile"]))
print(ImageCms.getProfileDescription(prof).strip()) # sRGB built-in (or similar)
out = ImageCms.profileToProfile(src, prof, srgb, outputMode="RGB",
renderingIntent=ImageCms.Intent.PERCEPTUAL)
print(out.getpixel((0, 0))) # (255, 0, 0) same spaceRemember
- ICC profiles in im.info define what RGB numbers mean
- Use ImageCms.profileToProfile or build/applyTransform to convert
- Convert to sRGB for the web; keep or embed the profile on save
- Rendering intent controls out-of-gamut handling
Watch out for
- Converting CMYK print files to RGB with plain convert() and getting badly shifted colours.