Mixing two colors "naturally" in javascript

The problem: I want to mix two colors in javascript, and get the result color. There are a lot of similar question on SO, however I doesn't find anything that actually works correctly. I know that mixing two different colored paints(pigments) and lights will give very different results (http://en.wikipedia.org/wiki/Color_mixing).

Here are the questions and suggested solutions I've already seen, and tried to implement:

1: Mixing two RGB color vectors to get resultant
So, mixing colors in RGB. I implemented it, and in some cases it works in some cases it doesn't.

Working example: Mixing red with yellow -> orange. Great!
http://jsbin.com/afomim/1/edit

Not working example: Mixing blue with yellow -> gray. Not so great! :) http://jsbin.com/afomim/5/edit
I know that in RGB mixing blue with yellow will never make green, and I understand why.

We will not find the answer here, let's go forward.

2: Adding Colours (Colors) Together like Paint (Blue + Yellow = Green, etc)

Let's try to work with CMYK values as suggested in this discussion. Mixing cyan with yellow gives green:
http://jsbin.com/igaveg/1/edit
but mixing blue with yellow results in black.
http://jsbin.com/igaveg/2/edit -> Not working!

3: How to mix colors "naturally" with C#?
A very similar question. The most upvoted answer suggests to convert colors to LAB, and this solution seems promising.
So I converted my colors to LAB. The conversion algo is correct, I tested it!

http://jsbin.com/oxefox/1/edit

Now I have the two colors in LAB, but how to mix them?

NOTE I know that probably I will not find an algo that mixes blue with yellow and will give the perfect green, but I hope I can generate something similar to green :)


Solution 1:

I dedicated 3-4 days to this question. It's a really complex problem.

Here is what you can do if you want to mix two colors "naturally":

  1. CMYK mixing: it's not the perfect solution, but if you need a solution now, and you don't want to spend months with learning about the subject, experimenting and coding, you can check this out: https://github.com/AndreasSoiron/Color_mixer

  2. Implementing the Kubelka-Munk theory. I spent a lot of time reading about it, and trying to understand it. This should be the way to go if you want a professional solution, but it needs 6 parameters (like reflectance, absorption, etc.) for each colors you want to mix. Having R, G, B isn't enough. Implementing the theory isn't hard, but getting those parameters you need about each color seems to be the missing part. If you figure it out how to do it, let me know :)

  3. Experimental: you can do something what the developers of the ipad app: Paper have done. They manually selected 100 pairs of popular colors and eyeball-tested how they should blend. Learn more about it here.

I personally will implement the CMYK mixing for the moment, and maybe later, if I have time I'll try to make something like the guys at Fiftythree. Will see :)

Solution 2:

I actually ran into the same issue when trying to mix 2 RGB colors together. These 2 functions worked for me:

//colorChannelA and colorChannelB are ints ranging from 0 to 255
function colorChannelMixer(colorChannelA, colorChannelB, amountToMix){
    var channelA = colorChannelA*amountToMix;
    var channelB = colorChannelB*(1-amountToMix);
    return parseInt(channelA+channelB);
}
//rgbA and rgbB are arrays, amountToMix ranges from 0.0 to 1.0
//example (red): rgbA = [255,0,0]
function colorMixer(rgbA, rgbB, amountToMix){
    var r = colorChannelMixer(rgbA[0],rgbB[0],amountToMix);
    var g = colorChannelMixer(rgbA[1],rgbB[1],amountToMix);
    var b = colorChannelMixer(rgbA[2],rgbB[2],amountToMix);
    return "rgb("+r+","+g+","+b+")";
}

To mix red ( [255,0,0] ) with blue ( [0,0,255] ) evenly, you can call

colorMixer([255,0,0], [0,0,255], 0.5);//returns "rgb(127,0,127)" (purple)

This may help, though you have to convert each color value to an array first. If you use Fabric.js to work with canvas elements, this becomes really easy. Just call

var rgbA = new fabric.Color(yourColor);
var rgbB = new fabric.Color(yourSecondColor);

then call

colorMixer(rgbA.getSource(),rgbB.getSource(),0.5);

Hope these functions help.

Solution 3:

The RYB Color Model could be a suitable choice for the color mixing calculations. According to Wikipedia, it is primarily used in art and design education, particularly painting.

To mix 2 colors, one converts both colors from RGB to RYB, mixes the colors by adding each color component, and converts the resulting color from RYB back to RGB.

I have tried this using the Online Color Mixing Tool, and the results are

  • 0000FF (blue) mixed with #FFFF00 (yellow) gives #008000 (dark green),

  • FF0000 (red) mixed with #FFFF00 (yellow) gives #FFA000 (orange).

So this method produces exactly the results that you expected.

Unfortunately, I was not able to find a reference with "ready-to-use" formula to convert from RGB to RYB and back to RGB.

The paper Paint Inspired Color Mixing and Compositing for Visualisation - Gossett and Chen describes the general idea of the RYB color model in the section "2 ACHIEVING INTUITIVE COLOR MIXING".

According to that paper, the conversion from RYB to RGB is done by Trilinear interpolation.

The difficult part is the conversion from RGB to RYB, because it requires the inversion of the trilinear interpolation. See Conversion between RGB and RYB color spaces for more more information.

Even if this answer does not provide complete formula for the calculation, I hope that it gives some ideas how to proceed.

Solution 4:

With CIELAB colors you have three coordinates for each of your two colors in the LAB color space. (By the way, excellent work in getting this far). What will work best and be easiest to implement for you is to find the three-dimensional midpoint of an imaginary line segment joining the two points in LAB space. You can do this easily by just averaging each of the components of your two colors: the average L, average a and average b. Then convert this color back into RGB space by reversing your transformation (make sure your lighting space stays the same both ways).

Your new color may be outside the RGB color space. You may decide to clip to the nearest visible color in this case. (Blues are especially vulnerable to this).