DEV Community

yue xing
yue xing

Posted on

Same numbers, different rulebook: why a re-saved photo loses its color


A photo gets exported, resized, or converted to WebP, and afterwards the reds look tired and the sky looks a bit chalky. Nothing is blurry, nothing is blocky, it just lost some color. I went down this rabbit hole while reading about image pipelines, and the explanation turned out to be simpler than I expected: the pixel numbers didn't change, the rulebook for reading them went missing. One scope note up front. Different screens can show different ranges of color, and that alone can make a photo look different from one device to another. I didn't test screens at all, so everything below is only about what happens inside the file.

Same numbers, different rulebook

Each pixel is stored as three numbers for red, green and blue. Those numbers don't mean a color by themselves. Think of a recipe that says "2 cups of flour" without saying whose cups: a US cup and a metric cup are close, but not the same, and the cake comes out different. In an image file, the note that says which cup you're using is the color profile (ICC profile). It tells software which color standard the numbers should be read in. sRGB is the default almost everywhere. Display P3 and Adobe RGB cover a wider range, and Apple's developer documentation mentions that iPhone cameras can capture P3 starting with the iPhone 7. Because the range is wider, a saturated red in P3 is written with smaller numbers than the same red in sRGB. If the profile gets dropped, software falls back to reading those smaller numbers as sRGB, and the red comes out duller.

How much color actually goes away

I wanted a number instead of a feeling. I took a CC0 photo from Wikimedia Commons (an airport apron shot on an iPhone 6), scaled it to 2048×1536, and used Pillow to convert it to Display P3 and embed the profile. Then I made a copy with exactly the same pixels and no profile, and compared both as they'd be shown when read as sRGB.

Same pixels: left read with the Display P3 profile, right after the profile is dropped and read as sRGB. Sample: CC0 photo from Wikimedia Commons, shot on an iPhone 6

On this sample, average chroma dropped by 16.6%. The average color difference (CIELAB ΔE76) across the whole image was only 1.83, which sounds small until you look at where it lands. For the most saturated 5% of pixels, ΔE was about 5.4, so the tail fin and the sky take the hit, while the white fuselage, the grey tarmac and the shadows barely move. I repeated it with Adobe RGB and it was worse: 20.1% less chroma on average, around 7.2 ΔE in the most saturated areas. One caveat I can't get around: this photo's colors all sit inside sRGB. A real wide-gamut photo with colors outside sRGB could behave differently, and I have no data for that.

Why it tends to happen after re-saving

The profile is extra data attached to the file, not part of the pixels, so every tool that re-saves the image decides whether to copy it over. Starting from the P3 JPEG and using default settings, Pillow 11.3 dropped the profile when saving JPEG or WebP, and also after resize or thumbnail followed by a JPEG save, but kept it for PNG. ffmpeg 7.1 kept it for JPEG and PNG and dropped it for WebP. macOS sips kept it when converting to PNG, scaling, or saving at quality 60. None of this is a bug as far as I can tell; in Pillow, passing the original profile through the icc_profile argument keeps it. It's just not the default, and I still don't know why PNG and JPEG behave differently there.

What I'd do to avoid it

Start from the original file whenever possible instead of a copy that has already been exported a few times. Keep the number of re-saves low, since each tool in the chain is another chance to lose the profile. And if the image is headed for the web, convert the colors to sRGB during processing rather than just stripping the profile. Converting rewrites the pixel values themselves, so even if some later step throws the profile away, reading the file as sRGB still gives the right colors.

Top comments (0)