[NOTE: The live web app that encompasses this functionality can be found here: https://www.paintmap.studio. All of the underlying code for that site can be found here: https://github.com/bytebodger/color-map.]
In the previous articles in this series, I talked about many aspects of color theory - and real-world paints - as they apply to web development (i.e., RGB) colors. Soon I'm going to show you how to apply algorithmically-created palettes to a digital image. But first, we need to cover a basic task: Virtually "mixing" paints with code.
Honestly, there are a bunch of sites out there that can already do this for you. But I didn't see too many that actually talk about how you do it. So I'm going to illustrate that here.
RGB mixing
Obviously, the RGB color space is comprised of... numbers. Three numbers, to be exact, representing red, green, and blue. So if you wanted to mix two (or more) RGB colors, the mathematical way to do that is to simply average the reds, and the greens, and the blues. The resulting average will yield the new, mixed color.
I've already determined the RGB equivalents for each paint in my inventory. For example: Golden: Light Magenta is:
const goldenLightMagenta = {
red: 208,
green: 117,
blue: 127,
}
And Golden: Cobalt Blue is:
const goldenCobaltBlue = {
red: 21,
green: 41,
blue: 134,
}
So if we mix those two together, the resulting color would be:
const newColor = {
red: Math.round((208 + 21) / 2),
green: Math.round((117 + 41) / 2),
blue: Math.round((127 + 134) / 2),
}
Which comes out to:
const newColor = {
red: 115,
green: 79,
blue: 131,
}
CMYK mixing
CMYK also consists of... numbers. But it has four numbers - cyan, magenta, yellow, and key - instead of red, green, blue. The color mixing I've implemented on Paint Map Studio uses CMYK mixing. Why? Because CMYK is a subtractive color space, and using CMYK values tends to lead to mixing results that are slightly better, in my experience.
For example, when I mix Golden: Cobalt Blue with Golden: Light Magenta, it gives me the following result:
const newColor = {
red: 100,
green: 74,
blue: 137,
}
And the mixRgbColorsSubtractively()
function looks like this:
const mixRgbColorsSubtractively = (rgbColors = [rgbModel]) => {
let cmykColors = rgbColors.map(rgbColor => convertRgbToCmyk(rgbColor));
let cyan = 0;
let magenta = 0;
let yellow = 0;
let key = 0;
cmykColors.forEach(cmykColor => {
cyan += cmykColor.cyan;
magenta += cmykColor.magenta;
yellow += cmykColor.yellow;
key += cmykColor.key;
});
const cmykColor = {
cyan: Math.round(cyan / cmykColors.length),
magenta: Math.round(magenta / cmykColors.length),
yellow: Math.round(yellow / cmykColors.length),
key: Math.round(key / cmykColors.length),
};
return convertCmykToRgb(cmykColor);
};
Since I'm building all of this in a web-based application, I'm always starting from an RGB color. That's why the function expects an array of RGB values. Inside the function, we convert the values to CMYK, calculate the average, and then convert that average back into RGB.
You can see this logic in action by going to https://www.paintmap.studio/mix. On that page, you'll be able to pick any colors from the palette of heavy body acrylic paints, mix them together in different proportions, and see the resulting color.
It may surprise you to see that the RGB model yields a color mix that's so visually similar to the CMYK model. Well... let's explain that.
Beware of color confusion!
In my experience, it's really easy to get your head wrapped up in knots when thinking about color mixing. I think this confusion comes about because of the way that we talk about color mixing and color models.
For example, I found this quote on a site that talks about RGB vs CMYK colors:
The color systems used by scientists and artists are entirely different. An artist will mix blue and yellow paint to get a shade of green; a scientist will mix green and red light to create yellow.
To be perfectly clear, this quote isn't "wrong". But it definitely has the potential to screw up our thinking because it's talking about two different types of mixing. Specifically, there's a difference between mixing and... combining. Let me show you.
According to that quote, a scientist would mix green and red light to create yellow. But that's a bit misleading, because the term "mixing" implies that I have two RGB colors - one green, and one red - and we're mixing them up.
So using the basic calculation that we outlined above for RGB colors, that means that we would be mixing these two colors:
const green = {
red: 0,
green: 255,
blue: 0,
}
const red = {
red: 255,
green: 0,
blue: 0,
}
What happens when we "mix" those up? We get this:
const mixedColor = {
red: (green.red + red.red) / 2,
green: (green.green + red.green) / 2,
blue: (green.blue + red.blue) / 2,
}
Which comes out to this (values rounded):
const mixedColor = {
red: 128,
green: 128,
blue: 0,
}
And that RGB value looks like this:
That's hardly what most people would call "yellow". Sure, it's kinda sorta... yellow-ish. But it's far from true yellow. (I think it would best be identified as a dirty chartreuse.)
Here's where the quote isn't entirely wrong, but it's definitely somewhat confusing. In the RGB color space, combining pure red and pure green in a single color will indeed create yellow. In other words, this value:
const fullRedAndFullGreen = {
red: 255,
green: 255,
blue: 0,
}
Creates this color:
Now that is yellow! But it only happens when we combine red and green into a single color. It's not what happens when you mix red and green together.
Here's another way to think about it. Imagine that the scientist mentioned above has a laser on his desk that emits wavelengths of pure red light. And he has another laser that emits wavelengths of pure green light. Using the RGB color space, we could represent his lasers as such:
const greenLaser = {
red: null,
green: 255,
blue: null,
}
const redLaser = {
red: 255,
green: null,
blue: null,
}
Notice that for the red laser, I didn't define the green
and blue
values as 0
. I defined them as... null
. And on the green laser, I defined the red
and blue
values as... null
. If you understand the difference between 0
and null
(and if you're reading programming blogs, I sure-as-hell hope that you understand the difference), you'll realize that an RGB object with null
values is not the same as one with 0
values.
Now if that scientist were to combine the light coming from the red and green lasers, the resulting color would indeed be yellow.
But if that same scientist was creating a web site to present his findings, and he wanted to "mix" the RGB color red with the RGB color green, the resulting color would be chartreuse. Not yellow!
The exact same thing happens if we use a color space that's subtractive (like CMYK) rather than one that's additive (like RGB). The CMYK Venn diagram tells us that combining pure yellow with pure magenta should give us... red. But let's look at what happens if we don't combine them, but instead, we mix them.
Our colors would look like this:
const yellow = {
cyan: 0,
magenta: 0,
yellow: 100,
key: 0,
}
const magenta = {
cyan: 0,
magenta: 100,
yellow: 0,
key: 0,
}
Mixing them is accomplished like this:
const newColor = {
cyan: (yellow.cyan + magenta.cyan) / 2,
magenta: (yellow.magenta + magenta.magenta) / 2,
yellow: (yellow.yellow + magenta.yellow) / 2,
key: (yellow.key + magenta.key) / 2,
}
Which would come out like this:
const newColor = {
cyan: 0,
magenta: 50,
yellow: 50,
key: 0,
}
And the here's the resulting color:
That's, umm... red-ish. But it's far from pure red. It's much more like a "salmon" color. (In fact, it's incredibly similar to the Golden: Light Magenta color illustrated near the top of this article.)
This is why, IMHO, color mixing can sometimes lead people down rabbit holes. They spin up a color-mixing algorithm that uses CMYK, for example, then they throw pure yellow and pure magenta into the mix, and when the resulting color isn't pure red, they think that there's some kinda flaw in their methodology. (I've read many threads that complain about this exact same "problem" on places like Stack Overflow.)
But if you have a pure yellow-colored paint, and you mix it with a pure-magenta colored paint, I promise you that you will not end up with pure red. You'll end up with... salmon.
In the next installment...
Although this topic interests me from the perspective of further understanding color theory, I didn't take you down this path purely for academic reasons. Being able to accurately mix paints, algorithmically, will have significant benefits in our quest to create a palette of (near) perfectly-matched paint colors against a digital image. In the next article, I'll show how we can leverage this to greatly increase the accuracy of our transformed images.
That being said, even if you're not interested in trying to match a digital image to a given palette of paints, it can be extremely useful to be able to "mix" paints virtually. I'm sure that skilled artists already have a good feel for the colors that result when they mix any two (or more) paints from their inventory. But for the rest of us, it's nice to be able to see the results without having to create an endless stream of paint swatches.
Top comments (1)
Сongratulations 🥳! Your article hit the top posts for the week - dev.to/fruntend/top-10-posts-for-f...
Keep it up 👍