Photoshop中的混合模式公式详解

图层混合简介

在这里插入图片描述

图层混合(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)=fgcbgc

前景与背景为同一个图片时,等同于如下曲线调节:
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,1min(fgc1bgc,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+bgc1,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(1fgc)(1bgc)

前景与背景为同一个图片时,等同于如下曲线调节:
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(1fgcbgc,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)= 2fgcbgc,12(1fgc)(1bgc),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(12fgc)bgc(1bgc),bgc+(2fgc1)(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)= ((16x12)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)= 2fgcbgc,12(1fgc)(1bgc),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)= bgc2fgc(1bgc)(12fgc),bgc+2(1fgc)bgc(2fgc1),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+2fg1

前景与背景为同一个图片时,等同于如下曲线调节:
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,2fg),max(bg,2fg1),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)=bgcfgc

排除(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+bgc2fgcbgc

减去(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)=bgcfgc

划分(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} AB,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 AB,area = ( 1 − α A ) α B (1-\alpha_A)\alpha_B (1αA)αB,color = c B c_B cB
  • description = A ∩ B A \cap B AB,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αfgcfgα(1bgα)+bgcbgα(1fgα)+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α=colorccolorα
那么预乘的公式为:
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α(1bgα)+bgcα(1fgα)+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/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/900191.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信号与系统学习:傅里叶级数

一、基本概念 1. 什么是傅里叶级数&#xff1f; 傅里叶级数是一种数学工具&#xff0c;可以将一个周期函数分解为一系列正弦和余弦函数&#xff08;即三角函数&#xff09;的和。这些正弦和余弦函数的频率是原函数的整数倍。 2. 为什么要使用傅里叶级数&#xff1f; 信号分…

J2学习打卡

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 ResNet50V2 import torch import torch.nn as nn import torch.nn.functional as Fclass Bottleneck(nn.Module):expansion 4def __init__(self, inplanes,…

软考(中级-软件设计师)计算机系统篇(1024)

#1024程序员节|正文# 六、树和二叉树 6.1 树的基本概念 描述结果结点的度子结点的个数树的度最大结点的度叶子结点没有子结点的结点内部结点除根结点和叶子结点外的结点父节点有子结点的结点子节点有父结点的结点兄弟节点有同一个父结点的结点层次4层 6.2 二叉树的基本概念…

AI时代LabVIEW程序员的未来出路

随着GPT等AI技术的迅速发展&#xff0c;AI已经能够自动完成大量的代码生成工作&#xff0c;这无疑给LabVIEW程序员带来了新的挑战和机遇。尽管AI能够替代部分编程工作&#xff0c;LabVIEW程序员依然可以通过以下几方面找到出路&#xff1a; 复杂系统集成&#xff1a; AI可以帮助…

【软考高级架构】关于分布式数据库缓存redis的知识要点汇总

一.分布式数据库的含义 分布式数据库缓存指的是在高并发的环境下&#xff0c;为了减轻数据库的压力和提高系统响应时间&#xff0c;在数据库系统和应用系统之间增加一个独立缓存系统。 二.常见的缓存技术 &#xff08;1&#xff09;MemCache: Memcache是一个高性能的分布式的内…

你对MySQL的having关键字了解多少?

在MySQL中&#xff0c;HAVING子句用于在数据分组并计算聚合函数之后&#xff0c;对结果进行进一步的过滤。它通常与GROUP BY子句一起使用&#xff0c;以根据指定的条件过滤分组。HAVING子句的作用类似于WHERE子句&#xff0c;但WHERE子句是在数据被聚合之前进行过滤&#xff0c…

闯关leetcode——205. Isomorphic Strings

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/isomorphic-strings/ 内容 Given two strings s and t, determine if they are isomorphic. Two strings s and t are isomorphic if the characters in s can be replaced to get t. All occur…

2021亚洲机器学习会议:面向单阶段跨域检测的域自适应YOLO(ACML2021)

原文标题&#xff1a;Domain Adaptive YOLO for One-Stage Cross-Domain Detection 中文标题&#xff1a;面向单阶段跨域检测的域自适应YOLO 1、Abstract 域转移是目标检测器在实际应用中推广的主要挑战。两级检测器的域自适应新兴技术有助于解决这个问题。然而&#xff0c;两级…

现场总是发生急停,很可能是PLC和设置间网络中断

如果你的现场总是发生急停&#xff0c;很可能是PLC和设置间网络中断&#xff0c;本文用一个真实案例告诉你问题背后的原因和解决方法&#xff01; 这是一台生产汽车配件的机器&#xff0c;使用1500F的控制器连接机器人控制器&#xff0c;现场装置总会莫名其妙的发生急停故障。…

部署前后端分离若依项目--CentOS7Docker版

一、准备 centos7虚拟机或服务器一台 若依前后端分离项目&#xff1a;可在下面拉取 RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分离权限管理系统&#xff0c;同时提供了 Vue3 的版本 二、环…

JavaEE进阶----19.<Mybatis进阶(动态SQL)>

详解动态SQL <if>标签、<trim>标签、<where>标签、<set>标签、<foreach>标签、<include>标签 & <SQL>标签 MySQL&#xff08;进阶&#xff09; 一、动态SQL 也就是SQL语句中指定的属性&#xff0c;若我们不想输入进行查询&…

查缺补漏----分组交换所需时间计算

总结以及图片来源&#xff1a;b站湖科大真题计网讲解 对于报文交换&#xff0c;路由器完整接收整个报文后&#xff0c;才能对报文进行转发。对于分组交换&#xff0c;则是将报文划为更小的分组进行传送&#xff0c;路由器边接收分组边转发分组。 报文交换&#xff1a; 分组交换…

文件上传漏洞及安全

文件上传 文件上传安全指的是攻击者通过利用上传实现后门的写入连接后门进行权限控制的安全问题&#xff0c;对于如何确保这类安全问题&#xff0c;一般会从原生态功能中的文件内容&#xff0c;文件后缀&#xff0c;文件类型等方面判断&#xff0c;但是漏洞可能不仅在本身的代码…

Java的查找算法和排序算法

Java的查找算法和排序算法 一、查找算法1. 基本查找a. 示例 2. 二分查找a. 示例 3. 插值查找4. 斐波那契查找5. 分块查找a. 示例 二、排序算法1. 冒泡排序a. 示例 2. 选择排序a. 示例 3、插入排序a. 示例 4. 快速排序&#xff08;效率最高&#xff09;a. 示例 一、查找算法 1.…

期权懂|2024年期权最新止损策略有哪些?

本期让我懂 你就懂的期权懂带大家来了解&#xff0c;2024年期权最新止损策略有哪些&#xff1f;有兴趣的朋友可以看一下。期权小懂每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 2024年期权最新止损策略有哪些&#xff1f; 一、浮亏比例…

Pandas模块之垂直或水平交错条形图

目录 df.plot() 函数Pandas模块之垂直条形图Pandas模块之水平交错条形图 df.plot() 函数 df.plot() 是 Pandas 中的一个函数&#xff0c;用于绘制数据框中的数据。它是基于 Matplotlib 库构建的&#xff0c;可以轻松地创建各种类型的图表&#xff0c;包括折线图、柱状图、散点…

PCM5102A具有PLL和32位、384kHz PCM/I2S接口的2.1VRMS、112dB音频立体声DAC

PCM5102A外观和丝印 1 特性 1•超低带外噪声 •具有BCK基准的高性能集成音频锁相环(PLL)&#xff0c;可在内部生成SCK •直接线路电平2.1VRMS输出 •无需隔直电容 •线路电平输出支持低至1kΩ的负载 •智能静音系统&#xff1b;软斜升或斜降搭配模拟静音&#xff0c;实现120dB…

深度学习实战项目】基于OPenCV的人脸识别考勤系统软件开发【python源码+UI界面+功能源码详解】

背景及意义 人脸识别&#xff08;Face Recognition&#xff09;是基于人的脸部特征信息进行身份识别的一种生物识别技术&#xff0c;可以用来确认用户身份。本文详细介绍了人脸识别基本的实现原理&#xff0c;并且基于python与pyqt开发了人脸识别与信息管理软件&#xff0c;主要…

Go第三方框架--gorm框架(一)

前言 orm模型简介 orm模型全称是Object-Relational Mapping&#xff0c;即对象关系映射。其实就是在原生sql基础之上进行更高程度的封装。方便程序员采用面向对象的方式来操作数据库&#xff0c;将表映射成对象。 这种映射带来几个好处&#xff1a; 代码简洁&#xff1a;不用…

AVL树介绍与构建

目录 AVL树的概念 二叉树的构建 平衡因子的更新 旋转 左单旋 旋转过程 左单旋代码 右单旋 旋转过程 右单旋代码 左右双旋 发生情况 抽象图 具体图 平衡因子更新 左右双旋代码 右左双旋 右左双旋旋代码 验证测试AVL树 测试成员函数 测试代码 AVL树实现代码…