iOS開發筆記--使用blend改變圖片顏色

最近對Core Animation和Core

Graphics的內容東西比較感興趣,自己之前也在這塊相對薄弱,趁此機會也想補習一下這塊的內容,所以之后幾篇可能都會是對CA和CG學習的記錄的文章。

在應用里一個很常見的需求是主題變換:同樣的圖標,同樣的素材,但是需要按照用戶喜愛變為不同的顏色。在iOS5和6的SDK里部分標準控件引入了tintColor,來滿足個性化界面的需求,但是Apple在這方面還遠遠做的不夠。一是現在用默認控件根本難以做出界面優秀的應用,二是tintColor所覆蓋的并不夠全面,在很多情況下開發者都無法使用其來完成個性化定義。解決辦法是什么?最簡單當然是拜托設計師大大出圖,想要藍色主題?那好,開PS蓋個藍色圖層出一套藍色的UI;又想加粉色UI,那好,再出一套粉色的圖然后導入Xcode。代碼上的話根據顏色需求使用image-blue或者image-pink這樣的名字來加載圖片。

如果有一丁點重構的意識,就會知道這不是一個很好的解決方案。工程中存在大量的冗余和重復(就算你要狡辯這些圖片顏色不同不算重復,你也會在內心里知道這種狡辯是多么無力),這是非常致命的。想象一下如果你有10套主題界面,先不論應用的體積會膨脹到多少,光是想做一點修改就會痛苦萬分,比如希望改一下某個按鈕的形狀,很好,設計師大大請重復地修改10遍,并出10套UI,然后一系列的重命名,文件移動和導入…一場災難。

當然有其他辦法,因為說白了就是tint不同的顏色到圖片上而已,如果我們能實現改變UIImage的顏色,那我們就只需要一套UI,然后用代碼來改變UI的顏色就可以了,生活有木有一下光明起來呀。嗯,讓我們先從一張圖片開始吧~下面是一張帶有alpha通道的圖片,原始顏色是純的灰色(當然什么顏色都可以,只不過我這個人現在暫時比較喜歡灰色而已)。

我們將用blending給這張圖片加上另一個純色作為tint,并保持原來的alpha通道。用Core Graphics來做的話,大概的想法很直接:

創建一個上下文用以畫新的圖片

將新的tintColor設置為填充顏色

將原圖片畫在創建的上下文中,并用新的填充色著色(注意保持alpha通道)

從當前上下文中取得圖片并返回

最麻煩的部分可能就是保持alpha通道了。UIImage的文檔中提供了使用blend繪圖的方法drawInRect:blendMode:alpha:,rect和alpha都沒什么問題,但是blendMode是個啥玩意兒啊…繼續看文檔,關于CGBlendMode的文檔,里面有一大堆看不懂的枚舉值,比如這樣:

[objc]view plaincopy

kCGBlendModeDestinationOver

R?=?S*(1-?Da)?+?D

Available?in?iOS2.0and?later.

Declared?in?CGContext.h.

完全不懂..直接看之后的Discussion部分:

The blend mode constants introduced in OS X v10.5 represent the Porter-Duff blend modes. The symbols in the equations for these blend modes are:

R is the premultiplied result

S is the source color, and includes alpha

D is the destination color, and includes alpha

Ra, Sa, and Da are the alpha components of R, S, and D

原來如此,R表示結果,S表示包含alpha的原色,D表示包含alpha的目標色,Ra,Sa和Da分別是三個的alpha。明白了這些以后,就可以開始尋找我們所需要的blend模式了。相信你可以和我一樣,很快找到這個模式:

[objc]view plaincopy

kCGBlendModeDestinationIn

R?=?D*Sa

Available?in?iOS2.0and?later.

Declared?in?CGContext.h.

結果 = 目標色和原色透明度的加成,看起來正式所需要的。啦啦啦,還等什么呢,開始動手實現看看對不對吧~

為了以后使用方便,當然是祭出Category,先創建一個UIImage的類別:

[objc]view plaincopy

//??UIImage+Tint.h

#import?

@interfaceUIImage?(Tint)

-?(UIImage*)imageWithTintColor:(UIColor*)tintColor;

@end

暫時先這樣,當然我們也可以創建一個類方法直接完成從bundle讀取圖片然后加tintColor,但是很多時候并不如上面一個實例方法方便(比如想要從非bundle的地方獲取圖片),這個問題之后再說。那么就按照之前設想的步驟來實現吧:

[objc]view plaincopy

//??UIImage+Tint.m

#import?"UIImage+Tint.h"

@implementationUIImage?(Tint)

-?(UIImage*)imageWithTintColor:(UIColor*)tintColor

{

//We?want?to?keep?alpha,?set?opaque?to?NO;?Use?0.0f?for?scale?to?use?the?scale?factor?of?the?device’s?main?screen.

UIGraphicsBeginImageContextWithOptions(self.size,NO,0.0f);

[tintColorsetFill];

CGRect?bounds?=?CGRectMake(0,0,self.size.width,self.size.height);

UIRectFill(bounds);

//Draw?the?tinted?image?in?context

[selfdrawInRect:boundsblendMode:kCGBlendModeDestinationInalpha:1.0f];

UIImage*tintedImage?=?UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returntintedImage;

}

@end

簡單明了,沒有任何難點。測試之:[[UIImage

imageNamed:@"image"] imageWithTintColor:[UIColor orangeColor]];,得到的結果為:

嗯…怎么說呢,雖然tintColor的顏色是變了,但是總覺得怪怪的。仔細對比一下就會發現,原來灰色圖里星星和周圍的灰度漸變到了橙色的圖里好像都消失了:星星整個變成了橙色,周圍的一圈漂亮的光暈也沒有了,這是神馬情況啊…這種圖能交差的話那算見鬼了,會被設計和產品打死的吧。對于無漸變的純色圖的圖來說直接用上面的方法是沒問題的,但是現在除了Metro的大色塊以外哪里無灰度漸變的設計啊…檢查一下使用的blend,R

= D * Sa,恍然大悟,我們雖然保留了原色的透明度,但是卻把它的所有的灰度信息弄丟了。怎么辦?繼續刨CGBlendMode的文檔吧,那么多blend模式應該總有我們需要的。功夫不負有心人,kCGBlendModeOverlay一副嗷嗷待選的樣子:

[objc]view plaincopy

kCGBlendModeOverlay

Either?multiplies?or?screens?the?source?image?samples?with?the?background?image?samples,?depending?on?the?background?color.?The?result?is?to?overlay?the?existing?image?sampleswhilepreserving?the?highlights?and?shadows?of?the?background.?The?background?color?mixes?with?the?source?image?to?reflect?the?lightness?or?darkness?of?the?background.

Available?in?iOS2.0and?later.

Declared?in?CGContext.h.

kCGBlendModeOverlay可以保持背景色的明暗,也就是灰度信息,聽起來正是我們需要的。加入到聲明中,并且添加相應的實現(

順便重構一下原來的代碼 :) ):

[objc]view plaincopy

//??UIImage+Tint.h

#import?

@interfaceUIImage?(Tint)

-?(UIImage*)imageWithTintColor:(UIColor*)tintColor;

-?(UIImage*)imageWithGradientTintColor:(UIColor*)tintColor;

@end

[objc]view plaincopy

//??UIImage+Tint.m

#import?"UIImage+Tint.h"

@implementationUIImage?(Tint)

-?(UIImage*)imageWithTintColor:(UIColor*)tintColor

{

return[selfimageWithTintColor:tintColorblendMode:kCGBlendModeDestinationIn];

}

-?(UIImage*)imageWithGradientTintColor:(UIColor*)tintColor

{

return[selfimageWithTintColor:tintColorblendMode:kCGBlendModeOverlay];

}

-?(UIImage*)imageWithTintColor:(UIColor*)tintColorblendMode:(CGBlendMode)blendMode

{

//We?want?to?keep?alpha,?set?opaque?to?NO;?Use?0.0f?for?scale?to?use?the?scale?factor?of?the?device’s?main?screen.

UIGraphicsBeginImageContextWithOptions(self.size,NO,0.0f);

[tintColorsetFill];

CGRect?bounds?=?CGRectMake(0,0,self.size.width,self.size.height);

UIRectFill(bounds);

//Draw?the?tinted?image?in?context

[selfdrawInRect:boundsblendMode:blendModealpha:1.0f];

UIImage*tintedImage?=?UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returntintedImage;

}

@end

完成,測試之…好吧,好尷尬,雖然顏色和周圍的光這次對了,但是透明度又沒了啊魂淡..一點不奇怪啊,因為kCGBlendModeOverlay本來就沒承諾給你保留原圖的透明度的說。

那么..既然我們用kCGBlendModeOverlay能保留灰度信息,用kCGBlendModeDestinationIn能保留透明度信息,那就兩個blendMode都用不就完事兒了么~嘗試之,如果在blend繪圖時不是kCGBlendModeDestinationIn模式的話,則再用kCGBlendModeDestinationIn畫一次:

[objc]view plaincopy

//??UIImage+Tint.m

#import?"UIImage+Tint.h"

@implementationUIImage?(Tint)

-?(UIImage*)imageWithTintColor:(UIColor*)tintColor

{

return[selfimageWithTintColor:tintColorblendMode:kCGBlendModeDestinationIn];

}

-?(UIImage*)imageWithGradientTintColor:(UIColor*)tintColor

{

return[selfimageWithTintColor:tintColorblendMode:kCGBlendModeOverlay];

}

-?(UIImage*)imageWithTintColor:(UIColor*)tintColorblendMode:(CGBlendMode)blendMode

{

//We?want?to?keep?alpha,?set?opaque?to?NO;?Use?0.0f?for?scale?to?use?the?scale?factor?of?the?device’s?main?screen.

UIGraphicsBeginImageContextWithOptions(self.size,NO,0.0f);

[tintColorsetFill];

CGRect?bounds?=?CGRectMake(0,0,self.size.width,self.size.height);

UIRectFill(bounds);

//Draw?the?tinted?image?in?context

[selfdrawInRect:boundsblendMode:blendModealpha:1.0f];

if(blendMode?!=?kCGBlendModeDestinationIn)?{

[selfdrawInRect:boundsblendMode:kCGBlendModeDestinationInalpha:1.0f];

}

UIImage*tintedImage?=?UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returntintedImage;

}

@end

結果如下:

已經很完美了,這樣的話只要在代碼里設定一下顏色,我們就能夠很輕易地使用同樣一套UI,將其blend為需要的顏色,來實現素材的重用了。唯一需要注意的是,因為每次使用UIImage+Tint的方法繪圖時,都使用了CG的繪制方法,這就意味著每次調用都會是用到CPU的Offscreen drawing,大量使用的話可能導致性能的問題(主要對于iPhone 3GS或之前的設備,可能同時處理大量這樣的繪制調用的能力會有不足)。關于CA和CG的性能的問題,打算在之后用一篇文章來介紹一下。對于這里的UIImage+Tint的實現,可以寫一套緩存的機制,來確保大量重復的元素只在load的時候blend一次,之后將其緩存在內存中以快速讀取。當然這是一個權衡的問題,在時間和空間中做出正確的平衡和選擇,也正是程序設計的樂趣所在。

這篇文章中作為示例的工程和UIImage+Tint可以在Github上找到,您可以隨意玩弄..我相信也會是個來研究每種blend的特性的好機會~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容