图层混合简介
图层混合(blend)顾名思义,就是把两个图层混合成一个。
最基本的混合是alpha融合(alpha compositing),这是一个遵循光的反射与透射等(简化版)物理学原理的混合方式。
各个图像处理软件中,往往还提供很多具有一定艺术风格的混合方法,这是本文的主要讨论内容。但须注意,为了更深入地理解混合模式,alpha融合是必需要掌握的前提知识。
为了更好地理解本文内容,我们做如下约定:
- 所有色彩是归一化的,也就是值域在[0, 1]之间(而非[0, 255]),否则不仅公式不容易写,而且还不适配hdr的情况。
- 一般情况下,混合都是逐像素,大部分时候甚至逐通道计算。(在Photoshop中,仅有
深色
和浅色
不是逐通道计算) - 部分混合模式满足交换律,即交换两个图层的上下位置,结果不变。
- 一些混合模式的结果要求截断到[0, 1]之间,否则会发生不可预测的结果,这些混合模式仅能作用于int类型的数值;还有一些则不要求,这些则能作用于float类型的数值,其结果可以越过[0, 1]范围。
Photoshop中的混合模式
截止2022版本,Photoshop中有27个混合模式,分为六大类,各类别以及包含的混合模式为:
- 正常类别:
正常,溶解
- 变暗类别:
变暗,正片叠底,颜色加深,线性加深,深色
- 变亮类别:
变亮,滤色,颜色减淡,线性减淡(添加),浅色
- 复杂类别:
叠加,柔光,强光,亮光,线性光,点光,实色混合
- 差异类别:
差值,排除,减去,划分
- HSL类别:
色相,饱和度,颜色,明度
除了溶解(Dissolve)
模式以外,其他的混合都有明确的公式,溶解靠随机数实现,随着透明度降低,有越来越多的随机像素点的alpha通道会变成0,这样就会随机地显示出下方图层,表现得好像溶解了一样。本文主要讲有公式的那些混合模式,溶解不在此列,所以这里简单描述一下,后面就不再提了。
为了全面理解混合模式的公式,需要首先从RGB出发(即alpha通道恒等于1),在了解了RGB的混合之后,要基于此知识进一步推导RGBA的混合公式,也就是含透明度信息情况下的公式,真正写程序时候一般使用RGBA的公式即可,因为RGBA涵盖了RGB的情况。
部分混合模式在RGB情况下,交换前景与背景图后效果相同,此类混合模式有:
变暗,正片叠底,线性加深,深色,变亮,滤色,线性减淡(添加),浅色,差值,排除,实色混合
公式符号约定
fg:foreground,表示前景,即处于上方的图层
bg:background,表示背景,即处于下方的图层
f
g
c
fg_c
fgc:表示前景的rgb部分,其中脚标_c是color的意思。背景的脚标含义同理,下面不再赘述。
f
g
α
fg_\alpha
fgα:表示前景的透明通道
f
g
c
α
fg_{c\alpha}
fgcα:表示前景rgb与alpha通道相乘的结果,即alpha预乘。
在写代码时,还需注意除法
的问题:所有的除法必需要考虑避免除以0的情况,因此碰到除法时应做如下保护:
a
b
=
a
m
a
x
(
b
,
e
p
s
)
\frac{a}{b} = \frac{a}{max(b, eps)}
ba=max(b,eps)a
接下来的公式中不会赘述这一点,但各位读者写代码时需自行注意。
RGB混合公式
正常(Normal)
仅显示上方图层。
N
o
r
m
a
l
(
f
g
c
,
b
g
c
)
=
f
g
c
Normal(fg_c,bg_c) = fg_c
Normal(fgc,bgc)=fgc
变暗(Darken)
取两图层中数值较小者。
D
a
r
k
e
n
(
f
g
c
,
b
g
c
)
=
m
i
n
(
f
g
c
,
b
g
c
)
Darken(fg_c, bg_c) = min(fg_c, bg_c)
Darken(fgc,bgc)=min(fgc,bgc)
正片叠底(Multiply)
上下两图层相乘。
M
u
l
t
i
p
l
y
(
f
g
c
,
b
g
c
)
=
f
g
c
∗
b
g
c
Multiply(fg_c, bg_c) = fg_c * bg_c
Multiply(fgc,bgc)=fgc∗bgc
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/8blh2zzaqc
颜色加深(Color Burn)
让背景图层颜色变暗,变暗的程度由前景决定,前景越暗,背景变暗的程度就越强。
C
o
l
o
r
B
u
r
n
(
f
g
c
,
b
g
c
)
=
{
0
,
if:
f
g
c
=
=
0
1
−
m
i
n
(
1
−
b
g
c
f
g
c
,
1
)
,
if:
f
g
c
>
0
ColorBurn(fg_c, bg_c) = \begin{cases} 0, &\text{if: } fg_c == 0 \\[2ex] 1 - min(\frac{1 - bg_c}{fg_c}, 1), &\text {if: } fg_c>0 \end{cases}
ColorBurn(fgc,bgc)=⎩
⎨
⎧0,1−min(fgc1−bgc,1),if: fgc==0if: fgc>0
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/5xdaojgl3p
线性加深(Linear Burn)
前景与背景相加后减1。效果类似颜色加深,只是变暗的曲线是线性的。
L i n e a r B u r n ( f g c , b g c ) = m a x ( f g c + b g c − 1 , 0 ) LinearBurn(fg_c, bg_c) =max(fg_c+bg_c-1, 0) LinearBurn(fgc,bgc)=max(fgc+bgc−1,0)
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/akmwl8cert
深色(Darker Color)
计算上下两通道的灰度值,谁的灰度值小,结果颜色就取相应图层的rgb颜色。
注意该模式是少有的非逐通道
计算的混合模式。该模式不产生fg和bg以外的新的颜色。
变亮(Lighten)
取两图层中数值较大者。
L i g h t e n ( f g c , b g c ) = m a x ( f g c , b g c ) Lighten(fg_c, bg_c)=max(fg_c,bg_c) Lighten(fgc,bgc)=max(fgc,bgc)
滤色(Screen)
滤色是模拟光的反射与透射原理设计的混合模式。如果光照到图片后,我们把反射回来的记为我们看到的图像本身,那么透射过去的就是(1-image),也可以称之为负片
,光透过两个图层以后的负片就是(1-fg)(1-bg)
,对这个结果整体再来个负片,就是滤色的效果。光透过多个图层后一定是越来越暗的,所以最终再做一个整体的负片,就一定是变亮的,并且根据物理原理(或更简单一些,可以根据公式),如果各个输入图层在[0, 1]之间,那么结果就一定也在[0, 1]之间,不会产生数值截断,这是一个非常好的特性。
滤色是非常重要的一种混合模式,是各类发光效果最最常用的混合模式(bloom、glow等)。
S
c
r
e
e
n
(
f
g
c
,
b
g
c
)
=
1
−
(
1
−
f
g
c
)
∗
(
1
−
b
g
c
)
Screen(fg_c,bg_c)=1-(1-fg_c)*(1-bg_c)
Screen(fgc,bgc)=1−(1−fgc)∗(1−bgc)
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/sa04opay5a
颜色减淡(Color Dodge)
让背景图层颜色变亮,变亮的程度由前景决定,前景越亮,背景变亮的程度就越强。
C o l o r D o d g e ( f g c , b g c ) = { 1 , if: f g c = = 1 m i n ( b g c 1 − f g c , 1 ) , otherwise ColorDodge(fg_c, bg_c) = \begin{cases} 1, &\text{if: } fg_c == 1 \\[2ex] min(\frac{bg_c}{1-fg_c}, 1), &\text {otherwise} \end{cases} ColorDodge(fgc,bgc)=⎩ ⎨ ⎧1,min(1−fgcbgc,1),if: fgc==1otherwise
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/8c5gfbfenq
线性减淡 / 添加(Linear Dodge / Add)
前景与背景相加。效果类似颜色减淡,只是变亮的曲线是线性的。
L i n e a r D o d g e ( f g c , b g c ) = f g c + b g c LinearDodge(fg_c, bg_c) =fg_c+bg_c LinearDodge(fgc,bgc)=fgc+bgc
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/f4rclxxoj8
浅色(Lighter Color)
计算上下两通道的灰度值,谁的灰度值大,结果颜色就取相应图层的rgb颜色。
注意该模式是少有的非逐通道
计算的混合模式。该模式不产生fg和bg以外的新的颜色。
叠加(Overlay)
当背景
小于0.5时,使用multiply混合(变暗效果);反之则使用screen混合(变亮效果)。也就是说混合结果使得暗的更暗,亮的更亮,即对比度增强的效果(s形曲线)。
特别注意
:该混合模式的控制图层是bg(即if条件中的图层),这与其他绝大部分的混合模式都不一样。
叠加与后面的强光(Hard Light)主体公式一样,但是条件不同。叠加的控制图层是bg,强光是fg,注意区别。
O v e r l a y ( f g c , b g c ) = { 2 ∗ f g c ∗ b g c , if: b g c < = 0.5 1 − 2 ∗ ( 1 − f g c ) ∗ ( 1 − b g c ) , otherwise Overlay(fg_c, bg_c) = \begin{cases} 2*fg_c*bg_c, &\text{if: } bg_c<=0.5 \\[2ex] 1-2*(1-fg_c)*(1-bg_c), &\text {otherwise} \end{cases} Overlay(fgc,bgc)=⎩ ⎨ ⎧2∗fgc∗bgc,1−2∗(1−fgc)∗(1−bgc),if: bgc<=0.5otherwise
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/oymxhxdn1l
柔光(Soft Light)
公式比较复杂,不好解释。效果类似叠加(overlay),但是程度要更缓一些。另外该效果的控制图层是fg(与大部分混合模式一样)。
如果想要融合图像和某个高频纹理,柔光往往是不错的选择。
S
o
f
t
L
i
g
h
t
(
f
g
c
,
b
g
c
)
=
{
b
g
c
−
(
1
−
2
∗
f
g
c
)
∗
b
g
c
∗
(
1
−
b
g
c
)
,
if:
f
g
c
<
=
0.5
b
g
c
+
(
2
∗
f
g
c
−
1
)
∗
(
D
(
b
g
c
)
−
b
g
c
)
,
otherwise
SoftLight(fg_c, bg_c) = \begin{cases} bg_c - (1-2*fg_c)*bg_c*(1-bg_c), &\text{if: } fg_c<=0.5 \\[2ex] bg_c + (2*fg_c-1)*(D(bg_c)-bg_c), &\text {otherwise} \end{cases}
SoftLight(fgc,bgc)=⎩
⎨
⎧bgc−(1−2∗fgc)∗bgc∗(1−bgc),bgc+(2∗fgc−1)∗(D(bgc)−bgc),if: fgc<=0.5otherwise
其中
D ( x ) = { ( ( 16 ∗ x − 12 ) ∗ x + 4 ) ∗ x , if: x < = 0.25 x , otherwise D(x) = \begin{cases} ((16*x-12)*x+4)*x, &\text{if: } x<=0.25 \\[2ex] \sqrt{x}, &\text {otherwise} \end{cases} D(x)=⎩ ⎨ ⎧((16∗x−12)∗x+4)∗x,x,if: x<=0.25otherwise
下图的两个注意点:
- 红色虚线是叠加模式,用于跟柔光做对比,可以看到柔光比叠加的曲线对比度小一些
- 当上下图层一样时,上面公式中D(x)的x<=0.25的条件不会触发。
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/ud2enyjmma
https://www.desmos.com/calculator/2elal9gopd
强光(Hard Light)
强光与叠加(Overlay)的主体公式是一样的,但是条件不一样,强光的控制图层是fg,而叠加是bg,注意区别。
H a r d L i g h t ( f g c , b g c ) = { 2 ∗ f g c ∗ b g c , if: f g c < = 0.5 1 − 2 ∗ ( 1 − f g c ) ∗ ( 1 − b g c ) , otherwise HardLight(fg_c, bg_c) = \begin{cases} 2*fg_c*bg_c, &\text{if: } fg_c<=0.5 \\[2ex] 1-2*(1-fg_c)*(1-bg_c), &\text {otherwise} \end{cases} HardLight(fgc,bgc)=⎩ ⎨ ⎧2∗fgc∗bgc,1−2∗(1−fgc)∗(1−bgc),if: fgc<=0.5otherwise
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/rbnfcptnyb
亮光(Vivid Light)
公式比较复杂,会产生有强烈截断现象的对比度增强效果。
V i v i d L i g h t ( f g c , b g c ) = { b g c − ( 1 − b g c ) ∗ ( 1 − 2 ∗ f g c ) 2 ∗ f g c , if: f g c < = 0.5 b g c + b g c ∗ ( 2 ∗ f g c − 1 ) 2 ∗ ( 1 − f g c ) , otherwise VividLight(fg_c, bg_c) = \begin{cases} bg_c - \frac{(1-bg_c)*(1-2*fg_c)}{2*fg_c}, &\text{if: } fg_c<=0.5 \\[2ex] bg_c + \frac{bg_c*(2*fg_c-1)}{2*(1-fg_c)}, &\text {otherwise} \end{cases} VividLight(fgc,bgc)=⎩ ⎨ ⎧bgc−2∗fgc(1−bgc)∗(1−2∗fgc),bgc+2∗(1−fgc)bgc∗(2∗fgc−1),if: fgc<=0.5otherwise
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/capvg8fb3p
线性光(Linear Light)
类似亮光,会产生有强烈截断现象的对比度增强效果,但曲线是线性的。
L i n e a r L i g h t ( f g c , b g c ) = b g + 2 ∗ f g − 1 LinearLight(fg_c, bg_c) =bg+2*fg-1 LinearLight(fgc,bgc)=bg+2∗fg−1
前景与背景为同一个图片时,等同于如下曲线调节:
https://www.desmos.com/calculator/kh3sslrzuf
点光(Pin Light)
根据前景图层的亮度信息,对背景图层进行色彩替代,至于替代成什么,参考如下公式即可,就不在语言上过多解释了,公式比语言更清楚。
P i n L i g h t ( f g c , b g c ) = { m i n ( b g , 2 ∗ f g ) , if: f g c < = 0.5 m a x ( b g , 2 ∗ f g − 1 ) , otherwise PinLight(fg_c, bg_c) = \begin{cases} min(bg, 2*fg), &\text{if: } fg_c<=0.5 \\[2ex] max(bg, 2*fg - 1), &\text {otherwise} \end{cases} PinLight(fgc,bgc)=⎩ ⎨ ⎧min(bg,2∗fg),max(bg,2∗fg−1),if: fgc<=0.5otherwise
实色混合(Hard Mix)
根据 fg+bg>=1 的条件进行判断,结果要么取1,要么取0。所以结果只有红、黄、蓝、青、黄、洋红、黑、白
八种颜色。
H a r d M i x ( f g c , b g c ) = { 1 , if: f g c + b g c > = 1 0 , otherwise HardMix(fg_c, bg_c) = \begin{cases} 1, &\text{if: } fg_c+bg_c>=1 \\[2ex] 0, &\text {otherwise} \end{cases} HardMix(fgc,bgc)=⎩ ⎨ ⎧1,0,if: fgc+bgc>=1otherwise
差值(Difference)
两图层之差的绝对值。
D i f f e r e n c e ( f g c , b g c ) = ∣ b g c − f g c ∣ Difference(fg_c, bg_c) =|bg_c-fg_c| Difference(fgc,bgc)=∣bgc−fgc∣
排除(Exclusion)
与差值
的效果类似,但是结果的对比度会低一些。
E x c l u s i o n ( f g c , b g c ) = f g c + b g c − 2 ∗ f g c ∗ b g c Exclusion(fg_c, bg_c) =fg_c + bg_c - 2*fg_c*bg_c Exclusion(fgc,bgc)=fgc+bgc−2∗fgc∗bgc
减去(Subtract)
背景减去前景。
S u b t r a c t ( f g c , b g c ) = b g c − f g c Subtract(fg_c, bg_c)=bg_c-fg_c Subtract(fgc,bgc)=bgc−fgc
划分(Divide)
背景除以前景。
D i v i d e ( f g c , b g c ) = b g c f g c Divide(fg_c, bg_c)=\frac{bg_c}{fg_c} Divide(fgc,bgc)=fgcbgc
下面几个是HSL混合模式,主要功能就是对背景图层色相、饱和度、明度等属性进行替换,替换为前景图层的相应值。
字面上似乎挺容易理解,很多资料会根据字面意思把色彩转到HSL或者HSV域来做替换行为,但实际上Photoshop不是这么干的。
具体可以参考Adobe早期的pdf reference,尽管这是为pdf软件写的说明,而非Photoshop,但里面有讲到这几个混合模式,需要依赖一些公用函数,截图如下:
色相(Hue)
背景图层的色相替换为前景图层的相应值。
H
u
e
B
l
e
n
d
(
f
g
c
,
b
g
c
)
=
S
e
t
L
u
m
(
S
e
t
S
a
t
(
f
g
c
,
S
a
t
(
b
g
c
)
)
,
L
u
m
(
b
g
c
)
)
HueBlend(fg_c, bg_c) = SetLum(SetSat(fg_c, Sat(bg_c)), Lum(bg_c))
HueBlend(fgc,bgc)=SetLum(SetSat(fgc,Sat(bgc)),Lum(bgc))
饱和度(Saturation)
背景图层的饱和度替换为前景图层的相应值。
S
a
t
u
r
a
t
i
o
n
B
l
e
n
d
(
f
g
c
,
b
g
c
)
=
S
e
t
L
u
m
(
S
e
t
S
a
t
(
b
g
c
,
S
a
t
(
f
g
c
)
)
,
L
u
m
(
b
g
c
)
)
SaturationBlend(fg_c, bg_c) = SetLum(SetSat(bg_c, Sat(fg_c)), Lum(bg_c))
SaturationBlend(fgc,bgc)=SetLum(SetSat(bgc,Sat(fgc)),Lum(bgc))
颜色(Color)
背景图层的色相和饱和度替换为前景图层的相应值。
C
o
l
o
r
B
l
e
n
d
(
f
g
c
,
b
g
c
)
=
S
e
t
L
u
m
(
f
g
c
,
L
u
m
(
b
g
c
)
)
ColorBlend(fg_c, bg_c) = SetLum(fg_c, Lum(bg_c))
ColorBlend(fgc,bgc)=SetLum(fgc,Lum(bgc))
明度(Luminosity)
背景图层的明度替换为前景图层的相应值。
L
u
m
i
n
o
s
i
t
y
B
l
e
n
d
(
f
g
c
,
b
g
c
)
=
S
e
t
L
u
m
(
b
g
c
,
L
u
m
(
f
g
c
)
)
LuminosityBlend(fg_c, bg_c) = SetLum(bg_c, Lum(fg_c))
LuminosityBlend(fgc,bgc)=SetLum(bgc,Lum(fgc))
代码
# -*- coding: utf-8 -*-
import numpy as np
EPS = 1e-5
# ------ util functions ------
def get_luminance(bgr):
return bgr[..., 2] * 0.3 + bgr[..., 1] * 0.59 + bgr[..., 0] * 0.11
def get_saturation(bgr):
min_color = np.minimum(np.minimum(bgr[..., 0], bgr[..., 1]), bgr[..., 2])
max_color = np.maximum(np.maximum(bgr[..., 0], bgr[..., 1]), bgr[..., 2])
return max_color - min_color
def clip_color(bgr):
lumi = get_luminance(bgr)
min_color = np.minimum(np.minimum(bgr[..., 0], bgr[..., 1]), bgr[..., 2])
max_color = np.maximum(np.maximum(bgr[..., 0], bgr[..., 1]), bgr[..., 2])
idx1 = min_color < 0.
idx2 = max_color > 1.
lumi = lumi[..., np.newaxis]
min_color = min_color[..., np.newaxis]
max_color = max_color[..., np.newaxis]
color = bgr.copy()
color[idx1] = (lumi + (color - lumi) * lumi /
np.maximum(lumi - min_color, EPS))[idx1]
color[idx2] = (lumi + (color - lumi) * (1. - lumi) /
np.maximum(max_color - lumi, EPS))[idx2]
return color
def set_luminance(bgr, lumi):
diff = lumi - get_luminance(bgr)
diff = diff[..., np.newaxis]
color = bgr + diff
color = clip_color(color)
return color
def set_saturation(bgr, sat):
color = bgr.copy()
h, w, c = color.shape
color = color.reshape([-1, c])
try:
sat = sat.reshape([-1])
except:
pass
idx_min = np.argmin(color, axis=1)
idx_max = 2 - np.argmax(color[..., ::-1], axis=1)
idx_mid = np.ones_like(idx_min) * 3 - (idx_min + idx_max)
idx_all = np.array([[0, 1, 2]]).repeat(repeats=h * w, axis=0)
idx_min = idx_all == idx_min[..., np.newaxis]
idx_max = idx_all == idx_max[..., np.newaxis]
idx_mid = idx_all == idx_mid[..., np.newaxis]
min_color = color[idx_min]
max_color = color[idx_max]
mid_color = color[idx_mid]
color[idx_mid] = (mid_color - min_color) * sat / np.maximum(
max_color - min_color, EPS)
color[idx_max] = sat
color[idx_min] = 0.
idx = max_color == min_color
color[idx, :] = 0.
color = color.reshape([h, w, c])
return color
# ------ blend functions ------
def normal_blend(fg, bg):
return fg
def darken_blend(fg, bg):
return np.minimum(fg, bg)
def multiply_blend(fg, bg):
return fg * bg
def color_burn_blend(fg, bg):
return 1. - np.minimum((1. - bg) / np.maximum(fg, EPS), 1.)
def linear_burn_blend(fg, bg):
return np.maximum(fg + bg - 1., 0)
def darker_color_blend(fg, bg):
fg_lumi = get_luminance(fg)
bg_lumi = get_luminance(bg)
idx = fg_lumi <= bg_lumi
res = bg.copy()
res[idx] = fg[idx]
return res
def lighten_blend(fg, bg):
return np.maximum(fg, bg)
def screen_blend(fg, bg):
return 1. - (1. - fg) * (1. - bg)
def color_dodge_blend(fg, bg):
return np.minimum(bg / np.maximum(1. - fg, EPS), 1.)
def linear_dodge_blend(fg, bg):
return np.minimum(fg + bg, 1.)
def lighter_color_blend(fg, bg):
fg_lumi = get_luminance(fg)
bg_lumi = get_luminance(bg)
idx = fg_lumi >= bg_lumi
res = bg.copy()
res[idx] = fg[idx]
return res
def overlay_blend(fg, bg):
idx = bg <= 0.5
res = 1. - 2. * (1. - fg) * (1. - bg)
res[idx] = (2. * fg * bg)[idx]
return res
def soft_light_blend(fg, bg):
def _fun(x):
return ((16. * x - 12.) * x + 4.) * x
idx_1 = fg <= 0.5
idx_2 = bg <= 0.25
res = fg.copy()
res[idx_1] = (bg - (1 - 2. * fg) * bg * (1. - bg))[idx_1]
idx = (~idx_1) & idx_2
res[idx] = (bg + (2. * fg - 1.) * (_fun(bg) - bg))[idx]
idx = (~idx_1) & (~idx_2)
res[idx] = (bg + (2. * fg - 1.) * (np.sqrt(bg) - bg))[idx]
return res
def hard_light_blend(fg, bg):
idx = fg <= 0.5
res = 1. - 2. * (1. - fg) * (1. - bg)
res[idx] = (2. * fg * bg)[idx]
return res
def vivid_light_blend(fg, bg):
idx = fg <= 0.5
res1 = bg - (1. - bg) * (1. - 2. * fg) / np.maximum(2. * fg, EPS)
res = bg + bg * (2. * fg - 1.) / np.maximum(2. * (1. - fg), EPS)
res[idx] = res1[idx]
res = np.clip(res, 0., 1.)
return res
def linear_light_blend(fg, bg):
res = bg + 2. * fg - 1.
res = np.clip(res, 0., 1.)
return res
def pin_light_blend(fg, bg):
idx = fg <= 0.5
res = np.maximum(bg, 2. * fg - 1.)
res[idx] = np.minimum(bg, 2. * fg)[idx]
return res
def hard_mix_blend(fg, bg):
idx = fg + bg >= 1. # NOTE: greater than or equal
res = np.zeros_like(fg)
res[idx] = 1.
return res
def difference_blend(fg, bg):
return np.abs(bg - fg)
def exclusion_blend(fg, bg):
return fg + bg - 2. * fg * bg
def subtract_blend(fg, bg):
res = bg - fg
res = np.maximum(res, 0.)
return res
def divide_blend(fg, bg):
return bg / np.maximum(fg, EPS)
def hue_blend(fg, bg):
return set_luminance(set_saturation(fg, get_saturation(bg)),
get_luminance(bg))
def saturation_blend(fg, bg):
return set_luminance(set_saturation(bg, get_saturation(fg)),
get_luminance(bg))
def color_blend(fg, bg):
return set_luminance(fg, get_luminance(bg))
def luminosity_blend(fg, bg):
return set_luminance(bg, get_luminance(fg))
RGBA混合(图层带透明度)
Porter(1984)在alpha融合相关的理论中,根据两个图层的透明度关系,把图像划分为如下四个区域:
其中有三个区域是我们可见的:
- description = A ∩ B ‾ A \cap \overline{B} A∩B,area = α A ( 1 − α B ) \alpha_A(1-\alpha_B) αA(1−αB),color = c A c_A cA
- description = A ‾ ∩ B \overline{A} \cap B A∩B,area = ( 1 − α A ) α B (1-\alpha_A)\alpha_B (1−αA)αB,color = c B c_B cB
- description = A ∩ B A \cap B A∩B,area = α A α B \alpha_A\alpha_B αAαB,color = B l e n d ( c A , c B ) Blend(c_A,c_B) Blend(cA,cB)
结合alpha融合的理论,以及上述区域划分,在RGBA情况下混合公式的通用计算方式为:
o
u
t
α
=
f
g
α
+
b
g
α
−
f
g
α
∗
b
g
α
o
u
t
c
=
f
g
c
∗
f
g
α
∗
(
1
−
b
g
α
)
+
b
g
c
∗
b
g
α
∗
(
1
−
f
g
α
)
+
f
g
α
∗
b
g
α
∗
B
l
e
n
d
(
f
g
c
,
b
g
c
)
o
u
t
α
\begin{aligned} out_\alpha &= fg_\alpha + bg_\alpha - fg_\alpha * bg_\alpha \\[2ex] out_c &= \frac {fg_c * fg_\alpha*(1-bg_\alpha) + bg_c * bg_\alpha*(1-fg_\alpha) + fg_\alpha * bg_\alpha *Blend(fg_c, bg_c)} {out_\alpha} \end{aligned}
outαoutc=fgα+bgα−fgα∗bgα=outαfgc∗fgα∗(1−bgα)+bgc∗bgα∗(1−fgα)+fgα∗bgα∗Blend(fgc,bgc)
上述是非预乘的公式,根据之前的符号约定:
c
o
l
o
r
c
α
=
c
o
l
o
r
c
∗
c
o
l
o
r
α
color_{c\alpha} = color_{c}*color_{\alpha}
colorcα=colorc∗colorα。
那么预乘的公式为:
o
u
t
α
=
f
g
α
+
b
g
α
−
f
g
α
∗
b
g
α
o
u
t
c
α
=
f
g
c
α
(
1
−
b
g
α
)
+
b
g
c
α
∗
(
1
−
f
g
α
)
+
f
g
α
∗
b
g
α
∗
B
l
e
n
d
(
f
g
c
,
b
g
c
)
\begin{aligned} out_\alpha &= fg_\alpha + bg_\alpha - fg_\alpha * bg_\alpha \\[2ex] out_{c\alpha} &= fg_{c\alpha}(1-bg_\alpha) + bg_{c\alpha}*(1-fg_\alpha) + fg_\alpha * bg_\alpha *Blend(fg_c, bg_c) \end{aligned}
outαoutcα=fgα+bgα−fgα∗bgα=fgcα(1−bgα)+bgcα∗(1−fgα)+fgα∗bgα∗Blend(fgc,bgc)
参考资料
Adobe官网资料(英文版):https://helpx.adobe.com/photoshop/using/blending-modes.html
Adobe官网资料(中文版,有些翻译不太准确):https://helpx.adobe.com/cn/photoshop/using/blending-modes.html
PDF reference:https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf
Porter(1984),https://dl.acm.org/doi/pdf/10.1145/964965.808606
https://www.w3.org/TR/compositing-1/