Now let us do something quantitative.
One of the terms I found most frustrating in books about color was “saturation”. And the first quantitative satisfaction I found was in the algorithm for converting from RGB to HSB. The S in all three of HSB, HSV, and HSI stands for “saturation”. HSB stands for hue, saturation, and brightness (V stands for value, I for intensity). Hue means color; but what are saturation and brightness?
I will take it for granted that we all understand that color on a television or computer monitor is specified by RGB: three numbers which specify amounts of red, green, and blue at each location on the screen.
Here is a picture of the HSB color picker on my computer.
Here is the definition of the HSB “coordinates” of an RGB color. All I’m doing at this point is changing coordinates. Nothing in the algorithm actually depnds on the colors, just on the RGB values, however they may be assigned to colors.
First off, the algorithm was written in terms of HSV — hue, saturation, and value — instead of HSB. As I understand it, HSB and HSV are identical — different names and acronyms for the very same thing.
There is a third, related, acronym: HSI. My understanding is that it differs from HSB and HSV in that it uses trigonometry, which they do not. Since the color picker on my computer uses HSB – and because I prefer it over RGB or CMYK for choosing colors, that’s the specific set of coordinates I want to understand.
When I first encountered all three acronyms, I found it useful to remember two things. One, the “i” in trigonometry goes with the “I” in HSI. Two, the ancient sound of “beta” in Greek was, not surprisingly, the sound of English “b”; but the modern sound of “beta” in Greek is the sound of English “v”. To put that another way, although the Greeks revere the poet Byron, they now pronounce his name “Vyron”. Anyway, the two pronunciations of beta help me to remember that HSV and HSB are the same thing.
I found an algorithm for RGB to HSV in Foley, van Dam, et al. I implemented it in Mathematica as follows.
I hope you are not offended by my both presenting the code and discussing it. Well, if you would be, just look at the code. In fact, if the code and my discussion disagree, the code takes precedence.
By the way, if I had written this code for a programming class, it would have been so thoroughly commented as to render additional discussion unnecessary. But I didn’t and it isn’t. The purpose of this code was to make sure I was reading the original algorithm correctly. I don’t use this code — I use the color picker on my computer.
The program assumes that the RGB values are in the closed interval [-1,1]. The first significant line sets the variable max to the maximum of the three RGB values; and next we get the minimum, min. Then, if the value max is greater than 1, I must have called it with RGB values in the interval [0,255], which is wrong here but what I am used to elsewhere.
Set the variable delta to the span, delta = max – min.
I could have defined the value v as soon as I had defined max, but I preferred to do it later. The value v — or the brightness — is the maximum of the three RGB values. (Yes, the word “value” is doing double duty in that sentence.)
Incidentally, one reason for calling it HSV in the algorithm is so that we do not have B / b in two places. We may think of it — and I do — as the brightness, but I dare not label it with a “b”.
The saturation s is s = delta / max, provided that we can do the division; otherwise, set the saturation to zero. (After all, if max = 0, then every one of the RGB values is zero, so s = 0 is a reasonable value.)
If saturation s = 0, then the hue h is to be undefined; I used “infinity”.
The definition of h will end up as an angle in degrees. Our color wheel will be divided into three sectors 120° wide, depending on which of the RGB values is maximal. An expression such as (g-b)/delta is a signed fractional part. It must be less than or equal to 1 in absolute value, but it could be negative. (In this specific case, r = max, the sector is centered on 0.)
The other two cases in the “which” (cf. “case” or “switch”)) statement specify an h in the other two sectors. We end up with an h in the closed interval [-1,5] — okay, okay, I refuse to worry about the border conditions. I wouldn’t be surprised if — in fact, I suspect that — h is in the half-open interval [-1,5). It just isn’t important to me.
The third line from the end multiplies h by 60; the second line from the end converts our negative angles (in -60° to 0) to positive (300° to 360°); and the final line of the program is returning a 3-element vector containing the HSV values.
We have seen that value v = max is the largest of the three RGB values. We have also seen that saturation s = 1 if and only if delta = max, i.e. if and only if min = 0. To put that in words, a color is fully saturated if and only if (at least) one of its RGB values is 1, and (at least) one of its RGB values is 0.
Note, however, that any hue h may be fully saturated. For example, if r = 1 and b = 0, then the range of possible values of g will give us h in the range 0-60°. Similarly for any other maximum of 1 and minimum of 0; the third value will span half a sector. (We would get the other 60° sector bordering on red, the range 300-360°, by choosing r = 1, g = 0 and letting b vary.)
Now you can see why most of my constructions in the previous post showed red on the right: trigonometry and complex analysis taught me a long time ago that an angle of 0° is to the right, on the x-axis. Nevertheless, for color harmony I draw my color wheels with red on top.
The inverse algorithm looks far more complicated, but it isn’t as bad as it looks. Here is how I coded it in Mathematica. (Again, the algorithm itself came from Foley, van Dam, et al.).
In fact, I should probably remove the first computational line: subsequent calculations will lead to the same answer if s = 0. That is, I think I do not need to handle the case s = 0 separately. But, frankly, I think I never called this code with s = 0. It just doesn’t matter.
Instead of dividing our circle into three sectors, we divide it into six sectors. The definition of h1 (which was initialized to the input h) says that from the range [0,360], we drop to the range [0,6) — yes, that interval is half open.
Then ip and fp are the integer part and the fractional part of h1. We need six sectors rather than three because now our fractional parts are always positive, in contrast to the previous algorithm where they could be positive or negative.
The integer parts are the integers 0-5.
Skip over the definitions of p, q, t for a moment. The switch (cf. case) command always assigns v to something, and it always assigns p to something else. Well, v needs to be assigned to whichever RGB value — to any one of them — which is maximal. That’s how v was defined.
If you guess that p is the minimum — one of the minimum — RGB values, you would be right. Okay, even if you never return to the definitions of q and t, you should look at the definition of p, and convince yourself that it is one of the minimum values. But which one, when? That just depends on which of the six sectors we are in.
The two values q and t are being used to account for the lost sign on the fractional part. Integer parts 1 and 2 are the original max = green sector, with blue and red, respectively, as the minimum, depending on whether we use q or t. Similarly for integer parts 3 and 4. And similarly for integer parts 0 and 5, which were separated when we converted the original negative angle into a very large positive one.
I encourage you to pick a sector and confirm that p is the appropriate minimum value and that q or t is the appropriate third RGB value.
In the next color post, I will show you why I like using HSV on my computer. Funny thing, when I use it, I call it HSB because I prefer the term “brightness” to “value”. Oh, and because my computer calls it HSB! But as I said, we want to use v instead of b in the algorithm.