40

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

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

1: 混合两个 RGB 颜色向量得到结果
所以,混合 RGB 中的颜色。我实现了它,在某些情况下它在某些情况下不起作用。

工作示例:redyellow->混合orange。伟大的!
http://jsbin.com/afomim/1/edit

不工作的例子:blueyellow->混合gray。不太好!:) http://jsbin.com/afomim/5/edit
我知道在 RGB 混合blueyellow永远不会产生green,我明白为什么。

我们不会在这里找到答案,让我们继续前进。

2: 像油漆一样添加颜色(颜色)(蓝色+黄色=绿色等)

让我们尝试使用本讨论中建议的 CMYK 值。cyanyellow给出混合:http green:
//jsbin.com/igaveg/1/edit
blueyellow结果混合black
http://jsbin.com/igaveg/2/edit -> 不工作!

3: 如何用C#“自然地”混合颜色?
一个非常相似的问题。最受欢迎的答案建议将颜色转换为 LAB,这个解决方案似乎很有希望。
所以我将我的颜色转换为 LAB。转换算法是正确的,我测试过!

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

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

注意我知道我可能不会找到一个混合并给出完美的算法blueyellowgreen我希望我能生成类似于绿色的东西:)

4

12 回答 12

46

我花了 3-4 天来回答这个问题。这是一个非常复杂的问题。

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

  1. CMYK 混合:这不是完美的解决方案,但如果您现在需要一个解决方案,并且您不想花费数月时间来学习主题、实验和编码,您可以查看:https ://github.com/ AndreasSoiron/Color_mixer

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

  3. 实验性:您可以做 ipad 应用程序的开发者:Paper所做的事情。他们手动选择了 100 对流行颜色,并用眼球测试了它们应该如何混合。在此处了解更多信息。

我个人将暂时实施 CMYK 混合,也许以后,如果我有时间,我会尝试制作类似于 Fiftythree 的人的东西。等着瞧 :)

于 2013-03-06T10:36:10.187 回答
13

在尝试将 2 种 RGB 颜色混合在一起时,我实际上遇到了同样的问题。这两个功能对我有用:

//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+")";
}

要将红色( [255,0,0] )与蓝色( [0,0,255] )混合均匀,您可以调用

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

这可能会有所帮助,尽管您必须先将每个颜色值转换为数组。如果您使用 Fabric.js 来处理画布元素,这将变得非常容易。打电话

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

然后打电话

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

希望这些功能有所帮助。

于 2015-08-23T20:15:50.100 回答
12

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

要混合两种颜色,需要将两种颜色从 RGB 转换为 RYB,通过添加每个颜色分量来混合颜色,然后将生成的颜色从 RYB 转换回 RGB。

我已经使用Online Color Mixing Tool进行了尝试,结果是

  • 0000FF(蓝色)与#FFFF00(黄色)混合得到#008000(深绿色),

  • FF0000(红色)与#FFFF00(黄色)混合得到#FFA000(橙色)。

因此,此方法会产生您所期望的结果。

不幸的是,我无法找到“即用型”公式的参考来将 RGB 转换为 RYB 并返回 RGB。

论文 Paint Inspired Color Mixing and Compositing for Visualization - Gossett and Chen 在“2 ACHIEVING INTUITIVE COLOR MIXING”一节中描述了 RYB 颜色模型的一般思想。

根据那篇论文,从 RYB 到 RGB 的转换是通过 三线性插值完成的。

困难的部分是从 RGB 到 RYB 的转换,因为它需要反转三线性插值。 有关详细信息,请参阅RGB 和 RYB 颜色空间之间的转换。

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

于 2013-02-12T20:07:39.583 回答
4

对于 CIELAB 颜色,您在 LAB 颜色空间中的两种颜色中的每一种都有三个坐标。(顺便说一句,在这方面做得很好)。对您来说最有效且最容易实现的方法是找到连接 LAB 空间中两点的假想线段的 3D 中点。您可以通过对两种颜色的每个分量进行平均来轻松做到这一点:average L、 averagea和 average b。然后通过反转变换将此颜色转换回 RGB 空间(确保两种方式的照明空间保持相同)。

您的新颜色可能在 RGB 颜色空间之外。在这种情况下,您可以决定剪辑到最接近的可见颜色。(蓝调尤其容易受到这种影响)。

于 2013-02-11T19:20:54.980 回答
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 颜色对它们进行平均的操作。

于 2013-02-11T19:20:10.587 回答
3

当前接受的答案链接到此存储库,该存储库具有过期的演示页面并使用冗长的过时代码。

所以我基于相同的代码写了一个普通的 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;
}
于 2020-06-14T06:55:09.500 回答
3

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

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

参见下面引用的“原则 5”部分:

ArtColors 应该提供一个简单的函数调用,用最少的代码以真实的方式减法混合两种颜色,只需要两个 RGB 输入和一个混合比,如下所示:

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

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

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

油漆混合

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

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

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

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

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

减法混合的 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;

艺术色彩混合

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

最后一行完成所有工作:

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

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

瞧!对于广泛的输入颜色,会产生非常好的结果。

于 2021-05-09T15:13:27.410 回答
1

这是我写的一篇关于 CIE-LCh 颜色空间中颜色混合的好文章,它产生的混合以与您的眼睛感知一致的方式保持色调、饱和度和亮度。

改进的颜色混合

于 2014-02-28T22:43:44.207 回答
1

使用它然后将 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;

最后使用这个将 CMYK 转换为 RGB

于 2015-07-30T09:30:41.460 回答
0

您需要使用CMYRGB颜色模型。

为什么Blue+Yellow不能是Gray

Blue+ Yellow= ( Cyan+ Magenta) + Yellow=> Gray。为什么不?

看这个。

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

于 2013-10-10T08:55:02.413 回答
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;

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

于 2019-12-15T10:18:54.703 回答
-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, 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);
于 2020-02-07T14:34:35.630 回答