0

I have this function that takes two arguments. They are arrays in the [r, g, b] format.

function mix(color1, color2) 
{
    var r = Math.round((color1[0] + color2[0])/2);
    var g = Math.round((color1[1] + color2[1])/2);
    var b = Math.round((color1[2] + color2[2])/2);

    return [r, g, b];
}

If I try to mix red (255, 0, 0) and blue (0, 0, 255), tt gives me [128,0,128], which is purple. But if I try mixing blue (0, 0, 255) and yellow (255, 255, 0)

console.log(mix([255,0,0], [0,0,255]));
console.log(mix([255,255,0], [0,0,255]));

it gives me gray [128, 128, 128], instead of green. Why is this happening?

Aillyn
  • 23,354
  • 24
  • 59
  • 84
  • 1
    You really should be declaring "r", "g", and "b" with `var` in that function, whether or not that's the cause of the problem. – Pointy Nov 20 '11 at 22:59
  • Also your script works perfectly well for me. – Pointy Nov 20 '11 at 23:01
  • 2
    Check out: http://stackoverflow.com/questions/1351442/is-there-an-algorithm-for-color-mixing-that-works-like-mixing-real-colors – LoveAndCoding Nov 20 '11 at 23:01
  • Wait - do you expect `mix([255,255,0], [0,0,255])` to be something other than "[128,128,128]"? If so, why? – Pointy Nov 20 '11 at 23:03
  • 1
    and http://stackoverflow.com/questions/4414673/android-color-between-two-colors-based-on-percentage/4416122#4416122 – Alnitak Nov 20 '11 at 23:04
  • @Pointy The code works in the sense that mathematically the results are correct. But they are not what I expect. Where am I going wrong, this is the question. – Aillyn Nov 20 '11 at 23:05
  • @Pointy (255,255,0) and (0,0,255) are both fully saturated (yellow and blue). The result _should_ also be fully saturated, and half way in _hue_ between those two (a bluey-green). – Alnitak Nov 20 '11 at 23:07

5 Answers5

4

You need to convert your colours into either HSL or HSV color models (plenty of samples here or on Google, e.g. like this page).

You then do the averaging with the resulting numbers, and then convert back to RGB.

This will ensure that the saturation and brightness remain consistent, and give you the correct hue that's half way between the two original colours.

Using the library linked above:

function mix(color1, color2, amount) 
{
    if (typeof amount === "undefined") {
        amount = 0.5;
    }

    var hsl1 = rgbToHsl.apply(this, color1);
    var hsl2 = rgbToHsl.apply(this, color2);

    var h = amount * hsl1[0] + (1 - amount) * hsl2[0];
    var s = amount * hsl1[1] + (1 - amount) * hsl2[1];
    var l = amount * hsl1[2] + (1 - amount) * hsl2[2];

   return hslToRgb(h, s, l);
}

NB: this is untested code (the algorithm is correct, I just didn't actually throw it at an interpreter yet), and the results may need to be rounded. However for bonus points it also allows you to specify a mix proportion other than 50/50.

Martin Schuhfuß
  • 6,814
  • 1
  • 36
  • 44
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • I wasn't able to get this to work as it stands. I might still be doing something wrong - but undefined should be a string to make it an optional parameter `if (typeof amount === "undefined") {` – mikevoermans Mar 15 '13 at 13:54
2

2 main issues here:

additive versus subtractive mixing - that's easy to read about

But I think, the main issue was that blue and yellow paint do not make green. The type of blue you would be thinking of is in fact not blue: more of a cyan or at least a blue with some green shift.

A pure "blue" is neither warm (towards red) or cool (towards yellow/green) and in paint would be something like ultramarine red shade - a pure blue but you would consider quite a dark (almost navy) blue.

When mixed in paint with yellow you would not get a bright green, more of a dull grey. In paint to get paint colors which are bright from mixes it is essential to use colors which are not across from each other on the color wheel , but in fact as close as possible ideally, so a bright green can in fact be only obtained in paint from mixing a greeny blue with a greeny yellow - ie. they are neighbours.

The green will still not be as bright as a pure green. That cannot be done in subtractive (paint mixing) - to combine colors and get new pure colors can only be done with colored lights - ie. additive mixing , like you see in the theatre.

However be aware that additive mixing is somewhat counter-intuitive - instead of your subtractive paint blue/yellow mix giving a neutral gray (black in theory) in fact with lights you would get white .... (still not green!).

It is a very complex field because there is also some psychology and physiology involved - our brains cheat or mistake color perception on a regular basis. eg. you mix black with yellow you get a dark green - that is related to the sensitivity or the red / green cones as the brightness decreases - it is in fact dark yellow but we see green.

bool.dev
  • 17,508
  • 5
  • 69
  • 93
paulus
  • 21
  • 1
2

Because you are calculating resulting color as the arythmetic average of two base colors.

Colors work in a different way depending on what you mix. If you would mix paints of many different colors, the result would be dark, nearly black. But if you would mix lights of different colors, the result would be nearly white. First define the process you want to simulate - I bet it is the one that is similar to mixing paints, correct?

Two basic methods of mixing colors are:

  • subtractive mixing, which looks like that:

    enter image description here

  • additive mixing, which looks like that:

    enter image description here

What you need is subtractive color mixing, whose output should be in RGB notation.

Tadeck
  • 132,510
  • 28
  • 152
  • 198
  • It is indeed. How can I do this with Javascript? – Aillyn Nov 20 '11 at 23:04
  • 1
    @pessimopoppotamus: I believe the easiest way is to convert your colors to **CMY** and then just add them, then convert the result back to **RGB**. It will look like this: 1) yellow (`rgb(255,255,0)`) will become `CMY(0.0,0.0,1.0)`, 2) blue (`rgb(0,0,255)`) will be `CMY(1.0,0.0,0.0)`, 3) and the sum of yellow and blue will be `CMY(0,0,1) + CMY(1,0,0) = CMY(1,0,1)`, which is green (`rgb(0,255,0)`). – Tadeck Nov 20 '11 at 23:26
  • 1
    @Tadeck your conversions are wrong - blue is _not_ `CMY(1, 0, 0)`, it's `CMY(1, 1, 0)`, and if you add that to yellow you get `CMY(1, 1, 1)` which is _black_ – Alnitak Nov 21 '11 at 08:01
1

To get what you expect you need to work in CMY space instead of RGB space.

Here's a simple RGB to CMY converter:

function rgb2cmy (r, g, b) {
  var c = 255-r;
  var m = 255-g;
  var y = 255-b;
  return [c,m,y];
}

And simply reverse the process to convert back to RGB:

function cmy2rgb (c, m, y) {
  var r = 255-c;
  var g = 255-m;
  var b = 255-y;
  return [r,g,b];
}

(if you've been paying attention you'll realize that both functions do the same thing)

slebetman
  • 109,858
  • 19
  • 140
  • 171
0

I think your formula works fine, it is what, say, photoshop would do if you put blue next to yellow and apply a blur...you'd get gray right in the middle. Blue and yellow don't mix to make green. Red, green, and blue are primaries on computer monitors, camera film and the human eye.

Yellow, meanwhile, is a mixture of red and green light, that is, the red and green sensitive cones in your eyes are stimulated when you see yellow. It may not seem intuitive, but it's true. Look at a yellow area of your screen under a magnifying glass and you'll see it is composed of red and green dots.

This is in contrast to what you may have learned in elementary school, and sometimes mixing paints or inks may end up with blue and yellow making a (typically muddy) green. So, I don't suggest you change your algorithm, that is a standard way of blending two colors together. Just change your expectations. :)

rob
  • 9,933
  • 7
  • 42
  • 73