Subtract colors - javascript

I will try to explain my question with an example:
Let's say we have a background colored in a shade of red (easy to find out - color picker)
There is a semi-transparent element in front. It is colored blue but appears to be purple.
Again the resulting purple shade is easy to find but is there a wayt to find/compute the semi transparent blue shade?
Given: rgb(100,0,0) + x = rgb(100,0,100)
Find: x = rgba(0,0,100,0.5)
My actual case:
Background:rgb(147,150,72), Element rgb(44,100,62)
This is partially solved in this question for the sole case of white backgrounds, I am actually looking for a more general solution. I have tried to adapt the javascript code but I don't really understand how it is done or if a general solution is even possible. I might be able to code it myself is someone understand how it works and tells me. (if solved I will post solution here of course)
Links:
Subtraction of colours - Need to get the transparent value of a colour
Convert RGB to RGBA over white

A general exact solution is not existent.
As stated in the answer you linked to
C = X * a + Y * (a-1)
is the formula to calculate the color channel C out of the colors X and Y, while a is the alpha value of X.
So, when you try to find out the color + alpha channel, you have to solve this equation 3 times (once for each color). Each of those equation does have 2 unknowns.
So without requiring additional conditions (e.g. the one mentioned in your second link) to apply, there is an infinite amount of different solutions (or in the specific case of one byte per color channel, up to 256).
While determining one exact solution is impossible, you can, of course, determine one or more possible solutions, with the following way:
X = C/a - Y + Y/a // Formula from above solved for X
So the possible solutions are
r3 = r2/a - r1 + r1/a
g3 = g2/a - g1 + g1/a
b3 = b2/a - b1 + b1/a
with
rgb(r1, g1, b1) being the background color,
rgb(r2, g2, b2) being the mixed color
and rgba(r3, g3, b3, a) being the color that was painted on the background.
If you now select a so, that none of r3, g3 and b3 are below 0 or above 255, you get a valid solution.

Related

Calculate if a certain color is in the same family of a given color

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);

How do you mix colors in CSS? (and WHAT is yellow?) [duplicate]

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);

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 :)
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);

Why won't my color mixing function work as expected?

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. :)

Suggestions on how to make a color map for a generated terrain map?

I got interested in this question someone posted yesterday about diamond-square algorithms, Node.js / coffeescript performance on a math-intensive algorithm.
I followed through with adapting his code and want to take the next step in generating some color maps for the resulting data grid.
My initial thoughts were to take the deepest point in the ocean to the height of Everest as my height ranges. That puts sea level at about 2076m and max height at around 10924m (Everest is 8848m). Anyway, so I'm generating my grid of data with values that are pretty close to what I want.
I was thinking of making an array of hex color values starting with say a dark shade of blue to light shade for water, then a dark green to white for sea level to mountains. I can setup ranges of height values to correspond to regions of color.
What are the common techniques for doing this kind of thing? I think more specifically, how do you generate a specific hex color between 2 hex values for a given height value?
yes, what you suggest sounds good. for in-between values, typically you use linear interpolation. the following snippet shows how to interpolate 5% of the way between #00 and #ff (and also shows the conversions you need):
> ("00" + Math.floor(parseInt('00',16) + 5 / 100 * parseInt('ff',16)).toString(16)).slice(-2)
"0c"
obviously, you'd wrap that in a function - i'm just cut+pasting from a chrome command line where i checked it.
[edit:] is that clear? in the case above, i'm calculating the value at 5m if you want #00 and 0m and #ff at 100m, where the value is just for one channel (r, g or b). you'd need to repeat that for various steps, and join r g and b together.
here's another example where you're going from #a0 down to #20 over a range of 50m, 10m from #a0. note that we have an extra subtraction because the initial value isn't zero.
> ("00" + Math.floor(parseInt('a0',16) + 10 / 50 * (parseInt('20',16) - parseInt('a0', 16))).toString(16)).slice(-2)
"86"
so, ignoring the conversion, the interpolation is:
start_value + (distance_from_start / total_distance) * (end_value - start_value)
and the conversions are
decimal_int = parseInt(hex_string, 16)
hex_string = decimal_int.toString(16)
ps i would imagine that processing.js has functions like this already written for you, if it's any help (not used it, but it's that kind of library...)

Categories