你和抖音可能就缺一個圖層混疊特效了

文章源于視覺哥哥凱凱的需求壳炎。在實(shí)現(xiàn)觸發(fā)特效的時(shí)搁痛,在視覺稿中使用了10% 的蒙板疊加在原色值上,而不是給出混合后的色值长搀,開發(fā)上并不好處理。于是鸡典,我決定憑著我程序員般縝密的思維與邏輯性的口舌一定要說服他源请,這個需求沒有必要做!
來到凱凱座位彻况,經(jīng)過一輪深入淺出的表達(dá)谁尸。凱凱似乎已經(jīng)認(rèn)識到了蒙板對于開發(fā)的不便利性。“這個效果纽甘,是不是做不了良蛮。你如果做不了的話。就用混合色值吧悍赢【鐾” 我轉(zhuǎn)念一想,怎么能說自己不行左权,下午先自己悄悄的看看資料吧皮胡。“不過安卓那邊實(shí)現(xiàn)沒問題啊∩统伲” 凱凱隨后默默的給我補(bǔ)了一刀屡贺。同時(shí)解釋視覺效果一般都是通過多個圖層混疊實(shí)現(xiàn)的。如果能實(shí)現(xiàn)瀑梗,會更利于視覺和開發(fā)統(tǒng)一視覺規(guī)范……
經(jīng)過凱凱的一番說服烹笔,我瞬間認(rèn)識到了這項(xiàng)任務(wù)的重要性和必要性,以及自己身上沉甸甸的使命感抛丽。既然安卓能做谤职,iOS 肯定也能做! 這個需求看來也是很有必要耙谙省允蜈!嗯冤吨?在么好像有哪里不對?

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


所謂圖層混合模式就是指一個層與其下圖層的色彩疊加方式饶套,在這之前我們所使用的是正常模式漩蟆,除了正常以外,還有很多種混合模式妓蛮,它們都可以產(chǎn)生迥異的合成效果怠李。

打開 Photoshop疊層菜單,可以發(fā)現(xiàn)除去最后幾欄其他都是RBG色值的混合操作蛤克。

圖層混合功能

從上到下數(shù),除去正常, 幾個分區(qū)可以分為: 變暗型, 變亮型, 溶合型,色差型, 明亮度
其中前四種 變暗型, 變亮型, 溶合型,色差型 均為前后兩個圖層的 RGB 混合捺癞,通過簡單的 RGB 混合公式即可實(shí)現(xiàn)

為了美觀构挤,我使用外鏈的素材。并且為了對比明顯筋现,圖片只會疊加下方部分70%唐础。而上方30%作為原圖對比。

原圖
混疊圖層

通用說明


公式指代

d: 混合后的結(jié)果色透明度矾飞。
A: 為上方疊層的色值或透明度一膨。
B:為原圖層色值或透明度,也就是下方的 base 層凰慈。
(因?yàn)橥愋突旌?R汞幢,G,B微谓,Alpha 的圖層混合計(jì)算一致,在各個值得的計(jì)算中输钩。A和 B 均指代為對應(yīng)的色值)如正底疊片公式:

C =A*B
等價(jià)于
Cr =Ar*Br
Cg =Ag*Bg,
Cb =Ab*Bb

為了方便計(jì)算豺型,公式中的數(shù)值均百分比,即 0.0 ~ 1.0 买乃。需要乘上255才是真正的色值姻氨。
需要說明的是,Photoshop 補(bǔ)色的概念即為顏色至飽和的差值剪验。簡單來說肴焊,1-A 即為 A 的補(bǔ)色,1-B 即為 B 的補(bǔ)色功戚。
Opacity 不透明度計(jì)算

C = dA+(1-d)B

A代表了上面圖層像素的色彩值(A=像素值/255)娶眷,d表示該層的透明度,B代表下面圖層像素的色彩值(B=像素值/255)啸臀,C代表了混合像素的色彩值(真實(shí)的結(jié)果像素值應(yīng)該為255*C)届宠。該公式也應(yīng)用于層蒙板,在這種情況下,d代表了蒙板圖層中給定位置像素的亮度豌注,下同伤塌,不再敘述。

效果實(shí)現(xiàn)


第一步: 工具類函數(shù)的準(zhǔn)備
extension UIColor {
     // MARK: 處理函數(shù)
    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)
    }
}
第二步:開始實(shí)現(xiàn)
通用型
1.前景疊圖 (Alpha Bend)

如果你發(fā)現(xiàn)視覺稿里沒有標(biāo)注特效類型轧铁,不知道視覺要你用的是什么效果每聪,那肯定就是這個了。別問我為什么 : )

計(jì)算公式:

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

代碼實(shí)現(xiàn):

    // 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)

在該模式下齿风,對混合的兩個圖層相對應(yīng)區(qū)域RGB通道中的顏色亮度值進(jìn)行比較药薯,在混合圖層中,比基色圖層暗的像素保留聂宾,亮的像素用基色圖層中暗的像素替換果善。總的顏色灰度級降低系谐,造成變暗的效果巾陕。

計(jì)算公式:

B<=A,則 C=B纪他。
B>=A鄙煤,則 C=A。

代碼實(shí)現(xiàn):

/// 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 }
}
變暗

該模式通過比較上下層像素后取相對較暗的像素作為輸出茶袒,注意梯刚,每個不同的顏色通道的像素都是獨(dú)立的進(jìn)行比較,色彩值相對較小的作為輸出結(jié)果薪寓,下層表示疊放次序位于下面的那個圖層亡资,即B。上層表示疊放次序位于上面的那個圖層向叉,即A锥腻。


3.正片疊底(Multiply)

將上下兩層圖層像素顏色的灰度級進(jìn)行乘法計(jì)算,獲得灰度級更低的顏色而成為合成后的顏色母谎,圖層合成后的效果簡單地說是低灰階的像素顯現(xiàn)而高灰階不顯現(xiàn)瘦黑。
計(jì)算公式:

C=A*B

代碼實(shí)現(xiàn):

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

該效果將兩層像素的標(biāo)準(zhǔn)色彩值(基于0..1之間)相乘后輸出,其效果可以形容成:兩個幻燈片疊加在一起然后放映奇唤,透射光需要分別通過這兩個幻燈片幸斥,從而被削弱了兩次。


4.顏色加深(Color Burn)

使用這種模式時(shí)咬扇,會加暗圖層的顏色值甲葬,加上的顏色越亮,效果越細(xì)膩冗栗。讓底層的顏色變暗演顾,有點(diǎn)類似于正片疊底供搀,但不同的是,它會根據(jù)疊加的像素顏色相應(yīng)增加對比度钠至。和白色混合沒有效果葛虐。
計(jì)算公式:

C=1-(1-B)/A

代碼實(shí)現(xiàn):

/// 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 }
 }
顏色加深

該模式如果上層越暗,則下層獲取的光越少棉钧,如果上層為全黑色屿脐,則下層越黑,如果上層為全白色宪卿,則根本不會影響下層的诵。結(jié)果最亮的地方不會高于下層的像素值。


5.線性加深(Linear Burn)

和顏色加深模式一樣佑钾,線性加深模式通過降低亮度西疤,讓底色變暗以反映混合色彩。和白色混合沒有效果休溶。
計(jì)算公式:

C=A+B-1

代碼實(shí)現(xiàn):

/// 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代赁,輸出結(jié)果將會是純黑色。如果將上層反相兽掰,結(jié)果將是純粹的數(shù)學(xué)減芭碍。


變亮型

6.變亮(Lighten)

在該模式與變暗模式相反,是對混合的兩個圖層相對應(yīng)區(qū)域RGB通道中的顏色亮度值進(jìn)行比較孽尽,取較高的的像素點(diǎn)為混合之后的顏色恭金,使得總的顏色灰度的亮度升高撞羽,造成變亮的效果。用黑色合成圖像時(shí)無作用满俗,用白色時(shí)則仍為白色器腋。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// 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 }
}
變亮

該模式是取色彩值較大的(也就是較亮的)作為輸出結(jié)果溶其。


7.濾色(Screen)

它與正片疊底模式相反斗躏,將上下兩層圖層像素顏色的灰度級進(jìn)行乘法計(jì)算柱告,獲得灰度級更高的顏色而成為合成后的顏色,圖層合成后的效果簡單地說是高灰階的像素顯現(xiàn)而低灰階不顯現(xiàn)(即淺色出現(xiàn)婆瓜,深色不出現(xiàn)),產(chǎn)生的圖像更加明亮贡羔。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// 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) }
 }
濾色

上下層像素的標(biāo)準(zhǔn)色彩值反相后相乘后輸出廉白,輸出結(jié)果比兩者的像素值都將要亮(就好像兩臺投影機(jī)分別對其中一個圖層進(jìn)行投影后,然后投射到同一個屏幕上)乖寒。如果兩個圖層反相后猴蹂,采用Multiply模式混合,則將和對這兩個圖層采用Screen模式混合后反相的結(jié)果完全一樣楣嘁。


8.顏色減淡(Color Dodge)

使用這種模式時(shí)磅轻,會加亮圖層的顏色值珍逸,加上的顏色越暗,效果越細(xì)膩聋溜。與顏色加深剛好相反谆膳,通過降低對比度,加亮底層顏色來反映混合色彩撮躁。與黑色混合沒有任何效果漱病。
計(jì)算公式:

C=B/(1-A)

代碼實(shí)現(xiàn):

/// 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的地方暴露外,其他地方全部為白色纠俭。結(jié)果最黑的地方不會低于下層的像素值沿量。


9.線性減淡(Linear Dodge)

類似于顏色減淡模式。但是通過增加亮度來使得底層顏色變亮冤荆,以此獲得混合色彩朴则。與黑色混合沒有任何效果。
計(jì)算公式:

C=A+B

代碼實(shí)現(xiàn):

/// 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) }
}
線性減淡

將上下層的色彩值相加钓简。結(jié)果將更亮乌妒。其中基色與混合色的數(shù)值大于255,系統(tǒng)就默認(rèn)為最大值也就是255外邓。


10.疊加(Overlay)

疊加模式比較復(fù)雜撤蚊,它是根據(jù)基色圖層的色彩來決定混合色圖層的像素是進(jìn)行正片疊底還是進(jìn)行濾色,一般來說损话,發(fā)生變化的都是中間色調(diào)侦啸,高色和暗色區(qū)域基本保持不變。像素是進(jìn)行正片疊底(Multiply)混合還是屏幕(Screen)混合丧枪,取決于基色層顏色光涂。顏色會被混合,但基色層顏色的高光與陰影部分的亮度細(xì)節(jié)就會被保留拧烦。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// 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) }
  }
}
疊加

依據(jù)下層色彩值的不同,該模式可能是Multiply齐佳,也可能是Screen模式本鸣。上層決定了下層中間色調(diào)偏移的強(qiáng)度。如果上層為0.5,則結(jié)果將完全為下層像素的值饲宛。如果上層比0.5暗,則下層的中間色調(diào)的將向暗地方偏移,如果上層比0.5亮絮重,則下層的中間色調(diào)的將向亮地方偏移青伤。


11.柔光(Soft Light)

將混合色圖層以柔光的方式加到基色圖層狠角,當(dāng)基色圖層的灰階趨于高或低丰歌,則會調(diào)整圖層合成結(jié)果的階調(diào)趨于中間的灰階調(diào),而獲得色彩較為柔和的合成效果偷仿。形成的結(jié)果是:圖像的中亮色調(diào)區(qū)域變得更亮羡玛,暗色區(qū)域變得更暗,圖像反差增大類似于柔光燈的照射圖像的效果让歼。變暗還是提亮畫面顏色谋右,取決于混合層顏色信息补箍。產(chǎn)生的效果類似于為圖像打上一盞散射的聚光燈。如果混合層顏色(光源)亮度高于0.5坑雅,基色層會被照亮(變淡)。如果混合層顏色(光源)亮度低于0.5裹粤,基色層會變暗终蒂,就好像被燒焦了似的。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// 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的方式來調(diào)制下層的色彩值早龟。結(jié)果將是一個非常柔和的組合。


12.強(qiáng)光(Hard Light)

如果兩層中顏色的灰階是偏向低灰階猜丹,作用與正片疊底模式類似芝加,而當(dāng)偏向高灰階時(shí),則與濾色模式類似射窒。中間階調(diào)作用不明顯藏杖。正片疊底或者是濾色混合基層顏色将塑,取決于混合層顏色。產(chǎn)生的效果就好像為圖像應(yīng)用強(qiáng)烈的聚光燈一樣蝌麸。如果混合層層顏色(光源)亮度高于0.5点寥,圖像就會被照亮,這時(shí)混合方式類似于濾色(Screen)模式来吩。反之敢辩,如果亮度低于0.5,圖像就會變暗弟疆,這時(shí)混合方式就類似于正片疊底(Multiply)模式戚长。該模式能為圖像添加陰影。如果用純黑或者純白來進(jìn)行混合兽间,得到的也將是純黑或者純白历葛。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// Hard Light 強(qiáng)光 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) }
  }
}
強(qiáng)光

該模式完全相對應(yīng)于Overlay模式下,兩個圖層進(jìn)行次序交換的情況嘀略。如過上層的顏色高于0.5恤溶,則下層越亮,反之越暗帜羊。


13.亮光(Vivid Light)

調(diào)整對比度以加深或減淡顏色咒程,取決于混合層圖像的顏色分布。如果混合層顏色(光源)亮度高于0.5讼育,圖像將被降低對比度并且變亮;如果混合層顏色(光源)亮度低于0.5帐姻,圖像會被提高對比度并且變暗。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// 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) }
  }
}
亮光

該模式非常強(qiáng)烈的增加了對比度奶段,特別是在高亮和陰暗處饥瓷。可以認(rèn)為是陰暗處應(yīng)用Color Burn和高亮處應(yīng)用Color Dodge痹籍。


14.線性光(Linear Light)

線性光通過減少或增加亮度呢铆,來使顏色加深或減淡。具體取決于混合色的數(shù)值蹲缠。如果混合層顏色(光源)亮度高于0.5棺克,則用增加亮度的方法來使得畫面變亮,反之用降低亮度的方法來使畫面變暗线定。
計(jì)算公式:

C=B+2*A-1

代碼實(shí)現(xiàn):

/// 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.點(diǎn)光(Pin Light)

點(diǎn)光模式她會根據(jù)混合色的顏色數(shù)值替換相應(yīng)的顏色纱皆。如果混合層顏色(光源)亮度高于0.5,比混合層顏色暗的像素將會被取代,而較之亮的像素則不發(fā)生變化抹剩。如果混合層顏色(光源)亮度低于0.5撑帖,比混合層顏色亮的像素會被取代,而較之暗的像素則不發(fā)生變化澳眷。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// Pin Light 點(diǎn)光
/// 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 }
  }
}
點(diǎn)光

該模式結(jié)果就是導(dǎo)致中間調(diào)幾乎是不變的下層,但是兩邊是Darken和Lighte年模式的組合蛉艾。


16.實(shí)色混合(Hard Mix)

實(shí)色混合是把混合色顏色中的紅钳踊、綠、藍(lán)通道數(shù)值勿侯,添加到基色的RGB值中拓瞪。結(jié)果色的R、G助琐、B通道的數(shù)值只能是255或0祭埂。因此結(jié)構(gòu)色只有一下八種可能:紅、綠兵钮、藍(lán)蛆橡、黃、青掘譬、洋紅泰演、白、黑葱轩。由此看以看出結(jié)果色是非常純的顏色睦焕。
計(jì)算公式:

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

代碼實(shí)現(xiàn):

/// Hard Mix 實(shí)色混合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 }
  }
}
實(shí)色混合

該模式導(dǎo)致了最終結(jié)果僅包含6種基本顏色,每個通道要么就是0靴拱,要么就是255垃喊。


差值型

17.差值(Difference)

將要混合圖層雙方的RGB值中每個值分別進(jìn)行比較,用高值減去低值作為合成后的顏色袜炕。所以這種模式也常使用本谜,白色與任何顏色混合得到反相色,黑色與任何顏色混合顏色不變妇蛀。
計(jì)算公式:

C=|A-B|

代碼實(shí)現(xiàn):

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

上下層色調(diào)的絕對值耕突。該模式主要用于比較兩個不同版本的圖片。如果兩者完全一樣评架,則結(jié)果為全黑


18.排除(Exclusion)

排除于差值的作用類似眷茁,只是排除模式的結(jié)果色對比度沒有差值模式強(qiáng)。白色與基色混合得到基色補(bǔ)色纵诞,黑色與基色混合得到基色上祈。
計(jì)算公式:

C=A+B-2*A*B

代碼實(shí)現(xiàn):

/// 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  }
}
排除

亮的圖片區(qū)域?qū)?dǎo)致另一層的反相,很暗的區(qū)域則將導(dǎo)致另一層完全沒有改變。


19.減去(Subtract)

減去模式的作用是查看各通道的顏色信息登刺,并從基色中減去混合色籽腕。如果出現(xiàn)負(fù)數(shù)就歸為零。與基色相同的顏色混合得到黑色;白色與基色混合得到黑色;黑色與基色混合得到基色纸俭。

計(jì)算公式:

C=A-B

代碼實(shí)現(xiàn):

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

20.劃分(Divide)

劃分模式的作用是查看每個通道的顏色信息皇耗,并用基色分割混合色∽岷埽基色數(shù)值大于或等于混合色數(shù)值郎楼,混合出的顏色為白色≈匣冢基色數(shù)值小于混合色呜袁,結(jié)果色比基色更暗。因此結(jié)果色對比非常強(qiáng)简珠。白色與基色混合得到基色阶界,黑色與基色混合得到白色。

計(jì)算公式:

C=A/B

代碼實(shí)現(xiàn):

/// 劃分 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)
            }
   }
}
劃分

結(jié)尾

附上 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 強(qiáng)光 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 點(diǎn)光
    /// 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 實(shí)色混合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: 處理函數(shù)
    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)
    }
}

參考資料

圖層混合開發(fā)案例: http://avnpc.com/pages/photoshop-layer-blending-algorithm
圖層混合樣式及說明: http://www.jb51.net/photoshop/249182.
圖層混合公式: https://www.zhihu.com/question/20293077

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膘融,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子珍策,更是在濱河造成了極大的恐慌托启,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件攘宙,死亡現(xiàn)場離奇詭異屯耸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蹭劈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門疗绣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人铺韧,你說我怎么就攤上這事多矮。” “怎么了哈打?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵塔逃,是天一觀的道長。 經(jīng)常有香客問我料仗,道長湾盗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任立轧,我火速辦了婚禮格粪,結(jié)果婚禮上躏吊,老公的妹妹穿的比我還像新娘。我一直安慰自己帐萎,他們只是感情好比伏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疆导,像睡著了一般赁项。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上澈段,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天肤舞,我揣著相機(jī)與錄音,去河邊找鬼均蜜。 笑死,一個胖子當(dāng)著我的面吹牛芒率,可吹牛的內(nèi)容都是我干的囤耳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼偶芍,長吁一口氣:“原來是場噩夢啊……” “哼充择!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起匪蟀,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤椎麦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后材彪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體观挎,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年段化,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嘁捷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡显熏,死狀恐怖雄嚣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喘蟆,我是刑警寧澤缓升,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站蕴轨,受9級特大地震影響港谊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尺棋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一封锉、第九天 我趴在偏房一處隱蔽的房頂上張望绵跷。 院中可真熱鬧,春花似錦成福、人聲如沸碾局。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽净当。三九已至,卻和暖如春蕴潦,著一層夾襖步出監(jiān)牢的瞬間像啼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工潭苞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忽冻,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓此疹,卻偏偏與公主長得像僧诚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝗碎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 作為攝影師們和設(shè)計(jì)師們后期常用的工具Photoshop湖笨,它強(qiáng)大的功能毋庸置疑。但在后期制作中對于各項(xiàng)工具的使...
    DIGITALMAN閱讀 2,610評論 1 52
  • 寫在前面的話 上一篇文章對簡單濾鏡實(shí)現(xiàn)有一定的講解蹦骑,那么這一篇則是對圖像處理更加深層次的說明慈省,對于一張圖片怎么處理...
    前世小書童閱讀 5,399評論 8 32
  • 作為攝影師們后期常用的工具Photoshop,它強(qiáng)大的功能毋庸置疑眠菇。但在后期制作中边败,對于各項(xiàng)工具的使用原理你清楚嗎...
    打豆豆閱讀 370評論 0 1
  • 基本概念 進(jìn)行圖層混合時(shí),上圖層為混合色琼锋,下圖層為基色放闺,混合產(chǎn)生的最終效果為結(jié)果色。 “基色”:“基色”是圖像中的...
    愛搗騰的吳大爺閱讀 15,277評論 1 29
  • 濾色:它與正片疊底模式相反缕坎,將上下兩層圖層像素顏色的灰度級進(jìn)行乘法計(jì)算怖侦,獲得灰度級更高的顏色而成為合成后的顏色,圖...
    快樂鳥兒閱讀 878評論 0 4