如何用alpha输入计算两种颜色的柔光混合值?

时间:2017-09-26 05:57:34

标签: javascript colors sass photoshop color-blending

我需要编写一个 JavaScript 函数和一个 SASS mixin来计算带有alpha输入的两种颜色的 Soft Light 混合值。< / p>

函数/ mixin将采用如下三个参数:

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

并将返回混合颜色。

理想情况下,该功能如下:

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

以下是关于 Wikipedia 的柔光混合模式的参考资料。我的目标是 Photoshop

使用的公式

我无法理解如何计算公式。为什么b的值介于01之间而不是颜色代码? 伽马校正的工作原理如何?我将如何使用颜色值执行公式中使用的乘法,减法等基本操作。

高度赞赏该解决方案的指南。提前致谢。 : - )

1 个答案:

答案 0 :(得分:5)

嗯,你正在触及许多人认为很简单的区域,但colorimetry(或&#34;用于量化和描述人类色彩感知的科学和技术。&# 34; )实际上是一个非常复杂的领域,它的整个范围相对较少。

这可能听起来很可怕,但不用担心,我们不需要为此任务了解其全部内容。

标准化值短缺

让我们从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(查找表)来进行伽玛转换,但为了清晰起见,我会保留它(在这种情况下,你会在值归一化之前使用它,否则它不是很有用。)

混合和Photoshop公式

现在我们可以处理颜色了。混合通常仅应用于前景(bsource),因此我们不必担心背景和alpha。

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

例如,我拥有的那个看起来像这样:

Photoshop soft-light v1

维基百科看起来像这样:

https://wikimedia.org/api/rest_v1/media/math/render/svg/47d5e1e1712e03e28c4a858adc621385d3f5753e

最重要的是,你有W3C version在浏览器中实现(IIRC许多混合公式是&#34;捐赠&#34;由Adobe,所以..)。

    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,以便我们将线性转换回对数以进行显示(您的浏览器可能已经为您进行了伽玛编码以用于显示目的,但是如果您需要将其保存出来...):

var gamma = 2.2;
var normCol = Math.pow(newCol, gamma);

var finalCol = (normCol * 255)|0;  // scale and convert to integer+floor

摘要

总而言之,步骤是:

  • 将位图/颜色标准化为范围[0,1] *
  • 应用反伽玛*
  • 混合
  • 组合
  • [Alpha混合]
  • 应用gamma *
  • 转换回位范围[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.

免责声明:由于范围较大,我没有测试上面的公式代码,因为我没有打算写下所有代码。即使有错误,你也应该得到一般的想法。如果有人发现错误,请随时直接更新帖子或发表评论。