你和抖音可能就缺一个图层混叠特效了

文章源于视觉哥哥凯凯的需求。在实现触发特效的时,在视觉稿中使用了10% 的蒙板叠加在原色值上,而不是给出混合后的色值,开发上并不好处理。于是,我决定凭着我程序员般缜密的思维与逻辑性的口舌一定要说服他,这个需求没有必要做!
来到凯凯座位,经过一轮深入浅出的表达。凯凯似乎已经认识到了蒙板对于开发的不便利性。“这个效果,是不是做不了。你如果做不了的话。就用混合色值吧。” 我转念一想,怎么能说自己不行,下午先自己悄悄的看看资料吧。“不过安卓那边实现没问题啊。” 凯凯随后默默的给我补了一刀。同时解释视觉效果一般都是通过多个图层混叠实现的。如果能实现,会更利于视觉和开发统一视觉规范……
经过凯凯的一番说服,我瞬间认识到了这项任务的重要性和必要性,以及自己身上沉甸甸的使命感。既然安卓能做,iOS 肯定也能做! 这个需求看来也是很有必要啊!嗯?在么好像有哪里不对?

Demo地址: https://github.com/Orange-W/PhotoshopBending


所谓图层混合模式就是指一个层与其下图层的色彩叠加方式,在这之前我们所使用的是正常模式,除了正常以外,还有很多种混合模式,它们都可以产生迥异的合成效果。

打开 Photoshop叠层菜单,可以发现除去最后几栏其他都是RBG色值的混合操作。

图层混合功能

从上到下数,除去正常, 几个分区可以分为: 变暗型, 变亮型, 溶合型,色差型, 明亮度
其中前四种 变暗型, 变亮型, 溶合型,色差型 均为前后两个图层的 RGB 混合,通过简单的 RGB 混合公式即可实现

为了美观,我使用外链的素材。并且为了对比明显,图片只会叠加下方部分70%。而上方30%作为原图对比。

原图
混叠图层

通用说明


公式指代

d: 混合后的结果色透明度。
A: 为上方叠层的色值或透明度。
B:为原图层色值或透明度,也就是下方的 base 层。
(因为同类型混合 R,G,B,Alpha 的图层混合计算一致,在各个值得的计算中。A和 B 均指代为对应的色值)如正底叠片公式:

C =A*B
等价于
Cr =Ar*Br
Cg =Ag*Bg,
Cb =Ab*Bb

为了方便计算,公式中的数值均百分比,即 0.0 ~ 1.0 。需要乘上255才是真正的色值。
需要说明的是,Photoshop 补色的概念即为颜色至饱和的差值。简单来说,1-A 即为 A 的补色,1-B 即为 B 的补色。
Opacity 不透明度计算

C = dA+(1-d)B

A代表了上面图层像素的色彩值(A=像素值/255),d表示该层的透明度,B代表下面图层像素的色彩值(B=像素值/255),C代表了混合像素的色彩值(真实的结果像素值应该为255*C)。该公式也应用于层蒙板,在这种情况下,d代表了蒙板图层中给定位置像素的亮度,下同,不再叙述。

效果实现


第一步: 工具类函数的准备
extension UIColor {
     // MARK: 处理函数
    func blendProcedure(
        coverColor: UIColor,
        alpha: CGFloat,
        procedureBlock: ((_ baseValue: CGFloat,_ topValue: CGFloat) -> CGFloat)?
        ) -> UIColor {
        let baseCompoment = self.rgbaTuple()
        let topCompoment = coverColor.rgbaTuple()
        
        // 该层透明度
        let mixAlpha = alpha * topCompoment.a + (1.0 - alpha) * baseCompoment.a
        
        // RGB 值
        let mixR = procedureBlock?(
            baseCompoment.r / 255.0,
            topCompoment.r / 255.0)
            ?? (baseCompoment.r) / 255.0
        
        let mixG = procedureBlock?(
            baseCompoment.g / 255.0,
            topCompoment.g / 255.0)
            ?? (baseCompoment.g) / 255.0
        
        let mixB = procedureBlock?(
            baseCompoment.b / 255.0,
            topCompoment.b / 255.0)
            ?? baseCompoment.b / 255.0
        
        
        return UIColor.init(red:   fitIn(mixR),
                            green: fitIn(mixG),
                            blue:  fitIn(mixB),
                            alpha: mixAlpha)
    }

    // 防止越界
    func fitIn(_ value: CGFloat, ceil: CGFloat = 255) -> CGFloat { return max(min(value,ceil),0) }
    func fitIn(_ value: Double, ceil: CGFloat = 255) -> CGFloat { return fitIn(CGFloat(value), ceil: ceil) }
    
    // 返回 RBGA
    func rgbaTuple() -> (r: CGFloat, g: CGFloat, b: CGFloat,a: CGFloat) {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        r = r * 255
        g = g * 255
        b = b * 255
        
        return ((r),(g),(b),a)
    }
}
第二步:开始实现
通用型
1.前景叠图 (Alpha Bend)

如果你发现视觉稿里没有标注特效类型,不知道视觉要你用的是什么效果,那肯定就是这个了。别问我为什么 : )

计算公式:

       C = A * A.alpha + B  * (1 - B.alpha)
       C.alpha = 1.0

代码实现:

    // Alpha Blending 前景色叠图
    func blendAlpha(coverColor: UIColor) -> UIColor {
        let c1 = coverColor.rgbaTuple()
        let c2 = self.rgbaTuple()
        
        let c1r = CGFloat(c1.r)
        let c1g = CGFloat(c1.g)
        let c1b = CGFloat(c1.b)
        
        let c2r = CGFloat(c2.r)
        let c2g = CGFloat(c2.g)
        let c2b = CGFloat(c2.b)
        
        // 前景色叠图公式
        let r = c1r * c1.a + c2r  * (1 - c1.a)
        let g = c1g * c1.a + c2g  * (1 - c1.a)
        let b = c1b * c1.a + c2b  * (1 - c1.a)
        return UIColor.init(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: 1.0)
    }

透明

变暗型

2.变暗(Darken)

在该模式下,对混合的两个图层相对应区域RGB通道中的颜色亮度值进行比较,在混合图层中,比基色图层暗的像素保留,亮的像素用基色图层中暗的像素替换。总的颜色灰度级降低,造成变暗的效果。

计算公式:

B<=A,则 C=B。
B>=A,则 C=A。

代码实现:

/// Darken 变暗  B<=A: C=B; B>=A: C=A
func blendDarken(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 <= $1) ? $0 : $1 }
}
变暗

该模式通过比较上下层像素后取相对较暗的像素作为输出,注意,每个不同的颜色通道的像素都是独立的进行比较,色彩值相对较小的作为输出结果,下层表示叠放次序位于下面的那个图层,即B。上层表示叠放次序位于上面的那个图层,即A。


3.正片叠底(Multiply)

将上下两层图层像素颜色的灰度级进行乘法计算,获得灰度级更低的颜色而成为合成后的颜色,图层合成后的效果简单地说是低灰阶的像素显现而高灰阶不显现。
计算公式:

C=A*B

代码实现:

/// Multiply 正片叠底 C = A*B
func blendMultiply(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return $0 * $1 }
}
正片叠底

该效果将两层像素的标准色彩值(基于0..1之间)相乘后输出,其效果可以形容成:两个幻灯片叠加在一起然后放映,透射光需要分别通过这两个幻灯片,从而被削弱了两次。


4.颜色加深(Color Burn)

使用这种模式时,会加暗图层的颜色值,加上的颜色越亮,效果越细腻。让底层的颜色变暗,有点类似于正片叠底,但不同的是,它会根据叠加的像素颜色相应增加对比度。和白色混合没有效果。
计算公式:

C=1-(1-B)/A

代码实现:

/// Color Burn 颜色加深 C=1-(1-B)/A
 func blendColorBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
   return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $0) / $1 }
 }
颜色加深

该模式如果上层越暗,则下层获取的光越少,如果上层为全黑色,则下层越黑,如果上层为全白色,则根本不会影响下层。结果最亮的地方不会高于下层的像素值。


5.线性加深(Linear Burn)

和颜色加深模式一样,线性加深模式通过降低亮度,让底色变暗以反映混合色彩。和白色混合没有效果。
计算公式:

C=A+B-1

代码实现:

/// Linear Burn 线性加深 C=A+B-1
func blendLinearBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($1 + $0) - 1.0 }
}
线性加深

如果上下层的像素值之和小于255,输出结果将会是纯黑色。如果将上层反相,结果将是纯粹的数学减。


变亮型

6.变亮(Lighten)

在该模式与变暗模式相反,是对混合的两个图层相对应区域RGB通道中的颜色亮度值进行比较,取较高的的像素点为混合之后的颜色,使得总的颜色灰度的亮度升高,造成变亮的效果。用黑色合成图像时无作用,用白色时则仍为白色。
计算公式:

B<=A: C=A
B>A: C=B

代码实现:

/// Lighten 变亮   B>=A: C=B; B<=A: C=A
func blendLighten(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 >= $1) ? $0 : $1 }
}
变亮

该模式是取色彩值较大的(也就是较亮的)作为输出结果。


7.滤色(Screen)

它与正片叠底模式相反,将上下两层图层像素颜色的灰度级进行乘法计算,获得灰度级更高的颜色而成为合成后的颜色,图层合成后的效果简单地说是高灰阶的像素显现而低灰阶不显现(即浅色出现,深色不出现),产生的图像更加明亮。
计算公式:

C=1-(1-A)*(1-B)

代码实现:

/// Screen 滤色 C=1-(1-A)*(1-B), 也可以写成 1-C=(1-A)*(1-B)
func blendScreen(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
   return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $1) * (1 - $0) }
 }
滤色

上下层像素的标准色彩值反相后相乘后输出,输出结果比两者的像素值都将要亮(就好像两台投影机分别对其中一个图层进行投影后,然后投射到同一个屏幕上)。如果两个图层反相后,采用Multiply模式混合,则将和对这两个图层采用Screen模式混合后反相的结果完全一样。


8.颜色减淡(Color Dodge)

使用这种模式时,会加亮图层的颜色值,加上的颜色越暗,效果越细腻。与颜色加深刚好相反,通过降低对比度,加亮底层颜色来反映混合色彩。与黑色混合没有任何效果。
计算公式:

C=B/(1-A)

代码实现:

/// Color Dodge 颜色减淡 C=B/(1-A)
 func blendColorDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 >= 1.0 { return $1 }
            else { return min(1.0, $0 / (1 - $1)) }
  }
}
颜色减淡

该模式下,上层的亮度决定了下层的暴露程度。如果上层越亮,下层获取的光越多,也就是越亮。如果上层是纯黑色,也就是没有亮度,则根本不会影响下层。如果上层是纯白色,则下层除了像素为255的地方暴露外,其他地方全部为白色。结果最黑的地方不会低于下层的像素值。


9.线性减淡(Linear Dodge)

类似于颜色减淡模式。但是通过增加亮度来使得底层颜色变亮,以此获得混合色彩。与黑色混合没有任何效果。
计算公式:

C=A+B

代码实现:

/// Linear Dodge 线性减淡 C=A+B
func blendLinearDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return min(1, $1 + $0) }
}
线性减淡

将上下层的色彩值相加。结果将更亮。其中基色与混合色的数值大于255,系统就默认为最大值也就是255。


10.叠加(Overlay)

叠加模式比较复杂,它是根据基色图层的色彩来决定混合色图层的像素是进行正片叠底还是进行滤色,一般来说,发生变化的都是中间色调,高色和暗色区域基本保持不变。像素是进行正片叠底(Multiply)混合还是屏幕(Screen)混合,取决于基色层颜色。颜色会被混合,但基色层颜色的高光与阴影部分的亮度细节就会被保留。
计算公式:

B<=0.5: C=2*A*B
B>0.5: C=1-2*(1-A)*(1-B)

代码实现:

/// Overlay 叠加 B<=0.5: C=2*A*B; B>0.5: C=1-2*(1-A)*(1-B)
func blendOverlay(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
  }
}
叠加

依据下层色彩值的不同,该模式可能是Multiply,也可能是Screen模式。上层决定了下层中间色调偏移的强度。如果上层为0.5,则结果将完全为下层像素的值。如果上层比0.5暗,则下层的中间色调的将向暗地方偏移,如果上层比0.5亮,则下层的中间色调的将向亮地方偏移。


11.柔光(Soft Light)

将混合色图层以柔光的方式加到基色图层,当基色图层的灰阶趋于高或低,则会调整图层合成结果的阶调趋于中间的灰阶调,而获得色彩较为柔和的合成效果。形成的结果是:图像的中亮色调区域变得更亮,暗色区域变得更暗,图像反差增大类似于柔光灯的照射图像的效果。变暗还是提亮画面颜色,取决于混合层颜色信息。产生的效果类似于为图像打上一盏散射的聚光灯。如果混合层颜色(光源)亮度高于0.5,基色层会被照亮(变淡)。如果混合层颜色(光源)亮度低于0.5,基色层会变暗,就好像被烧焦了似的。
计算公式:

A<=0.5: C=(2*A-1)*(B-B*B)+B 
A>0.5: C=(2*A-1)*(sqrt(B)-B)+B

代码实现:

/// Soft Light 柔光 A<=0.5: C=(2*A-1)*(B-B*B)+B; A>0.5: C=(2*A-1)*(sqrt(B)-B)+B
func blendSoftLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return (2 * $1 - 1) * ($0 - $0 * $0) + $0 }
            else { return (2 * $1 - 1)*( sqrt($0) - $0) + $0 }
  }
}
柔光

该模式类似上层以Gamma值范围为2.0到0.5的方式来调制下层的色彩值。结果将是一个非常柔和的组合。


12.强光(Hard Light)

如果两层中颜色的灰阶是偏向低灰阶,作用与正片叠底模式类似,而当偏向高灰阶时,则与滤色模式类似。中间阶调作用不明显。正片叠底或者是滤色混合基层颜色,取决于混合层颜色。产生的效果就好像为图像应用强烈的聚光灯一样。如果混合层层颜色(光源)亮度高于0.5,图像就会被照亮,这时混合方式类似于滤色(Screen)模式。反之,如果亮度低于0.5,图像就会变暗,这时混合方式就类似于正片叠底(Multiply)模式。该模式能为图像添加阴影。如果用纯黑或者纯白来进行混合,得到的也将是纯黑或者纯白。
计算公式:

A<=0.5: C=2*A*B
A>0.5: C=1-2*(1-A)*(1-B)

代码实现:

/// Hard Light 强光 A<=0.5: C=2*A*B; A>0.5: C=1-2*(1-A)*(1-B)
func blendHardLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
  }
}
强光

该模式完全相对应于Overlay模式下,两个图层进行次序交换的情况。如过上层的颜色高于0.5,则下层越亮,反之越暗。


13.亮光(Vivid Light)

调整对比度以加深或减淡颜色,取决于混合层图像的颜色分布。如果混合层颜色(光源)亮度高于0.5,图像将被降低对比度并且变亮;如果混合层颜色(光源)亮度低于0.5,图像会被提高对比度并且变暗。
计算公式:

A<=0.5: C=1-(1-B)/2*A
A>0.5: C=B/(2*(1-A))

代码实现:

/// Vivid Light 亮光 A<=0.5: C=1-(1-B)/(2*A); A>0.5: C=B/(2*(1-A))
func blendVividLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return self.fitIn((1 - (1 - $0) / (2 * $1)), ceil: 1.0) }
            else { return self.fitIn($0 / (2 * (1 - $1)), ceil: 1.0) }
  }
}
亮光

该模式非常强烈的增加了对比度,特别是在高亮和阴暗处。可以认为是阴暗处应用Color Burn和高亮处应用Color Dodge。


14.线性光(Linear Light)

线性光通过减少或增加亮度,来使颜色加深或减淡。具体取决于混合色的数值。如果混合层颜色(光源)亮度高于0.5,则用增加亮度的方法来使得画面变亮,反之用降低亮度的方法来使画面变暗。
计算公式:

C=B+2*A-1

代码实现:

/// Linear Light 线性光 C=B+2*A-1
func blendLinearLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { return self.fitIn($0 + 2 * $1 - 1, ceil: 1.0) }
 }
线性光

相对于前一种模式而言,该模式增加的对比度要弱些。其类似于Linear Burn,只不过是加深了上层的影响力。


15.点光(Pin Light)

点光模式她会根据混合色的颜色数值替换相应的颜色。如果混合层颜色(光源)亮度高于0.5,比混合层颜色暗的像素将会被取代,而较之亮的像素则不发生变化。如果混合层颜色(光源)亮度低于0.5,比混合层颜色亮的像素会被取代,而较之暗的像素则不发生变化。
计算公式:

B<2*A-1: C=2*A-12*A-1<B<2*A: C=B
B>2*A: C=2*A

代码实现:

/// Pin Light 点光
/// B<2*A-1:     C=2*A-1
/// 2*A-1<B<2*A: C=B
/// B>2*A:       C=2*A
func blendPinLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 2 * $1 - 1 { return 2 * $1 - 1 }
            else if (2 * $1 - 1 < $0) && ($0 < 2 * $1) { return $0}
            else { return 2 * $1 }
  }
}
点光

该模式结果就是导致中间调几乎是不变的下层,但是两边是Darken和Lighte年模式的组合。


16.实色混合(Hard Mix)

实色混合是把混合色颜色中的红、绿、蓝通道数值,添加到基色的RGB值中。结果色的R、G、B通道的数值只能是255或0。因此结构色只有一下八种可能:红、绿、蓝、黄、青、洋红、白、黑。由此看以看出结果色是非常纯的颜色。
计算公式:

A<1-B: C=0
A>1-B: C=1

代码实现:

/// Hard Mix 实色混合A<1-B: C=0; A>1-B: C=1
func blendHardMix(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 1 - $0 { return 0 }
            else { return 1 }
  }
}
实色混合

该模式导致了最终结果仅包含6种基本颜色,每个通道要么就是0,要么就是255。


差值型

17.差值(Difference)

将要混合图层双方的RGB值中每个值分别进行比较,用高值减去低值作为合成后的颜色。所以这种模式也常使用,白色与任何颜色混合得到反相色,黑色与任何颜色混合颜色不变。
计算公式:

C=|A-B|

代码实现:

/// Difference 差值 C=|A-B|
func blendDifference(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { fabs($1 - $0) }
}
差值

上下层色调的绝对值。该模式主要用于比较两个不同版本的图片。如果两者完全一样,则结果为全黑


18.排除(Exclusion)

排除于差值的作用类似,只是排除模式的结果色对比度没有差值模式强。白色与基色混合得到基色补色,黑色与基色混合得到基色。
计算公式:

C=A+B-2*A*B

代码实现:

/// Exclusion 排除 C = A+B-2*A*B
func blendExclusion(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 + $0 - 2 * $1 * $0  }
}
排除

亮的图片区域将导致另一层的反相,很暗的区域则将导致另一层完全没有改变。


19.减去(Subtract)

减去模式的作用是查看各通道的颜色信息,并从基色中减去混合色。如果出现负数就归为零。与基色相同的颜色混合得到黑色;白色与基色混合得到黑色;黑色与基色混合得到基色。

计算公式:

C=A-B

代码实现:

/// 减去 C=A-B
func blendMinus(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 - $0 }
}
减去

20.划分(Divide)

划分模式的作用是查看每个通道的颜色信息,并用基色分割混合色。基色数值大于或等于混合色数值,混合出的颜色为白色。基色数值小于混合色,结果色比基色更暗。因此结果色对比非常强。白色与基色混合得到基色,黑色与基色混合得到白色。

计算公式:

C=A/B

代码实现:

/// 划分 C=A/B
func blendDivision(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
  return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 == 0{
                return 1.0
            }else {
                return self.fitIn($1 / $0, ceil: 1.0)
            }
   }
}
划分

结尾

附上 github https://github.com/Orange-W/PhotoshopBending
源码,和总文件:

//
//  UIColor+Combine.swift
//  iScales
//
//  Created by OrangeEvan on 2017/10/26.
//  Copyright © 2017年 NetEase. All rights reserved.
//

import Foundation
import UIKit

extension UIColor {
    // MARK: - 常用叠图
    // Alpha Blending 前景色叠图
    func blendAlpha(coverColor: UIColor) -> UIColor {
        let c1 = coverColor.rgbaTuple()
        let c2 = self.rgbaTuple()
        
        let c1r = CGFloat(c1.r)
        let c1g = CGFloat(c1.g)
        let c1b = CGFloat(c1.b)
        
        let c2r = CGFloat(c2.r)
        let c2g = CGFloat(c2.g)
        let c2b = CGFloat(c2.b)
        
        // 前景色叠图公式
        let r = c1r * c1.a + c2r  * (1 - c1.a)
        let g = c1g * c1.a + c2g  * (1 - c1.a)
        let b = c1b * c1.a + c2b  * (1 - c1.a)
      
        return UIColor.init(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: 1.0)
    }
    
    
    // MARK: - 去亮度型
    /// Darken 变暗  B<=A: C=B; B>=A: C=A
    func blendDarken(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 <= $1) ? $0 : $1 }
    }
    
    /// Multiply 正片叠底 C = A*B
    func blendMultiply(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return $0 * $1 }
    }
    
    /// Color Burn 颜色加深 C=1-(1-B)/A
    func blendColorBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $0) / $1 }
    }
    
    /// Linear Burn 线性加深 C=A+B-1
    func blendLinearBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($1 + $0) - 1.0 }
    }
    
    // MARK: - 去暗型
    /// Lighten 变亮   B>=A: C=B; B<=A: C=A
    func blendLighten(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 >= $1) ? $0 : $1 }
    }
    
    /// Screen 滤色 C=1-(1-A)*(1-B), 也可以写成 1-C=(1-A)*(1-B)
    func blendScreen(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $1) * (1 - $0) }
    }
    
    /// Color Dodge 颜色减淡 C=B/(1-A)
    func blendColorDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 >= 1.0 { return $1 }
            else { return min(1.0, $0 / (1 - $1)) }
        }
    }
    
    /// Linear Dodge 线性减淡 C=A+B
    func blendLinearDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return min(1, $1 + $0) }
    }
    
    // MARK: - 溶合型
    /// Overlay 叠加 B<=0.5: C=2*A*B; B>0.5: C=1-2*(1-A)*(1-B)
    func blendOverlay(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
        }
    }
    
    /// Soft Light 柔光 A<=0.5: C=(2*A-1)*(B-B*B)+B; A>0.5: C=(2*A-1)*(sqrt(B)-B)+B
    func blendSoftLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return (2 * $1 - 1) * ($0 - $0 * $0) + $0 }
            else { return (2 * $1 - 1)*( sqrt($0) - $0) + $0 }
        }
    }
    
    /// Hard Light 强光 A<=0.5: C=2*A*B; A>0.5: C=1-2*(1-A)*(1-B)
    func blendHardLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
        }
    }
    
    /// Vivid Light 亮光 A<=0.5: C=1-(1-B)/(2*A); A>0.5: C=B/(2*(1-A))
    func blendVividLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return self.fitIn((1 - (1 - $0) / (2 * $1)), ceil: 1.0) }
            else { return self.fitIn($0 / (2 * (1 - $1)), ceil: 1.0) }
        }
    }
    
    /// Linear Light 线性光 C=B+2*A-1
    func blendLinearLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return self.fitIn($0 + 2 * $1 - 1, ceil: 1.0) }
    }
    
    /// Pin Light 点光
    /// B<2*A-1:     C=2*A-1
    /// 2*A-1<B<2*A: C=B
    /// B>2*A:       C=2*A
    func blendPinLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 2 * $1 - 1 { return 2 * $1 - 1 }
            else if (2 * $1 - 1 < $0) && ($0 < 2 * $1) { return $0}
            else { return 2 * $1 }
        }
    }
    
    /// Hard Mix 实色混合A<1-B: C=0; A>1-B: C=1
    func blendHardMix(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 1 - $0 { return 0 }
            else { return 1 }
        }
    }
    
    // MARK: - 色差型
    /// Difference 差值 C=|A-B|
    func blendDifference(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { fabs($1 - $0) }
    }
    
    /// Exclusion 排除 C = A+B-2*A*B
    func blendExclusion(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 + $0 - 2 * $1 * $0  }
    }
    
    /// 减去 C=A-B
    func blendMinus(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 - $0 }
    }
    
    /// 划分 C=A/B
    func blendDivision(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 == 0{
                return 1.0
            }else {
                return self.fitIn($1 / $0, ceil: 1.0)
            }
        }
    }
    
    // MARK: 处理函数
    func blendProcedure(
        coverColor: UIColor,
        alpha: CGFloat,
        procedureBlock: ((_ baseValue: CGFloat,_ topValue: CGFloat) -> CGFloat)?
        ) -> UIColor {
        let baseCompoment = self.rgbaTuple()
        let topCompoment = coverColor.rgbaTuple()
        
        // 该层透明度
        let mixAlpha = alpha * topCompoment.a + (1.0 - alpha) * baseCompoment.a
        
        // RGB 值
        let mixR = procedureBlock?(
            baseCompoment.r / 255.0,
            topCompoment.r / 255.0)
            ?? (baseCompoment.r) / 255.0
        
        let mixG = procedureBlock?(
            baseCompoment.g / 255.0,
            topCompoment.g / 255.0)
            ?? (baseCompoment.g) / 255.0
        
        let mixB = procedureBlock?(
            baseCompoment.b / 255.0,
            topCompoment.b / 255.0)
            ?? baseCompoment.b / 255.0
        
        
        return UIColor.init(red:   fitIn(mixR),
                            green: fitIn(mixG),
                            blue:  fitIn(mixB),
                            alpha: mixAlpha)
    }
    
    // 防止越界
    func fitIn(_ value: CGFloat, ceil: CGFloat = 255) -> CGFloat { return max(min(value,ceil),0) }
    func fitIn(_ value: Double, ceil: CGFloat = 255) -> CGFloat { return fitIn(CGFloat(value), ceil: ceil) }
    
    // 返回 RBGA
    func rgbaTuple() -> (r: CGFloat, g: CGFloat, b: CGFloat,a: CGFloat) {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        r = r * 255
        g = g * 255
        b = b * 255
        
        return ((r),(g),(b),a)
    }
}

参考资料

图层混合开发案例: http://avnpc.com/pages/photoshop-layer-blending-algorithm
图层混合样式及说明: http://www.jb51.net/photoshop/249182.
图层混合公式: https://www.zhihu.com/question/20293077

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,302评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,232评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,337评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,977评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,920评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,194评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,638评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,319评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,455评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,379评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,426评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,106评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,696评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,786评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,996评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,467评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,043评论 2 341

推荐阅读更多精彩内容

  • 作为摄影师们和设计师们后期常用的工具Photoshop,它强大的功能毋庸置疑。但在后期制作中对于各项工具的使...
    DIGITALMAN阅读 2,595评论 1 52
  • 写在前面的话 上一篇文章对简单滤镜实现有一定的讲解,那么这一篇则是对图像处理更加深层次的说明,对于一张图片怎么处理...
    前世小书童阅读 5,377评论 8 32
  • 作为摄影师们后期常用的工具Photoshop,它强大的功能毋庸置疑。但在后期制作中,对于各项工具的使用原理你清楚吗...
    打豆豆阅读 363评论 0 1
  • 基本概念 进行图层混合时,上图层为混合色,下图层为基色,混合产生的最终效果为结果色。 “基色”:“基色”是图像中的...
    爱捣腾的吴大爷阅读 14,836评论 1 29
  • 滤色:它与正片叠底模式相反,将上下两层图层像素颜色的灰度级进行乘法计算,获得灰度级更高的颜色而成为合成后的颜色,图...
    快乐鸟儿阅读 858评论 0 4