Color Mixing With JavaScript
Tags: webdev, javascript, tutorial, programming
Published: 2023-02-28 06:35:32

[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.
<br/><br/>




![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x838862vtu0z3a77hmxh.png)
## 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:

```javascript
const goldenLightMagenta = {
  red: 208,
  green: 117,
  blue: 127,
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eu0rsmd4ibus5jc3lifh.png)

And Golden: Cobalt Blue is:

```javascript
const goldenCobaltBlue = {
  red: 21,
  green: 41,
  blue: 134,
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtil8902vq2o22g1prlq.png)

So if we mix those two together, the resulting color would be:

```javascript
const newColor = {
  red: Math.round((208 + 21) / 2),
  green: Math.round((117 + 41) / 2),
  blue: Math.round((127 + 134) / 2),
}
```

Which comes out to:

```javascript
const newColor = {
  red: 115,
  green: 79,
  blue: 131,
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/foi82ar2xju7b8qgbw20.png)
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ax6xwe2udwpv6slakdza.png)
## 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:

```javascript
const newColor = {
  red: 100,
  green: 74,
  blue: 137,
}
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/986hth5tps5jib2h4zpx.png)

And the `mixRgbColorsSubtractively()` function looks like this:

```javascript
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.
<br/><br/>



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3b40n68u7lpwbcka6njq.jpg)
## 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:
<br/>

> _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._

<br/>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:

```javascript
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:

```javascript
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):

```javascript
const mixedColor = {
  red: 128,
  green: 128,
  blue: 0,
}
```

And that RGB value looks like this:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y19ubrhbqf1dylyvlg9l.png)

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:

```javascript
const fullRedAndFullGreen = {
  red: 255,
  green: 255,
  blue: 0,
}
```

Creates this color:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qjm05rgi606ou3z73s69.png)

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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
const newColor = {
  cyan: 0,
  magenta: 50,
  yellow: 50,
  key: 0,
}
```

And the here's the resulting color:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zgho9q3wzzfdlnjp4uh9.png)

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.
<br/><br/>


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jmmjb7n4hnwm2jj22fa6.jpg)
## 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.