我正在寻找一种算法来对 RGB 值进行加色混合。
是否像将 RGB 值加在一起最多为 256 一样简单?
(r1, g1, b1) + (r2, g2, b2) =
(min(r1+r2, 256), min(g1+g2, 256), min(b1+b2, 256))
要使用 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;
这些公式将计算出背景或油漆颜色必须是产生给定的结果颜色。
如果您的背景不透明,则结果也将是不透明的。然后,前景色可以采用具有不同 alpha 值的一系列值。对于每个通道(红色、绿色和蓝色),您必须检查哪个 alpha 范围会产生有效值 (0 - 1)。
这取决于您想要什么,它可以帮助您了解不同方法的结果。
如果你想
Red + Black = Red Red + Green = Yellow Red + Green + Blue = White Red + White = White Black + White = White
然后使用夹子添加(例如min(r1 + r2, 255)
)这更像是您提到的灯光模型。
如果你想
Red + Black = Dark Red Red + Green = Dark Yellow Red + Green + Blue = Dark Gray Red + White = Pink Black + White = Gray
那么您需要对这些值进行平均(例如 (r1 + r2) / 2
) 这对于淡化/加深颜色和创建渐变效果更好。
有趣的事实:计算机 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)) 读起来不干净。
几点:
我认为您想使用 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)
用于混合 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))
。那会准确吗?
通过 CMYK 空间中的添加进行 PYTHON 颜色混合
一种可能的方法是首先将颜色转换为 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% 的第二种颜色混合。
是的,就是这么简单。另一种选择是找到平均值(用于创建渐变)。
这真的取决于你想要达到的效果。
但是,当添加 Alpha 时,它会变得复杂。有许多不同的方法可以使用 Alpha 进行混合。
简单 Alpha 混合的示例:http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
在这里找到 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_a
和 color_b
。在“混合”模式下,t=0
左侧的颜色是color_a
与 color_b
混合(和白色背景)时产生的颜色。在示例中,color_a
然后变得越来越半透明,直到到达 color_b
。
请注意,如果 alpha 值为 1.0,则混合和混合是等效的。
https://i.stack.imgur.com/4Y2fQ.png
为了完整起见,这里是重现上述情节的代码。
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)
最后,here 阅读有关伽马校正的有用信息。
当我来到这里时,我没有找到我真正需要的“加色混合”算法,它也可以在 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);
}
b3 = {r: (b1.r + b2.r) / 2, g: (b1.g + b2.g) /2, ...
使用 C++ 编写/使用过类似 @Markus Jarderot 的 sRGB
混合答案(未进行 gamma 校正,因为这是默认遗留问题)
//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;
更多信息:what-every-coder-should-know-about-gamma
我正在研究一个类似的问题并最终来到这里,但最后不得不编写我自己的实现。我想基本上将新的前景色“覆盖”在现有的背景色上。 (并且没有使用像 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);
}
对于上下文,在我的环境中,我将颜色 int
写入一维像素数组,该数组初始化为 0 字节,增加 alpha 将使像素趋于黑色。 (0
0 0 0
将是不透明的黑色,而 255
255 255 255
将是透明的白色......也就是黑色。)
这是一个高度优化的独立 c++ 类,公共领域,具有浮点和两种不同优化的函数和宏格式的 8 位混合机制,以及对手头问题和如何解决的问题以及重要性的技术讨论的,优化这个问题:
https://github.com/fyngyrz/colorblending
谢谢 Markus Jarderot、Andras Zoltan 和 hkurabko;这是用于混合 RGB 图像列表的 Python 代码。
使用 Markus Jarderot 的代码,我们可以生成 RGBA 颜色,然后我使用 Andras Zoltan and hkurabko's 方法将 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 ])