Blending Colors
How do you blend between two colors? Chances are, you’re just doing a standard lerp (linear interpolation) between RGB values, right? If we were to blend between red and green, we should see something like this:
Result = (ColorB - ColorA) * t + ColorA
We love this method because computers love this method, and when computers love something, they do it fast. But, have you ever noticed the weird dull splotch in the center? It’s kind of a weird dark desaturated yellow.
We really lose the vibrancy of the red and green. But shouldn’t halfway between two vibrant colors be another vibrant color? So what happened here?
RGB
The problem lies in the RGB color space. Let’s visualise what RGB looks like by plotting the red, green and blue values on different axes.
RGB Color Space
A cube is a great way to represent a color space because we can easily see what the stored values (RGB) are just by looking at the axes (XYZ).
So what is happening when we do a lerp? We are just drawing a straight line from one point on the cube to another.
Linear Interpolation between red and green in RGB
Notice how it pierces through the cube to create a straight line. That yucky murky yellow color must be somewhere between those two points. But if you just look at the cube, you can kind of see where you would rather it to go; we want a nice vibrant yellow! This means that we want to bend the line so that it goes along the surface of the cube instead.
How do we achieve this?
Okay, so what crazy geometry math hack are we going to need to pull this off? Well, I’d rather stay away from that sort of crazy math. Instead, let’s find an easier way to think about the problem. What if, instead of changing the line to suit the cube, we change the cube to suit the line? In other words, we need to change the color space?
HSV
HSV Color Space
Introducing HSV (Hue Saturation Value)! It’s a lot more intuitive, isn’t it? The vertical axis increases the lightness, outward for vibrancy, and rotation to pick a hue. Easy! That’s why artists love it.
But there’s something odd with this. If you put hue, saturation and value on different axes, you get this instead.
HSV Color Space?
Kinda ugly isn’t it? Yet we get a good glimpse of what our 3 values are actually storing. So what happens now if we do our lerp between our red and green?
It goes through the vibrant yellow which looks great. Problem solved!
But hang on, what about if we try some other colors. What about magenta to yellow?
Ouch, that’s not right. Kinda cool, but really not right. Maybe a better way to think about this is to just look at how artists solve this. How does a painter, with full creative liberty over their palette, blend between two colors? If we were to lay it out on the HSV cylinder, you can see how simple it really is.
Artist Blending Colors, simplified
See how it’s just a straight line across the cylinder? It’s so simple! Yet we haven’t been able to achieve this with these damn cubes. What if we ditched the cubes and just got the XYZ coordinates on the HSV cylinder. Then it would just be a normal lerp between the coordinates. Another way of thinking about it is putting our color space cube around the cylinder.
HSVxyz
HSV Cylinder in XYZ Coordinates (HSVxyz)
Now you might be asking, “What about the empty space outside the cylinder (yet still within the cube)? What if you have values in that empty space? What color will it be?” A color in this empty space is invalid. If you want, you can clamp it to the cylinder, but it ultimately serves no purpose (which means we’re technically wasting data).
Now, let’s do the magic. The steps to pull this off is mostly just a series of conversions between color space.
RGB -> HSV -> XYZ -> Interpolate -> HSV -> RGB
The code to do this can be a bit intimidating. I won’t go into detail how it all works, but I’ll have it here for completeness.
|
|
So now you can use it like this:
|
|
Obviously this is going to be a lot more computationally expensive than the traditional lerp but screw computers, we want nice looking mid-tones! So, brace yourself. Now we get to see the result of all this hard work!
RGB² (XYZ)
Okay, there’s one more strategy for lerping color that I should really mention. We’ve mostly been talking about what looks good artistically, but we should also talk about what is physically correct. You see, the way computer colors work is kind of fudged. Light works on a logarithmic scale when compared to what we see from our human eyes. For example, if you wanted to double the brightness of something, you would actually need four times the amount of light. It’s important to note that this is a simplification of the XYZ color space (which is kind of the holy grail of color spaces).
Alright, so how can we harness this to improve our lerping? We just need to convert our RGB color into RGB².
RGB²
If you compare this with the original RGB cube, you’ll see that the colors are more vibrant in the RGB² cube. So, what if we do our lerp here?
RGB²
It looks a lot closer to RGB than HSVxyz, but it’s significantly brighter in the center. But the greatest part of RGB² is how computationally simpler it is.
|
|
Comparison
Finally, here’s a comparison with the different types of blending from red to green:
RGB | |
HSV | |
HSVxyz | |
RGB² |
But what about that horrible magenta to yellow?
RGB | |
HSV | |
HSVxyz | |
RGB² |
Conclusion
So what is the best way to blend your colors? It really depends on what you want, but I would summarize with:
RGB | Super fast, can have dull mid-tones |
HSV | Slow and has all sorts of weird rainbow effects |
HSVxyz | Super slow by gives vibrant mid-tones |
RGB² | Physically accurate, still quite fast |