PS图层混合模式超详细解答-图层混合模式的原理

前言

本教程非常详细,请用心看完
本教程如果有如何问题,欢迎评论区留言讨论
本教程为了避免冗余,一些不必要的截图就省略了
本教程只讨论8bit的情形下的混合

未经许可,不可转载

饮茶

在一切开始之前,我们先泡一杯茶🍵
泡一杯茶需要一杯开水,一袋茶叶,如果太苦我们还需要一些水来兑一下


好的我们来温习一下泡茶的过程

🍵1:我们准备一壶开水
🍵2:我们准备一包茶叶,放多少取决于个人口味
🍵3:我们把茶叶放到开水杯子中,在这个过程中可以静置或者搅拌也可以煮一下,等待茶叶和热水充分融合
🍵4:我们想喝茶就把茶从茶壶中倒到一个杯子中,如果感觉苦了就加一点开水
🍵5:最后我们会得到一杯符合我们口味的茶

一般我们泡茶就是这几个步骤,你会泡茶吗,如果你会,那么恭喜你,你已经掌握了图层混合模式。

🖼一壶开水相当于基础图层
🖼一包茶叶相当于混合图层,它将和一壶开水组成一个新的饮品-茶
🖼茶叶放了多少就是填充,可以是一包,半包,也可以不放,放的越多,得到的那壶茶就越浓郁
🖼静置或者搅拌也可以煮一下就是混合模式,这里的方式多种多样
🖼喝茶的时候倒到杯子里面,茶水会和开水混合,茶水占据的比例就是不透明度
🖼一杯符合我们口味的茶就是结果图层

图层混合模式在PS中一共有272727种,对分组来说还有一种叫做穿透,另外在其他一些菜单中还有一些不常见的,比如笔刷菜单中的背后和清除,以及计算工具中的相加和相减。
由此,我们便可以得出,PS中一共有27+1+2+2=3227+1+2+2=3227+1+2+2=32种图层混合模式。


接下来,我们将会对每一种模式进行详细解读。

什么是图层混合模式

图层混合模式究竟是什么,他的本质代表什么?

图层混合模式的本质就是对像素的运算,就像是泡茶的过程中的搅拌

比如x+y=zx+y=zx+y=z1+1=21+1=21+1=2

一种图层混合模式,就代表一种运算方式,这种运算方式会把像素点111和像素点222融合成为像素点333


在本教程中,对公式本身进行讨论,一些所谓的衍生概念我们尽量不提,因为会造成冗余,比如增色减色等衍生概念,我们只对本质问题讨论。如果某些过程在公式当中已经包含了,我们就不再截图了,因为不想冗余,比如图层混合模式之间的相互转换。

前置概念

像素点

像素点事PS可以处理的最小单元,为了方便我们使用PixPixPix或者CCC来表示

图层

像素点可以组合形成图层也就是由点到面,图层我们使用LayerLayerLayer来表示
那么一个m×nm\times nm×n的图层和像素的关系可以表示为
Layer={Pix(1,1)⋯Pix(1,n)⋮⋮Pix(i,1)⋯Pix(i,n)⋮⋮Pix(m,1)⋯Pix(m,n)}Layer=\left\{ \begin{aligned}&Pix_{(1,1)} &&\cdots&&Pix_{(1,n)}\\&\vdots &&&&\vdots\\ &Pix_{(i,1)} &&\cdots&&Pix_{(i,n)}\\&\vdots && &&\vdots\\ &Pix_{(m,1)} &&\cdots&&Pix_{(m,n)}\\\end{aligned}\right\}Layer= Pix(1,1)Pix(i,1)Pix(m,1)Pix(1,n)Pix(i,n)Pix(m,n)
图层混合模式 一共涉及三个图层分别是
1、基础图层或者叫做底图
就是下方的图层
我们使用英文单词below的开头字母B来表示,符号是LayerBLayer_BLayerB
2、混合图层或者叫做调整图层绘画图层
混合图层或者叫绘画图层,就是上方的图层
我们使用英文单词above的开头字母A来表示,符号是LayerALayer_ALayerA
3、结果图层
就是以何种方式处理之后的结果,他是通过LayerBLayer_BLayerBLayerALayer_ALayerA的结合来表示。
Image

图层大小

PS新建的过程中,我们可以看到一些图片属性,其中就有图片大小
这里我们讨论的图层大小都默认是m×nm\times nm×n,也就是高度是mmm宽度是nnn的图层大小

色彩空间

在图层混合模式中,涉及到的色彩空间一共有两种 ,第一是RGB,也就是红绿蓝三通道,第二是HSY,也就是色相,饱和度,明度。
如果是RGB空间则使用PixPixPix来表示像素点,如果是HSY空间则使用CCC来表示像素点。

RGB

如果使用RGB来表示一个像素点那么像素点可以表示为
Pix=(红通道,绿通道,蓝通道)Pix=(红通道,绿通道,蓝通道)Pix=(红通道,绿通道,蓝通道)
为了便于和后面的简称区分开,我们使用颜色Red、Green、Blue和通道的英文字母channel的开头字母来表示各个通道。于是上面的公式也可以表示为
Pix=(RC,GC,BC)Pix= (RC,GC,BC)Pix=(RC,GC,BC)
在8bit的图像中,使用8个字节来表示一个像素,于是可以表示28=2562^8=25628=256个数字,也就是[0,255][0,255][0,255]这个区间内的数字。如果数字越大那么这个通道就越亮。

几个典型的颜色

红色(255,0,0)(255,0,0)(255,0,0),黑色(0,0,0)(0,0,0)(0,0,0),白色(255,255,255)(255,255,255)(255,255,255),中性灰色(128,128,128)(128,128,128)(128,128,128)
如果我们对每个通道都用归一化操作,

那我们可以得到RC255⇒rc∈[0,1]\dfrac{RC}{255}\Rightarrow rc\in [0,1]255RCrc[0,1]

那么以上的四种颜色就可以表示为
红色(1,0,0)(1,0,0)(1,0,0),黑色(0,0,0)(0,0,0)(0,0,0),白色(1,1,1)(1,1,1)(1,1,1),中性灰色(0.5,0.5,0.5)(0.5,0.5,0.5)(0.5,0.5,0.5)
Pix=(RC,GC,BC)=(rc,gc,bc)Pix= (RC,GC,BC)=(rc,gc,bc)Pix=(RC,GC,BC)=(rc,gc,bc)

一些运算规则

如果一个像素需要扩大或者缩小xxx

通道级别

通道数值乘以xxx
RC×xRC\times xRC×x

像素级别

如果一个像素点Pix×xPix\times xPix×x则表示其中所有通道数值都乘xxx
也就是说
Pix×x=(RC×x,GC×x,BC×x)Pix\times x = (RC\times x,GC\times x,BC\times x)Pix×x=(RC×x,GC×x,BC×x)

图层级别

如果是图层Layer×xLayer\times xLayer×x则表示所有像素点都乘xxx
也就是说
Layer×x={Pix(1,1)×x⋯Pix(1,n)×x⋮⋮Pix(1,n)×x⋯Pix(i,n)×x⋮⋮Pix(m,1)×x⋯Pix(m,n)×x}={(RC(1,1)×x,GC(1,1)×x,BC(1,1)×x)⋯(RC(1,n)×x,GC(1,n)×x,BC(1,n)×x)⋮⋮(RC(1,n)×x,GC(1,n)×x,BC(1,n)×x)⋯(RC(i,n)×x,GC(i,n)×x,BC(i,n)×x)⋮⋮(RC(m,1)×x,GC(m,1)×x,BC(m,1)×x)⋯(RC(m,n)×x,GC(m,n)×x,BC(m,n)×x)}\begin{aligned} Layer\times x&=\left\{ \begin{aligned} &Pix_{(1,1)}\times x&&\cdots&&Pix_{(1,n)}\times x\\&\vdots && &&\vdots\\ &Pix_{(1,n)}\times x &&\cdots&&Pix_{(i,n)}\times x\\&\vdots && &&\vdots\\ &Pix_{(m,1)}\times x &&\cdots&&Pix_{(m,n)}\times x\\ \end{aligned}\right\}\\\\&=\left\{ \begin{aligned} &(RC_{(1,1)}\times x,GC_{(1,1)}\times x,BC_{(1,1)}\times x)&&\cdots&&(RC_{(1,n)}\times x,GC_{(1,n)}\times x,BC_{(1,n)}\times x)\\&\vdots && &&\vdots\\ &(RC_{(1,n)}\times x,GC_{(1,n)}\times x,BC_{(1,n)}\times x) &&\cdots&&(RC_{(i,n)}\times x,GC_{(i,n)}\times x,BC_{(i,n)}\times x)\\&\vdots && &&\vdots\\ &(RC_{(m,1)}\times x,GC_{(m,1)}\times x,BC_{(m,1)}\times x) &&\cdots&&(RC_{(m,n)}\times x,GC_{(m,n)}\times x,BC_{(m,n)}\times x)\\ \end{aligned}\right\} \end{aligned}Layer×x= Pix(1,1)×xPix(1,n)×xPix(m,1)×xPix(1,n)×xPix(i,n)×xPix(m,n)×x = (RC(1,1)×x,GC(1,1)×x,BC(1,1)×x)(RC(1,n)×x,GC(1,n)×x,BC(1,n)×x)(RC(m,1)×x,GC(m,1)×x,BC(m,1)×x)(RC(1,n)×x,GC(1,n)×x,BC(1,n)×x)(RC(i,n)×x,GC(i,n)×x,BC(i,n)×x)(RC(m,n)×x,GC(m,n)×x,BC(m,n)×x)

辅助计算程序

为了辅助我们计算并且验证这些理论公式和PS中实际运行的结果是否一致,我们借助java代码来实现具体的计算过程,手动计算可以,但是浪费时间,我们有更简单的方式。
我们定义一个java类BlendColor,这个类用RGB作为基础,每次计算都以RGB存储,如果需要其他表达式,则我们通过RGB转换。

public class BlendColor {

    public ColorItem red;
    public ColorItem green;
    public ColorItem blue;
    public List<ColorItem> orderColorList;

    public BlendColor(double redv, double greenv, double bluev) {
        ColorItem red = new ColorItem("red", redv);
        ColorItem green = new ColorItem("green", greenv);
        ColorItem blue = new ColorItem("blue", bluev);
        this.orderColorList = new ArrayList<ColorItem>();
        this.red = red;
        this.green = green;
        this.blue = blue;
        this.orderColorList.add(red);
        this.orderColorList.add(green);
        this.orderColorList.add(blue);

        // 排序
        Collections.sort(this.orderColorList);
    }

    ...

}
HSY

如果使用HSY来表示一个像素点
C=(Hue,Stratuation,Luminosity)=(H,S,Y)C=(Hue,Stratuation,Luminosity)=(H,S,Y)C=(Hue,Stratuation,Luminosity)=(H,S,Y)
为了方便统一表述,在涉及到HSY时,如果我们需要表达RGB数值,我们使用C=(Cred,Cgreen,Cblue)  ⟺  (RC,GC,BC)C=(C_{red},C_{green},C_{blue})\iff(RC,GC,BC)C=(Cred,Cgreen,Cblue)(RC,GC,BC)
Hue也就是色相,取值范围是[0∘,360∘][0^{\circ},360^{\circ}][0,360],决定是什么颜色
Stratuation饱和度,取值范围是[0,100][0,100][0,100],决定这种颜色鲜艳程度或者换一种说法-颜色有多少
Luminosity明度,取值范围是[0,100][0,100][0,100],决定这种颜色有多亮
上述两种色彩空间的转换关系如下:

Hue={0若 max=min60∘×gc−bcmax−min若 rc=max 且 gc≥bc60∘×gc−bcmax−min+360∘若 rc=max 且 gc<bc60∘×bc−rcmax−min+120∘若 gc=max60∘×rc−gcmax−min+240∘若 bc=maxHue = \left\{ \begin{aligned}&0& 若\space max = min \\&60^{\circ}\times \dfrac{gc-bc}{max-min} & 若\space rc=max\space且\space gc \geq bc\\&60^{\circ}\times \dfrac{gc-bc}{max-min} +360^{\circ}& 若\space rc=max\space且\space gc<bc\\&60^{\circ}\times \dfrac{bc-rc}{max-min} +120^{\circ}& 若 \space gc=max\\&60^{\circ}\times \dfrac{rc-gc}{max-min} +240^{\circ}& 若 \space bc=max\\\end{aligned}\right.Hue= 060×maxmingcbc60×maxmingcbc+36060×maxminbcrc+12060×maxminrcgc+240 max=min rc=max  gcbc rc=max  gc<bc gc=max bc=max

Stratuation={max−min}Stratuation = \left\{ \begin{aligned}max-min\end{aligned}\right\}Stratuation={maxmin}
Luminosity={0.3rc+0.59gc+0.11bc}Luminosity = \left\{ \begin{aligned}0.3rc+0.59gc+0.11bc\end{aligned}\right\}Luminosity={0.3rc+0.59gc+0.11bc}
代码实现如下

public double getHue() {
    ColorItem max = this.getMax();
    ColorItem mid = this.getMid();
    ColorItem min = this.getMin();
    if (Double.doubleToLongBits(max.value) == Double.doubleToLongBits(min.value)) {
        return 0;
    }
    double hueAbs = (mid.value - min.value) / (max.value - min.value);
    if (max.name == "red") {
        if (this.green.value > this.blue.value) {
            return 60 *hueAbs;
            } else {
            return -60* hueAbs + 360;
            }
        }
    if (max.name == "green") {
            return 60 *hueAbs + 120;
        }
    if (max.name == "blue") {
            return 60* hueAbs + 240;
        }
        return 0;
    }

public double getLum() {
    double lum = 0.3 * this.red.value + 0.59 * this.green.value + 0.11 * this.blue.value;
    return lum;
}

public double getSat() {
    double max = this.getMax().value;
    double min = this.getMin().value;
    return max - min;
}

不透明度和填充

不透明度的英文单词是OpacityOpacityOpacity,填充的英文单词是fillfillfill
两者在图层混合模式中有时相同有时不同。具体我们会在后面说明。
简单描述二者的区别,就是,不透明度就是将基础图层B和基础图层A,使用Opacity比例进行混合,公式如下:
Opacity(b,a)=op×b+(1−op)×BlendMode(b,a)Opacity(b,a)= op\times b + (1-op)\times BlendMode(b,a)Opacity(b,a)=op×b+(1op)×BlendMode(b,a)
其中opopop代表不透明度

代码实现
public static BlendColor Opacity(BlendColor colorBase, BlendColor colorResult, double opacity) {
      double redOpacity = colorResult.red.value *opacity + colorBase.red.value* (1 - opacity);
      double greenOpacity = colorResult.green.value *opacity + colorBase.green.value* (1 - opacity);
      double blueOpacity = colorResult.blue.value *opacity + colorBase.blue.value* (1 - opacity);
      return new BlendColor(redOpacity, greenOpacity, blueOpacity);
  }

如果是FillFillFill
Fill(b,a)=BlendMode(b,a×fill)Fill(b,a)= BlendMode(b,a\times fill)Fill(b,a)=BlendMode(b,a×fill)

这只是一个原则公式,具体情况要视具体模式确定,每一种计算方式都有细微区别。我们在此处不给出具体的公式和代码,但是我们在每一种中,我们会具体实现。
填充的计算方式有八个和其他的不同,并且此时和不透明度的计算方法不一样。除了这八个,不透明度和填充变化的结果是相同的。
他们分别是

  • 线性加深
  • 颜色加深
  • 线性减淡
  • 颜色减淡
  • 线性光(线性减淡+线性加深)
  • 亮光(颜色减淡+颜色加深)
  • 实色混合(线性减淡+线性加深 或者 颜色减淡+颜色加深)
  • 差值

并且,填充的优先级高于不透明度,如果让两者一起产生效果则公式为:
Opacity(b,a)=op×b+(1−op)×Fill(b,a)Opacity(b,a)= op\times b + (1-op)\times Fill(b,a)Opacity(b,a)=op×b+(1op)×Fill(b,a)

如果使用一句话概括这二者的区别就是:
填充会让当前混合模式的效果弱化,弱化的程度取决于fillfillfill的大小。
不透明度决定了当前混合模式结果图层和基础图层混合的比例,opacity越大,结果图层占比越大。
结合前面的例子,填充越大放的茶叶就越多,不透明度越大,最后添加的水就越少

混合模式的组合

一般来说,混合模式的组合会以基础图层B或者混合图层A的中性灰分割线为界,组合两种不同的混合模式。

例如,强光模式,它是由正片叠底和滤色两种模式以混合图层的中性灰分割线为界完成组合
正片叠底r=Multiply(b,a)=b×ar=Multiply(b,a)=b\times ar=Multiply(b,a)=b×a
滤色r=Screen(b,a)=1−(1−b)(1−a)r=Screen(b,a)=1-(1-b)(1-a)r=Screen(b,a)=1(1b)(1a)
强光r=HardLight(b,a)={Multiply(b,2a)a≤0.5Screen(b,2(a−0.5))a>0.5={2baa≤0.51−2(1−b)(1−a)a>0.5\begin{aligned} r&=HardLight(b,a)\\\\&=\left\{ \begin{aligned}&Multiply(b,2a)&a\leq0.5\\ &Screen(b,2(a-0.5))&a > 0.5\end{aligned}\right.\\\\&=\left\{ \begin{aligned}&2ba&a\leq0.5\\ &1-2(1-b)(1-a)&a > 0.5\end{aligned}\right. \end{aligned}r=HardLight(b,a)={Multiply(b,2a)Screen(b,2(a0.5))a0.5a>0.5={2ba12(1b)(1a)a0.5a>0.5

映射面

对于任意的一个混合模式,如果我们将它产生的所有基础图层和混合图层的结果,都在一个三维坐标中标记出来,则我们就会得到一个由256×256=65536256\times 256=65536256×256=65536个点组成的平面,换句话说,不管什么混合模式,基础图层和混合图层是什么值,在8bit的模式下,都只有655366553665536种可能结果。

这个面就是映射面
下图是我们将每一种图层混合模式表达式输入MatlabMatlabMatlab之后MatlabMatlabMatlab为我们呈现的结果,xxx轴为基础图层,yyy轴为混合图层,zzz轴为结果图层,我们可以看到越大的带你则越白,111最白,越小的点越黑,000 最黑。于是我们就可以通过黑白程度来判断结果大小。

映射面示例

Image
映射面和原图面的对比
Image
使用混合模式后的映射面和原图映射面的对比

由上图可以得知,这是一种变暗模式并且在混合图层和基础图层中选择了比较小的作为结果,因为可以明显看到一部分平面在原平面(图中的蓝色平面)的下方

同图平面

如果我们把基础图层和混合图层取值相同的点都标记出来,我们会得到一个对角平面,这个平面就是我们所说的同图平面

同图平面以及其和映射面相交的图像
Image
图中绿色的平面就是同图平面,他和映射面的交线就是我们可以使用曲线工具模拟的曲线。

同图混合曲线

此时相当于a=ba=ba=b带入到某个图层混合模式的表达式中,
我们此处以线性加深为例
线性加深原本的表达式是

r=LinearBurn(b,a)=b−(1−a)=b+a−1r=LinearBurn(b,a)=b-(1-a)=b+a-1r=LinearBurn(b,a)=b(1a)=b+a1
b=ab=ab=a则表达式可以写成

r=(b,b)=b−(1−b)=b+b−1=2b−1\begin{aligned} r&=(b,b)\\&=b-(1-b)\\&=b+b-1\\&=2b-1 \end{aligned}r=(b,b)=b(1b)=b+b1=2b1

Image
此时,在坐标系中画出该图像就如同上图所示。

这时候我们只要打开曲线工具,然后拉一条曲线,和上图相同,就可以得到和这种混合模式在基础图层和混合图层相同的情况下的到结果图层相同的效果。
Image
同图平面和映射面的交线就是可以使用曲线工具模拟这种混合模式时画出的曲线。
同图平面有时候也可以作为分割平面,比如在实色混合模式的时候。

中性灰平面

如果我们把基础图层或者绘画图层中128中性灰的点都标记出来,我们会得到一个中性灰平面
这个平面一般作为结合两种混合模式的分割面

基础图层的中性灰平面

如果两种混合模式是以基础图层的取值作为分界依据的时候,使用此平面作为分割和结合依据。
Image

混合图层的中性灰平面

如果两种混合模式是以混合图层的取值作为分界依据的时候,使用此平面作为分割和结合依据。
同图平面,中性灰平面(基础中性灰平面,混图层中性灰平面)都是结合两种混合模式的依据。
Image

关系总揽

为了方便大家理解,我们在此给出27中图层混合模式的总览
Image

  • 可转换:代表两种混合模式可以在一定条件下等效另外一种
  • 可交换:底图和绘画图层交换,两种混合模式产生的结果相同,也就是互逆
  • 组成:一种混合模式可以由另外一种组成

特殊关系

从总览那幅图我们可以得出一些有趣的结论

就是有的混合模式在结合一些操作之后,结果等价于另外一种

有的图层混合模式和另外一种是互逆的关系

有些可以组合,有些对黑色无效,有些对白色无效,有些对中性灰无效

总之,他们的一切都蕴含在了公式中,如果你想搞清楚,就仔细研究一下。

可转换

可转换可能有些在实际操作中有偏差,因为在计算过程中可能出现大于1或者小于0的情况,导致被取舍。以下都是理论存在的情况。

  • 变亮⇔\Leftrightarrow变暗(三次负片)
  • 滤色⇔\Leftrightarrow正片叠底(三次负片)
  • 线性减淡⇔\Leftrightarrow线性加深(三次负片)
  • 颜色减淡⇔\Leftrightarrow颜色加深(三次负片)
  • 浅色⇔\Leftrightarrow深色(三次负片)
  • 线性加深⇔\Leftrightarrow减去(一次负片)
  • 颜色减淡⇔\Leftrightarrow划分(一次负片)
  • 颜色加深⇔\Leftrightarrow划分(两次负片)
互逆(可交换)
  • 叠加⇔\Leftrightarrow强光
  • 颜色⇔\Leftrightarrow明度
  • 实色混合⇔\Leftrightarrow线性光(当实色混合填充设置为0.5也就是50%)
组成
  • 叠加 === 正片叠底+++滤色
  • 强光 === 正片叠底+++滤色
  • 线性光 ===线性加深+++线性减淡
  • 实色混合 === 线性加深+++线性减淡
  • 亮光 === 颜色加深+++颜色减淡
  • 点光 === 变亮+++变暗
  • 柔光===系数2的伽马矫正 +++ 系数为0.5的伽马矫正

另外五种

  • 穿透
  • 背后
  • 擦除
  • 相加
  • 相减

图层混合模式的种类

色彩空间分类

分类方法有很多,如果按照色彩空间,可以分成两类,RGB和HSY

RGB

  • 除颜色组

HSY

  • 颜色组

是否产生新的数值

如果根据运算方式,可以分成两类,一类是替换式,一类是融合式

替换式

包括:

  • 正常组(正常,溶解)
  • 变暗组(变暗,深色)
  • 变亮组(变亮,浅色)
  • 颜色组(色相,饱和度,颜色,明度)
    这种式的特点是,不会产生新的东西,只会复用之前的东西

比如正常组,正常就是直接使用混合图层作为结果
变暗组的变暗是使用基础图层和混合图层中通道较小的值组成新的像素,深色则是比较基础图层和混合图层通道值的和取小的作为结果
颜色组,则是根据HSY色彩空间,直接替换 HSY的数值,并且再转换为RGB,本质也是没有新的东西产生。

融合式

包括:

  • 变暗组(正片叠底,颜色加深,线性加深)
  • 变亮组(滤色,颜色减淡,线性减淡)
  • 对比度组(叠加,柔光,强光,亮光,线性光,点光,实色混合)
  • 差值组(差值,排除,减去,划分)

虽然点光是变暗和变亮的混合,但是混合过程中做了处理,所以此处我们也按照融合式对待
此类的特点是,会产生新的数值。

融合式就是产生了新的数值,比如正片叠底,结果图层的数值由基础图层和混合图层的乘积得到,他是不同于基础图层和混合图层的值。

是否顺序相关

如果根据是否受到图层顺序影响
如果BlendMode(b,a)=BlendMode(a,b)BlendMode(b,a)=BlendMode(a,b)BlendMode(b,a)=BlendMode(a,b)
则说明结果不受图层顺序影响,前提是不调节fillfillfillOpacityOpacityOpacity

顺序无关

  • 变暗组 (变暗,线性加深,正片叠底,深色)
  • 变亮组 (变亮,线性减淡,滤色,浅色)
  • 对比度组 (实色混合)
  • 差值组(差值,排除)

顺序相关

  • 正常组 (正常,溶解)
  • 变暗组(颜色加深)
  • 变亮组(颜色减淡)
  • 对比度组(叠加,柔光,强光,亮光,线性光,点光)
  • 差值组(减去,划分)
  • 颜色组(色相,饱和度,颜色,明度)

是否fill和Opacity不同

也就是说,调节填充的百分比,和不透明度的百分比,即使数值相同,结果也有可能不同。

fill和Opacity产生不同影响

  • 变暗组(线性加深,颜色加深)
  • 变亮组(线性减淡,颜色减淡)
  • 对比度组(亮光,线性光,实色混合)
  • 差值组(差值)

fill和Opacity产生相同影响

  • 正常组 (正常,溶解)
  • 变暗组(变暗,正片叠底,深色)
  • 变亮组(变亮,滤色,浅色)
  • 对比度组(叠加,柔光,强光,点光)
  • 差值组(排除,减去,划分)
  • 颜色组(色相,饱和度,颜色,明度)

混合模式相关符号

图层混合模式我们使用BlendModeBlendModeBlendMode来表示,这里是一个泛指,如果涉及到具体的混合模式,有专门的表示符号,比如正常模式使用NormalNormalNormal
使用矩阵我们有:

LayerA={PixA(1,1)⋯PixA(1,n)⋮⋮PixA(i,1)⋯PixA(i,n)⋮⋮PixA(m,1)⋯PixA(m,n)}LayerA=\left\{ \begin{aligned} &PixA_{(1,1)}&&\cdots&&PixA_{(1,n)}\\&\vdots && &&\vdots\\ &PixA_{(i,1)} &&\cdots&&PixA_{(i,n)}\\&\vdots && &&\vdots\\ &PixA_{(m,1)} &&\cdots&&PixA_{(m,n)}\\ \end{aligned}\right\}LayerA= PixA(1,1)PixA(i,1)PixA(m,1)PixA(1,n)PixA(i,n)PixA(m,n)
LayerB={PixB(1,1)⋯PixB(1,n)⋮⋮PixB(i,1)⋯PixB(i,n)⋮⋮PixB(m,1)⋯PixB(m,n)}LayerB=\left\{ \begin{aligned} &PixB_{(1,1)}&&\cdots&&PixB_{(1,n)}\\&\vdots && &&\vdots\\ &PixB_{(i,1)} &&\cdots&&PixB_{(i,n)}\\&\vdots && &&\vdots\\ &PixB_{(m,1)} &&\cdots&&PixB_{(m,n)}\\ \end{aligned}\right\}LayerB= PixB(1,1)PixB(i,1)PixB(m,1)PixB(1,n)PixB(i,n)PixB(m,n)
LayerR={PixR(1,1)⋯PixR(1,n)⋮⋮PixR(i,1)⋯PixR(i,n)⋮⋮PixR(m,1)⋯PixR(m,n)}LayerR=\left\{ \begin{aligned} &PixR_{(1,1)}&&\cdots&&PixR_{(1,n)}\\&\vdots && &&\vdots\\ &PixR_{(i,1)} &&\cdots&&PixR_{(i,n)}\\&\vdots && &&\vdots\\ &PixR_{(m,1)} &&\cdots&&PixR_{(m,n)}\\ \end{aligned}\right\}LayerR= PixR(1,1)PixR(i,1)PixR(m,1)PixR(1,n)PixR(i,n)PixR(m,n)
再结合上面的三者关系公式LayerR=BlendMode(LayerB,LayerA)LayerR=BlendMode(LayerB,LayerA)LayerR=BlendMode(LayerB,LayerA)
我们得到
LayerR={BlendMode(PixB(1,1),PixA(1,1))⋯BlendMode(PixB(1,n),PixA(1,n))⋮⋮BlendMode(PixB(i,1),PixA(i,1))⋯BlendMode(PixB(i,n),PixA(i,n))⋮⋮BlendMode(PixB(m,1),PixA(m,1))⋯BlendMode(PixB(m,n),PixA(m,n))}LayerR=\left\{ \begin{aligned} &BlendMode(PixB_{(1,1)},PixA_{(1,1)})&&\cdots&&BlendMode(PixB_{(1,n)},PixA_{(1,n)})\\&\vdots && &&\vdots\\&BlendMode(PixB_{(i,1)},PixA_{(i,1)})&&\cdots&&BlendMode(PixB_{(i,n)},PixA_{(i,n)})\\&\vdots && &&\vdots\\&BlendMode(PixB_{(m,1)},PixA_{(m,1)})&&\cdots&&BlendMode(PixB_{(m,n)},PixA_{(m,n)})\\\end{aligned}\right\}LayerR= BlendMode(PixB(1,1),PixA(1,1))BlendMode(PixB(i,1),PixA(i,1))BlendMode(PixB(m,1),PixA(m,1))BlendMode(PixB(1,n),PixA(1,n))BlendMode(PixB(i,n),PixA(i,n))BlendMode(PixB(m,n),PixA(m,n))

对于其中任意项
PixR=BlendMode(PixB,PixA)Pix_R=BlendMode(Pix_B,Pix_A)PixR=BlendMode(PixB,PixA)
如果这种混合模式基于RGB色彩空间则
上述表达式可以写为

(rcR,gcR,bcR)=BlendMode((rcB,gcB,bcB),(rcA,gcA,bcA))(rc_{R},gc_{R},bc_{R})=BlendMode((rc_{B},gc_{B},bc_{B}),(rc_{A},gc_{A},bc_{A}))(rcR,gcR,bcR)=BlendMode((rcB,gcB,bcB),(rcA,gcA,bcA))
或者未归一化形式
(RCR,GCR,BCR)=BlendMode((RCB,GCB,BCB),(RCA,GCA,BCA))(RC_{R},GC_{R},BC_{R})=BlendMode((RC_{B}, GC_{B}, BC_{B}),(RC_{A}, GC_{A}, BC_{A}))(RCR,GCR,BCR)=BlendMode((RCB,GCB,BCB),(RCA,GCA,BCA))
如果这种混合模式基于HSY色彩空间
(HR,SR,YR)=BlendMode((HB,SB,YB),(HA,SA,YA))(H_{R},S_{R},Y_{R})=BlendMode((H_{B},S_{B},Y_{B}),(H_{A},S_{A},Y_{A}))(HR,SR,YR)=BlendMode((HB,SB,YB),(HA,SA,YA))

在下面的所有组中,如果不特别说明基于RGB色彩空间的混合模式都是使用归一化后的数值进行计算。

并且,为了方便大家验证计算的正确性,这里提供一个java程序的链接给各位,GitHub的地址如下,输入你要混合的像素的数值,就可以得到对应混合模式混合的结果数值。

普通组

正常Normal

正常模式是一切的基础,也是我们理解和掌握混合模式的基础,
正常模式可以看作是基于RGB颜色空间,也可以看作是基于HSY色彩空间

公式(泡茶的方式,比如搅拌、静置)

对于像素维度公式
Pixr=Normal(Pixb,Pixa)=PixaPix_r=Normal(Pix_b,Pix_a)=Pix_aPixr=Normal(Pixb,Pixa)=Pixa
正常模式通道维度初始公式
r=Normal(b,a)=ar=Normal(b,a)=ar=Normal(b,a)=a

融合填充(放多少茶叶)

r=Fill(b,a)=fill×a+(1−fill)×br=Fill(b,a)= fill\times a + (1-fill)\times br=Fill(b,a)=fill×a+(1fill)×b

融合不透明度(太苦了,最后加点水)

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)= op\times Fill(b,a) + (1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b

对于整个像素
(rrc,rgc,rbc)=Normal((brc,bgc,bbc),(arc,agc,abc))=(rrc,rgc,rbc)(r_{rc},r_{gc},r_{bc})=Normal((b_{rc},b_{gc},b_{bc}),(a_{rc},a_{gc},a_{bc}))=(r_{rc},r_{gc},r_{bc})(rrc,rgc,rbc)=Normal((brc,bgc,bbc),(arc,agc,abc))=(rrc,rgc,rbc)
后面的混合模式我们不再讨论整个像素的公式,我们只讨论某个通道的结果。

整个像素融合填充

这里不同的混合模式公式不一定相同。
(rrc,rgc,rbc)=Fill((brc,bgc,bbc),(arc,agc,abc))=((fill×arc+(1−fill)×brc),(fill×agc+(1−fill)×bgc),(fill×abc+(1−fill)×bbc))\begin{aligned}(r_{rc},r_{gc},r_{bc})&=Fill((b_{rc},b_{gc},b_{bc}),(a_{rc},a_{gc},a_{bc}))\\&=\left((fill\times a_{rc} + (1-fill)\times b_{rc}),(fill\times a_{gc} + (1-fill)\times b_{gc}),(fill\times a_{bc} + (1-fill)\times b_{bc})\right)\end{aligned}(rrc,rgc,rbc)=Fill((brc,bgc,bbc),(arc,agc,abc))=((fill×arc+(1fill)×brc),(fill×agc+(1fill)×bgc),(fill×abc+(1fill)×bbc))

整个像素融合不透明度

(rrc,rgc,rbc)=Opacity((brc,bgc,bbc),(arc,agc,abc))=((op×Fill(arc,brc)+(1−fill)×brc),(op×Fill(agc,bgc+(1−op)×bgc),(op×Fill(abc,bbc+(1−op)×bbc))\begin{aligned}(r_{rc},r_{gc},r_{bc})&= Opacity((b_{rc},b_{gc},b_{bc}),(a_{rc},a_{gc},a_{bc}))\\&=\left((op\times Fill(a_{rc},b_{rc}) + (1-fill)\times b_{rc}),(op\times Fill(a_{gc},b_{gc} + (1-op)\times b_{gc}),(op\times Fill(a_{bc},b_{bc} + (1-op)\times b_{bc})\right)\end{aligned}(rrc,rgc,rbc)=Opacity((brc,bgc,bbc),(arc,agc,abc))=((op×Fill(arc,brc)+(1fill)×brc),(op×Fill(agc,bgc+(1op)×bgc),(op×Fill(abc,bbc+(1op)×bbc))

映射面和相关拓展

Image

程序模拟该模式计算结果

// 正常模式
public static BlendColor Normal(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double red = colorBlend.red.get01Value() *fill + colorBase.red.get01Value()* (1 - fill);
    double green = colorBlend.green.get01Value() *fill + colorBase.green.get01Value()* (1 - fill);
    double blue = colorBlend.blue.get01Value() *fill + colorBase.blue.get01Value()* (1 - fill);
    return ColorUtils.Opacity(colorBase, new BlendColor(red *255, green* 255, blue * 255), opacity);
}

借助正常模式
我们可以看到不透明度和填充的关系
后面的模式讨论我们都不再说明像素级别的公式,因为没有必要,我们只讨论通道级别的公式。
我们设定fillfillfill4040%40OpacityOpacityOpacity6060%60

基础图层                RGB[111.00,  80.00,  60.00]~ HSY[23.53,  51.00,  87.10 ]~ HSB[ 23.53,  45.95,  43.53]
混合图层                RGB[ 80.00,  70.00, 156.00]~ HSY[246.98,  86.00,  82.46]~ HSB[246.98,  55.13,  61.18]
========================正常组===================================(Normal)        RGB[103.56,  77.60,  83.04]~ HSY[347.43,  25.96,  85.99]~ HSB[347.43,  25.07,  40.61]

我们在PS中使用这两种颜色进行验证,发现符合我们的算法。
Image

用途示例

该模式是默认模式,在不调节填充和不透明度的情况下,就是上方像素点覆盖下方的像素点。
结合不透明度或者填充,可以实现和下方图层的简单混色。

溶解Dissolve

溶解模式初始公式

r=Dissolve(b,a)=ar=Dissolve(b,a)=ar=Dissolve(b,a)=a

如果融合了填充

r=Fill(b,a)=Randomfill(Dissolve(b,a),b)r=Fill(b,a)= Random_{fill}(Dissolve(b,a),b)r=Fill(b,a)=Randomfill(Dissolve(b,a),b)
不透明度对溶解模式是无效的。
fillfillfill的值越大,则下层像素暴露的可能性越小

程序模拟该模式计算结果

// 溶解模式
public static BlendColor Dissovle(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double rand = Math.random(); // 产生随机数
    if (rand < fill) {
        return colorBlend;
       } else {
        return colorBase;
       }
    }
(Dissolve)      RGB[111.00,  80.00,  60.00]~ HSY[23.53,  51.00,  87.10 ]~ HSB[ 23.53,  45.95,  43.53]

验证

溶解模式是根据概率来实现决定显示下方像素还是上方像素

于是取样点不同,则结果可以是上方的像素也可以是下方的像素
Image

用途示例

可以通过该模式实现一下粒子效果

变暗组

这一组是基于RGB色彩空间,RGB三个通道的数值取值范围是[0,255][0,255][0,255],归一化之后取值范围是[0,1][0,1][0,1]。这一组就是利用一系列运算让基础图层的三个通道的数值或者他们的和小于等于原值。这也是变暗组的本质。
和下面的变亮组一样,变暗组有两种方式将通道值或者通道值的和变小,那就是替换运算,替换包括变暗和深色,运算包括其他三种。

变暗模式

如果将基础图层每个像素每个通道的数值都变小,最简单的方式就是选择原图图层像素通道值和混合图层像素通道值中比较小的那个作为结果图层像素通道值,这样的图层中每个像素点的通道值都小于等于原值,图像自然就会变暗。

公式

r=Darken(b,a)=Min(b,a)r=Darken(b,a)=Min(b,a)r=Darken(b,a)=Min(b,a)

融合填充

r=Fill(b,a)=fill×Darken(b,a)+(1−fill)×br= Fill(b,a) =fill\times Darken(b,a)+(1-fill)\times br=Fill(b,a)=fill×Darken(b,a)+(1fill)×b

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)=op\times Fill(b,a)+(1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b

映射面和同图等效曲线

Image

同图曲线表达式

r=Darken(b,b)=br=Darken(b,b)=br=Darken(b,b)=b
这里我们简单认为fill和opacity都是100,因为我们日常使用中几乎用不到改变这两个值并且需要模拟同图曲线的情况,使用这里只讨论最简单的类型。但是其他类型可以通过我们提供的公式自行推导,但是这里没有必要写出来。下同。

程序模拟该模式计算结果

 // 变暗
public static BlendColor Darken(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double red = DarkenChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
    double green = DarkenChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
    double blue = DarkenChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
    return ColorUtils.Opacity(colorBase, new BlendColor(red *255, green* 255, blue * 255), opacity);
    }

private static double DarkenChannel(double base, double blend, double fill) {
    return Math.min(base, blend) * fill + (1 - fill) * base;
    }
(Darken)        RGB[103.56,  77.60,  60.00]~ HSY[24.24,  43.56,  83.45 ]~ HSB[ 24.24,  42.06,  40.61]

验证

Image

用途示例

1:组合成为对比度组的点光Pinlight模式
2:此处是深色模式的简化版本,但是我们还是可以通过它来实现一些溶图操作

正片叠底Multiply

如果将混合图层像素的通道数值和原图像素通道数值相乘,则因为归一化之后的数值都是小于等于1的所以,原值乘一个小于1的数值一定小于等于原来的值。所以图像会变暗。

公式

r=Multiply(b,a)=b×ar=Multiply(b,a)=b\times ar=Multiply(b,a)=b×a

融合填充

r=Fill(b,a)=fill×Multiply(b,a×fill)+(1−fill)×br= Fill(b,a) =fill\times Multiply(b,a\times fill)+(1-fill)\times br=Fill(b,a)=fill×Multiply(b,a×fill)+(1fill)×b

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−op)×br=Opacity(b,a)=op\times Fill(b,a)+(1-op)\times br=Opacity(b,a)=op×Fill(b,a)+(1op)×b

映射面和同图等效曲线

正片叠底的映射面,同图面,同图曲线和中性灰平面
Image

同图曲线表达式

r=Multiply(b,b)=b2r=Multiply(b,b)= b^2r=Multiply(b,b)=b2

程序模拟该模式计算结果

    // 正片叠底
public static BlendColor Mulitply(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double red = MulitplyChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
    double green = MulitplyChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
    double blue = MulitplyChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
    return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

private static double MulitplyChannel(double base, double blend, double fill) {
    return ColorUtils.round((base * blend) * fill + (1 - fill) * (base), 1, 0);
    }
正片叠底(Mulitply)      RGB[ 92.72,  66.07,  54.41]~ HSY[18.26,  38.31,  72.78 ]~ HSB[ 18.26,  41.32,  36.36]

验证

Image

用途示例

1:和滤色模式组合成强光和叠加模式
2:给比较亮的图片添加纹理
3:扣除白色背景

线性加深LinearBurn

此模式本质是减法,就是使用混合图层像素通道数值的补也就是附片和原图相减,如果大于1则取1小于0则取0,并且此模式需要对填充特殊处理。线性加深可以通过划分和颜色加深转化。

公式

r=LinearBurn(b,a)=b−(1−a)=b+a−1r=LinearBurn(b,a)=b-(1-a)=b+a-1r=LinearBurn(b,a)=b(1a)=b+a1

融合填充

r=Fill(b,a)=b−(1−a)×fillr= Fill(b,a) =b-(1-a)\times fillr=Fill(b,a)=b(1a)×fill

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−op)×br=Opacity(b,a)=op\times Fill(b,a)+(1-op)\times br=Opacity(b,a)=op×Fill(b,a)+(1op)×b

映射面和同图等效曲线

线性加深的映射面,同图面,同图曲线和中性灰平面
Image

同图曲线表达式

r=LinearBrun(b,b)=2×b−1 r=LinearBrun(b,b)=2\times b-1r=LinearBrun(b,b)=2×b1

程序模拟该模式计算结果

    // 线性加深
public static BlendColor LinearBurn(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double red = LinearBurnChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
    double green = LinearBurnChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
    double blue = LinearBurnChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
    return ColorUtils.Opacity(colorBase, new BlendColor(red *255, green* 255, blue * 255), opacity);
    }

private static double LinearBurnChannel(double base, double blend, double fill) {
    return ColorUtils.round(base - (1 - blend) * fill, 1, 0);
}

线性加深(LinearBurn)    RGB[ 69.00,  35.60,  36.24]~ HSY[358.85,  33.40,  45.69]~ HSB[358.85,  48.41,  27.06]

验证

Image

用途示例

1:组成线性光
2:添加光效可做滤镜

颜色加深ColorBurn

此模式的本质是线性加深和混合图层相除,由此可以得知他可以和线性加深通过正片叠底转化。

公式

r=ColorBurn(b,a)=1−1−b1−(1−a)\begin{aligned}&r=ColorBurn(b,a)\\&=1-\dfrac{1-b}{1-(1-a)}\end{aligned}r=ColorBurn(b,a)=11(1a)1b
这里之所以这么写,是因为后面要加入fill,也就是填充。


推导一下
r=ColorBurn(b,a)=1−1−b1−(1−a)=1−(1−a)−(1−b)1−(1−a)=b−(1−a)1−(1−a)=LinearBurn(b,a)1−(1−a)=LinearBurn(b,a)a\begin{aligned}&r=ColorBurn(b,a)\\&=1-\dfrac{1-b}{1-(1-a)}=\dfrac{1-(1-a)-(1-b)}{1-(1-a)}\\&\\&=\dfrac{b-(1-a)}{1-(1-a)}\\&\\&=\dfrac{LinearBurn(b,a)}{1-(1-a)}=\dfrac{LinearBurn(b,a)}{a}\end{aligned}r=ColorBurn(b,a)=11(1a)1b=1(1a)1(1a)(1b)=1(1a)b(1a)=1(1a)LinearBurn(b,a)=aLinearBurn(b,a)
从这里可以看出,线性加深可以和颜色加深相互转换,通过划分和正片叠底。但是这里涉及到其他的混合模式,而非单纯负片,所以不把他们放到可以互相转化的分类中


融合填充

r=Fill(b,a)=1−1−b1−(1−a)×fillr= Fill(b,a) =1-\dfrac{1-b}{1-(1-a)\times fill}r=Fill(b,a)=11(1a)×fill1b

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−op)×br=Opacity(b,a)=op\times Fill(b,a)+(1-op)\times br=Opacity(b,a)=op×Fill(b,a)+(1op)×b

映射面和同图等效曲线

颜色加深的映射面,同图面,同图曲线和中性灰平面
Image

同图曲线表达式

r=ColorBurn(b,b)=2−1b r= ColorBurn(b,b)=2-\dfrac{1}{b}r=ColorBurn(b,b)=2b1

两次负片转划分

r=1−ColorBurn(1−b,a)=1−(1−1−(1−b)a)=ba\begin{aligned}&r=1-ColorBurn(1-b,a)\\&=1-(1-\dfrac{1-(1-b)}{a})\\&=\dfrac{b}{a}\end{aligned}r=1ColorBurn(1b,a)=1(1a1(1b))=ab

程序模拟该模式计算结果

    // 颜色加深
public static BlendColor ColorBurn(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double red = ColorBurnChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
    double green = ColorBurnChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
    double blue = ColorBurnChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
    return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

private static double ColorBurnChannel(double base, double blend, double fill) {
    return ColorUtils.round(1 - Math.min(1, (1 - base) / ((1 - (1 - blend) * fill))), 1, 0);
}
颜色加深(ColorBurn)     RGB[ 78.31,  37.07,  38.49]~ HSY[357.94,  41.24,  49.60]~ HSB[357.94,  52.66,  30.71]

验证

Image

用途示例

1:组合成为亮光模式
2:完成特殊光影效果,例如给太阳添加一些颜色或光晕,并且保留亮部细节,该模式的特点也是相对线性加深,可以保留底图的亮部细节。

深色Darker

深色模式可以理解为变暗模式的加强变暗模式,或者是粗略的变暗模式,因为其不产生新的像素,就像溶解模式一样。
计算细节简单来说就是求和,比较大小,小的留下了,若求和的结果一样,就计算明度,明度小的留下了。

公式

Pixr=Darker(Pixb,Pixa)={PixaSum(Pixa)<Sum(Pixb)PixbSum(Pixa)>Sum(Pixb)PixbSum(Pixa)=Sum(Pixb)且Lum(Pixb)<Lum(Pixa)PixaSum(Pixa)=Sum(Pixb)且Lum(Pixb)>Lum(Pixa)\begin{aligned} Pix_r &=Darker(Pix_b,Pix_a)\\&=\left\{\begin{aligned}&Pix_a && Sum(Pix_a)<Sum(Pix_b)\\&Pix_b&& Sum(Pix_a)>Sum(Pix_b)\\&Pix_b&& Sum(Pix_a)=Sum(Pix_b)且Lum(Pix_b)<Lum(Pix_a)\\&Pix_a&& Sum(Pix_a)=Sum(Pix_b)且Lum(Pix_b)>Lum(Pix_a)\end{aligned}\right. \end{aligned}Pixr=Darker(Pixb,Pixa)= PixaPixbPixbPixaSum(Pixa)<Sum(Pixb)Sum(Pixa)>Sum(Pixb)Sum(Pixa)=Sum(Pixb)Lum(Pixb)<Lum(Pixa)Sum(Pixa)=Sum(Pixb)Lum(Pixb)>Lum(Pixa)
Lum(Pix)=0.3RC+0.59GC+0.11BCLum(Pix) = 0.3RC+0.59GC+0.11BCLum(Pix)=0.3RC+0.59GC+0.11BC
Sum(Pix)=RC+GC+BCSum(Pix) = RC+GC+BCSum(Pix)=RC+GC+BC

融合填充

r=Fill(Pixb,Pixa×fill)=fill×Pixr+Pixbr= Fill(Pix_b,Pix_a\times fill) =fill\times Pix_r + Pix_br=Fill(Pixb,Pixa×fill)=fill×Pixr+Pixb

融合不透明度

r=Opacity(Pixb,Pixa)=op×Fill(Pixb,Pixa)+(1−op)×Pixbr=Opacity(Pix_b,Pix_a)=op\times Fill(Pix_b,Pix_a)+(1-op)\times Pix_b r=Opacity(Pixb,Pixa)=op×Fill(Pixb,Pixa)+(1op)×Pixb

映射面和同图等效曲线

Pixr=PixbPix_r=Pix_bPixr=Pixb

程序模拟该模式计算结果

// 深色
public static BlendColor Darker(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
    double sumBase = colorBase.red.value + colorBase.green.value + colorBase.blue.value;
        double sumBlend = (colorBlend.red.value + colorBlend.green.value + colorBlend.blue.value) * fill;
        if (sumBase == sumBlend) {
            if (colorBase.getLum() < colorBlend.getLum()) {
                double red = colorBase.red.get01Value();
                double green = colorBase.green.get01Value();
                double blue = colorBase.blue.get01Value();
                return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
            } else {
                double red = colorBase.red.get01Value() * (1 - fill) + colorBlend.red.get01Value() * fill;
                double green = colorBase.green.get01Value() * (1 - fill) + colorBlend.green.get01Value() * fill;
                double blue = colorBase.blue.get01Value() * (1 - fill) + colorBlend.blue.get01Value() * fill;
                return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
            }
        }
        if (sumBase < sumBlend) {
            double red = colorBase.red.get01Value();
            double green = colorBase.green.get01Value();
            double blue = colorBase.blue.get01Value();
            return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
        }
        double red = colorBase.red.get01Value() * (1 - fill) + colorBlend.red.get01Value() * fill;
        double green = colorBase.green.get01Value() * (1 - fill) + colorBlend.green.get01Value() * fill;
        double blue = colorBase.blue.get01Value() * (1 - fill) + colorBlend.blue.get01Value() * fill;
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);

    }
(Darker)        RGB[103.56,  77.60,  83.04]~ HSY[347.43,  25.96,  85.99]~ HSB[347.43,  25.07,  40.61]

验证

Image

用途示例

用于替换图中某些像素点,一般用于两张十分相似的图片的融合,比如连拍,延时摄影的一组照片融合成一张图。

变亮组

变亮组是变暗组的相反模式,并且都可以通过负片操作来实现相互转换
变亮组的本质就是以混合图层的像素为参数,对原图层像素的数值进行增大,数值增大了,图片就变亮了。如果混合图层中像素点的通道值时0,则此时该通道的计算结果和原图一致。

变亮Lighten

变亮模式和变暗模式相反,变暗时取最小值,变亮就是取最大值,具体做法就是取原图层和混合图层像素点中三个通道各自的最大值,保留最大值组成的像素作为结果像素。

公式

r=Lighten(b,a)=Max(b,a)r=Lighten(b,a)=Max(b,a)r=Lighten(b,a)=Max(b,a)

融合填充

r=Fill(b,a)=fill×Lighten(b,a×fill)+(1−fill)×br= Fill(b,a) =fill\times Lighten(b,a\times fill)+(1-fill)\times br=Fill(b,a)=fill×Lighten(b,a×fill)+(1fill)×b

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)=op\times Fill(b,a)+(1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b
可以写作

三次负片操作相互转换

r=Lighten(b,a)=1−Min(1−b,1−a)=1−Darken(1−b,1−a)\begin{aligned} r&=Lighten(b,a)\\&=1-Min(1-b,1-a)\\&= 1-Darken(1-b,1-a) \end{aligned}r=Lighten(b,a)=1Min(1b,1a)=1Darken(1b,1a)
也就是说,三次负片操作可以实现变暗模式和变亮模式的相互转换

映射面和同图等效曲线

Image

程序模拟该模式计算结果

    // 变亮模式
    public static BlendColor Lighten(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
        double red = LightenChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
        double green = LightenChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
        double blue = LightenChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

    private static double LightenChannel(double base, double blend, double fill) {
        return Math.max(base, blend * fill) * fill + (1 - fill) * base;
    }

(Lighten)       RGB[111.00,  80.00,  83.04]~ HSY[354.12,  31.00,  89.63]~ HSB[354.12,  27.93,  43.53

验证

Image

用途示例

1:组成叠加和强光
2:保留通道中较大的值,并且组成新的像素,可用于将背景较暗或相似但是部分不相同的图片融合。相当于做了一个复杂的通道蒙版,并且这个蒙版非常精确,精确到通道。

滤色Screen

滤色时正片叠底的一种负片组合,和变暗变亮一样可以通过三次负片操作相互转换。

公式

r=Screen(b,a)=1−(1−b)(1−a)r=Screen(b,a)=1-(1-b)(1-a)r=Screen(b,a)=1(1b)(1a)

融合填充

r=Fill(b,a)=fill×Screen(b,a)+(1−fill)×br= Fill(b,a) =fill\times Screen(b,a)+(1-fill)\times br=Fill(b,a)=fill×Screen(b,a)+(1fill)×b

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)=op\times Fill(b,a)+(1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b

三次负片操作相互转换

r=Screen(b,a)=1−(1−b)(1−a)=1−Multipy(1−b,1−a)\begin{aligned} r&=Screen(b,a)\\&=1-(1-b)(1-a)\\&= 1- Multipy(1-b,1-a) \end{aligned}r=Screen(b,a)=1(1b)(1a)=1Multipy(1b,1a)

映射面和同图等效曲线

Image

程序模拟该模式计算结果

    // 滤色
    public static BlendColor Screen(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
        double red = ScreenChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
        double green = ScreenChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
        double blue = ScreenChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

    private static double ScreenChannel(double base, double blend, double fill) {
        return (1 - (1 - base) * (1 - blend)) * fill + (1 - fill) * base;
    }
(Screen)        RGB[121.84,  91.53,  88.63]~ HSY[5.24,  33.21, 100.30  ]~ HSB[  5.24,  27.26,  47.78]

验证

Image

用途示例

1:组成叠加模式和强光模式
2:扣除黑色背景

线性减淡LinearDodge

线性减淡就是让原图和混合图层做加法
他和线性加深也是可以互相转化的

公式

r=LinearDodge(b,a)=b+ar=LinearDodge(b,a)=b+ar=LinearDodge(b,a)=b+a

融合填充

r=Fill(b,a)=b+a×fillr= Fill(b,a) =b+a\times fill r=Fill(b,a)=b+a×fill

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)=op\times Fill(b,a)+(1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b

三次负片操作相互转换

r=LinearDodge(b,a)=1−LinearBurn(1−b,1−a)=1−(1−b+1−a−1)=b+a\begin{aligned} r&=LinearDodge(b,a)\\&= 1-LinearBurn(1-b,1-a)\\& = 1-(1-b+1-a-1)=b+a \end{aligned}r=LinearDodge(b,a)=1LinearBurn(1b,1a)=1(1b+1a1)=b+a

映射面和同图等效曲线

Image

程序模拟该模式计算结果

    // 线性减淡
    public static BlendColor LinearDodge(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {

        double red = LinearDodgeChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
        double green = LinearDodgeChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
        double blue = LinearDodgeChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

    private static double LinearDodgeChannel(double base, double blend, double fill) {
        return ColorUtils.round(base + blend * fill, 1, 0);
    }

线性减淡(LinearDodge)   RGB[130.20,  96.80,  97.44]~ HSY[358.85,  33.40, 106.89]~ HSB[358.85,  25.65,  51.06]

验证

Image

用途示例

1:组成线性光
2: 制作特殊光效

颜色减淡ColorDodge

颜色减淡可以由颜色加深通过三次负片转化,颜色减淡也可以转化为划分,通过一次负片可以实现

公式

r=ColorDodge(b,a)=b1−ar=ColorDodge(b,a)=\dfrac{b}{1-a}r=ColorDodge(b,a)=1ab

融合填充

r=Fill(b,a)=b1−a×fillr= Fill(b,a) =\dfrac{b}{1-a\times fill} r=Fill(b,a)=1a×fillb

融合不透明度

r=Opacity(b,a)=op×Fill(b,a)+(1−fill)×br=Opacity(b,a)=op\times Fill(b,a)+(1-fill)\times br=Opacity(b,a)=op×Fill(b,a)+(1fill)×b

三次负片操作相互转换

r=ColorDodge(b,a)=1−ColorBurn(1−b,1−a)=1−(1−1−(1−b)1−(1−(1−a)))=b1−a\begin{aligned} r&=ColorDodge(b,a)\\&\\&= 1-ColorBurn(1-b,1-a)\\&\\& =1-(1-\dfrac{1-(1-b)}{1-(1-(1-a))})\\&=\dfrac{b}{1-a} \end{aligned}r=ColorDodge(b,a)=1ColorBurn(1b,1a)=1(11(1(1a))1(1b))=1ab

一次负片转为划分

r=ColorDodge(b,1−a)=b1−1+a=ba\begin{aligned} r&=ColorDodge(b,1-a)\\\\&= \dfrac{b}{1-1+a}\\\\&=\dfrac{b}{a} \end{aligned}r=ColorDodge(b,1a)=11+ab=ab

映射面和同图等效曲线

Image

程序模拟该模式计算结果

    // 颜色减淡
    public static BlendColor ColorDodge(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
        double red = ColorDodgeChannel(colorBase.red.get01Value(), colorBlend.red.get01Value(), fill);
        double green = ColorDodgeChannel(colorBase.green.get01Value(), colorBlend.green.get01Value(), fill);
        double blue = ColorDodgeChannel(colorBase.blue.get01Value(), colorBlend.blue.get01Value(), fill);
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
    }

    private static double ColorDodgeChannel(double base, double blend, double fill) {
        return ColorUtils.round((base) / ((1 - blend * fill)), 1, 0);
    }

颜色减淡(ColorDodge)    RGB[120.56,  85.92,  71.66]~ HSY[17.50,  48.89,  94.74 ]~ HSB[ 17.50,  40.56,  47.28]

验证

Image

用途示例

1:组合成为亮光模式
2:制造光线,同时保护暗部细节

浅色Lighter

浅色模式是深色模式的相反模式,可以通过深色三次负片操作得到
计算细节简单来说就是求和,比较大小,大的留下了,若求和的结果一样,就计算明度,明度大的留下了。

公式

Pixr=Lighter(Pixb,Pixa)={PixaSum(Pixa)>Sum(Pixb)PixbSum(Pixa)<Sum(Pixb)PixbSum(Pixa)=Sum(Pixb)且Lum(Pixb)>Lum(Pixa)PixaSum(Pixa)=Sum(Pixb)且Lum(Pixb)<Lum(Pixa)\begin{aligned} Pix_r &=Lighter(Pix_b,Pix_a)\\&=\left\{\begin{aligned}&Pix_a && Sum(Pix_a)>Sum(Pix_b)\\&Pix_b&& Sum(Pix_a)<Sum(Pix_b)\\&Pix_b&& Sum(Pix_a)=Sum(Pix_b)且Lum(Pix_b)>Lum(Pix_a)\\&Pix_a&& Sum(Pix_a)=Sum(Pix_b)且Lum(Pix_b)<Lum(Pix_a)\end{aligned}\right. \end{aligned}Pixr=Lighter(Pixb,Pixa)= PixaPixbPixbPixaSum(Pixa)>Sum(Pixb)Sum(Pixa)<Sum(Pixb)Sum(Pixa)=Sum(Pixb)Lum(Pixb)>Lum(Pixa)Sum(Pixa)=Sum(Pixb)Lum(Pixb)<Lum(Pixa)
Lum(Pix)=0.3RC+0.59GC+0.11BCLum(Pix) = 0.3RC+0.59GC+0.11BCLum(Pix)=0.3RC+0.59GC+0.11BC
Sum(Pix)=RC+GC+BCSum(Pix) = RC+GC+BCSum(Pix)=RC+GC+BC

融合填充

r=Fill(Pixb,Pixa×fill)=fill×Pixr+Pixbr=Fill(Pix_b,Pix_a\times fill) =fill\times Pix_r+ Pix_br=Fill(Pixb,Pixa×fill)=fill×Pixr+Pixb

融合不透明度

r=Opacity(Pixb,Pixa)=op×Fill(Pixb,Pixa)+(1−op)×Pixbr=Opacity(Pix_b,Pix_a)=op\times Fill(Pix_b,Pix_a)+(1-op)\times Pix_b r=Opacity(Pixb,Pixa)=op×Fill(Pixb,Pixa)+(1op)×Pixb

三次负片操作相互转换

Pixr=Lighter(Pixb,Pixa)={Pixb当arc+agc+abc<brc+bgc+bbcPixa当arc+agc+abc>brc+bgc+bbc=1−Darker(1−Pixb,1−Pixa)={1−(1−Pixb)当1−arc+1−agc+1−abc>1−brc+1−bgc+1−bbc1−(1−Pixa)当1−arc+1−agc+1−abc<brc+1−bgc+1−bbc={Pixb当arc+agc+abc<brc+bgc+bbcPixa当arc+agc+abc>brc+bgc+bbc\begin{aligned}Pix_r& =Lighter(Pix_b,Pix_a)\\\\&=\left\{\begin{aligned}&Pix_b &&当 a_{rc}+a_{gc}+a_{bc}<b_{rc}+b_{gc}+b_{bc}\\&Pix_a&&当a_{rc}+a_{gc}+a_{bc}>b_{rc}+b_{gc}+b_{bc}\end{aligned}\right.\\\\&=1-Darker(1-Pix_b,1-Pix_a)\\\\&=\left\{\begin{aligned}&1-(1-Pix_b) &&当 1-a_{rc}+1-a_{gc}+1-a_{bc}>1-b_{rc}+1-b_{gc}+1-b_{bc}\\&1-(1-Pix_a)&& 当 1-a_{rc}+1-a_{gc}+1-a_{bc}<b_{rc}+1-b_{gc}+1-b_{bc}\end{aligned}\right. \\\\&=\left\{\begin{aligned}&Pix_b&&当 a_{rc}+a_{gc}+a_{bc}<b_{rc}+b_{gc}+b_{bc}\\&Pix_a&& 当 a_{rc}+a_{gc}+a_{bc}>b_{rc}+b_{gc}+b_{bc}\end{aligned}\right.\end{aligned}Pixr=Lighter(Pixb,Pixa)={PixbPixaarc+agc+abc<brc+bgc+bbcarc+agc+abc>brc+bgc+bbc=1Darker(1Pixb,1Pixa)={1(1Pixb)1(1Pixa)1arc+1agc+1abc>1brc+1bgc+1bbc1arc+1agc+1abc<brc+1bgc+1bbc={PixbPixaarc+agc+abc<brc+bgc+bbcarc+agc+abc>brc+bgc+bbc
相等的情况同理可得

程序模拟该模式计算结果


    // 浅色
    public static BlendColor Lighter(BlendColor colorBase, BlendColor colorBlend, double fill, double opacity) {
        double sumBase = colorBase.red.value + colorBase.green.value + colorBase.blue.value;
        double sumBlend = (colorBlend.red.value + colorBlend.green.value + colorBlend.blue.value);
        if (sumBase == sumBlend) {
            if (colorBase.getLum() > colorBlend.getLum()) {
                double red = colorBase.red.get01Value();
                double green = colorBase.green.get01Value();
                double blue = colorBase.blue.get01Value();
                return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
            } else {
                double red = colorBase.red.get01Value() * (1 - fill) + colorBlend.red.get01Value() * fill;
                double green = colorBase.green.get01Value() * (1 - fill) + colorBlend.green.get01Value() * fill;
                double blue = colorBase.blue.get01Value() * (1 - fill) + colorBlend.blue.get01Value() * fill;
                return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
            }
        }
        if (sumBase > sumBlend) {
            double red = colorBase.red.get01Value();
            double green = colorBase.green.get01Value();
            double blue = colorBase.blue.get01Value();
            return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);
        }
        double red = colorBase.red.get01Value() * (1 - fill) + colorBlend.red.get01Value() * fill;
        double green = colorBase.green.get01Value() * (1 - fill) + colorBlend.green.get01Value() * fill;
        double blue = colorBase.blue.get01Value() * (1 - fill) + colorBlend.blue.get01Value() * fill;
        return ColorUtils.Opacity(colorBase, new BlendColor(red * 255, green * 255, blue * 255), opacity);

    }
(Lighter)       RGB[111.00,  80.00,  60.00]~ HSY[23.53,  51.00,  87.10 ]~ HSB[ 23.53,  45.95,  43.53]

验证

Image

用途示例

保留像素中通道和较大的像素,可用于将背景较暗或相似但是部分不相同的图片融合。相当于做了一个复杂的通道蒙版,并且这个蒙版非常精确,精确到像素。
对比度组
对比度组都是上述两组某两项的组合,组合的分割界限是基础图层或者混合图层中性灰平面,并且他们之间也有一些关系。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐