Related
How to calculate if a certain RGB value is in the same approximation of color
for example if we have the set
var color = {
r : 20,
g : 20,
b : 80
}
then the same color when lit or blacked should produce approx the same ratio maybe something like
var colorBrighter = {
r : 40,
g : 40,
g : 100
}
The question if anyone can think of a function or formula to check if the colors are in the same range... a way to determain if a color is a brighter or darker variation of a given color...
Thanks!
Direct computations in RGB would be very far from what the eye perceives. Don't even attempt to compute distances in this space, they mean nothing.
The usual solution when looking for simple computations that match human perception (for example finding a darker, or near color, or looking for a pair of contrasted colors) is to convert your colors to another color space.
HSL gives very good results for that.
An excerpt from Wikipedia:
The HSL model describes colors in terms of hue, saturation, and
lightness (also called luminance). (Note: the definition of saturation
in HSL is substantially different from HSV, and lightness is not
intensity.) The model has two prominent properties:
The transition from black to a hue to white is symmetric and is
controlled solely by increasing lightness
Decreasing saturation
transitions to a shade of gray dependent on the lightness, thus
keeping the overall intensity relatively constant The properties
mentioned above have led to the wide use of HSL, in particular, in the
CSS3 color model.3
As in HSV, hue corresponds directly to the concept of hue in the Color
Basics section. The advantages of using hue are
The angular relationship between tones around the color circle is
easily identified Shades, tints, and tones can be generated easily
without affecting the hue
You'll find many snippets and libraries doing the conversion (one example).
Additionally, if your goal is to set colors on DOM elements, you should consider using hsl directly as it's supported by all browsers:
color: hsl(120, 100%, 25%);
a formula to check if the colors are in the same range
const difference = (one, two) => Math.sqrt(
(one.r - two.r) ** 2 +
(one.g - two.g) ** 2 +
(one.b - two.b) ** 2
);
Usable as:
console.log(difference(color, colorBrighter));
This basically calculates the distance in a 3dimensional "color room", which is (afaik) a common way to determine the difference between two colors.
a way to determain if a color is a brighter or darker variation of a given color
If you imagine the colors as vectors, one vector has to be the multiple of another:
color1 * k = color2
So we just have to check that equation:
const differOnlyInBrightness = (one, two) => (
k => one.g * k === two.g && one.b * k === two.b
)(two.r / one.r);
I'm trying to create a tool to help me with choosing colours for web applications. Basically, I want to be able to specify a final colour and some overlay parameters and determine what the underlying colour needs to be (or if it's possible to achieve with the specified parameters).
All overlays will be translucent black.
An example of how the system would work:
I enter the following variables:
finalColourRed: 128 [0-255]
finalColourGreen: 118 [0-255]
finalColourBlue: 107 [0-255]
overlayOpacity: 0.21 [0-1]
The system returns:
rgb(183, 169, 154)
I should note that I don't need help with writing the actual code for this, I just don't know the mathematical formula to use and my Google-fu is weak today. The closest I could find was this excellent answer (https://stackoverflow.com/a/12228643/4027341) but it's missing an iteration for "knowing final desired colour, knowing overlay colour, knowing overlay opacity"
You can do it one channel at a time; the formula works like that:
The final color for a channel would be computed as:
finalColor = originalColor * (1.0 - opacity) + overlayColor * opacity;
In your case, you know the final color, the overlay color and the opacity; doing some algebra, we arrive at the conclusion that:
originalColor = (finalColor - overlayColor * opacity) / (1.0 - opacity);
Just be aware of the edge case where opacity = 1.0; you simply can't compute the original color in this case; more specifically, if opacity = 1.0, any color will do the job.
Also, the formula may end up producing negative values or values greater than 255; those values mean that you can't quite obtain a color that will give the results you want.
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 :)
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":
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
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 :)
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 :)
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.
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.
The currently accepted answer links to this repo which has an expired demo page and uses verbose, archaic code.
So I wrote a vanilla JavaScript color mixer based on the same code:
Github Repository
console.log(mix_hexes('#3890b9', '#f6ff00')); // #8cc46f
function hex2dec(hex) {
return hex.replace('#', '').match(/.{2}/g).map(n => parseInt(n, 16));
}
function rgb2hex(r, g, b) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
r = Math.min(r, 255);
g = Math.min(g, 255);
b = Math.min(b, 255);
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
}
function rgb2cmyk(r, g, b) {
let c = 1 - (r / 255);
let m = 1 - (g / 255);
let y = 1 - (b / 255);
let k = Math.min(c, m, y);
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
return [c, m, y, k];
}
function cmyk2rgb(c, m, y, k) {
let r = c * (1 - k) + k;
let g = m * (1 - k) + k;
let b = y * (1 - k) + k;
r = (1 - r) * 255 + .5;
g = (1 - g) * 255 + .5;
b = (1 - b) * 255 + .5;
return [r, g, b];
}
function mix_cmyks(...cmyks) {
let c = cmyks.map(cmyk => cmyk[0]).reduce((a, b) => a + b, 0) / cmyks.length;
let m = cmyks.map(cmyk => cmyk[1]).reduce((a, b) => a + b, 0) / cmyks.length;
let y = cmyks.map(cmyk => cmyk[2]).reduce((a, b) => a + b, 0) / cmyks.length;
let k = cmyks.map(cmyk => cmyk[3]).reduce((a, b) => a + b, 0) / cmyks.length;
return [c, m, y, k];
}
function mix_hexes(...hexes) {
let rgbs = hexes.map(hex => hex2dec(hex));
let cmyks = rgbs.map(rgb => rgb2cmyk(...rgb));
let mixture_cmyk = mix_cmyks(...cmyks);
let mixture_rgb = cmyk2rgb(...mixture_cmyk);
let mixture_hex = rgb2hex(...mixture_rgb);
return mixture_hex;
}
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).
Now that you have your two colors in LAB (or L*a*b*) format, you can average them together.
L(result) = L(first color) + L(second color) / 2
A(result) = A(first color) + A(second color) / 2
B(result) = B(first color) + B(second color) / 2
You already knew this, right? because this is what you were doing with your original RGB colors to average them.
This project really helped me:
https://github.com/ProfJski/ArtColors
I converted its code to Objective-C and verified it works as described.
See the section on "Principle 5" quoted below:
ArtColors should provide a simple function call that subtractively mixes two colors in a realistic way with a minimum of code, taking only two RGB inputs and a blending ratio, like this:
Return Color=SubtractiveMix(Color a, Color b, percentage)
ArtColors uses an algorithm which (I think) gives pretty good results with a fraction of the code of the other methods, and no need for calculating or storing reflectance data or computationally complex formulas. The goal is 80% realism with only 20% of the code.
The basic approach was inspired by considering how paints actually mix. Examine this close-up of paints mixing:
If you look carefully, you can see that in some areas, the two paints are completely blended, and the result is subtractive: yellow and blue are making a much darker green. Red and blue are making a very dark purple. Yet in other areas, where the blending is not so thorough, fine lines of yellow and blue exist side-by-side. These paints are reflecting yellow and blue light. At a distance, these colors are additively blended by the eye when the distict swirls are too small to be seen.
Consider further that mixing paints is a mixture in the Chemistry sense: no chemical change happens. The red and blue molecules are still there in the thorough blend, doing exactly what they were doing when separate: reflecting red and blue light. There's just a lot of subsurface physical effects going on as light bounces around in the medium. Incident light is absorbed and reflected by one molecule, and then by another, and eventually the result reflects out to the eye.
How does this help solve our problem?
Strictly subtractive approaches start with White, and then subtract the RGB values of Color A and Color B from White, and return what is left. This approach is often too dark. Why? Some of each pigment is still reflecting its distinctive color on a tiny scale. If we take an approach that is part additive, part subtractive, we get a more realistic result!
Moreover, if Color A = Color B, our function should return that same color. Mixing the same color with the same color should equal the same color! Using a strictly subtractive algorithm, the result is a darker version of the original hue (because the input color values are subtracted from White twice). The closer the two input colors, the less change should be seen in the blend.
The ArtColor code for subtractive mixing is:
Color ColorMixSub(Color a, Color b, float blend) {
Color out;
Color c,d,f;
c=ColorInv(a);
d=ColorInv(b);
f.r=max(0,255-c.r-d.r);
f.g=max(0,255-c.g-d.g);
f.b=max(0,255-c.b-d.b);
float cd=ColorDistance(a,b);
cd=4.0*blend*(1.0-blend)*cd;
out=ColorMixLin(ColorMixLin(a,b,blend),f,cd);
out.a=255;
return out;
}
Explanation of Code:
Color a and Color b are the input colors. blend specifies how much of each color to blend, from 0 to 1.0, like a linear interpolation (LERP). 0.0 = All color A, 1.0 = All color B. 0.5 = 50%-50% mix of A and B.
First we find the RGB inverses of Color a and b, and assign them to new colors c and d.
c=ColorInv(a);
d=ColorInv(b);
Then we subtract both c and d from pure RGB White, clamping the result to zero, and assign the result to color f.
f.r=max(0,255-c.r-d.r);
f.g=max(0,255-c.g-d.g);
f.b=max(0,255-c.b-d.b);
So far, f is the purely subtractive result, which suffers from the problems mentioned above.
Next, we calculate the "Color Distance" between Color a and Color b, which is just the vector distance between them in RGB space, normalized between 0.0 (identical colors) and 1.0 (completely opposite, like white and black).
float cd=ColorDistance(a,b);
This value will help solve the problem that mixing two similar hues should not change the result very much. The color distance factor cd is then tranformed by a quadratic transfer function, which regulates how much subtractive and additive mixing we do:
cd=4.0*blend*(1.0-blend)*cd;
The endpoints ensure that blend percentages near 0% or 100% look very close to the original input colors. The quadratic curve gives a good color gamut for the mix that comes next. The peak of the curve is determined by color distance. The output of this function determines the amount of additive vs. subtractive blending in our result. More distant colors will blend with a more subtractive dynamic (fully subtractive at y=1.0). Similar colors blend with a more additive dynamic (a flatter curve) that still has a subtractive factor. The maximum of the quadratic transfer function is the normalized color distance, so colors diametrically opposed in the color space will blend fully subtractively.
The last line does all the work:
out=ColorMixLin(ColorMixLin(a,b,blend),f,cd);`
First, we additively mix Color A and Color B in the specified blend ratio, which is accomplished by ColorMixLin(a,b,blend). This represents the additive blending effect of those fine swirls of color in the image above and subsurface interaction. Absence of this factor may be where a strictly subtractive approach yields odd results. This additive result is then blended with our purely subtractive result color f, according to the transfer function mentioned above, which is based on the color distance between Color a and Color b.
Voila! A pretty good result occurs for a wide range of input colors.
Here's a good article I wrote on color mixing in the CIE-LCh color-space, which produces a mixture that preserves hue, saturation, and luminance in a way that consistent with your eye's perception.
Improved Color Blending
What about converting RGB to CMYK using this and then:
// CMYK colors
colorA = [2, 90, 94, 0];
colorB = [4, 0, 80, 0];
colorMixC = (colorA[0] + colorB[0]) / 2;
colorMixM = (colorA[1] + colorB[1]) / 2;
colorMixY = (colorA[2] + colorB[2]) / 2;
colorMixK = (colorA[3] + colorB[3]) / 2;
And finaly convert CMYK to RGB using this
Create the element you want to paint:
<DIV ID="SWATCH" STYLE="HEIGHT:50PX;WIDTH:50PX;"></DIV>
Place the rgb colours you want to combine into an array (as many as you like):
var colourArray=['#012345','#6789AB','#CDEFED','#CBA987','#654321'];
Next convert any letters to numbers and trap them in sequence:
var tempString=[],convertedColourArray=[];
for(i=0;i<colourArray.length;i++){
for(x=1;x<=6;x++){
var oldPigment=colourArray[i].charAt(x);
if(oldPigment<=9)tempString.push(oldPigment);
if(oldPigment=='A')tempString.push(10);
if(oldPigment=='B')tempString.push(11);
if(oldPigment=='C')tempString.push(12);
if(oldPigment=='D')tempString.push(13);
if(oldPigment=='E')tempString.push(14);
if(oldPigment=='F')tempString.push(15);}
convertedColourArray.push(tempString);
tempString=[];}
Then add each of the index position numbers together:
var colourTotal=0,newColour='#';
for(i=0;i<=5;i++){
for(x=0;x<convertedColourArray.length;x++)colourTotal+=parseFloat(convertedColourArray[x][i]);
Finally take the new number, convert it into the matching character and add it to the newColour variable:
var newPigment=(Math.floor(colourTotal/colourArray.length));
if(newPigment<=9)newColour+=newPigment;
if(newPigment==10)newColour+='A';
if(newPigment==11)newColour+='B';
if(newPigment==12)newColour+='C';
if(newPigment==13)newColour+='D';
if(newPigment==14)newColour+='E';
if(newPigment==15)newColour+='F';
colourTotal=0;}
Now you can paint whatever you want with the new colour:
document.getElementById('SWATCH').style.backgroundColor=newColour;
I hope this helps and feel free to throw eggs at it :)
You need to use CMY or RGB color model.
Why Blue + Yellow cannot be Gray?
Blue + Yellow = (Cyan + Magenta) + Yellow => Gray. Why not?
Look at this.
So you can use RGB (CMY) to mix colors.
const getHexChannel = (hex, index) => {
if (hex.length <= 5) {
const channel = hex.substr(1 + index, 1);
return `${channel}${channel}`;
}
return hex.substr(1 + index * 2, 2);
};
function hexToRGB(hex) {
if (typeof hex === 'string' && hex[0] === '#') {
return [0, 1, 2].map(i => parseInt(getHexChannel(hex, i), 16));
}
return hex;
}
function channelMixer(channelA, channelB, amount) {
const a = channelA * (1 - amount);
const b = channelB * amount;
return parseInt(a + b, 10);
}
export function blendColors(colorA, colorB, amount = 0.5) {
const rgbA = hexToRGB(colorA);
const rgbB = hexToRGB(colorB);
return [0, 1, 2].map(i => channelMixer(rgbA[i], rgbB[i], amount));
}
export const lighten = (color, amount) => blendColors(color, '#fff', amount);
export const darken = (color, amount) => blendColors(color, '#000', amount);
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 :)
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":
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
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 :)
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 :)
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.
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.
The currently accepted answer links to this repo which has an expired demo page and uses verbose, archaic code.
So I wrote a vanilla JavaScript color mixer based on the same code:
Github Repository
console.log(mix_hexes('#3890b9', '#f6ff00')); // #8cc46f
function hex2dec(hex) {
return hex.replace('#', '').match(/.{2}/g).map(n => parseInt(n, 16));
}
function rgb2hex(r, g, b) {
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
r = Math.min(r, 255);
g = Math.min(g, 255);
b = Math.min(b, 255);
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
}
function rgb2cmyk(r, g, b) {
let c = 1 - (r / 255);
let m = 1 - (g / 255);
let y = 1 - (b / 255);
let k = Math.min(c, m, y);
c = (c - k) / (1 - k);
m = (m - k) / (1 - k);
y = (y - k) / (1 - k);
return [c, m, y, k];
}
function cmyk2rgb(c, m, y, k) {
let r = c * (1 - k) + k;
let g = m * (1 - k) + k;
let b = y * (1 - k) + k;
r = (1 - r) * 255 + .5;
g = (1 - g) * 255 + .5;
b = (1 - b) * 255 + .5;
return [r, g, b];
}
function mix_cmyks(...cmyks) {
let c = cmyks.map(cmyk => cmyk[0]).reduce((a, b) => a + b, 0) / cmyks.length;
let m = cmyks.map(cmyk => cmyk[1]).reduce((a, b) => a + b, 0) / cmyks.length;
let y = cmyks.map(cmyk => cmyk[2]).reduce((a, b) => a + b, 0) / cmyks.length;
let k = cmyks.map(cmyk => cmyk[3]).reduce((a, b) => a + b, 0) / cmyks.length;
return [c, m, y, k];
}
function mix_hexes(...hexes) {
let rgbs = hexes.map(hex => hex2dec(hex));
let cmyks = rgbs.map(rgb => rgb2cmyk(...rgb));
let mixture_cmyk = mix_cmyks(...cmyks);
let mixture_rgb = cmyk2rgb(...mixture_cmyk);
let mixture_hex = rgb2hex(...mixture_rgb);
return mixture_hex;
}
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).
Now that you have your two colors in LAB (or L*a*b*) format, you can average them together.
L(result) = L(first color) + L(second color) / 2
A(result) = A(first color) + A(second color) / 2
B(result) = B(first color) + B(second color) / 2
You already knew this, right? because this is what you were doing with your original RGB colors to average them.
This project really helped me:
https://github.com/ProfJski/ArtColors
I converted its code to Objective-C and verified it works as described.
See the section on "Principle 5" quoted below:
ArtColors should provide a simple function call that subtractively mixes two colors in a realistic way with a minimum of code, taking only two RGB inputs and a blending ratio, like this:
Return Color=SubtractiveMix(Color a, Color b, percentage)
ArtColors uses an algorithm which (I think) gives pretty good results with a fraction of the code of the other methods, and no need for calculating or storing reflectance data or computationally complex formulas. The goal is 80% realism with only 20% of the code.
The basic approach was inspired by considering how paints actually mix. Examine this close-up of paints mixing:
If you look carefully, you can see that in some areas, the two paints are completely blended, and the result is subtractive: yellow and blue are making a much darker green. Red and blue are making a very dark purple. Yet in other areas, where the blending is not so thorough, fine lines of yellow and blue exist side-by-side. These paints are reflecting yellow and blue light. At a distance, these colors are additively blended by the eye when the distict swirls are too small to be seen.
Consider further that mixing paints is a mixture in the Chemistry sense: no chemical change happens. The red and blue molecules are still there in the thorough blend, doing exactly what they were doing when separate: reflecting red and blue light. There's just a lot of subsurface physical effects going on as light bounces around in the medium. Incident light is absorbed and reflected by one molecule, and then by another, and eventually the result reflects out to the eye.
How does this help solve our problem?
Strictly subtractive approaches start with White, and then subtract the RGB values of Color A and Color B from White, and return what is left. This approach is often too dark. Why? Some of each pigment is still reflecting its distinctive color on a tiny scale. If we take an approach that is part additive, part subtractive, we get a more realistic result!
Moreover, if Color A = Color B, our function should return that same color. Mixing the same color with the same color should equal the same color! Using a strictly subtractive algorithm, the result is a darker version of the original hue (because the input color values are subtracted from White twice). The closer the two input colors, the less change should be seen in the blend.
The ArtColor code for subtractive mixing is:
Color ColorMixSub(Color a, Color b, float blend) {
Color out;
Color c,d,f;
c=ColorInv(a);
d=ColorInv(b);
f.r=max(0,255-c.r-d.r);
f.g=max(0,255-c.g-d.g);
f.b=max(0,255-c.b-d.b);
float cd=ColorDistance(a,b);
cd=4.0*blend*(1.0-blend)*cd;
out=ColorMixLin(ColorMixLin(a,b,blend),f,cd);
out.a=255;
return out;
}
Explanation of Code:
Color a and Color b are the input colors. blend specifies how much of each color to blend, from 0 to 1.0, like a linear interpolation (LERP). 0.0 = All color A, 1.0 = All color B. 0.5 = 50%-50% mix of A and B.
First we find the RGB inverses of Color a and b, and assign them to new colors c and d.
c=ColorInv(a);
d=ColorInv(b);
Then we subtract both c and d from pure RGB White, clamping the result to zero, and assign the result to color f.
f.r=max(0,255-c.r-d.r);
f.g=max(0,255-c.g-d.g);
f.b=max(0,255-c.b-d.b);
So far, f is the purely subtractive result, which suffers from the problems mentioned above.
Next, we calculate the "Color Distance" between Color a and Color b, which is just the vector distance between them in RGB space, normalized between 0.0 (identical colors) and 1.0 (completely opposite, like white and black).
float cd=ColorDistance(a,b);
This value will help solve the problem that mixing two similar hues should not change the result very much. The color distance factor cd is then tranformed by a quadratic transfer function, which regulates how much subtractive and additive mixing we do:
cd=4.0*blend*(1.0-blend)*cd;
The endpoints ensure that blend percentages near 0% or 100% look very close to the original input colors. The quadratic curve gives a good color gamut for the mix that comes next. The peak of the curve is determined by color distance. The output of this function determines the amount of additive vs. subtractive blending in our result. More distant colors will blend with a more subtractive dynamic (fully subtractive at y=1.0). Similar colors blend with a more additive dynamic (a flatter curve) that still has a subtractive factor. The maximum of the quadratic transfer function is the normalized color distance, so colors diametrically opposed in the color space will blend fully subtractively.
The last line does all the work:
out=ColorMixLin(ColorMixLin(a,b,blend),f,cd);`
First, we additively mix Color A and Color B in the specified blend ratio, which is accomplished by ColorMixLin(a,b,blend). This represents the additive blending effect of those fine swirls of color in the image above and subsurface interaction. Absence of this factor may be where a strictly subtractive approach yields odd results. This additive result is then blended with our purely subtractive result color f, according to the transfer function mentioned above, which is based on the color distance between Color a and Color b.
Voila! A pretty good result occurs for a wide range of input colors.
Here's a good article I wrote on color mixing in the CIE-LCh color-space, which produces a mixture that preserves hue, saturation, and luminance in a way that consistent with your eye's perception.
Improved Color Blending
What about converting RGB to CMYK using this and then:
// CMYK colors
colorA = [2, 90, 94, 0];
colorB = [4, 0, 80, 0];
colorMixC = (colorA[0] + colorB[0]) / 2;
colorMixM = (colorA[1] + colorB[1]) / 2;
colorMixY = (colorA[2] + colorB[2]) / 2;
colorMixK = (colorA[3] + colorB[3]) / 2;
And finaly convert CMYK to RGB using this
Create the element you want to paint:
<DIV ID="SWATCH" STYLE="HEIGHT:50PX;WIDTH:50PX;"></DIV>
Place the rgb colours you want to combine into an array (as many as you like):
var colourArray=['#012345','#6789AB','#CDEFED','#CBA987','#654321'];
Next convert any letters to numbers and trap them in sequence:
var tempString=[],convertedColourArray=[];
for(i=0;i<colourArray.length;i++){
for(x=1;x<=6;x++){
var oldPigment=colourArray[i].charAt(x);
if(oldPigment<=9)tempString.push(oldPigment);
if(oldPigment=='A')tempString.push(10);
if(oldPigment=='B')tempString.push(11);
if(oldPigment=='C')tempString.push(12);
if(oldPigment=='D')tempString.push(13);
if(oldPigment=='E')tempString.push(14);
if(oldPigment=='F')tempString.push(15);}
convertedColourArray.push(tempString);
tempString=[];}
Then add each of the index position numbers together:
var colourTotal=0,newColour='#';
for(i=0;i<=5;i++){
for(x=0;x<convertedColourArray.length;x++)colourTotal+=parseFloat(convertedColourArray[x][i]);
Finally take the new number, convert it into the matching character and add it to the newColour variable:
var newPigment=(Math.floor(colourTotal/colourArray.length));
if(newPigment<=9)newColour+=newPigment;
if(newPigment==10)newColour+='A';
if(newPigment==11)newColour+='B';
if(newPigment==12)newColour+='C';
if(newPigment==13)newColour+='D';
if(newPigment==14)newColour+='E';
if(newPigment==15)newColour+='F';
colourTotal=0;}
Now you can paint whatever you want with the new colour:
document.getElementById('SWATCH').style.backgroundColor=newColour;
I hope this helps and feel free to throw eggs at it :)
You need to use CMY or RGB color model.
Why Blue + Yellow cannot be Gray?
Blue + Yellow = (Cyan + Magenta) + Yellow => Gray. Why not?
Look at this.
So you can use RGB (CMY) to mix colors.
const getHexChannel = (hex, index) => {
if (hex.length <= 5) {
const channel = hex.substr(1 + index, 1);
return `${channel}${channel}`;
}
return hex.substr(1 + index * 2, 2);
};
function hexToRGB(hex) {
if (typeof hex === 'string' && hex[0] === '#') {
return [0, 1, 2].map(i => parseInt(getHexChannel(hex, i), 16));
}
return hex;
}
function channelMixer(channelA, channelB, amount) {
const a = channelA * (1 - amount);
const b = channelB * amount;
return parseInt(a + b, 10);
}
export function blendColors(colorA, colorB, amount = 0.5) {
const rgbA = hexToRGB(colorA);
const rgbB = hexToRGB(colorB);
return [0, 1, 2].map(i => channelMixer(rgbA[i], rgbB[i], amount));
}
export const lighten = (color, amount) => blendColors(color, '#fff', amount);
export const darken = (color, amount) => blendColors(color, '#000', amount);
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?
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.
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:
additive mixing, which looks like that:
What you need is subtractive color mixing, whose output should be in RGB notation.
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.
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)
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. :)