【问题标题】:Mixing two colors "naturally" in javascript在javascript中“自然地”混合两种颜色
【发布时间】:2013-01-26 23:06:44
【问题描述】:

问题:我想在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 的算法,但我希望我能生成类似于绿色的东西: )

【问题讨论】:

  • 我只是好奇:为什么蓝色和黄色要混合成绿色(或类似的东西)?
  • 蓝色并不完全是#0000ff,或者黄色#ffff00,但在现实世界中,如果你将一些蓝色和一些黄色混合在一起,大多数时候你会得到一些绿色。这个应用程序:fiftythree.com/paper(由苹果公司授予)有一些很棒的颜色混合功能,他们说他们已经研究了一年多:) 如果你查看他们的页面,你会发现他们对蓝色和黄色应用了相同的理论.
  • 除了死灵术,为什么不用 HSL/HSV 而不是 RGB/CMYK 来混色呢?我的意思是,如果可见色谱只不过是一个比例(以纳米为单位),那么合并两种色调只是取平均值,与饱和度和光/值相同。我错了吗??

标签: javascript math colors color-scheme


【解决方案1】:

现在您已经有了 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 颜色进行平均处理的方法。

【讨论】:

  • 正在尝试实现它!谢谢!
  • 这是一个旧线程,但我从另一个 SO 问题中找到了它。一个问题:不是 LAB 和加色空间,而油漆(和 CMYK)是减色空间吗?我不希望黄色 + 蓝色在任何加色模型中产生绿色。
【解决方案2】:

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

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

【讨论】:

  • 现在试一试,结果会回来的!谢谢!
  • Jsbin 已关闭,所以我在 codepen 上实现了它:codepen.io/anon/pen/pscJr,但没有给出预期的结果:(还有其他想法吗?:)
【解决方案3】:

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

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

我用Online Color Mixing Tool试过这个,结果是

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

  • FF0000 (red) 与 #FFFF00 (yellow) 混合得到 #FFA000 (orange)。

所以这个方法产生的结果完全符合你的预期。

很遗憾,我找不到“即用型”公式的参考,可以将 RGB 转换为 RYB 再转换回 RGB。

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

根据那篇论文,从 RYB 到 RGB 的转换是通过 Trilinear interpolation.

困难的部分是从RGB到RYB的转换,因为它需要反转 三线性插值。 见Conversion between RGB and RYB color spaces 了解更多信息。

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

【讨论】:

  • 感谢您的详细解答。这是非常有帮助的。我尝试了很多东西,并在 HSL 和 LAB 空间中取得了一些成功,但我仍在寻找更好的解决方案。明天我将检查 RYB 模型,并将返回我的结果。
  • 我仍处于学习和实验阶段,但我会提供结果。 :)
  • 我现在可以将 ryb 转换为 rgb,并且我正在将 rgb 转换为 ryb。在您的第一个示例中:#0000FF (blue) mixed with #FFFF00 (yellow) gives #008000 (dark green),您能否解释一下 00800 是如何通过添加 0000FF 和 FFFF00 产生的。因为如果我简单地添加组件,我会得到 FFFFFF,如果我计算它们的平均值,我会得到 7A7A7A。那么,在 RYB 中拥有 2 种颜色之后,我应该如何“混合”它们呢?我希望我们能弄清楚:)
  • @TamasPap:我只用Online Color Mixing Tool 做到了这一点。据我了解:红黄蓝中的蓝色是 (1.0, 0.0, 0.0),红黄蓝中的黄色是 (0.0, 1.0, 0.0)。如果你在 RYB 中添加它,你会得到 (1.0, 1.0, 0.0)。现在您必须将此值转换回 RGB,这应该会产生一些深绿色。 - 在我看来,“在线颜色混合工具”使用的转换与 Gossett 和 Chen 论文中的值略有不同。该工具使用 JavaScript,因此可以对其进行检查。
【解决方案4】:

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

如果你想“自然地”混合两种颜色,你可以这样做:

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

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

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

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

【讨论】:

  • 感谢分享您的结果!
【解决方案5】:

您需要使用 CMYRGB 颜色模型。

为什么Blue + Yellow 不能是Gray

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

Look at this.

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

【讨论】:

    【解决方案6】:

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

    Improved Color Blending

    【讨论】:

    • 请注意 link-only answers 是不鼓励的,所以答案应该是寻找解决方案的终点(而不是另一个参考中途停留,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。
    • 谢谢分享。我会尽快仔细看看!
    • 我认为我确实包含了一个作为概要的答案:在 CIE-LCh 色彩空间中混合颜色。如果链接干涸,谷歌很容易。
    • 您的链接已损坏。
    【解决方案7】:

    如何使用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

    【讨论】:

      【解决方案8】:

      在尝试将 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);
      

      希望这些功能有所帮助。

      【讨论】:

      • 这个真的很有帮助。谢谢!
      【解决方案9】:

      创建要绘制的元素:

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

        【讨论】:

        • 嗨,你能补充一下你在那里做了什么吗?只提供代码没有帮助。
        【解决方案11】:

        当前接受的答案链接到 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;
        }
        

        【讨论】:

          【解决方案12】:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          瞧!对于各种输入颜色,都会产生非常好的结果。

          【讨论】:

            猜你喜欢
            • 2012-10-20
            • 1970-01-01
            • 2010-09-28
            • 2014-05-04
            • 2023-01-31
            • 2019-10-20
            • 2014-03-13
            • 1970-01-01
            相关资源
            最近更新 更多