Quartz 2D繪製2D圖形和Core Image

在iOS中可以很容易的開發出絢麗的界面效果,一方面得益於成功系統的設計,另一方面得益於它強大的開發框架。今天我們將圍繞iOS中兩大圖形、圖像繪圖框架進行介紹:Quartz 2D繪製2D圖形和Core Image中強大的濾鏡功能。

  1. Quartz 2D

  2. 基本圖形繪製

  3. 視圖刷新

  4. 其他圖形上下文

  5. Core Image

Quartz 2D

在iOS中常用的繪圖框架就是Quartz 2D,Quartz 2D是Core Graphics框架的一部分,是一個強大的二維圖像繪製引擎。Quartz 2D在UIKit中也有很好的封裝和集成,我們日常開發時所用到的UIKit中的組件都是由Core Graphics進行繪製的。不僅如此,當我們引入UIKit框架時系統會自動引入Core Graphics框架,並且爲了方便開發者使用在UIKit內部還對一些常用的繪圖API進行了封裝。

在iOS中繪圖一般分爲以下幾個步驟:

1.獲取繪圖上下文

2.創建並設置路徑

3.將路徑添加到上下文

4.設置上下文狀態

5.繪製路徑

6.釋放路徑

圖形上下文CGContextRef代表圖形輸出設備(也就是繪製的位置),包含了繪製圖形的一些設備信息,Quartz 2D中的所有對象最終都必須繪製到圖形上下文。這樣一來,我們在繪製圖形時就不必關心具體的設備信息,統一了代碼編寫方式(在Quartz 2D中的繪圖上下文可以是位圖Bitmap、PDF、窗口Window、層Layer、打印對象Printer)。

基本圖形繪製

在UIKit中默認已經爲我們準備好了一個圖形上下文對象,在UI控件的drawRect:方法(這個方法在loadView、viewDidLoad方法後執行)中我們可以通過UIKit封裝函數UIGraphicsGetCurrentContext()方法獲得這個圖形上下文(注意在其他UI控件方法中無法取得這個對象),然後我們只要按照繪圖步驟一步步執行即可。下面自定義一個KCView繼承自UIView,重寫drawRect:方法繪製兩條直線說明上面繪圖的步驟:

KCView.m

//
//  KCView.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//
/**
 基本繪圖
 */#import "KCView.h"@implementation KCView#pragma mark 繪圖//繪圖只能在此方法中調用,否則無法得到當前圖形上下文-(void)drawRect:(CGRect)rect{    //1.取得圖形上下文對象    CGContextRef context = UIGraphicsGetCurrentContext();    //2.創建路徑對象    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 20, 50);//移動到指定位置(設置路徑起點)    CGPathAddLineToPoint(path, nil, 20, 100);//繪製直線(從起始位置開始)    CGPathAddLineToPoint(path, nil, 300, 100);//繪製另外一條直線(從上一直線終點開始繪製)


    //3.添加路徑到圖形上下文    CGContextAddPath(context, path);    //4.設置圖形上下文狀態屬性    CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);//設置筆觸顏色    CGContextSetRGBFillColor(context, 0, 1.0, 0, 1);//設置填充色    CGContextSetLineWidth(context, 2.0);//設置線條寬度    CGContextSetLineCap(context, kCGLineCapRound);//設置頂點樣式,(20,50)和(300,100)是頂點    CGContextSetLineJoin(context, kCGLineJoinRound);//設置連接點樣式,(20,100)是連接點
    /*設置線段樣式
    phase:虛線開始的位置
    lengths:虛線長度間隔(例如下面的定義說明第一條線段長度8,然後間隔3重新繪製8點的長度線段,當然這個數組可以定義更多元素)
    count:虛線數組元素個數
    */    CGFloat lengths[2] = { 18, 9 };
    CGContextSetLineDash(context, 0, lengths, 2);    /*設置陰影
    context:圖形上下文
    offset:偏移量
    blur:模糊度
    color:陰影顏色
    */    CGColorRef color = [UIColor grayColor].CGColor;//顏色轉化,由於Quartz 2D跨平臺,所以其中不能使用UIKit中的對象,但是UIkit提供了轉化方法    CGContextSetShadowWithColor(context, CGSizeMake(2, 2), 0.8, color);    //5.繪製圖像到指定圖形上下文
    /*CGPathDrawingMode是填充方式,枚舉類型
    kCGPathFill:只有填充(非零纏繞數填充),不繪製邊框
    kCGPathEOFill:奇偶規則填充(多條路徑交叉時,奇數交叉填充,偶交叉不填充)
    kCGPathStroke:只有邊框
    kCGPathFillStroke:既有邊框又有填充
    kCGPathEOFillStroke:奇偶填充並繪製邊框
    */    CGContextDrawPath(context, kCGPathFillStroke);//最後一個參數是填充類型

    //6.釋放對象    CGPathRelease(path);
}@end

在視圖控制器創建KCView並添加到根視圖中:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds];
    view.backgroundColor=[UIColor whiteColor];
    [self.view addSubview:view];
}

運行效果如下:

DrawPath

簡化繪圖方式

上面的繪圖方式未免顯得有些麻煩,其實Core Graphics 內部對創建對象添加到上下文這兩步操作進行了封裝,可以一步完成。另外前面也說過UIKit內部其實封裝了一些以“UI”開頭的方法幫助大家進行圖形繪製。就拿前面的例子來說我們改進一些繪製方法:

-(void)drawLine2{    //1.獲得圖形上下文    CGContextRef context=UIGraphicsGetCurrentContext();    
    //2.繪製路徑(相當於前面創建路徑並添加路徑到圖形上下文兩步操作)    CGContextMoveToPoint(context, 20, 50);
    CGContextAddLineToPoint(context, 20, 100);
    CGContextAddLineToPoint(context, 300, 100);    //封閉路徑:a.創建一條起點和終點的線,不推薦
    //CGPathAddLineToPoint(path, nil, 20, 50);
    //封閉路徑:b.直接調用路徑封閉方法    CGContextClosePath(context);    
    //3.設置圖形上下文屬性    [[UIColor redColor]setStroke];//設置紅色邊框    [[UIColor greenColor]setFill];//設置綠色填充
    //[[UIColor blueColor]set];//同時設置填充和邊框色
    
    //4.繪製路徑    CGContextDrawPath(context, kCGPathFillStroke);
}

上面的操作相比前面的方法應該說已經簡化了不少,除了路徑之外其他矩形、橢圓等都有對應的創建方法。另外上面我們也演示了封閉路徑的方法,大家可以運行看一下效果。

其他圖形繪製

相信大家瞭解了上面的繪製步驟其他圖形繪製並不麻煩,下面以一個例子簡單演示一下其他圖形的繪製,包括文字和圖像的繪製。

繪製矩形

在下面的方法中還可以看到UIKit對繪圖方法的封裝,使用起來更加簡單。

#pragma mark 繪製矩形
-(void)drawRectWithContext:(CGContextRef)context{    //添加矩形對象    CGRect rect=CGRectMake(20, 50, 280.0, 50.0);
    CGContextAddRect(context,rect);    //設置屬性    [[UIColor blueColor]set];    //繪製    CGContextDrawPath(context, kCGPathFillStroke);
}#pragma mark 繪製矩形(利用UIKit的封裝方法)
-(void)drawRectByUIKitWithContext:(CGContextRef)context{
    CGRect rect= CGRectMake(20, 150, 280.0, 50.0);
    CGRect rect2=CGRectMake(20, 250, 280.0, 50.0);    //設置屬性    [[UIColor yellowColor]set];    //繪製矩形,相當於創建對象、添加對象到上下文、繪製三個步驟    UIRectFill(rect);//繪製矩形(只有填充)    
    [[UIColor redColor]setStroke];
    UIRectFrame(rect2);//繪製矩形(只有邊框)}
@end

運行效果:

DrawRect

繪製橢圓

#pragma mark 繪製橢圓
-(void)drawEllipse:(CGContextRef)context{    //添加對象,繪製橢圓(圓形)的過程也是先創建一個矩形    CGRect rect=CGRectMake(50, 50, 220.0, 200.0);
    CGContextAddEllipseInRect(context, rect);    //設置屬性    [[UIColor purpleColor]set];    //繪製    CGContextDrawPath(context, kCGPathFillStroke);
}
@end

運行效果:

DrawEllipse

繪製弧形

-(void)drawArc:(CGContextRef)context{    /*添加弧形對象
     x:中心點x座標
     y:中心點y座標
     radius:半徑
     startAngle:起始弧度
     endAngle:終止弧度
     closewise:是否逆時針繪製,0則順時針繪製
    */    CGContextAddArc(context, 160, 160, 100.0, 0.0, M_PI_2, 1);    
    //設置屬性    [[UIColor yellowColor]set];    
    //繪製    CGContextDrawPath(context, kCGPathFillStroke);
}

運行效果:

DrawArc

繪製貝塞爾曲線

要繪製規則圖形在iOS中相當簡單,但是不規則圖形怎麼繪製呢?此時就要利用路徑。前面我們繪製了直線,它和曲線繪製都屬於路徑繪製。和直線繪製相比曲線繪製就要複雜一些,但是路徑作爲高級動畫的基礎又是我們必須掌握的,因此這裏我們就一起來熟悉一下曲線繪製。在Quartz 2D中曲線繪製分爲兩種:二次貝塞爾曲線和三次貝塞爾曲線。二次曲線只有一個控制點,而三次曲線有兩個控制點,如下圖所示:

Bezier

當然,在iOS中兩種曲線分別對應兩種方法:

CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y);

CGContextAddCurveToPoint(context, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y);
下面就演示一下這兩種曲線的繪製方法

#pragma mark 繪製貝塞爾曲線
-(void)drawCurve:(CGContextRef)context{    
    //繪製曲線    CGContextMoveToPoint(context, 20, 100);//移動到起始位置
    /*繪製二次貝塞爾曲線
     c:圖形上下文
     cpx:控制點x座標
     cpy:控制點y座標
     x:結束點x座標
     y:結束點y座標
    */    CGContextAddQuadCurveToPoint(context, 160, 0, 300, 100);
    
    CGContextMoveToPoint(context, 20, 500);    /*繪製三次貝塞爾曲線
     c:圖形上下文
     cp1x:第一個控制點x座標
     cp1y:第一個控制點y座標
     cp2x:第二個控制點x座標
     cp2y:第二個控制點y座標
     x:結束點x座標
     y:結束點y座標
    */    CGContextAddCurveToPoint(context, 80, 300, 240, 500, 300, 300);    
    //設置圖形上下文屬性    [[UIColor yellowColor]setFill];
    [[UIColor redColor]setStroke];    
    
    //繪製路徑    CGContextDrawPath(context, kCGPathFillStroke);
    
}

運行效果:

BesizerEffice

 

備註:貝塞爾曲線是由法國數學家“貝塞爾”發現的,他發現:任何一條曲線都能夠由和它相切的直線的兩個端點來描述,這種曲線表示方式後來被廣泛應用到計算機中,稱爲“貝塞爾曲線”。

文字繪製

除了繪製圖形還可以繪製文本內容。

-(void)drawText:(CGContextRef)context{    //繪製到指定的區域內容    NSString *str=@"Star Walk is the most beautiful stargazing app you’ve ever seen on a mobile device. It will become your go-to interactive astro guide to the night sky, following your every movement in real-time and allowing you to explore over 200, 000 celestial bodies with extensive information about stars and constellations that you find.";
    CGRect rect= CGRectMake(20, 50, 280, 300);
    UIFont *font=[UIFont systemFontOfSize:18];//設置字體    UIColor *color=[UIColor redColor];//字體顏色    NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];//段落樣式    NSTextAlignment align=NSTextAlignmentLeft;//對齊方式    style.alignment=align;
    [str drawInRect:rect withAttributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:color,NSParagraphStyleAttributeName:style}];
}

運行效果:

DrawText 

圖像繪製

Quartz 2D還可以將圖像繪製到圖形上下文。

-(void)drawImage:(CGContextRef)context{
    UIImage *image=[UIImage imageNamed:@"image2.jpg"];    //從某一點開始繪製    [image drawAtPoint:CGPointMake(10, 50)];    //繪製到指定的矩形中,注意如果大小不合適會會進行拉伸
//    [image drawInRect:CGRectMake(10, 50, 300, 450)];
    //平鋪繪製
//    [image drawAsPatternInRect:CGRectMake(0, 0, 320, 568)];}

運行效果:

DrawImage 

繪製漸變填充

從前面的示例中我們可以看到如何設置填充顏色,事實上很多時候純色的填充並不能滿足我們的需求,例如有時候我們要繪製一些圖形可能需要設置一個漂亮的背景,這個時候我們可能就會選擇漸變填充方式。Quartz 2D的漸變方式分爲兩種:

a.線性漸變線:漸變色以直線方式從開始位置逐漸向結束位置漸變

b.徑向漸變:以中心點爲圓心從起始漸變色向四周輻射,直到終止漸變色

要做漸變則必須先設置從開始位置到結束位置的漸變顏色,做過photoshop的朋友相信對於漸變色設置並不陌生,只要在指定位置指定不同的顏色,剩下的事情交給系統處理即可,如下圖在起始位置、3/10位置、結束位置指定了三種顏色就形成由三種顏色組成的漸變色:

GradientColor

另外,在iOS中繪製漸變還需要注意一點就是指定顏色空間,所謂顏色空間就是不同顏色在不同的維度上取值最終組成一種顏色的過程。就拿RGB來說,如果將紅色、綠色、藍色看成是x、y、z軸座標系,那麼在三個座標上分別取0~255範圍內的不同值則可以組成各類顏色。當然,不同顏色空間的“座標系”也是不同的(也就是說顏色表示的方式是不同的),常用的顏色空間除了RGB還有CMYK(印刷業常用這種顏色模式)、Gray。

在使用Quartz 2D繪圖時我們的顏色除了使用常規的方法(如何前面CGContextSetRGBFillColor(CGContextRef context, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)方法)設置RGB和透明度外,有時還會遇到顏色參數是一個數組情況。如使用顏色空間填充時用到的CGContextSetFillColor(CGContextRef context, const CGFloat *components)方法,這個時候components數組中具體是如何存儲顏色就要根據顏色空間而定,如果顏色空間使用RGB則數組中的元素四個爲一組,分別是red(紅)、green(綠)、blue(藍)、alpha(透明度);如果使用CMYK顏色空間,那麼數組中的元素五個爲一組,分別是cyan(青)、magenta(洋紅)、yellow(黃)、black(黑)、alpha(透明度)。

下面的代碼分別演示了兩種漸變方式,具體漸變繪製函數參數代碼中已經註釋的很清楚了:

//
//  KCView3.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCView3.h"@implementation KCView3

-(void)drawRect:(CGRect)rect{
    CGContextRef context=UIGraphicsGetCurrentContext();//    [self drawLinearGradient:context];    [self drawRadialGradient:context];
}#pragma mark 線性漸變
-(void)drawLinearGradient:(CGContextRef)context{    //使用rgb顏色空間    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();    
    /*指定漸變色
     space:顏色空間
     components:顏色數組,注意由於指定了RGB顏色空間,那麼四個數組元素表示一個顏色(red、green、blue、alpha),
                如果有三個顏色則這個數組有4*3個元素
     locations:顏色所在位置(範圍0~1),這個數組的個數不小於components中存放顏色的個數
     count:漸變個數,等於locations的個數
     */    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3]={0,0.3,1.0};
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);    
    /*繪製線性漸變
     context:圖形上下文
     gradient:漸變色
     startPoint:起始位置
     endPoint:終止位置
     options:繪製方式,kCGGradientDrawsBeforeStartLocation 開始位置之前就進行繪製,到結束位置之後不再繪製,
             kCGGradientDrawsAfterEndLocation開始位置之前不進行繪製,到結束點之後繼續填充
     */    CGContextDrawLinearGradient(context, gradient, CGPointZero, CGPointMake(320, 300), kCGGradientDrawsAfterEndLocation);    
    //釋放顏色空間    CGColorSpaceRelease(colorSpace);
}#pragma mark 徑向漸變
-(void)drawRadialGradient:(CGContextRef)context{    //使用rgb顏色空間    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();    
    /*指定漸變色
     space:顏色空間
     components:顏色數組,注意由於指定了RGB顏色空間,那麼四個數組元素表示一個顏色(red、green、blue、alpha),
     如果有三個顏色則這個數組有4*3個元素
     locations:顏色所在位置(範圍0~1),這個數組的個數不小於components中存放顏色的個數
     count:漸變個數,等於locations的個數
     */    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3]={0,0.3,1.0};
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);    
    /*繪製徑向漸變
     context:圖形上下文
     gradient:漸變色
     startCenter:起始點位置
     startRadius:起始半徑(通常爲0,否則在此半徑範圍內容無任何填充)
     endCenter:終點位置(通常和起始點相同,否則會有偏移)
     endRadius:終點半徑(也就是漸變的擴散長度)
     options:繪製方式,kCGGradientDrawsBeforeStartLocation 開始位置之前就進行繪製,但是到結束位置之後不再繪製,
             kCGGradientDrawsAfterEndLocation開始位置之前不進行繪製,但到結束點之後繼續填充
     */    CGContextDrawRadialGradient(context, gradient, CGPointMake(160, 284),0, CGPointMake(165, 289), 150, kCGGradientDrawsAfterEndLocation);    //釋放顏色空間    CGColorSpaceRelease(colorSpace);
}
@end

運行效果:

LinearGradient     RadiaGradient

擴展--漸變填充

上面我們只是繪製漸變到圖形上下文,實際開發中有時候我們還需要填充對應的漸變色,例如現在繪製了一個矩形,如何填充成漸變色呢?在此可以利用漸變裁切來完成(當然利用層CALayer更加方便但這不在今天的話題討論範圍內),特別說明一下區域裁切並不僅僅適用於漸變填充,對於其他圖形繪製仍然適用,並且注意裁切只能限於矩形裁切。

-(void)drawRectWithLinearGradientFill:(CGContextRef)context{
    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();    
    //裁切處一塊矩形用於顯示,注意必須先裁切再調用漸變
    //CGContextClipToRect(context, CGRectMake(20, 50, 280, 300));
    //裁切還可以使用UIKit中對應的方法    UIRectClip(CGRectMake(20, 50, 280, 300));
    
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3]={0,0.3,1.0};
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);

    CGContextDrawLinearGradient(context, gradient, CGPointMake(20, 50), CGPointMake(300, 300), kCGGradientDrawsAfterEndLocation);    
    
    //釋放顏色空間    CGColorSpaceRelease(colorSpace);
}

運行效果:

ClipRectEffect

其他狀態設置

常用的圖形上下文狀態設置上面基本都用到了,我們不再一一解釋,這裏着重說一下疊加模式和填充模式,初學者對於這兩個狀態設置往往容易產生疑惑。

疊加模式

使用Quartz 2D繪圖時後面繪製的圖像會覆蓋前面的,默認情況下如果前面的被覆蓋後將看不到後面的內容,但是有時候這個結果並不是我們想要的,因此在Quartz 2D中提供了填充模式供開發者配置調整。由於填充模式類別特別多,因此下面以一個例子來說明:

-(void)drawRectByUIKitWithContext2:(CGContextRef)context{
    CGRect rect= CGRectMake(0, 130.0, 320.0, 50.0);
    CGRect rect1= CGRectMake(0, 390.0, 320.0, 50.0);
    
    
    CGRect rect2=CGRectMake(20, 50.0, 10.0, 250.0);
    CGRect rect3=CGRectMake(40.0, 50.0, 10.0, 250.0);
    CGRect rect4=CGRectMake(60.0, 50.0, 10.0, 250.0);
    CGRect rect5=CGRectMake(80.0, 50.0, 10.0, 250.0);
    CGRect rect6=CGRectMake(100.0, 50.0, 10.0, 250.0);
    CGRect rect7=CGRectMake(120.0, 50.0, 10.0, 250.0);
    CGRect rect8=CGRectMake(140.0, 50.0, 10.0, 250.0);
    CGRect rect9=CGRectMake(160.0, 50.0, 10.0, 250.0);
    CGRect rect10=CGRectMake(180.0, 50.0, 10.0, 250.0);
    CGRect rect11=CGRectMake(200.0, 50.0, 10.0, 250.0);
    CGRect rect12=CGRectMake(220.0, 50.0, 10.0, 250.0);
    CGRect rect13=CGRectMake(240.0, 50.0, 10.0, 250.0);
    CGRect rect14=CGRectMake(260.0, 50.0, 10.0, 250.0);
    CGRect rect15=CGRectMake(280.0, 50.0, 10.0, 250.0);
    
    CGRect rect16=CGRectMake(30.0, 310.0, 10.0, 250.0);
    CGRect rect17=CGRectMake(50.0, 310.0, 10.0, 250.0);
    CGRect rect18=CGRectMake(70.0, 310.0, 10.0, 250.0);
    CGRect rect19=CGRectMake(90.0, 310.0, 10.0, 250.0);
    CGRect rect20=CGRectMake(110.0, 310.0, 10.0, 250.0);
    CGRect rect21=CGRectMake(130.0, 310.0, 10.0, 250.0);
    CGRect rect22=CGRectMake(150.0, 310.0, 10.0, 250.0);
    CGRect rect23=CGRectMake(170.0, 310.0, 10.0, 250.0);
    CGRect rect24=CGRectMake(190.0, 310.0, 10.0, 250.0);
    CGRect rect25=CGRectMake(210.0, 310.0, 10.0, 250.0);
    CGRect rect26=CGRectMake(230.0, 310.0, 10.0, 250.0);
    CGRect rect27=CGRectMake(250.0, 310.0, 10.0, 250.0);
    CGRect rect28=CGRectMake(270.0, 310.0, 10.0, 250.0);
    CGRect rect29=CGRectMake(290.0, 310.0, 10.0, 250.0);

    
    [[UIColor yellowColor]set];
    UIRectFill(rect);
    
    [[UIColor greenColor]setFill];
    UIRectFill(rect1);
    
    [[UIColor redColor]setFill];
    UIRectFillUsingBlendMode(rect2, kCGBlendModeClear);
    UIRectFillUsingBlendMode(rect3, kCGBlendModeColor);
    UIRectFillUsingBlendMode(rect4, kCGBlendModeColorBurn);
    UIRectFillUsingBlendMode(rect5, kCGBlendModeColorDodge);
    UIRectFillUsingBlendMode(rect6, kCGBlendModeCopy);
    UIRectFillUsingBlendMode(rect7, kCGBlendModeDarken);
    UIRectFillUsingBlendMode(rect8, kCGBlendModeDestinationAtop);
    UIRectFillUsingBlendMode(rect9, kCGBlendModeDestinationIn);
    UIRectFillUsingBlendMode(rect10, kCGBlendModeDestinationOut);
    UIRectFillUsingBlendMode(rect11, kCGBlendModeDestinationOver);
    UIRectFillUsingBlendMode(rect12, kCGBlendModeDifference);
    UIRectFillUsingBlendMode(rect13, kCGBlendModeExclusion);
    UIRectFillUsingBlendMode(rect14, kCGBlendModeHardLight);
    UIRectFillUsingBlendMode(rect15, kCGBlendModeHue);
    UIRectFillUsingBlendMode(rect16, kCGBlendModeLighten);
    
    UIRectFillUsingBlendMode(rect17, kCGBlendModeLuminosity);
    UIRectFillUsingBlendMode(rect18, kCGBlendModeMultiply);
    UIRectFillUsingBlendMode(rect19, kCGBlendModeNormal);
    UIRectFillUsingBlendMode(rect20, kCGBlendModeOverlay);
    UIRectFillUsingBlendMode(rect21, kCGBlendModePlusDarker);
    UIRectFillUsingBlendMode(rect22, kCGBlendModePlusLighter);
    UIRectFillUsingBlendMode(rect23, kCGBlendModeSaturation);
    UIRectFillUsingBlendMode(rect24, kCGBlendModeScreen);
    UIRectFillUsingBlendMode(rect25, kCGBlendModeSoftLight);
    UIRectFillUsingBlendMode(rect26, kCGBlendModeSourceAtop);
    UIRectFillUsingBlendMode(rect27, kCGBlendModeSourceIn);
    UIRectFillUsingBlendMode(rect28, kCGBlendModeSourceOut);
    UIRectFillUsingBlendMode(rect29, kCGBlendModeXOR);
}

運行效果:

BlendModeEffect

相信大家對比代碼和顯示效果並不難發現每種疊加的效果。例子中只是使用UIKit的封裝方法進行疊加模式設置,更一般的方法當然是使用CGContextSetBlendMode(CGContextRef context, CGBlendMode mode)方法進行設置。

填充模式

前面的示例中已經演示過純色填充、漸變填充,而有時我們需要按一定的自定義樣式進行填充,這種方式有點類似於貼瓷磚的方式。我們知道如果家裏貼地板或瓷磚時,通常我們會先選擇一種瓷磚樣式,根據房間面積我們購買不同量的瓷磚。但是不管買多少,這些瓷磚的樣式都是一模一樣的。填充模式就是爲了達到這種效果而產生的:我們只需要繪製一個瓷磚的樣式,然後讓程序自動調用這種樣式填充指定大小的區域。

Quartz 2D支持兩種填充模式:有顏色填充和無顏色填充。兩種模式使用起來區別很小,有顏色填充就是在繪製瓷磚時就指定顏色,在調用填充時就不用再指定瓷磚顏色;無顏色填充模式就是繪製瓷磚時不用指定任何顏色,在調用填充時再指定具體填充顏色。相比較無顏色填充模式而言,有顏色填充模式更加的靈活,推薦使用。

下面我們具體看一下如何按指定模式進行圖形填充:

1.在使用填充模式時首先要構建一個符合CGPatternDrawPatternCallback簽名的方法,這個方法專門用來創建“瓷磚”。注意:如果使用有顏色填充模式,需要設置填充色。例如我們定義一個方法drawTile繪製以下瓷磚(有顏色填充):

Tile

2.接着需要指定一個填充的顏色空間,這個顏色空間跟前面繪製漸變的顏色空間不太一樣,前面創建漸變使用的顏色空間是設備無關的,我們需要基於這個顏色空間創建一個顏色空間專門用於填充(注意對於有顏色填充創建填充顏色空間參數爲NULL,不用基於設備無關的顏色空間創建)。

3.然後我們就可以使用CGPatternCreate方法創建一個填充模式,創建填充模式時需要注意其中的參數,在代碼中已經做了一一解釋(這裏注意對於有顏色填充模式isColored設置爲true,否則爲false)。

4.最後調用CGContextSetFillPattern方法給圖形上下文指定填充模式(這個時候注意最後一個參數,如果是有顏色填充模式最後一個參數爲透明度alpa的地址,對於無顏色填充模式最後一個參數是當前填充顏色空間的顏色數組)。

5.繪製圖形,這裏我們繪製一個矩形。

6.釋放資源。

下面是具體代碼(包含兩種填充模式代碼,可以一一運行)

//
//  UIView4.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCView.h"#define TILE_SIZE 20

@implementation KCView

-(void)drawRect:(CGRect)rect{
    CGContextRef context=UIGraphicsGetCurrentContext();
    [self drawBackgroundWithColoredPattern:context];//    [self drawBackgroundWithPattern:context];    
}#pragma mark - 有顏色填充模式void drawColoredTile(void *info,CGContextRef context){    //有顏色填充,這裏設置填充色    CGContextSetRGBFillColor(context, 254.0/255.0, 52.0/255.0, 90.0/255.0, 1);
    CGContextFillRect(context, CGRectMake(0, 0, TILE_SIZE, TILE_SIZE));
    CGContextFillRect(context, CGRectMake(TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE));
}
-(void)drawBackgroundWithColoredPattern:(CGContextRef)context{    //設備無關的顏色空間
//    CGColorSpaceRef rgbSpace= CGColorSpaceCreateDeviceRGB();
    //模式填充顏色空間,注意對於有顏色填充模式,這裏傳NULL    CGColorSpaceRef colorSpace=CGColorSpaceCreatePattern(NULL);    //將填充色顏色空間設置爲模式填充的顏色空間    CGContextSetFillColorSpace(context, colorSpace);    
    //填充模式回調函數結構體    CGPatternCallbacks callback={0,&drawColoredTile,NULL};    /*填充模式
     info://傳遞給callback的參數
     bounds:瓷磚大小
     matrix:形變
     xStep:瓷磚橫向間距
     yStep:瓷磚縱向間距
     tiling:貼磚的方法
     isClored:繪製的瓷磚是否已經指定了顏色(對於有顏色瓷磚此處指定位true)
     callbacks:回調函數
     */    CGPatternRef pattern=CGPatternCreate(NULL, CGRectMake(0, 0, 2*TILE_SIZE, 2*TILE_SIZE), CGAffineTransformIdentity,2*TILE_SIZE+ 5,2*TILE_SIZE+ 5, kCGPatternTilingNoDistortion, true, &callback);
    
    CGFloat alpha=1;    //注意最後一個參數對於有顏色瓷磚指定爲透明度的參數地址,對於無顏色瓷磚則指定當前顏色空間對應的顏色數組    CGContextSetFillPattern(context, pattern, &alpha);
    
    UIRectFill(CGRectMake(0, 0, 320, 568));    
//    CGColorSpaceRelease(rgbSpace);    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}#pragma mark - 無顏色填充模式//填充瓷磚的回調函數(必須滿足CGPatternCallbacks簽名)void drawTile(void *info,CGContextRef context){
    CGContextFillRect(context, CGRectMake(0, 0, TILE_SIZE, TILE_SIZE));
    CGContextFillRect(context, CGRectMake(TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE));
}
-(void)drawBackgroundWithPattern:(CGContextRef)context{    //設備無關的顏色空間    CGColorSpaceRef rgbSpace= CGColorSpaceCreateDeviceRGB();    //模式填充顏色空間    CGColorSpaceRef colorSpace=CGColorSpaceCreatePattern(rgbSpace);    //將填充色顏色空間設置爲模式填充的顏色空間    CGContextSetFillColorSpace(context, colorSpace);    
    //填充模式回調函數結構體    CGPatternCallbacks callback={0,&drawTile,NULL};    /*填充模式
     info://傳遞給callback的參數
     bounds:瓷磚大小
     matrix:形變
     xStep:瓷磚橫向間距
     yStep:瓷磚縱向間距
     tiling:貼磚的方法(瓷磚擺放的方式)
     isClored:繪製的瓷磚是否已經指定了顏色(對於無顏色瓷磚此處指定位false)
     callbacks:回調函數
     */    CGPatternRef pattern=CGPatternCreate(NULL, CGRectMake(0, 0, 2*TILE_SIZE, 2*TILE_SIZE), CGAffineTransformIdentity,2*TILE_SIZE+ 5,2*TILE_SIZE+ 5, kCGPatternTilingNoDistortion, false, &callback);
    
    CGFloat components[]={254.0/255.0,52.0/255.0,90.0/255.0,1.0};    //注意最後一個參數對於無顏色填充模式指定爲當前顏色空間顏色數據    CGContextSetFillPattern(context, pattern, components);    //    CGContextSetStrokePattern(context, pattern, components);    UIRectFill(CGRectMake(0, 0, 320, 568));
    
    CGColorSpaceRelease(rgbSpace);
    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}
@end

運行效果:

FillWithPattern

這裏強調一點,在drawTile回調方法中不要使用UIKit封裝方法進行圖形繪製(例如UIRectFill等),由於這個方法由Core Graphics內部調用,而Core Graphics考慮到跨平臺問題,內部是不允許調用UIKit方法的。

上下文變換

我們知道在UIKit開發中UIView有一個transform屬性用於控件的形變,其實在繪圖中我們也經常用到圖形形變,這個時候可以藉助圖形上下文的形變方法來完成。在弄清形變之前我們要清楚圖形上下文的座標原點,因爲無論是位移還是旋轉都是相對於座標原點進行的。其實Quartz 2D的座標系同UIKit並不一樣,它的座標原點在屏幕左下方,但是爲了統一編程方式,UIKit對其進行了轉換,座標原點統一在屏幕左上角。注意在設置圖形上下文形變之前一定要注意保存上下文的初始狀態,在使用完之後進行恢復。否則在處理多個圖形形變的時候很容易弄不清楚到底是基於怎樣的座標系進行繪圖,容易找不到原點(做過html5 canvas繪圖的朋友對這一點應該很熟悉,在html5中繪圖也經常進行狀態保存和恢復)。下面通過一個圖片的變換演示一下圖形上下文的形變(其他圖形也是一樣的,就不再演示):

//
//  KCView2.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCView2.h"@implementation KCView2

-(void)drawRect:(CGRect)rect{
    CGContextRef context=UIGraphicsGetCurrentContext();
    [self drawImage:context];
}#pragma mark 圖形上下文形變
-(void)drawImage:(CGContextRef)context{    //保存初始狀態    CGContextSaveGState(context);    
    //形變第一步:圖形上下文向右平移40    CGContextTranslateCTM(context, 100, 0);    
    //形變第二步:縮放0.8    CGContextScaleCTM(context, 0.8, 0.8);    
    //形變第三步:旋轉    CGContextRotateCTM(context, M_PI_4/4);
    
    UIImage *image=[UIImage imageNamed:@"photo1.jpg"];
    [image drawInRect:CGRectMake(0, 50, 240, 300)];    
    
    //恢復到初始狀態    CGContextRestoreGState(context);
}

@end

最終運行效果見第四幅截圖,下圖描繪出了整個程序的運行過程(移動->縮放->旋轉):

CGContentRefState

擴展--使用Core Graphics繪製圖像

在前面基本繪圖部分,繪製圖像時使用了UIKit中封裝的方法進行了圖像繪製,我們不妨看一下使用Quartz 2D內置方法繪製是什麼效果。

-(void)drawImage2:(CGContextRef)context{
    UIImage *image=[UIImage imageNamed:@"image2.jpg"];    //圖像繪製    CGRect rect= CGRectMake(10, 50, 300, 450);
    CGContextDrawImage(context, rect, image.CGImage);
}

運行效果:

DrawImage2Effect

看起來整個圖像是倒過來的,原因正是前面說的:在Core Graphics中座標系的y軸正方向是向上的,座標原點在屏幕左下角,y軸方向剛好和UIKit中y軸方向相反。而使用UIKit進行繪圖之所以沒有問題是因爲UIKit中進行了處理,事實上對於其他圖形即使使用Core Graphics繪製也沒有問題,因爲UIKit統一了編程方式。但是使用Core Graphics中內置方法繪製圖像是存在這種問題的,如何解決呢?

其實圖形上下文只要沿着x軸旋轉180度,然後向上平移適當的高度即可(但是注意不要沿着z軸旋轉,這樣得不到想要的結果)。可是通過前面介紹的CGContextRotateCTM方法只能通過沿着z軸旋轉,此時不妨使用另外一種方法,那就是在y軸方向縮放-1,同樣可以達到想要的效果:

-(void)drawImage2:(CGContextRef)context{
    UIImage *image=[UIImage imageNamed:@"image2.jpg"];
    CGSize size=[UIScreen mainScreen].bounds.size;
    CGContextSaveGState(context);
    CGFloat height=450,y=50;    //上下文形變    CGContextScaleCTM(context, 1.0, -1.0);//在y軸縮放-1相當於沿着x張旋轉180    CGContextTranslateCTM(context, 0, -(size.height-(size.height-2*y-height)));//向上平移
    //圖像繪製    CGRect rect= CGRectMake(10, y, 300, height);
    CGContextDrawImage(context, rect, image.CGImage);
    
    CGContextRestoreGState(context);
}

視圖刷新

在UIView的drawRect:中繪製的圖形會在控件顯示的時候調用(而且顯示時會重繪所有圖形),有時候我們希望繪製內容的顯示是實時的,此時我們就需要調用繪圖方法重新繪製,但是在iOS開發中不允許開發者直接調用drawRect:方法,刷新繪製內容需要調用setNeedsDisplay方法。下面以一個調整字體大小的界面演示一下視圖的刷新。

KCView.h

//
//  KCView.h
//  RefreshView
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import <UIKit/UIKit.h>@interface KCView : UIView

@property (nonatomic,copy) NSString *title;

@property (nonatomic,assign) CGFloat fontSize;

@end

KCView.m

//
//  KCView.m
//  RefreshView
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCView.h"@implementation KCView

-(void)drawRect:(CGRect)rect{
    
    NSString *str=_title;
    UIFont *font=[UIFont fontWithName:@"Marker Felt" size:_fontSize];
    UIColor *foreignColor=[UIColor redColor];
    [str drawInRect:CGRectMake(100, 120, 300, 200) withAttributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:foreignColor}];
}

@end

KCMainViewController.m

//
//  KCMainViewController.m
//  RefreshView
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"#import "KCView.h"@interface KCMainViewController ()<UIPickerViewDataSource,UIPickerViewDelegate>{
    KCView *_contentView;
    NSArray *_fontSize;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self initLayout];
    
    [self addPickerView];
}

-(void)initLayout{
    _fontSize=@[@15,@18,@20,@22,@25,@28,@30,@32,@35,@40];
    _contentView=[[KCView alloc]initWithFrame:CGRectMake(0, 0, 320, 300)];
    _contentView.backgroundColor=[UIColor whiteColor];
    _contentView.title=@"Hello world!";
    _contentView.fontSize=[_fontSize[0] intValue];
    [self.view addSubview:_contentView];
}

-(void)addPickerView{
    UIPickerView *picker=[[UIPickerView alloc]initWithFrame:CGRectMake(0, 300, 320, 268)];
    picker.dataSource=self;
    picker.delegate=self;
    
    [self.view addSubview:picker];
}

-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{    return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{    return _fontSize.count;
}

-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{    return [NSString stringWithFormat:@"%@號字體",_fontSize[row] ];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    _contentView.fontSize=[[_fontSize objectAtIndex:row] intValue];    
    //刷新視圖    [_contentView setNeedsDisplay];
}
@end

運行效果:

RefreshViewEffect

其他圖形上下文

前面我們也說過,Quartz 2D的圖形上下方除了可以繪製到層上還可以繪製到位圖、PDF等,這裏我們就介紹一下如何利用Quartz 2D繪製圖像到位圖及PDF中。

上面的示例中一直都是在drawRect:方法中利用UIGraphicsGetCurrentContext()方法取得上下文,要得到位圖或者PDF的上下文可以利用UIGraphicsBeginImageContext(CGSize size)和UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary *pageInfo)方法。位圖圖形上下文和PDF圖形上下文UIKit是不會負責創建的,所以需要用戶手動創建,並且在使用完後關閉它。在使用UIKit中系統創建的圖形上下文的時候,我們只能在drawRect:方法中使用,由於這兩類圖形上下文是由我們手動創建的因此可以放到任何方法中調用。此外,這兩個方法開啓的圖形上下文並沒有返回值,如果我們要得到我們創建的圖形上下文只要在創建上下文之後、關閉之前調用UIGraphicsGetCurrentContext()方法,此時取得的上下文即是我們自己創建的圖形上下文。

繪製到位圖

下面利用位圖圖形上下文給一個圖片添加水印,在下面的程序中我們首先創建上下文,然後在上下文中繪製圖片、直線和文本,最後從當前位圖上下文中取得最終形成的新圖片顯示到界面。

//
//  KCMainViewController.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import <CoreText/CoreText.h>#import "KCMainViewController.h"#import "KCView.h"#import "KCView2.h"#import "KCView3.h"@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIImage *image=[self drawImageAtImageContext];
    UIImageView *imageView=[[UIImageView alloc]initWithImage:image];
    imageView.center=CGPointMake(160, 284);
    
    [self.view addSubview:imageView];
}#pragma mark 利用位圖上下文添加水印效果
-(UIImage *)drawImageAtImageContext{    //獲得一個位圖圖形上下文    CGSize size=CGSizeMake(300, 188);//畫布大小    UIGraphicsBeginImageContext(size);
    
    UIImage *image=[UIImage imageNamed:@"photo2.png"];
    [image drawInRect:CGRectMake(0, 0, 300, 188)];//注意繪圖的位置是相對於畫布頂點而言,不是屏幕
    
    
    //添加水印    CGContextRef context=UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(context, 200, 178);
    CGContextAddLineToPoint(context, 270, 178);
    
    [[UIColor redColor]setStroke];
    CGContextSetLineWidth(context, 2);
    
    CGContextDrawPath(context, kCGPathStroke);
    
    NSString *str=@"Kenshin Cui";
    [str drawInRect:CGRectMake(200, 158, 100, 30) withAttributes:@{NSFontAttributeName:[UIFont fontWithName:@"Marker Felt" size:15],NSForegroundColorAttributeName:[UIColor redColor]}];    
    //返回繪製的新圖形    UIImage *newImage=UIGraphicsGetImageFromCurrentImageContext();    
    //最後一定不要忘記關閉對應的上下文    UIGraphicsEndImageContext();    
    //保存圖片
//    NSData *data= UIImagePNGRepresentation(newImage);
//    [data writeToFile:@"/Users/kenshincui/Desktop/myPic.png" atomically:YES];    
    return newImage;
}

@end

運行效果:

ImageContextEffect

注意:上面這種方式繪製的圖像除了可以顯示在界面上還可以調用對應方法進行保存(代碼註釋中已經包含保存方法);除此之外這種方法相比在drawRect:方法中繪製圖形效率更高,它不用每次展示時都調用所有圖形繪製方法。

繪製到PDF

繪製到PDF則要啓用pdf圖形上下文,PDF圖形上下文的創建使用方式跟位圖圖形上下文是類似的,需要注意的一點就是繪製內容到PDF時需要創建分頁,每頁內容的開始都要調用一次IGraphicsBeginPDFPage();方法。下面的示例演示了文本繪製和圖片繪製(其他圖形繪製也是類似的):

//
//  KCMainViewController.m
//  Quartz2D
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import <CoreText/CoreText.h>#import "KCMainViewController.h"#import "KCView.h"#import "KCView2.h"@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self drawContentToPdfContext];
}#pragma mark 利用pdf圖形上下文繪製內容到pdf文檔
-(void)drawContentToPdfContext{    
    //沙盒路徑(也就是我們應用程序文件運行的路徑)    NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path=[[paths firstObject] stringByAppendingPathComponent:@"myPDF.pdf"];
    NSLog(@"%@",path);    //啓用pdf圖形上下文
    /**
     path:保存路徑
     bounds:pdf文檔大小,如果設置爲CGRectZero則使用默認值:612*792
     pageInfo:頁面設置,爲nil則不設置任何信息
     */    UIGraphicsBeginPDFContextToFile(path,CGRectZero,[NSDictionary dictionaryWithObjectsAndKeys:@"Kenshin Cui",kCGPDFContextAuthor, nil]);    
    //由於pdf文檔是分頁的,所以首先要創建一頁畫布供我們繪製    UIGraphicsBeginPDFPage();
    
    NSString *title=@"Welcome to Apple Support";
    NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];
    NSTextAlignment align=NSTextAlignmentCenter;
    style.alignment=align;
    [title drawInRect:CGRectMake(26, 20, 300, 50) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSParagraphStyleAttributeName:style}];
    NSString *content=@"Learn about Apple products, view online manuals, get the latest downloads, and more. Connect with other Apple users, or get service, support, and professional advice from Apple.";
    NSMutableParagraphStyle *style2=[[NSMutableParagraphStyle alloc]init];
    style2.alignment=NSTextAlignmentLeft;//    style2.firstLineHeadIndent=20;    [content drawInRect:CGRectMake(26, 56, 300, 255) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor grayColor],NSParagraphStyleAttributeName:style2}];
    
    UIImage *image=[UIImage imageNamed:@"applecare_folks_tall.png"];
    [image drawInRect:CGRectMake(316, 20, 290, 305)];
    
    UIImage *image2=[UIImage imageNamed:@"applecare_page1.png"];
    [image2 drawInRect:CGRectMake(6, 320, 600, 281)];    
    //創建新的一頁繼續繪製其他內容    UIGraphicsBeginPDFPage();
    UIImage *image3=[UIImage imageNamed:@"applecare_page2.png"];
    [image3 drawInRect:CGRectMake(6, 20, 600, 629)];    
    //結束pdf上下文    UIGraphicsEndPDFContext();
}
@end

生成的pdf文檔:

DrawToPDF

 

知識補充

1.Core Graphics是基於C語言的一套框架,開發時無法像使用Obj-C一樣調用;

2.在Quartz 2D中凡是使用帶有“Create”或者“Copy”關鍵字方法創建的對象,在使用後一定要使用對應的方法釋放(由於這個框架基於C語言編寫無法自動釋放內存);

3.Quartz 2D是跨平臺的,因此其中的方法中不能使用UIKit中的對象(UIKit只有iOS可用),例如用到的顏色只能用CGColorRef而不能用UIColor,但是UIKit中提供了對應的轉換方法;

4.在C語言中枚舉一般以“k”開頭,由於Quartz 2D基於C語言開發,所以它也不例外(參數中很多枚舉都是k開頭的);

5.由於Quartz 2D是Core Graphics的一部分,所以API多數以CG開頭;

6.在使用Quartz 2D繪圖API中所有以“Ref”結尾對象,在聲明時都不必聲明爲指針類型;

7.在使用Quartz 2D繪圖API時,凡是“UI”開頭的相關繪圖函數,都是UIKit對Core Graphics的封裝(主要爲了簡化繪圖操作);




Core Image

利用Quartz 2D我們可以繪製各類圖形、圖像,功能確實強大。用過photoshop的朋友都知道,使用photoshop可以製作各種濾鏡特效,那麼在iOS中能否實現濾鏡呢?在iOS5.0之前這些算法基本全部要靠程序員編程實現,實現過程相當複雜。從iOS5.0開始蘋果官方已經提供了Core Image框架來幫助開發者進行特效製作。

先來看一下濾鏡使用過程中常用的基類對象:

CIContext:圖像上下文,用於管理整個圖片處理過程,不同的圖形上下文將利用不同的圖像處理硬件進行圖像處理(在iOS中可以通過不同的方式創建圖像上下文,例如可以創建基於CPU的圖像上下方、創建基於GPU的圖像上下方以及創建OpenGL優化過的圖像上下文)。

CIFilter:圖像處理濾鏡,每種濾鏡有不同的參數設置。

CIImage:Core Image框架中的圖像類型,主要用於輸入和輸出圖像。

在使用濾鏡之前我們先要弄清平臺主要支持哪些濾鏡,以及這些濾鏡的方法和參數如何設置,此時不妨使用下面的方法進行打印查看:

#pragma mark 查看所有內置濾鏡
-(void)showAllFilters{
    NSArray *filterNames=[CIFilter filterNamesInCategory:kCICategoryBuiltIn];    for (NSString *filterName in filterNames) {
        CIFilter *filter=[CIFilter filterWithName:filterName];
        NSLog(@"\rfilter:%@\rattributes:%@",filterName,[filter attributes]);
    }
}

在iOS7中打印會發現有127中濾鏡,如果我們把每種濾鏡都介紹一遍恐怕用幾章內容也很難介紹詳細,事實上也沒有這個必要。這些濾鏡使用方法是類似的,只是參數設置有所區別。在iOS文檔中可以搜索“core image filter reference”一節的內容,裏面有每種濾鏡的詳細介紹和圖片使用效果。

使用Core Image框架創建濾鏡效果一般分爲以下幾步:

1.創建圖像上下文CIContext

2.創建濾鏡CIFilter

3.創建過濾原圖片CIImage

4.調用CIFilter的setValue: forKey:方法爲濾鏡指定源圖片

5.設置濾鏡參數【可選】

6.取得輸出圖片顯示或保存

大家都知道在美圖秀秀中有一個“增強”功能,利用它可以調整照片的飽和度、亮度、對比度,其實在Core Image中也有這樣一款濾鏡,下面就以顏色濾鏡來演示一下Core Image中濾鏡的使用。

//
//  KCMainViewController.m
//  CoreImage
//
//  Created by Kenshin Cui on 14-3-17.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"#define CONSTROLPANEL_FONTSIZE 12

@interface KCMainViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>{
    UIImagePickerController *_imagePickerController;//系統照片選擇控制器    UIImageView *_imageView;//圖片顯示控件    CIContext *_context;//Core Image上下文    CIImage *_image;//我們要編輯的圖像    CIImage *_outputImage;//處理後的圖像    CIFilter *_colorControlsFilter;//色彩濾鏡}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self initLayout];
}#pragma mark 初始化佈局
-(void)initLayout{    //初始化圖片選擇器    _imagePickerController=[[UIImagePickerController alloc]init];
    _imagePickerController.delegate =self;    //創建圖片顯示控件    _imageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 64, 320, 502)];
    _imageView.contentMode=UIViewContentModeScaleAspectFit;
    [self.view addSubview:_imageView];    
    //上方導航按鈕    self.navigationItem.title=@"Enhance";
    self.navigationItem.leftBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"Open" style:UIBarButtonItemStyleDone target:self action:@selector(openPhoto:)];
    self.navigationItem.rightBarButtonItem=[[UIBarButtonItem alloc]initWithTitle:@"Save" style:UIBarButtonItemStyleDone target:self action:@selector(savePhoto:)];    
    //下方控制面板    UIView *controlView=[[UIView alloc]initWithFrame:CGRectMake(0, 450, 320, 118)];//    controlView.alpha=0.2;
//    controlView.backgroundColor=[UIColor colorWithRed:46.0/255.0 green:178.0/255.0 blue:235.0/255.0 alpha:1];    [self.view addSubview:controlView];    //飽和度(默認爲1,大於飽和度增加小於1則降低)    UILabel *lbSaturation=[[UILabel alloc]initWithFrame:CGRectMake(10, 10, 60, 25)];
    lbSaturation.text=@"Saturation";
    lbSaturation.font=[UIFont systemFontOfSize:CONSTROLPANEL_FONTSIZE];
    [controlView addSubview:lbSaturation];
    UISlider *sldStaturation=[[UISlider alloc]initWithFrame:CGRectMake(80, 10, 230, 30)];//注意UISlider高度雖然無法調整,很多朋友會說高度設置位0即可,事實上在iOS7中設置爲0後是無法拖動的    [controlView addSubview:sldStaturation];
    sldStaturation.minimumValue=0;
    sldStaturation.maximumValue=2;
    sldStaturation.value=1;
    [sldStaturation addTarget:self action:@selector(changeStaturation:) forControlEvents:UIControlEventValueChanged];    //亮度(默認爲0)    UILabel *lbBrightness=[[UILabel alloc]initWithFrame:CGRectMake(10, 40, 60, 25)];
    lbBrightness.text=@"Brightness";
    lbBrightness.font=[UIFont systemFontOfSize:CONSTROLPANEL_FONTSIZE];
    [controlView addSubview:lbBrightness];
    UISlider *sldBrightness=[[UISlider alloc]initWithFrame:CGRectMake(80, 40, 230, 30)];
    [controlView addSubview:sldBrightness];
    sldBrightness.minimumValue=-1;
    sldBrightness.maximumValue=1;
    sldBrightness.value=0;
    [sldBrightness addTarget:self action:@selector(changeBrightness:) forControlEvents:UIControlEventValueChanged];    //對比度(默認爲1)    UILabel *lbContrast=[[UILabel alloc]initWithFrame:CGRectMake(10, 70, 60, 25)];
    lbContrast.text=@"Contrast";
    lbContrast.font=[UIFont systemFontOfSize:CONSTROLPANEL_FONTSIZE];
    [controlView addSubview:lbContrast];
    UISlider *sldContrast=[[UISlider alloc]initWithFrame:CGRectMake(80, 70, 230, 30)];
    [controlView addSubview:sldContrast];
    sldContrast.minimumValue=0;
    sldContrast.maximumValue=2;
    sldContrast.value=1;
    [sldContrast addTarget:self action:@selector(changeContrast:) forControlEvents:UIControlEventValueChanged];    
    
    //初始化CIContext
    //創建基於CPU的圖像上下文
    //    NSNumber *number=[NSNumber numberWithBool:YES];
    //    NSDictionary *option=[NSDictionary dictionaryWithObject:number forKey:kCIContextUseSoftwareRenderer];
    //    _context=[CIContext contextWithOptions:option];    _context=[CIContext contextWithOptions:nil];//使用GPU渲染,推薦,但注意GPU的CIContext無法跨應用訪問,例如直接在UIImagePickerController的完成方法中調用上下文處理就會自動降級爲CPU渲染,所以推薦現在完成方法中保存圖像,然後在主程序中調用
    //    EAGLContext *eaglContext=[[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES1];
    //    _context=[CIContext contextWithEAGLContext:eaglContext];//OpenGL優化過的圖像上下文
    
    //取得濾鏡    _colorControlsFilter=[CIFilter filterWithName:@"CIColorControls"];

}#pragma mark 打開圖片選擇器
-(void)openPhoto:(UIBarButtonItem *)btn{    //打開圖片選擇器    [self presentViewController:_imagePickerController animated:YES completion:nil];
}#pragma mark 保存圖片
-(void)savePhoto:(UIBarButtonItem *)btn{    //保存照片到相冊    UIImageWriteToSavedPhotosAlbum(_imageView.image, nil, nil, nil);
    UIAlertView *alert=[[UIAlertView alloc]initWithTitle:@"Sytem Info" message:@"Save Success!" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
    [alert show];
}#pragma mark 圖片選擇器選擇圖片代理方法
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{    //關閉圖片選擇器    [self dismissViewControllerAnimated:YES completion:nil];    //取得選擇圖片    UIImage *selectedImage=[info objectForKey:UIImagePickerControllerOriginalImage];
    _imageView.image=selectedImage;    //初始化CIImage源圖像    _image=[CIImage imageWithCGImage:selectedImage.CGImage];
    [_colorControlsFilter setValue:_image forKey:@"inputImage"];//設置濾鏡的輸入圖片}#pragma mark 將輸出圖片設置到UIImageView
-(void)setImage{
    CIImage *outputImage= [_colorControlsFilter outputImage];//取得輸出圖像    CGImageRef temp=[_context createCGImage:outputImage fromRect:[outputImage extent]];
    _imageView.image=[UIImage imageWithCGImage:temp];//轉化爲CGImage顯示在界面中    
    CGImageRelease(temp);//釋放CGImage對象}#pragma mark 調整飽和度
-(void)changeStaturation:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputSaturation"];//設置濾鏡參數    [self setImage];
}#pragma mark 調整亮度
-(void)changeBrightness:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputBrightness"];
    [self setImage];
}#pragma mark 調整對比度
-(void)changeContrast:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputContrast"];
    [self setImage];
}
@end

運行效果:

CoreImageEffect

再次給大家強調一下:

  • 在上面的代碼中除了使用了基於GPU的圖像上下文(推薦方式),也創建了其他圖像上下文,儘管已經被註釋大家還是需要熟悉。

  • Core Image允許你一次給圖像或視頻幀疊加多種效果,同時Core Image還能保證強大的處理效率。

  • 和在使用Core Graphics繪圖一樣,UIKit中也封裝了一些方法直接轉換爲Core Image中的對象,例如UIImage對象可以直接調用CIImage屬性轉換爲CIImage類型。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章