RGB值的加法混合算法

89

我正在寻找一种算法来执行RGB颜色值的加法混合。

是否只需要将RGB值相加,最大值为256?

(r1, g1, b1) + (r2, g2, b2) =
    (min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))  

为什么需要这个?简单的加法可能并不总是能给出你想要的结果。 - dirkgently
4
理想情况下,它应该像彩色光一样呈现颜色混合效果。我可能不正确,但我认为这就是加法混色所代表的。也许我错了。 - Gaidin
你说得完全正确,应该是最小值。 - Gaidin
1
通常,颜色通道的值范围是从0到255,而不是1到256。 - Mr Fooz
13个回答

99

使用 alpha 通道进行混合,您可以使用以下公式:

r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A);
if (r.A < 1.0e-6) return r; // Fully transparent -- R,G,B not important
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A;
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A;
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A;

fg 是绘画颜色。 bg 是背景。 r 是结果颜色。 1.0e-6 只是一个非常小的数字,用于补偿舍入误差。

注意:此处使用的所有变量范围均为[0.0, 1.0]。如果您想使用[0,255]范围内的值,则必须进行除以或乘以255的运算。

例如,50%红色放在50%绿色之上:

// background, 50% green
var bg = new Color { R = 0.00, G = 1.00, B = 0.00, A = 0.50 };
// paint, 50% red
var fg = new Color { R = 1.00, G = 0.00, B = 0.00, A = 0.50 };
// The result
var r = new Color();
r.A = 1 - (1 - fg.A) * (1 - bg.A); // 0.75
r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A; // 0.67
r.G = fg.G * fg.A / r.A + bg.G * bg.A * (1 - fg.A) / r.A; // 0.33
r.B = fg.B * fg.A / r.A + bg.B * bg.A * (1 - fg.A) / r.A; // 0.00

结果颜色为:(0.67,0.33,0.00,0.75),或者是75%的棕色(或深橙色)。


您也可以反转这些公式:

var bg = new Color();
if (1 - fg.A <= 1.0e-6) return null; // No result -- 'fg' is fully opaque
if (r.A - fg.A < -1.0e-6) return null; // No result -- 'fg' can't make the result more transparent
if (r.A - fg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
bg.A = 1 - (1 - r.A) / (1 - fg.A);
bg.R = (r.R * r.A - fg.R * fg.A) / (bg.A * (1 - fg.A));
bg.G = (r.G * r.A - fg.G * fg.A) / (bg.A * (1 - fg.A));
bg.B = (r.B * r.A - fg.B * fg.A) / (bg.A * (1 - fg.A));

或者

var fg = new Color();
if (1 - bg.A <= 1.0e-6) return null; // No result -- 'bg' is fully opaque
if (r.A - bg.A < -1.0e-6) return null; // No result -- 'bg' can't make the result more transparent
if (r.A - bg.A < 1.0e-6) return bg; // Fully transparent -- R,G,B not important
fg.A = 1 - (1 - r.A) / (1 - bg.A);
fg.R = (r.R * r.A - bg.R * bg.A * (1 - fg.A)) / fg.A;
fg.G = (r.G * r.A - bg.G * bg.A * (1 - fg.A)) / fg.A;
fg.B = (r.B * r.A - bg.B * bg.A * (1 - fg.A)) / fg.A;

这些公式将计算出要产生给定结果颜色所需的背景或涂料颜色。


如果您的背景是不透明的,则结果也将是不透明的。前景颜色可以采用不同透明度值的范围。对于每个通道(红色、绿色和蓝色),您必须检查哪个透明度范围会产生有效值(0-1)。


1
如果fg.A和bg.A都是0.0,你该如何解决“除以零”的问题?这会导致f.A = 0。然后我在这里遇到了一个除以零的问题:r.R = fg.R * fg.A / r.A + bg.R * bg.A * (1 - fg.A) / r.A; - nodepond
1
这些公式需要如何调整才能将带有 alpha 的前景色与不透明的背景混合?我已经有了期望的结果颜色和背景颜色,根据这些信息,我需要确定前景色应该是什么。 - ubiquibacon

78

这取决于您想要什么,并且查看不同方法的结果可以帮助您。

如果你想

红 + 黑        = 红
红 + 绿        = 黄色
红 + 绿 + 蓝   = 白色
红 + 白        = 白色 
黑 + 白        = 白色

那么使用夹紧加法就可以实现(例如 min(r1 + r2, 255)) 这更像是你所提到的光模型。

如果你想

红 + 黑        = 暗红
红 + 绿        = 暗黄色
红 + 绿 + 蓝   = 暗灰色
红 + 白        = 粉色
黑 + 白        = 灰色

那么你需要对值进行平均(例如 (r1 + r2) / 2) 这对于添加渐变和调整颜色亮度较好。


我错了还是你的第二个选项不是“加性颜色混合”,而是“减性颜色混合”?(问题的标题是错误的,但无关紧要) - Alfonso Nishikawa
5
不,它不是减色混合 - 第二个选项最好描述为“颜色混合”。只有在使用CMYK时,减色混合才是适当的。 - Mark Ransom

69

有趣的事实:计算机RGB值是由光子通量的平方根得出的。因此,作为一般函数,您的数学计算应该考虑到这一点。对于给定通道的这个函数可以表示为:

blendColorValue(a, b, t)
    return sqrt((1 - t) * a^2 + t * b^2)

其中a和b是要混合的颜色,t是表示你想在a和b之间混合的位置的0-1之间的数字。

阿尔法通道是不同的;它不代表光子强度,只是应该显示背景的百分比;因此,在混合alpha值时,线性平均就足够了:

blendAlphaValue(a, b, t)
    return (1-t)*a + t*b;

因此,为了处理混合两种颜色,使用这两个函数,以下伪代码应该能很好地帮助你完成:
blendColors(c1, c2, t)
    ret
    [r, g, b].each n ->
        ret[n] = blendColorValue(c1[n], c2[n], t)
    ret.alpha = blendAlphaValue(c1.alpha, c2.alpha, t)
    return ret

顺便说一下,我渴望一种编程语言和键盘,可以更清晰地表示数学(或更多),结合上划线的Unicode字符无法用于上标、符号和大量其他字符)并正确解释它。sqrt((1-t)*pow(a, 2) + t * pow(b, 2)) 看起来就不够清晰。


1
谢谢,这正是我在寻找混合颜色算法时所需要的。上面的答案存在问题(使用RGB值的线性混合),会导致颜色之间的过渡看起来不自然,有些地方太暗了。 - James
9
平方根不完全正确。我们程序中处理的数字已经经过伽马校正,因为我们的眼睛对光的反应是非线性的,这样可以更好地利用有限的范围。典型的伽马校正是2.2,接近但并不完全相同于您在此答案中推荐的2.0。在某些情况下,在经过伽马校正的空间中工作更好,例如生成灰度渐变。 - Mark Ransom
我一直在玩一个玩具,它会快速交替显示两种颜色,直到它们融合成单一的颜色,这个混合算法看起来完美无缺!很明显这是正确的数学方法,我以前从未知道!谢谢!! - luqui

9

几点说明:

  • 我认为你想使用min而不是max
  • 我认为你想使用255而不是256

这将会得到:

(r1, g1, b1) + (r2, g2, b2) = (min(r1+r2, 255), min(g1+g2, 255), min(b1+b2, 255))

然而,混合颜色的“自然”方式是使用平均值,这时就不需要min了:

(r1, g1, b1) + (r2, g2, b2) = ((r1+r2)/2, (g1+g2)/2, (b1+b2)/2)


抱歉,我在幻觉中。min(a,b)将给出a和b的最小值。min(a,constant)将限制a在常量以下。 - Markus Jarderot
@Markus,它们都是一样的!(将给出最小值)。 - UpTheCreek

7

Javascript混合rgba颜色的函数

c1、c2和result - 类似JSON格式的数据 c1={r:0.5,g:1,b:0,a:0.33}

    var rgbaSum = function(c1, c2){
       var a = c1.a + c2.a*(1-c1.a);
       return {
         r: (c1.r * c1.a  + c2.r * c2.a * (1 - c1.a)) / a,
         g: (c1.g * c1.a  + c2.g * c2.a * (1 - c1.a)) / a,
         b: (c1.b * c1.a  + c2.b * c2.a * (1 - c1.a)) / a,
         a: a
       }
     } 

这个能修改来处理超过两种颜色吗? - Indiana Kernick
或者你可以只使用rgbaSum(rgbaSum(c1,c2),rgbaSum(c3,c4))。那样准确吗? - Indiana Kernick

5

PYTHON 通过 在CMYK空间中进行添加 实现色彩混合

一个可能的方法是先将颜色转换为CMYK格式,然后在此处添加它们,最后再转换回RGB。

以下是Python示例代码:

rgb_scale = 255
cmyk_scale = 100


def rgb_to_cmyk(self,r,g,b):
    if (r == 0) and (g == 0) and (b == 0):
        # black
        return 0, 0, 0, cmyk_scale

    # rgb [0,255] -> cmy [0,1]
    c = 1 - r / float(rgb_scale)
    m = 1 - g / float(rgb_scale)
    y = 1 - b / float(rgb_scale)

    # extract out k [0,1]
    min_cmy = min(c, m, y)
    c = (c - min_cmy) 
    m = (m - min_cmy) 
    y = (y - min_cmy) 
    k = min_cmy

    # rescale to the range [0,cmyk_scale]
    return c*cmyk_scale, m*cmyk_scale, y*cmyk_scale, k*cmyk_scale

def cmyk_to_rgb(self,c,m,y,k):
    """
    """
    r = rgb_scale*(1.0-(c+k)/float(cmyk_scale))
    g = rgb_scale*(1.0-(m+k)/float(cmyk_scale))
    b = rgb_scale*(1.0-(y+k)/float(cmyk_scale))
    return r,g,b

def ink_add_for_rgb(self,list_of_colours):
    """input: list of rgb, opacity (r,g,b,o) colours to be added, o acts as weights.
    output (r,g,b)
    """
    C = 0
    M = 0
    Y = 0
    K = 0

    for (r,g,b,o) in list_of_colours:
        c,m,y,k = rgb_to_cmyk(r, g, b)
        C+= o*c
        M+=o*m
        Y+=o*y 
        K+=o*k 

    return cmyk_to_rgb(C, M, Y, K)

您的问题的答案将会是这样(假设两种颜色的比例各占一半):
r_mix, g_mix, b_mix = ink_add_for_rgb([(r1,g1,b1,0.5),(r2,g2,b2,0.5)])

在这里,0.5 的意思是我们将第一种颜色的50%与第二种颜色的50%混合。


5
在这里找到Fordi和Markus Jarderot建议的混合方法,并将其放入一个Python函数中,逐步混合两种颜色A和B。
"mix"模式用于在两个颜色之间进行插值。当一个半透明的颜色被涂在另一个(可能是半透明的)颜色上时,"blend"模式(其中)有用于计算结果颜色。gamma校正可以得到更好的结果,因为它考虑了物理光强度和人类感知亮度之间的非线性关系。
import numpy as np

def mix_colors_rgba(color_a, color_b, mode="mix", t=None, gamma=2.2):
    """
    Mix two colors color_a and color_b.

    Arguments:
        color_a:    Real-valued 4-tuple. Foreground color in "blend" mode.
        color_b:    Real-valued 4-tuple. Background color in "blend" mode.
        mode:       "mix":   Interpolate between two colors.
                    "blend": Blend two translucent colors.
        t:          Mixing threshold.
        gamma:      Parameter to control the gamma correction.

    Returns: 
        rgba:       A 4-tuple with the result color.

    To reproduce Markus Jarderot's solution:
            mix_colors_rgba(a, b, mode="blend", t=0, gamma=1.)
    To reproduce Fordi's solution:
            mix_colors_rgba(a, b, mode="mix", t=t, gamma=2.)
    To compute the RGB color of a translucent color on white background:
            mix_colors_rgba(a, [1,1,1,1], mode="blend", t=0, gamma=None)
    """
    assert(mode in ("mix", "blend"))
    assert(gamma is None or gamma>0)
    t = t if t is not None else (0.5 if mode=="mix" else 0.)
    t = max(0,min(t,1))
    color_a = np.asarray(color_a)
    color_b = np.asarray(color_b)
    if mode=="mix" and gamma in (1., None):
        r, g, b, a = (1-t)*color_a + t*color_b
    elif mode=="mix" and gamma > 0:
        r,g,b,_ = np.power((1-t)*color_a**gamma + t*color_b**gamma, 1/gamma)
        a = (1-t)*color_a[-1] + t*color_b[-1]
    elif mode=="blend":
        alpha_a = color_a[-1]*(1-t)
        a = 1 - (1-alpha_a) * (1-color_b[-1])
        s = color_b[-1]*(1-alpha_a)/a
        if gamma in (1., None):
            r, g, b, _ = (1-s)*color_a + s*color_b
        elif gamma > 0:
            r, g, b, _ = np.power((1-s)*color_a**gamma + s*color_b**gamma,
                                  1/gamma)

    return tuple(np.clip([r,g,b,a], 0, 1))

请见下文如何使用。在“混合”模式中,左侧和右侧的颜色完全匹配color_acolor_b。在“混合”模式下,当color_a与白色背景混合时,在t=0处的左侧颜色是结果颜色。在本示例中,color_a逐渐变得越来越透明,直到到达color_b
请注意,如果alpha值为1.0,则混合和混合等效。

Results


为了完整起见,这里是重现上述图形的代码。
import matplotlib.pyplot as plt
import matplotlib as mpl

def plot(pal, ax, title):
    n = len(pal)
    ax.imshow(np.tile(np.arange(n), [int(n*0.20),1]),
              cmap=mpl.colors.ListedColormap(list(pal)),
              interpolation="nearest", aspect="auto")
    ax.set_xticks([])
    ax.set_yticks([])
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    ax.set_title(title)

_, (ax1, ax2, ax3, ax4) = plt.subplots(nrows=4,ncols=1)

n = 101
ts = np.linspace(0,1,n)
color_a = [1.0,0.0,0.0,0.7] # transparent red
color_b = [0.0,0.0,1.0,0.8] # transparent blue

plot([mix_colors_rgba(color_a, color_b, t=t, mode="mix", gamma=None)
      for t in ts], ax=ax1, title="Linear mixing")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="mix", gamma=2.2)
      for t in ts], ax=ax2, title="Non-linear mixing (gamma=2.2)")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="blend", gamma=None)
      for t in ts], ax=ax3, title="Linear blending")
plot([mix_colors_rgba(color_a, color_b, t=t, mode="blend", gamma=2.2)
      for t in ts], ax=ax4, title="Non-linear blending (gamma=2.2)")
plt.tight_layout()
plt.show()

Formulas:
    Linear mixing (gamma=1):
                r,g,b,a:    (1-t)*x + t*y
    Non-linear mixing (gama≠1):
                r,g,b:      pow((1-t)*x**gamma + t*y**gamma, 1/gamma)
                a:          (1-t)*x + t*y
    Blending (gamma=1):
                a:          1-(1-(1-t)*x)*(1-y)
                s:          alpha_b*(1-alpha_a)*a
                r,g,b:      (1-s)*x + s*y
    Blending (gamma≠1):
                a:          1-(1-(1-t)*x)*(1-y)
                s:          alpha_b*(1-alpha_a)/a
                r,g,b:      pow((1-s)*x**gamma + s*y**gamma, 1/gamma)

最后,这里有一篇关于伽马校正的有用阅读材料。


3
当我来到这里时,我没有找到我实际正在寻找的“添加颜色混合”算法,它也可以在Photoshop中使用,并被描述为维基百科上的“屏幕”(又称“变亮”或“反转乘法”)。 它产生的结果类似于两个光源的组合。
使用屏幕混合模式,两个图层中像素的值被反转、相乘,然后再次反转。这产生了与乘法相反的效果。结果是一张更亮的图片。
这就是它:
// (rgb values are 0-255)
function screen(color1, color2) {
    var r = Math.round((1 - (1 - color1.R / 255) * (1 - color2.R / 255)) * 255);
    var g = Math.round((1 - (1 - color1.G / 255) * (1 - color2.G / 255)) * 255);
    var b = Math.round((1 - (1 - color1.B / 255) * (1 - color2.B / 255)) * 255);
    return new Color(r, g, b);
}

我希望这就是我要找的,这个方法可以混合两种不同的颜色/色调吗?例如,两个蓝色值定义为:blue1 =(17,43,154)和blue2 =(22,56,186)。 - Mohd
1
@Mohd 取决于您期望的结果。由于这是加性混合,结果颜色将始终更亮,因为它添加了两种颜色。如果您想要“混合”颜色,从而产生介于两种颜色之间的色调,则不行。为此,可以使用 b3 = {r: (b1.r + b2.r) / 2, g: (b1.g + b2.g) /2, ... - marsze
你是正确的;确实每个通道的平均值完成了任务。 - Mohd

3
是的,就这么简单。另一个选项是找到平均值(用于创建渐变效果)。
这实际上取决于您想要实现的效果。
但是,当 Alpha 被添加时,它就变得复杂了。有许多不同的方法可以使用 alpha 进行混合。
简单 alpha 混合的示例: http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending

2

我曾经写过/使用过类似@Markus JarderotsRGB混合答案(该答案未进行伽马校正,因为这是默认的遗留问题),使用C ++。

//same as Markus Jarderot's answer
float red, green, blue;
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red   = (front.red   * front.alpha / alpha + back.red   * back.alpha * (1.0 - front.alpha));
green = (front.green * front.alpha / alpha + back.green * back.alpha * (1.0 - front.alpha));
blue  = (front.blue  * front.alpha / alpha + back.blue  * back.alpha * (1.0 - front.alpha));

//faster but equal output
alpha = (1.0 - (1.0 - back.alpha)*(1.0 - front.alpha));
red   = (back.red   * (1.0 - front.alpha) + front.red   * front.alpha);
green = (back.green * (1.0 - front.alpha) + front.green * front.alpha);
blue  = (back.blue  * (1.0 - front.alpha) + front.blue  * front.alpha);

//even faster but only works when all values are in range 0 to 255
int red, green, blue;
alpha = (255 - (255 - back.alpha)*(255 - front.alpha));
red   = (back.red   * (255 - front.alpha) + front.red   * front.alpha) / 255;
green = (back.green * (255 - front.alpha) + front.green * front.alpha) / 255;
blue  = (back.blue  * (255 - front.alpha) + front.blue  * front.alpha) / 255;

更多信息:程序员应该了解的Gamma知识


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接