在javascript中“自然地”混合两种颜色

时间:2013-02-11 19:04:31

标签: javascript math colors color-scheme

问题:我想在javascript中混合两种颜色,并获得结果颜色。 在SO上有很多类似的问题,但我没有发现任何实际上正常工作的问题。我知道混合两种不同颜色的颜料(颜料)和灯会产生非常不同的结果(http://en.wikipedia.org/wiki/Color_mixing)。

以下是我已经看过的问题和建议的解决方案,并尝试实施:

1: Mixing two RGB color vectors to get resultant
所以,混合RGB的颜色。我实现了它,在某些情况下它可以在某些情况下起作用。

工作示例:redyellow混合 - > orange。太好了!
http://jsbin.com/afomim/1/edit

不工作示例:blueyellow混合 - > gray。不太好! :) http://jsbin.com/afomim/5/edit
我知道在RGB混合blueyellow混合将永远不会green,我理解为什么。

我们在这里找不到答案,让我们继续吧。

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

让我们尝试使用本讨论中建议的CMYK值。将cyanyellow混合可得green
http://jsbin.com/igaveg/1/edit,但blueyellow混合会产生black
http://jsbin.com/igaveg/2/edit - >不工作了!

3: How to mix colors "naturally" with C#?
一个非常相似的问题。最受欢迎的答案建议将颜色转换为LAB,这个解决方案似乎很有希望 所以我将我的颜色转换为LAB。转换算法是正确的,我测试了它!

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

现在我在LAB中有两种颜色,但是如何混合呢?

注意我知道我可能找不到将blueyellow混合在一起的算法,并且会给出完美的green,但我希望我能生成类似于绿色的东西:)

11 个答案:

答案 0 :(得分:29)

这个问题专门用了3-4天。这是一个非常复杂的问题。

如果您想“自然地”混合两种颜色,可以执行以下操作:

  1. CMYK混音:它不是一个完美的解决方案,但如果您现在需要一个解决方案,并且您不想花费数月时间来学习该主题,试验和编码,您可以查看:{{ 3}}

  2. 实施Kubelka-Munk理论。我花了很多时间阅读它,并试图理解它。如果你想要一个专业的解决方案,这应该是要走的路,但它需要6个参数(如反射,吸收等),你想要混合的每种颜色。有R,G,B是不够的。实现理论并不难,但是获取每种颜色所需的参数似乎是缺失的部分。如果您弄明白该怎么做,请告诉我:)

  3. 实验:您可以做一些ipad应用程序的开发人员:https://github.com/AndreasSoiron/Color_mixer已经完成的事情。他们手动选择了100对流行的颜色和眼球测试它们应该如何混合。详细了解Paper

  4. 我个人将暂时实施CMYK混音,也许以后,如果我有时间,我会尝试制作像Fiftythree这样的人。会看到:))

答案 1 :(得分:12)

RYB颜色模型可能是混色计算的合适选择。 根据{{​​3}},它主要用于艺术和设计教育,尤其是绘画。

要混合2种颜色,可将两种颜色从RGB转换为RYB,通过添加颜色来混合颜色 颜色分量,并将结果颜色从RYB转换回RGB。

我使用Wikipedia尝试了这个,结果是

    与#FFFF00(黄色)混合的
  • 0000FF(蓝色)表示#008000(深绿色),

  • 与#FFFF00(黄色)混合的FF0000(红色)提供#FFA000(橙色)。

    < / LI>

因此,此方法可以准确地生成您期望的结果。

不幸的是,我无法找到带有“准备使用”公式的参考,无法将RGB转换为RYB并返回RGB。

论文 Online Color Mixing Tool 在“2实现直观的颜色混合”一节中描述了RYB颜色模型的一般概念。

根据该论文,从RYB到RGB的转换是通过 Paint Inspired Color Mixing and Compositing for Visualisation - Gossett and Chen

困难的部分是从RGB到RYB的转换,因为它需要反转 三线插值。 见Trilinear interpolation 了解更多信息。

即使这个答案没有提供完整的计算公式,我希望它能提供一些如何继续的想法。

答案 2 :(得分:4)

使用CIELAB颜色,LAB颜色空间中的每种颜色都有三个坐标。 (顺便说一句,这方面的出色工作)。对您来说最好和最容易实现的是找到连接LAB空间中两点的假想线段的三维中点。您可以通过平均两种颜色的每个组件来轻松完成此操作:平均L,平均a和平均b。然后通过反转变换将此颜色转换回RGB空间(确保您的照明空间保持不变)。

您的新颜色可能位于RGB色彩空间之外。在这种情况下,您可以决定剪切到最近的可见颜色。 (蓝调特别容易受此影响)。

答案 3 :(得分:3)

既然你有两种颜色的LAB(或L * a * b *)格式,你可以将它们放在一起。

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

你已经知道了,对吗?因为这是你用原始RGB颜色做的平均值。

答案 4 :(得分:2)

当前接受的答案链接到this repo,该页面的演示页面已过期,并使用冗长的原始代码。

所以我基于相同的代码编写了一个普通的JavaScript颜色混合器:

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

答案 5 :(得分:2)

这个项目真的帮助了我: https://github.com/ProfJski/ArtColors

我将其代码转换为 Objective-C 并验证了它的工作原理。

请参阅下面引用的关于 "Principle 5" 的部分:

<块引用>

ArtColors 应该提供一个简单的函数调用,以最少的代码以逼真的方式将两种颜色相减混合,仅采用两个 RGB 输入和一个混合比率,如下所示:

Return Color=SubtractiveMix(Color a, Color b, percentage)

ArtColors 使用的算法(我认为)使用其他方法的一小部分代码即可提供非常好的结果,并且无需计算或存储反射率数据或计算复杂的公式。目标是仅使用 20% 的代码实现 80% 的真实性。

基本方法的灵感来自考虑颜料的实际混合方式。检查这张油漆混合的特写:

Paints Mixing

如果仔细观察,您会发现在某些区域,两种颜料完全混合,结果是相减:黄色和蓝色使绿色更深。红色和蓝色使非常深紫色。然而,在其他混合不那么彻底的区域,黄色和蓝色的细纹并排存在。这些颜料反射黄色和蓝色光。在远处,当远处的漩涡太小而无法看到时,这些颜色会相加被眼睛混合。

进一步考虑,混合油漆在化学意义上是一种混合物:不会发生化学变化。红色和蓝色分子仍然存在于彻底混合中,它们在分离时完全一样:反射红光和蓝光。当光线在介质中反射时,会产生很多次表面物理效果。入射光被一个分子吸收和反射,然后被另一个分子吸收和反射,最终结果反射到眼睛。

这如何帮助解决我们的问题?

严格减法从白色开始,然后从白色中减去颜色 A 和颜色 B 的 RGB 值,并返回剩余的值。 这种方法通常太黑了。为什么?每种颜料中的一些仍然在很小的范围内反映其独特的颜色。如果我们采用部分相加,部分相减的方法,我们会得到更真实的结果!

此外,如果颜色 A = 颜色 B,我们的函数应该返回相同的颜色。同色混同色应该等于同色!使用严格的减法算法,结果是原始色调的较暗版本(因为输入颜色值从白色中减去两次)。两种输入颜色越接近,在混合中应该看到较少变化。

减色混合的 ArtColor 代码是:

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

代码说明: Color aColor b 是输入颜色。 blend 指定每种颜色的混合量,从 0 到 1.0,就像线性插值 (LERP)。 0.0 = 所有颜色 A,1.0 = 所有颜色 B。0.5 = A 和 B 的 50%-50% 混合。

首先我们找到颜色 a 和 b 的 RGB 反色,并将它们分配给新的颜色 c 和 d。

    c=ColorInv(a);
    d=ColorInv(b);

然后我们从纯 RGB 白色中减去 c 和 d,将结果钳位为零,并将结果分配给颜色 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);

到目前为止,f 是纯减法结果,存在上述问题。

接下来,我们计算颜色 a 和颜色 b 之间的“颜色距离”,也就是它们在 RGB 空间中的向量距离,归一化在 0.0(相同颜色)和 1.0(完全相反,如白色和黑色)之间。

float cd=ColorDistance(a,b);

该值将有助于解决混合两种相似色调不会对结果产生太大影响的问题。然后颜色距离因子 cd 由二次传递函数转换,该函数调节我们进行多少减法和加法混合:

cd=4.0*blend*(1.0-blend)*cd;

ArtColor Blend

端点确保接近 0% 或 100% 的混合百分比看起来非常接近原始输入颜色。二次曲线为接下来的混合提供了良好的色域。曲线的峰值由颜色距离决定。这个函数的输出决定了我们结果中加法与减法混合的数量。更远的颜色将与更减色的动态混合(在 y=1.0 处完全减色)。相似的颜色与更多的附加动态(更平坦的曲线)混合,但仍然具有减法因子。二次传递函数的最大值是归一化的颜色距离,因此颜色空间中完全相反的颜色将完全减色混合。

最后一行完成所有工作:

out=ColorMixLin(ColorMixLin(a,b,blend),f,cd);`

首先,我们以指定的 blend 比率相加混合颜色 A 和颜色 B,这由 ColorMixLin(a,b,blend) 完成。这代表了上图中那些精细的颜色漩涡和地下相互作用的叠加混合效果。如果没有这个因素,严格的减法方法可能会产生奇怪的结果。然后,根据上述基于 color f 和 {{1} 之间的颜色距离的传递函数,将该 加法 结果与我们的纯减法结果 Color a 混合}.

瞧!对于很宽的输入颜色范围,都会出现相当不错的结果。

答案 6 :(得分:1)

这是一篇关于CIE-LCh色彩空间中色彩混合的好文章,它产生的混合物可以保持色调,饱和度和亮度,与眼睛的感知一致。

Improved Color Blending

答案 7 :(得分:0)

您需要使用 CMY RGB 颜色模型。

为什么Blue + Yellow不能Gray

Blue + Yellow =(Cyan + Magenta)+ Yellow =&gt; Gray。为什么不呢?

Look at this.

因此您可以使用RGB(CMY)来混合颜色。

答案 8 :(得分:0)

如何使用this将RGB转换为CMYK然后:

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

最后使用this

将CMYK转换为RGB

答案 9 :(得分:0)

创建要绘制的元素:

<DIV ID="SWATCH" STYLE="HEIGHT:50PX;WIDTH:50PX;"></DIV>

将要组合的rgb颜色放置到数组中(任意数量):

var colourArray=['#012345','#6789AB','#CDEFED','#CBA987','#654321'];

下一步,将任何字母转换为数字并按顺序捕获它们:

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=[];}

然后将每个索引位置编号加在一起:

var colourTotal=0,newColour='#';
for(i=0;i<=5;i++){
    for(x=0;x<convertedColourArray.length;x++)colourTotal+=parseFloat(convertedColourArray[x][i]);

最后获取新数字,将其转换为匹配的字符并将其添加到newColour变量中:

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

现在,您可以使用新颜色绘制任何内容:

    document.getElementById('SWATCH').style.backgroundColor=newColour;

我希望这会有所帮助,并随时向它扔鸡蛋:)

答案 10 :(得分:-1)

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, ammount) {
  const a = channelA * (1 - ammount);
  const b = channelB * ammount;
  return parseInt(a + b, 10);
}

export function blendColors(colorA, colorB, ammount = 0.5) {
  const rgbA = hexToRGB(colorA);
  const rgbB = hexToRGB(colorB);
  return [0, 1, 2].map(i => channelMixer(rgbA[i], rgbB[i], ammount));
}

export const lighten = (color, ammount) => blendColors(color, '#fff', ammount);
export const darken = (color, ammount) => blendColors(color, '#000', ammount);