【问题标题】:How to calculate soft light blended value of two colors with an alpha input?如何使用 alpha 输入计算两种颜色的柔光混合值?
【发布时间】:2018-03-07 05:25:54
【问题描述】:

我需要编写一个 JavaScript 函数和一个 SASS 混合器来计算具有 alpha 输入的两种颜色的 柔光 混合值。

函数/mixin会带三个参数,如下:

  • 背景/基色值
  • 前景/顶部颜色值
  • 前景色/顶部颜色的 Alpha 值

并将返回混合后的颜色。

理想情况下,函数如下所示:

function soft_light(bgcol, fgcol, alpha) {
    return soft_light_blended_color;
}

这是来自 Wikipedia 的关于柔光混合模式的参考。我的目标是 Photoshop 使用的公式。

我不明白如何计算公式。为什么b 的值在01 之间而不是颜色代码? 伽马校正是如何工作的?我将如何使用颜色值执行公式中使用的乘法、减法等基本运算。

非常感谢您提供解决方案指南。提前致谢。 :-)

【问题讨论】:

    标签: javascript colors sass photoshop color-blending


    【解决方案1】:

    好吧,您正在触及一个被许多人认为简单的领域,但colorimetry(或“用于量化和描述人类颜色感知的科学技术。”)是实际上是一个非常复杂的领域,其整个范围被相对较少的人了解。

    这听起来可能很吓人,但不用担心,我们不需要了解它的全部内容来完成这项任务。

    标准化值不足

    让我们从为什么 0 到 1 开始:这表示 标准化 值,它独立于 bit 分辨率(即 8 位、10 位组件值等。 )。 线性化过程(例如伽玛和色彩空间)也需要它,否则我们最终会得到非常错误的值。由于是浮点数,它还减少了处理时的舍入误差。缺点当然是性能下降和一些内存开销。

    伽马不足

    如果您使用图像源,则需要了解并使用其伽玛值。如果图像存储为 sRGB,您可以assume the value to be 2.2。尽管它不是 100% 准确,但对于此目的来说已经足够了。

    Gamma 是对其他线性颜色值的对数补偿,以匹配 亮度 在屏幕(CRT、LCD 等)上的显示方式,并应用于当您有,比如说,一个灰度渐变,它实际上会被我们的人眼感知为从黑色到白色的均匀渐变。

    但是,所有合成和混合都需要线性值(上述渐变的中间值实际上是 127-128)才能产生正确的结果,因此我们首先需要进行归一化并应用 对值进行反伽玛(在伪代码中 - 我使用 Col 来表示所有组件,但每个步骤都必须单独应用于每个组件):

    第一步(可选)

    如果准确性不那么重要而性能更重要,您可以跳过这些步骤。

    var normCol = col / 255;    // 255 for 8-bit values
    

    然后应用逆伽玛(解码伽玛):

    var invGamma = 1 / 2.2;     // assumes sRGB
    var normColLin = Math.pow(normCol, invGamma);
    

    现在,我们通常会使用 LUT(查找表)来进行 Gamma 转换,但为了清楚起见,我会保持这样(在这种情况下,您应该在值标准化之前使用它,否则它不是t 非常有用)。

    混合和 Photoshop 公式

    现在我们可以处理颜色了。 Blend 通常只应用于前景(bsource),所以我们还不必担心背景和 alpha。

    维基百科文章中的公式是我见过的一个版本,声称是 Photoshop 公式。

    例如,我拥有的那个是这样的:

    维基百科是这样的:

    除此之外,您还拥有在浏览器中实现的 W3C version(IIRC 的许多混合公式都是由 Adob​​e“捐赠”的,所以......)。

        if(Cs <= 0.5)
            B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
        else
            B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
    with
        if(Cb <= 0.25)
            D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
        else
            D(Cb) = sqrt(Cb)
    

    但让我们坚持您引用的那个(如果您检查 Photoshop 及其结果以查看哪个最匹配,您可以随时将其替换为另一个)。另请参阅文章中显示的公式上的discussion

    仍然是伪的(请记住,b 是前景/顶层,a 是维基百科文章中的背景/底层):

    // todo normalizing + inverse gamma (apply once to entire bitmaps)
    
    function soft_light(bgcol, fgcol, alpha) {
    
      var newCol;
      if (fgcol < 0.5) {
        newCol = 2 * bgcol * fgcol + bgcol * bgcol * (1 - 2 * fgcol);
      }
      else {
        newCol = 2 * bgcol * (1 - fgcol) + Math.sqrt(bgcol) * (2 * fgcol - 1);
      }
    
      // todo Composition
      // todo Alpha blending (maybe)
    }
    
    // todo gamma + scale
    

    合成

    现在我们有了新的混合前景,是时候使用 alpha 值在背景上合成它了。我们将为此使用标准source-over method,与混合模式一样。我们可以使用使用标准 Porter-Duff 的 W3C:

    Fa = 1; Fb = 1 – αs
    co = αs x Cs + αb x Cb x (1 – αs)
    αo = αs + αb x (1 – αs)
    

    最后我们可能需要一个 alpha 混合步骤(检查您的情况)。

    在当前的伪函数中,这将转化为:

    function soft_light(bgcol, fgcol, alpha) {
      // Blending
      var newCol;
      if (fgcol < 0.5) {
        newCol = 2 * bgcol * fgcol + bgcol * bgcol * (1 - 2 * fgcol);
      }
      else {
        newCol = 2 * bgcol * (1 - fgcol) + Math.sqrt(bgcol) * (2 * fgcol - 1);
      }
    
      // Composition
      var as = alpha; // just for clarity. Value in range [0, 1]
      var ab = 1;     // assumes full opaque background here, otherwise use actual alpha of bg
      var Fb = 1 - as;
      newCol = as * newCol + ab * Fb * bgcol;
    
      // Alpha blending (you may not need this step)
      var a = as + ab * Fb;
      return a ? newCol / a : 0;
    }
    

    Gamma 编码和归一化

    然后我们需要再次应用 gamma,因此我们将线性转换回对数以进行显示(您的浏览器可能已经为您进行了 gamma 编码以用于显示目的,但如果您需要保存它..):

    var gamma = 2.2;
    var normCol = Math.pow(newCol, gamma);
    
    var finalCol = (normCol * 255)|0;  // scale and convert to integer+floor
    

    总结

    所以总的来说,步骤是:

    • 将位图/颜色标准化为 [0, 1] *
    • 应用反伽马 *
    • 混合
    • 复合
    • [Alpha 混合]
    • 应用伽玛 *
    • 转换回位范围 [0, 255] *

    *:如果可以牺牲准确性,则可以跳过,但不能牺牲性能。

    颜色例如:

    var fgcol = {
      r: rn,
      g: gn,
      b: bn
    };
    

    所以混合代码实际上看起来像这样:

    newCol.r = fgcol.r < 0.5 ? 
      2 * bgcol.r * fgcol.r + bgcol.r * bgcol.r * (1 - 2 * fgcol.r) :
      2 * bgcol.r * (1 - fgcol.r) + Math.sqrt(bgcol.r) * (2 * fgcol.r - 1);
    
    newCol.b = fgcol.b < 0.5 ? 
      2 * bgcol.b * fgcol.b + bgcol.b * bgcol.b * (1 - 2 * fgcol.b) :
      2 * bgcol.b * (1 - fgcol.b) + Math.sqrt(bgcol.b) * (2 * fgcol.b - 1);
    
    // etc.
    

    免责声明:由于范围大,我没有测试上面的代码和公式是否有错误,我也不打算写下所有的代码。即使有错误,您也应该了解总体思路。如果有人发现错误,请随时直接更新帖子或发表评论。

    【讨论】:

    • 我必须承认,你是一个非常了不起的人。对您的回答的回复简直是一个大大的WOW!我已经在没有 alpha 混合和 gamma 校正的情况下解决了这个问题。我的意图是将 alpha 黑色/白色混合为顶部/前景色。所以我用不同的灰色阴影来接近一些东西。我使用alpha 值来计算灰色阴影。但是由于您的代码包含 alpha 和 gamma 解决方案,所以让我更深入地了解一下。如果我有其他问题,我会告诉你的。非常感谢哥们! :-)
    猜你喜欢
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    • 2013-06-05
    • 1970-01-01
    • 2015-05-08
    • 2010-10-19
    • 2012-10-20
    • 1970-01-01
    相关资源
    最近更新 更多