What I recently learned about color

October 26, 2024

The target audience of this blog post is a little hard to define. It’s basically me, but a few months ago. I assume you know some things about computer graphics, you have a rough idea of what ICC color profiles are, you are aware that light waves correspond to colors, and you have a strangely broad and nerdy interest in a wide variety of fields. Like me.

I also want to mention that by its very nature, this topic is far from universally accessible. If you experience color in a different way than some “average observer”, or if you don’t experience light at all, then a sentence like “In this picture you can see that …” clearly might not apply to you. If that is the case, I hope I have still managed to describe the topic in a way that is interesting to you.



Color management is a funny thing.

If you scour the internet because you want to learn a little more about the theory and practice of color management (beyond the basics like “red, green, and blue” or “there is such a thing as ICC profiles”), you’ll find very disparate content across a variety of different niches:

  • There are forums for printing professionals who talk about things like “dmax” and “black point compensation” and “rendering intents”.

  • There is physics content that mentions “Planck’s law” and “spectral power distributions” and “black bodies”.

  • There are technical conversations about color profiles that discuss “A2B0” and “cLUT” and “gamut mapping”.

  • There is neuroscience and physiology content that talks about “metamerism” and “spectral sensitivities”.

  • And then there’s the CIE, the International Commission on Illumination, who tells us things about the “2° standard observer” and the “XYZ color space”.

  • And finally there’s the ICC, the International Color Consortium, after whom the ICC profiles have their name, and who publish lots of content about all things color management.

What I’ve not found is content that talks about the whole thing, going into some detail in all the areas, but without being written for people with a PhD or twenty years of industry experience (in whatever industry may be relevant).

So this is my attempt at writing something like that, the kind of post I would’ve loved to have as a starting point.

Motivation

I started down this rabbit hole because I was curious to see how far I could get in printing with a decent amount of color accuracy, but without using things like colorimeters, spectrophotometers, and controlled viewing environments, like you would use in a professional setting.

I’m pretty happy with how far I got:

A screenshot of the linked Mastodon post, in which I write the following: 'If you’ve ever tried to get your printer to spit out even roughly the shade of green you were expecting, you might appreciate how close I got to matching the color of the card stock. This is using color profiles that I created without having any specialized hardware.* Not perfect, but you can get pretty far without expensive calibration equipment. I think I need to undust my blog and write something about this. * Full disclosure: I bought an IT8 target for 30€, if you count that as hardware.' The Mastodon post also contains a photo of a hand holding two pieces of cardboard. On the bottom is a piece of regular green card stock, on the top is a piece of white paper with a green square printed on it, and the word 'green' in the middle of the square. The two shades of green are not a 100% match, but fairly close.

In a follow-up post, I’m going to describe how I create those color profiles.

But in this very post, I want to talk a little more about the theoretical background I learned along the way.

I will be simplifying a lot, both because I don’t want to go too far down into any specific thing, and because I am not an expert in any of these fields.

Don’t take this blog post as a scientific reference; take it as a conceptual overview from which you can start to delve into whatever details you’re interested in.

Printing green

Okay, so how do I get from the green card stock to the green ink on paper?

I start by putting the card into my flatbad scanner and clicking “Scan”. I open the resulting image file in GIMP, and the color picker tells me that the scanner thinks the card stock has an RGB color of [77, 168, 100].

The thing about RGB values is that the numbers alone are pretty meaningless. Every device (every scanner, every monitor, every printer) has different ideas of what the actual color is behind those three numbers.

(Sidenote: You probably know that printers don’t actually print in red, green, and blue; they print in cyan, magenta, yellow, and sometimes also black. But unless you are a digital offset printing professional, your printer still represents itself as an RGB device. You give it RGB values, and it figures out what inks to put on the paper. So for the purposes of this, let’s just say your printer talks RGB.)

So what is the actual color, the one for which the scanner uses the value [77, 168, 100]? To find that out, I ask the ICC color profile that I created for the scanner.

Using argyll’s icclu (“ICC lookup”) tool with that profile, I get this response:

77.000000 168.000000 100.000000 [RGB]
-> Lut_A2B0
-> 63.388740 -46.752611 33.467207 [Lab]

So now we know that the actual color of the card stock (ignoring the imprecision of my home-made profile) is [63.4, -46.8, 33.5] in something called “Lab”.

We’ll get to what this “Lab” actually means, but for now let’s just say it’s a system in which you can measure “actual” colors.

Okay, now that we know what the actual green of the card stock is, we have to tell the printer to print it. And to find out how to tell the printer that, we need to use a second ICC profile: The color profile for the printer/paper/ink combination that I’m using.

For that profile, I have the opposite question. I have an actual color in “Lab”, and I want to know the RGB values that make the printer put that particular color on my paper. Here’s what icclu says:

63.388740 -46.752611 33.467207 [Lab] 
-> Lut_B2A1 
-> 95.491294 252.410753 60.007395 [RGB]

So if I want my special green, I have to tell the printer to print RGB [95, 252, 60]. Wow, that’s quite a different tuple compared to [77, 168, 100]. Have a look:

Three squares of varying shades of green. The first square is a little more blue and cold than the second one; the third square has an extremely satured green

The three greens in that image are, from left to right:

  1. RGB [77, 168, 100], the value that my scanner gave me,
  2. RGB [87, 172, 73], the actual color’s value in sRGB (if your browser does color management right, you should see the real green of the card stock), and
  3. RGB [95, 252, 60], the value that the printer needed.

As weird as that super-saturated green on the right seems, you can see from the photo in my Mastodon post that it does print just the right color! (Again, modulo the amateurish nature of my profiles.)

Of course in practice, you don’t actually look up individual colors manually. Instead, you let your software know which profile applies to which part of the chain, and the software does all the looking up automatically.

Color in the eye

So let’s talk about this “Lab” thing in which you measure the “actual” color, and what the concept of an “actual” color even means. “Lab” is also known as “L*a*b*” or “CIELAB”.

But before we get to Lab, we first need to talk about XYZ. And to talk about XYZ, we need to talk about light and the human eye.

I’ll assume you know about light waves and how different wavelengths correspond to different colors. When light of a wavelength around 580 nm hits your eye, you see yellow, because that’s the wavelength of yellow light.

But here’s the thing: When there is no yellow light (of wavelength 580 nm) at all, but instead there is some red light and some green light (maybe around 620 nm and 540 nm, respectively), you will also see yellow.

And that’s because the color sensors in your eyes don’t see individual wavelengths. Instead, the eye has three different kinds of receptors called “cones” that are triggered in different amounts by lights of different wavelengths. The combination of the three different responses is what then gets sent to your brain as color sensation.

Let’s look at how that works. This image shows the response curves of the three different types of cone that we will call the “blue cone”, “green cone”, and “red cone”.

A graph with wavelengths of light from 320 to 700 nanometers on the x axis, and intensity percentages on the y axis. There are three different curves; one in blue, one in green, one in red. All three curves are bell-shaped, all peaking at 100% but at different x values. The curves overlap enough so that except for the very edges, there is no wavelength at which all of the curves are zero.

This is a little simplified and handwavy – I’m explaining things conceptually, not scientifically.

For example, the green curve is the response curve of the green cone. At 500 nm, that curve is at 50%. So this little “sensor”, the green cone, is triggered at a level of 50% when you hit it will full intensity light of 500 nm wavelength.

So far so good. I have hightlighted the three example wavelengths from earlier. None of them excite the blue cones, so we just look at the green and red cones.

  • With spectral yellow of 580 nm, you can see that the green cones respond at 65%, and the red cones respond pretty much at a full 100%.

  • A spectral red of 620 nm causes a response in the green cones of 15% and in the red cones of 55%.

  • A spectral green of 540 nm triggers the green cones at 100% and the red cones at 90%.

Now if you hit the eye with half-intensity green, the response is half of that – that’s 50% in the green cones and 45% in the red cones.

And if you shine full-intensity red at the same time, you have a total of 50% + 15% = 65% in the green cones, and 45% + 55% = 100% in the red cones. And those are exactly the same values as for the yellow light.

(Reality surely isn’t quite as linear as I’m pretending here – again, this is a conceptual explanation).

So we’ve shown that pure spectral yellow light triggers these receptors in exactly the same way as a mix of red and green does. Our eyes cannot tell the difference. That’s what the word “metamerism” means that I mentioned above.

And that is why your monitor can show you all kinds of colors, even though it only consists of tiny red, green, and blue lights. You can trigger basically all of the possible receptor responses just by using combinations of red, green, and blue light.

In particular, and that is something that only really became clear to me as I was reading up on all this: There is nothing physically special about the colors red, green, and blue. When somebody says “You can create any color light just by combining those three”, then that is not a statement about light itself, but a statement about human perception.

If we had a fourth kind of receptor that was most sensitive at 580 nm (spectral yellow), then our eyes could indeed tell the difference between yellow light and mixed red/green light, because they would trigger different responses in our visual system.

The “actual” color

a sky at sunset, transitioning from blue at the top to orange at the bottom, with silhouettes of trees and houses

And that brings us back to the question of the “actual” color. If we want a universal system to describe colors in an unambiguous way, we don’t need to describe a full light spectrum. If the “yellow only” spectrum and the “mixed red/green” spectrum both trigger the same response in our eyes, then for the purposes of describing a color we should consider those two spectral distributions to be identical.

So how do you create a measure of color that ignores differences we can’t see anyway? By using the response levels of the three different color receptors in our eye as the values.

And that’s what XYZ does. Values in the XYZ (or CIEXYZ) color space are triples that correspond to “measurements” happening in our eyes (or, to be precise, in the eyes of an “average standard observer”, because there are of course small variations even among humans with full color vision).

The values XYZ don’t directly correspond to the three sensory response levels (that’s what the LMS color space does), but they’re a simple matrix multiplication away from that.

That’s why the XYZ representation of a color is often called its “tristimulus values”, because it’s based on how the three types of cones are stimulated.

There are some ICC profiles that use XYZ instead of Lab to describe “actual” colors. In fact, my scanner profile actually does just that; I just used Lab values for simplification in the example above.

So how and why do we get from XYZ to Lab?

The problem with XYZ is that the values aren’t really great to work with. Here’s my green card stock color in XYZ, according to the scanner profile:

77.000000 168.000000 100.000000 [RGB] 
-> Lut_A2B0 
-> 0.198915 0.320555 0.114025 [XYZ]

Here, Y=0.32 is some measure of brightness, Z=0.11 is some measure of blueness, and X=0.2 is “the rest of the information”. That’s not very intuitive to grasp.

And XYZ is also not good at describing differences in human perception. Ideally, we want a color space where if you take the euclidean distance between two points (two colors), that number is a good measure of how similar or different the colors appear to a human.

CIELAB

And that’s where Lab (or L*a*b*, or CIELAB) comes in. It’s calculated from XYZ in such a way that a) the euclidean distance is meaningful, and b) the values themselves are also meaningful:

  • L* or L is the lightness, where 0 means completely black and 100 is some loosely defined maximum, depending on the use case
  • a* or a is green-red, where negative numbers mean green colors and positive numbers mean red colors
  • b* or b is blue-yellow, with negative meaning blue and positive meaning yellow

Practical values of a and b are usually in the -100 to 100 range, often less.

The extremes of those ranges are not the very deep colors you might be thinking about. For example, the red side of the a* range is quite pink. The “real” red you might imagine contains quite some yellow in this model.

Our example green had the Lab values [63.388740, -46.752611, 33.467207], and now we have the means of interpreting those numbers. L=63 means a slightly above medium brightness, a=-47 means a decently strong green, and b=33 means a pretty strong (but not quite as strong) yellow. And indeed, our color is a medium-bright warm yellowish green.

There’s an interesting observation you can make here. A yellowish green is clearly representable in Lab. A blue-green is also doable (with a* and b* both negative).

But what about a reddish green? A blueish yellow? You can’t have a value be both positive and negative, so how would those colors be described in Lab?

Let’s think about that a little more. Can you actually imagine a color of such a description? Chances are that you can’t. It’s not 100% clear; some people do report being able to imagine or even see such colors, but most people don’t. I definitely don’t.

Here’s a visualization that helps me to convince myself of that:

A diagram with four solid colored squares at the top, and six gradients between those colors. Next to the six gradients, there are six colored rectangles showing the center color of each gradient, labeled with words like 'yellowish red' and 'greenish blue', all with a question mark.

At the top, you see the four solid colors of the a/b axes. On the bottom left, you see six linear RGB gradients between each possible pairing of those four colors. On the right, you see a magnified view of the very center of each gradient; the “middle color” if you will.

Now ask yourself if each color on the right could be described by the words written next to it. If you’re like me, “yellowish red” is definitely a reasonable description for the orange. In fact, the top two and the bottom two seem quite right, but the middle two? Not so much. I can’t get myself to see a “reddish green” in that blue-gray, or a “blueish yellow” in that green.

Now you could say that this is cheating – you’re probably looking at this on an RGB screen, built on technology that is based on these very assumptions. So ask yourself then whether you can imagine any color that would match such a description. If you’re like most people, you cannot.

White

Let’s get back to Lab. Neutral colors (gray/white) have a=b=0, which makes a lot of sense from a “numbers are meaningful to humans” perspective, but it creates an entirely new problem: What is a neutral color? What is white?

The color white is not a physical property of light, and it’s also not a specific kind of sensory response in our eye. The color white is an invention of our brain.

Our brain defines white as “the color of the surrounding light that illuminates the scene”.

Imagine looking at the same light source (say, a flashlight) in a warmly lit living room and in the cold light of a hospital hallway. The same light source will appear much bluer to you in the former setting and much more orange in the latter, because your brain compares the flashlight to the surrounding light.

The response to the flashlight in your eye’s receptors has not changed, but your brain’s interpretation of those values has changed. Or in other words, that light has the same XYZ values in both surroundings, but it has different Lab values.

That seems like bad news for Lab if we want a universal, unambiguous measure of “actual” color.

And indeed, in order to fix that problem, you need one additional piece of information beyond the L*a*b* coordinates: The definition of white. That value (given in XYZ) makes Lab unambiguous again, and at least as long as your surrounding light has that particular color, you will perceive colors with a=b=0 as neutral.

Because the ICC says so, spec-conformant ICC profiles define white as the XYZ values [96.4212, 100, 82.5188], which is also known as the illuminant D50. The CIE actually prefers D65 (which is [95.047, 100, 108.883]) because that’s roughly what daylight on a clear day looks like.

The color of things

a group of purple flowers

So far we’ve talked about the color of light, but since this is about printing, we also need to talk about the color of things.

The color of a thing, in particular a sheet of paper, is the color that it diffusely (i.e. not in a mirroring way) reflects. A (theoretical) perfectly white paper would reflect all incoming light of all wavelengths, and thus this paper will always have the same color as the illumination. In other words, your brain would always consider it white, regardless of the surrounding light.

In practice, no such thing exists, and thus the paper color that your brain sees also depends on the light. Untreated paper is typically a little yellowish – it reflects most light, but it absorbs some blue. When you look at such paper in daylight (which includes all the wavelengths to a good amount), you will easily notice the yellow tint of the paper: Your brain compares the surrounding light (which has all the blue) to the light that’s reflected by the paper (which has less blue).

But if you then look at the same paper at night, in a room that’s lit by warm-white LEDs which emit very little on the blue end of the spectrum, then the paper has no light to absorb. All the surrounding light can be reflected, and thus the paper appears completely white to you.

You might say this sounds similar to the flashlight story from earlier. You wouldn’t be wrong, but there’s a major difference. The flashlight never changes its actual color in response to a change in surrounding light – its XYZ value always stays the same. The paper, on the other hand, can have different XYZ values depending on the illumination. Unlike the flashlight which produces its own light, the paper has to work with what it gets from the surroundings.

Therefore, when you want to measure the color of the paper (and we’ll get to the reason for that soon), you have to specify the illuminant under which you made that measurement.

For ICC profiles, that illuminant is standardized to be the same D50 that we mentioned earlier.

Illuminating

And that is also something that has confused me for a long time. Because it’s D50 in both cases and it’s also about something called a “white point” in both cases, I’ve always assumed that these two things are sort of the same thing, and only when I was writing all this down I realized that they’re two completely different things:

  1. The first one specifies the “white point” of the Lab color space, i.e. it defines the color of a=b=0 when converting between Lab and XYZ. This is mostly a math thing, where you need a value to make Lab unambiguous.
  2. The second one specifies the light used for mesasuring the “white point” of the paper (the brightest color your print can possibly be expected to have – the color of the paper itself). This is a much more consequential specification, because it influences the measured XYZ value.

In the first case, all you need is the color of D50 light in XYZ. In the second use case, the color isn’t enough; you actually need to specify the full spectral distribution of this standard illuminant.

To see why that is the case, let’s use an extreme hypothetical example. Let’s say you have two different kinds of paper. The first one absorbs all light except for a very narrow band around 580 nm (spectral yellow). The second paper absorbs all light except for two bands in the green and red areas of the spectrum.

Under daylight, both papers will be yellow, because both reflect the light such that it triggers a “yellow” reponse in our eyes.

But now lets construct a slightly different light: It’s almost the same as our daylight, but we filter out the band around 580 nm, and then we adjust the rest of the spectrum to compensate, so that our new light causes the same response in our eye as the original daylight did (it has the same XYZ color).

We now have two lights of the same color, but under the second light, our papers look very different. The second paper will have a similar color as before, maybe only slightly different depending how we did that “compensation”.

The first paper however will be completely black. Since it can only reflect one wavelength of light, and that wavelength doesn’t exist in our spectrum, that paper doesn’t reflect anything.

Both lights have the same color, but the paper looks vastly different.

This is of course an extreme thought experiment that is far beyond reality, but it shows why the light’s color alone is not enough when you need to accurately describe how you measured the paper’s color.

So why do we need the color of the paper at all?

Absolute and relative

There are two different uses for knowing the exact color of a paper.

Say you send a file to a print service, to be printed on a very nice artsy paper that has a strong yellowish tint. Before you get that expensive print done, you would like to have an idea of what it looks like, so you want to print it at home to look the same way. If you simply print the image on your white office paper, you don’t know how things will appear on that yellower art paper. So you also need to print the paper color itself, so that things look 100% identical. And therefore you need to know that art paper’s exact color.

This process is called “proofing”. In reality, you’ll never be able to do that on your home printer with office paper, but you get the idea.

The second (and more important) use for the paper color comes back to perception. Just like your brain uses the difference between an objective color and the surrounding light to compute its perception of that color, the same is also true for a photo printed on paper. If you print a green square in the middle of a slightly blueish paper, and you print that exact same green in the middle of a yellower paper, your brain will see different greens, even though objectively they are the same, and they cause the same response in your eyes (the same XYZ values).

If you will, the paper color is part of what your brain considers the color of the surrounding.

And when you print a photo onto different papers, you usually don’t want the objectively identical colors to be printed on all the papers; rather, you want all the photos to “look the same”. And therefore you need to adjust the colors you’re printing based on the paper color.

In the first case, you want the same absolute color; in the second, more common case, you want the same color relative to the paper.

That’s why these two different ways of deciding what color to print are called the “absolute colorimetric rendering intent” and the “relative colorimetric rendering intent”.

Final thoughts

Okay, that’s the most important things I learned about color management and its background recently. I probably got some things wrong; I’m not an expert in any of this. I’m happy to be corrected!

But I do hope that you found it useful. I would’ve loved to read something like this when I started out.

Many thanks to David Haney for his excellent feedback on earlier drafts of this post.

In the next post, I will tell you how I’m actually creating those “amateuer ICC profiles”. Once that post exists, I’ll link it here.


previous post: Hello (Virtual) World: Your first Daydream app

To see comments or leave your own, . Here is their privacy policy.

You can also find me on Mastodon.