iOS中使用blend改變圖片顏色

使用代碼改變圖片的顏色需要用到blend屬性

目標(biāo):

在應(yīng)用里一個(gè)很常見(jiàn)的需求是主題變換:同樣的圖標(biāo),同樣的素材,但是需要按照用戶喜愛(ài)變?yōu)椴煌念伾?。在iOS5和6的SDK里部分標(biāo)準(zhǔn)控件引入了tintColor,來(lái)滿足個(gè)性化界面的需求,但是Apple在這方面還遠(yuǎn)遠(yuǎn)做的不夠。一是現(xiàn)在用默認(rèn)控件根本難以做出界面優(yōu)秀的應(yīng)用,二是tintColor所覆蓋的并不夠全面,在很多情況下開(kāi)發(fā)者都無(wú)法使用其來(lái)完成個(gè)性化定義。

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

當(dāng)然有其他辦法,因?yàn)檎f(shuō)白了就是tint不同的顏色到圖片上而已,如果我們能實(shí)現(xiàn)改變UIImage的顏色,那我們就只需要一套UI,然后用代碼來(lái)改變UI的顏色就可以了,生活有木有一下光明起來(lái)呀。嗯,讓我們先從一張圖片開(kāi)始吧~下面是一張帶有alpha通道的圖片,原始顏色是純的灰色

原始圖片

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

1. 創(chuàng)建一個(gè)上下文用以畫新的圖片
2. 將新的tintColor設(shè)置為填充顏色
3. 將原圖片畫在創(chuàng)建的上下文中,并用新的填充色著色(注意保持alpha通道)
4. 從當(dāng)前上下文中取得圖片并返回

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

kCGBlendModeDestinationOver  
R = S*(1 - Da) + D  
Available in iOS 2.0 and 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

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

kCGBlendModeDestinationIn  
R = D*Sa  
Available in iOS 2.0 and later.  
Declared in CGContext.h.  

結(jié)果 = 目標(biāo)色和原色透明度的加成,看起來(lái)正式所需要的。啦啦啦,還等什么呢,開(kāi)始動(dòng)手實(shí)現(xiàn)看看對(duì)不對(duì)吧~

為了以后使用方便,當(dāng)然是祭出Category,先創(chuàng)建一個(gè)UIImage的類別:

//  UIImage+Tint.h  
#import <UIKit/UIKit.h>  
@interface UIImage (Tint)  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor;  
@end

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

//  UIImage+Tint.m  
  
#import "UIImage+Tint.h"  
  
@implementation UIImage (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);  
    [tintColor setFill];  
    CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);  
    UIRectFill(bounds);  
  
    //Draw the tinted image in context  
    [self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];  
  
    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();  
    UIGraphicsEndImageContext();  
  
    return tintedImage;  
}  
@end 

簡(jiǎn)單明了,沒(méi)有任何難點(diǎn)。測(cè)試之:[[UIImage imageNamed:@"image"] imageWithTintColor:[UIColor orangeColor]];得到的結(jié)果為:

測(cè)試結(jié)果

嗯…怎么說(shuō)呢,雖然tintColor的顏色是變了,但是總覺(jué)得怪怪的。仔細(xì)對(duì)比一下就會(huì)發(fā)現(xiàn),原來(lái)灰色圖里星星和周圍的灰度漸變到了橙色的圖里好像都消失了:星星整個(gè)變成了橙色,周圍的一圈漂亮的光暈也沒(méi)有了,這是神馬情況啊…這種圖能交差的話那算見(jiàn)鬼了,會(huì)被設(shè)計(jì)和產(chǎn)品打死的吧。對(duì)于無(wú)漸變的純色圖的圖來(lái)說(shuō)直接用上面的方法是沒(méi)問(wèn)題的,但是現(xiàn)在除了Metro的大色塊以外哪里無(wú)灰度漸變的設(shè)計(jì)啊…檢查一下使用的blend,R = D * Sa,恍然大悟,我們雖然保留了原色的透明度,但是卻把它的所有的灰度信息弄丟了。怎么辦?繼續(xù)刨CGBlendMode的文檔吧,那么多blend模式應(yīng)該總有我們需要的。功夫不負(fù)有心人,kCGBlendModeOverlay一副嗷嗷待選的樣子:

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 samples while preserving 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 iOS 2.0 and later.  
Declared in CGContext.h.

kCGBlendModeOverlay可以保持背景色的明暗,也就是灰度信息,聽(tīng)起來(lái)正是我們需要的。加入到聲明中,并且添加相應(yīng)的實(shí)現(xiàn)( 順便重構(gòu)一下原來(lái)的代碼 :) ):

//  UIImage+Tint.h  
  
#import <UIKit/UIKit.h>  
  
@interface UIImage (Tint)  
  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor;  
- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor;  
  
@end  
//  UIImage+Tint.m  
  
#import "UIImage+Tint.h"  
  
@implementation UIImage (Tint)  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor  
{  
    return [self imageWithTintColor:tintColor blendMode:kCGBlendModeDestinationIn];  
}  
  
- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor  
{  
    return [self imageWithTintColor:tintColor blendMode:kCGBlendModeOverlay];  
}  
  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor blendMode:(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);  
    [tintColor setFill];  
    CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);  
    UIRectFill(bounds);  
  
    //Draw the tinted image in context  
    [self drawInRect:bounds blendMode:blendMode alpha:1.0f];  
  
    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();  
    UIGraphicsEndImageContext();  
  
    return tintedImage;  
}  
  
@end 

完成,測(cè)試之…好吧,好尷尬,雖然顏色和周圍的光這次對(duì)了,但是透明度又沒(méi)了啊魂淡..一點(diǎn)不奇怪啊,因?yàn)閗CGBlendModeOverlay本來(lái)就沒(méi)承諾給你保留原圖的透明度的說(shuō)。


沒(méi)有透明度

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

//  UIImage+Tint.m  
  
#import "UIImage+Tint.h"  
  
@implementation UIImage (Tint)  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor  
{  
    return [self imageWithTintColor:tintColor blendMode:kCGBlendModeDestinationIn];  
}  
  
- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor  
{  
    return [self imageWithTintColor:tintColor blendMode:kCGBlendModeOverlay];  
}  
  
- (UIImage *) imageWithTintColor:(UIColor *)tintColor blendMode:(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);  
    [tintColor setFill];  
    CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);  
    UIRectFill(bounds);  
  
    //Draw the tinted image in context  
    [self drawInRect:bounds blendMode:blendMode alpha:1.0f];  
  
    if (blendMode != kCGBlendModeDestinationIn) {  
        [self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];  
    }  
  
    UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();  
    UIGraphicsEndImageContext();  
  
    return tintedImage;  
}  
  
@end 

結(jié)果如下:


結(jié)果圖片

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

這篇文章中作為示例的工程和UIImage+Tint可以在Github上找到
轉(zhuǎn)自:onevcat的博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容