RGB值的加法混色算法


75

我正在寻找一种算法来对RGB值进行加色混合。

它像将RGB值加起来最大为256一样简单吗?

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

你为什么需要这个?简单的加法不一定总能得到您想要的结果。
2009年

3
理想情况下,它将代表颜色融合,就好像它是有色灯光一样。我可能是不正确的,但我认为这就是加色混合的含义。也许我错了。
Gaidin

您绝对正确,应该是分钟。
Gaidin

典型地,所述色彩通道值的范围从0到255,而不是1到256
福兹先生

Answers:


64

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

如果你想

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

然后使用夹具进行添加(例如min(r1 + r2, 255)),这更像您所引用的光源模型。

如果你想

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

那么您需要对这些值取平均值(例如(r1 + r2) / 2)。这对于变亮/变暗颜色以及创建渐变效果更好。


我是不是错了,或者您的第二个选择不是“添加颜色混合”而是“次要颜色混合”?(问题的标题是错误的,但没关系)
Alfonso Nishikawa

3
@AlfonsoNishikawa不,它不是减法-最好将第二个选项描述为“颜色混合”。减法仅在您在CMYK工作时才适用。
Mark Ransom

82

要使用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或乘以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;

公式将计算出必须产生背景颜色或油漆颜色才能产生给定的结果颜色。


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


如果fg.A和bg.A都为0.0,该怎么办?结果是fA =0。然后我得到了除以零的问题,即:rR = fg.R * fg.A / rA + bg.R * bg.A *(1- fg.A)/ rA;
nodepond 2014年

如何调整这些公式以将fg颜色和alpha混合到不透明的bg上?我有所需的结果颜色和bg颜色,并需要使用该信息来确定fg颜色是什么。
ubiquibacon,2015年

59

有趣的事实:计算机RGB值是从光子通量的平方根得出的。因此,作为一般功能,您的数学应该考虑到这一点。给定通道的一般功能是:

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

其中a和b是要混合的颜色,t是从0-1开始的数字,表示要在a和b之间的混合中的点。

alpha通道不同;它不代表光子强度,而仅代表背景光的百分比。因此,当混合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值的线性混合)的问题在于,它们会在颜色之间产生看起来不自然的过渡,并且斑点太暗。
詹姆斯

5
平方根不是很正确。在我们的程序中操作的数字已经过伽马校正,因为我们的眼睛对光线产生非线性响应,因此可以更好地利用有限的范围。典型的伽马校正为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)))

但是,混合颜色的“自然”方法是使用平均值,然后您不需要最小值:

(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,他们都是一样的!(价值最小)。
2012年

6

混合rgba颜色的Javascript函数

c1,c2和结果-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
       }
     } 

可以将其修改为处理两种以上的颜色吗?
印第安纳·可尼克

或者你只是rgbaSum(rgbaSum(c1,c2),rgbaSum(c3,c4))。这样准确吗?
印第安纳·可尼克

5

PYTHON 彩色 混音 THROUGH 加成 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%混合在一起。



3

在此处找到由Fordi和Markus Jarderot建议的一种python函数中的混合方法,该函数逐渐在两种颜色A和B之间进行混合或混合。

“混合”模式可用于在两种颜色之间进行插值。t=0如果将一种半透明颜色绘制在另一种(可能是半透明的)颜色之上,则“混合”模式(带有)可用于计算所得的颜色。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。在“混合”模式下,处的左侧颜色t=0是将其color_a混合color_b(和白色背景)后产生的颜色。在此示例中,color_a然后变得越来越透明,直到到达color_b

请注意,如果alpha值为1.0,则混合和混合是等效的。

结果


为了完整起见,这里的代码可以复制上面的图。

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中也可用,在Wikipedia上称为“屏幕”。(又称“变亮”逆”。)它产生的结果类似于两个光源的组合。

使用屏幕混合模式时,两层像素的值将被反转,相乘然后再次反转。这产生相反的乘积效果。结果是更明亮的画面。

这里是:

// (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

1

使用C ++编写/使用过类似@Markus JarderotsRGB混合答案(由于这是默认的遗留内容,因此未进行伽玛校正)。

//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;

更多信息:每个编码器都应该了解伽玛


1

我当时正在研究类似的问题,并在这里结束,但最后必须编写自己的实现。我想基本上将新的前景色“覆盖”在现有的背景色上。(并且不使用像t。我相信我的实现仍然是“可加的”。)这似乎在我的所有测试用例中都非常清晰地融合在一起。

在这里,new_argb只需将转换int为4的结构,unsigned char这样我就可以减少移位量。

int blend_argb(int foreground, int background)
{
    t_argb fg;
    t_argb bg;
    t_argb blend;
    double ratio;

    fg = new_argb(foreground);
    bg = new_argb(background);

    // If background is transparent,
    // use foreground color as-is and vice versa.
    if (bg.a == 255)
        return (foreground);
    if (fg.a == 255)
        return (background);

    // If the background is fully opaque,
    // ignore the foreground alpha. (Or the color will be darker.)
    // Otherwise alpha is additive.
    blend.a = ((bg.a == 0) ? 0 : (bg.a + fg.a));

    // When foreground alpha == 0, totally covers background color.
    ratio = fg.a / 255.0;
    blend.r = (fg.r * (1 - ratio)) + (bg.r * ratio);
    blend.g = (fg.g * (1 - ratio)) + (bg.g * ratio);
    blend.b = (fg.b * (1 - ratio)) + (bg.b * ratio);

    return (blend.a << 24 | blend.r << 16 | blend.g << 8 | blend.b);
}

对于上下文,在我的环境中,我正在将colorint写入一维像素数组,该数组以0字节初始化,并且增加alpha值会使像素趋向于黑色。(0 0 0 0将是不透明的黑色,255 255 255 255将是透明的白色...又名黑色。)


0

这是一个高度优化的独立c ++类,公共领域,具有浮点数和两种针对功能和宏格式的不同优化的8位混合机制,并就手头的问题,如何解决以及重要性进行了技术讨论问题的优化:

https://github.com/fyngyrz/colorblending


仅链接的答案不是一个好主意,链接变坏了,您会失去一文不值的东西。如果您无法在此处发布代码,请至少对其进行描述和总结。
Mark Ransom

我很高兴能在这里发布的代码,如果因此不得不甚至好一个机制,Github上一样。但事实并非如此。更不用说SO的令人讨厌的许可政策。至于与Github相关的链接变坏,那与与SO相关的链接变坏的可能性差不多。链接就是网络的全部内容。我发布的内容与该问题相关,而且与该特定问题相关的解决方案都具有100%的相关性,这些解决方案已在公共领域中发布。您对您的言论没有任何帮助或帮助,您只是在制造麻烦。
fyngyrz

1
不,我正在尝试确定StackOverflow有答案,而不是答案的链接。谷歌已经有很多。
Mark Ransom

3
我给出了与该问题有关的答案。答案包含代码链接。因此,SO没有包含重要C类的机制,也没有针对网站上显示的代码的可接受许可,因此根本无法实现您想要的。选择是在包含链接的回答还是根本不回答之间进行选择。后者没有任何好处。前者是相关的,信息丰富的并且非常有用。对于Google而言,结果必然与广告无关。因此,也许您应该考虑重新考虑自己的位置。硬道理是你的。我对我的回答感到满意。
fyngyrz

1
拒绝投票支持一个人发布指向他自己的github存储库中的解决方案的链接似乎有点肤浅。
BobRodes

0

谢谢Markus Jarderot,Andras Zoltan和hkurabko;这是用于混合RGB图像列表的Python代码。

使用Markus Jarderot的代码,我们可以生成RGBA颜色,然后我使用Andras Zoltan和hkurabko的方法将RGBA转换为RGB。

谢谢!

import numpy as np
def Blend2Color(C1,C2):
    c1,c1a=C1
    c2,c2a=C2
    A = 1 - (1 - c1a) * (1 - c2a);
    if (A < 1.0e-6): 
        return (0,0,0) #Fully transparent -- R,G,B not important
    Result=(np.array(c1)*c1a+np.array(c2)*c2a*(1-c1a))/A
    return Result,A
def RGBA2RGB(RGBA,BackGround=(1,1,1)):# whilt background
    A=RGBA[-1]
    RGB=np.add(np.multiply(np.array(RGBA[:-1]),A),
               np.multiply(np.array(BackGround),1-A))
    return RGB

def BlendRGBList(Clist,AlphaList=None,NFloat=2,ReturnRGB=True,
                 RGB_BackGround=(1,1,1)):
    N=len(Clist)
    if AlphaList==None:
        ClistUse=Clist.copy()
    else:
        if len(AlphaList)==N:
            AlphaListUse=np.multiply(AlphaList,10**NFloat).astype(int)
            ClistUse=np.repeat(np.array(Clist), AlphaListUse, axis=0)
        else:
            raise('len of AlphaList must equal to len of Clist!')
    while N!=1:
        temp=ClistUse.copy()
        ClistUse=[]
        for C in temp[:-1]:
            c1,a1=C
            c2,a2=temp[-1]
            ClistUse.append(Blend2Color(C1=(c1,a1*(1-1/N)),C2=(c2,a2*1/N)))
        N=len(ClistUse)
    Result=np.append(ClistUse[0][0],ClistUse[0][1])
    if ReturnRGB:
        Result=RGBA2RGB(Result,BackGround=RGB_BackGround)

    return Result

测试

BlendRGBList([[(1,0,0),1],[(0,1,0),1]],ReturnRGB=True)
#array([0.75, 0.5 , 0.25])
BlendRGBList([[(1,0,0),1],[(0,1,0),1]],ReturnRGB=False)
#array([0.66666667, 0.33333333, 0.        , 0.75      ])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.