iOS開(kāi)發(fā)之--圖形上下文以及繪圖

前言:
對(duì)《iOS開(kāi)發(fā)系列--打造自己的“美圖秀秀”》文章做了整理
如果對(duì)于CGPaht 或CGContextPath有不理解,那么請(qǐng)查:《IOS圖形繪制路徑 CGPATH & CGCONTEXT相關(guān)聯(lián)的CGPath & UIBezierPath》

在iOS中常用的繪圖框架就是Quartz 2D,Quartz 2D是Core Graphics框架的一部分,是一個(gè)強(qiáng)大的二維圖像繪制引擎。Quartz 2D在UIKit中也有很好的封裝和集成,我們?nèi)粘i_(kāi)發(fā)時(shí)所用到的UIKit中的組件都是由Core Graphics進(jìn)行繪制的。不僅如此,當(dāng)我們引入U(xiǎn)IKit框架時(shí)系統(tǒng)會(huì)自動(dòng)引入Core Graphics框架,并且為了方便開(kāi)發(fā)者使用在UIKit內(nèi)部還對(duì)一些常用的繪圖API進(jìn)行了封裝。

在iOS中繪圖一般分為以下幾個(gè)步驟:

1.獲取繪圖上下文
2.創(chuàng)建并設(shè)置路徑
3.將路徑添加到上下文
4.設(shè)置上下文狀態(tài)
5.繪制路徑
6.釋放路徑
圖形上下文CGContextRef代表圖形輸出設(shè)備(也就是繪制的位置),包含了繪制圖形的一些設(shè)備信息,Quartz 2D中的所有對(duì)象最終都必須繪制到圖形上下文。這樣一來(lái),我們?cè)诶L制圖形時(shí)就不必關(guān)心具體的設(shè)備信息,統(tǒng)一了代碼編寫方式(在Quartz 2D中的繪圖上下文可以是位圖Bitmap、PDF、窗口Window、層Layer、打印對(duì)對(duì)象Printer)。

基本圖形繪制
在UIKit中默認(rèn)已經(jīng)為我們準(zhǔn)備好了一個(gè)圖形上下文對(duì)象,在UI控件的drawRect:方法(這個(gè)方法在loadView、viewDidLoad方法后執(zhí)行)中我們可以通過(guò)UIKit封裝函數(shù)UIGraphicsGetCurrentContext()方法獲得這個(gè)圖形上下文(注意在其他UI控件方法中無(wú)法取得這個(gè)對(duì)象),然后我們只要按照繪圖步驟一步步執(zhí)行即可。下面自定義一個(gè)KCView繼承自UIView,重寫drawRect:方法繪制兩條直線說(shuō)明上面繪圖的步驟:

KCView.m

//繪圖只能在此方法中調(diào)用,否則無(wú)法得到當(dāng)前圖形上下文
-(void)drawRect:(CGRect)rect{
    //1.取得圖形上下文對(duì)象
    CGContextRef context = UIGraphicsGetCurrentContext();

    //2.創(chuàng)建路徑對(duì)象
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, 20, 50);//移動(dòng)到指定位置(設(shè)置路徑起點(diǎn))
    CGPathAddLineToPoint(path, nil, 20, 100);//繪制直線(從起始位置開(kāi)始)
    CGPathAddLineToPoint(path, nil, 300, 100);//繪制另外一條直線(從上一直線終點(diǎn)開(kāi)始繪制)


    //3.添加路徑到圖形上下文
    CGContextAddPath(context, path);

    //4.設(shè)置圖形上下文狀態(tài)屬性
    CGContextSetRGBStrokeColor(context, 1.0, 0, 0, 1);//設(shè)置筆觸顏色
    CGContextSetRGBFillColor(context, 0, 1.0, 0, 1);//設(shè)置填充色
    CGContextSetLineWidth(context, 2.0);//設(shè)置線條寬度
    CGContextSetLineCap(context, kCGLineCapRound);//設(shè)置頂點(diǎn)樣式,(20,50)和(300,100)是頂點(diǎn)
    CGContextSetLineJoin(context, kCGLineJoinRound);//設(shè)置連接點(diǎn)樣式,(20,100)是連接點(diǎn)
    /*設(shè)置線段樣式
    phase:虛線開(kāi)始的位置    lengths:虛線長(zhǎng)度間隔(例如下面的定義說(shuō)明第一條線段長(zhǎng)度8,然后間隔3重新繪制8點(diǎn)的長(zhǎng)度線段,當(dāng)然這個(gè)數(shù)組可以定義更多元素)
    count:虛線數(shù)組元素個(gè)數(shù)
    */
    CGFloat lengths[2] = { 18, 9 };
    CGContextSetLineDash(context, 0, lengths, 2);
    /*設(shè)置陰影
    context:圖形上下文
    offset:偏移量
    blur:模糊度
    color:陰影顏色
    */
    CGColorRef color = [UIColor grayColor].CGColor;//顏色轉(zhuǎn)化,由于Quartz 2D跨平臺(tái),所以其中不能使用UIKit中的對(duì)象,但是UIkit提供了轉(zhuǎn)化方法
    CGContextSetShadowWithColor(context, CGSizeMake(2, 2), 0.8, color);

    //5.繪制圖像到指定圖形上下文
    /*CGPathDrawingMode是填充方式,枚舉類型
    kCGPathFill:只有填充(非零纏繞數(shù)填充),不繪制邊框
    kCGPathEOFill:奇偶規(guī)則填充(多條路徑交叉時(shí),奇數(shù)交叉填充,偶交叉不填充)
    kCGPathStroke:只有邊框
    kCGPathFillStroke:既有邊框又有填充
    kCGPathEOFillStroke:奇偶填充并繪制邊框
    */
    CGContextDrawPath(context, kCGPathFillStroke);//最后一個(gè)參數(shù)是填充類型

    //6.釋放對(duì)象
    CGPathRelease(path);
}

簡(jiǎn)化繪圖方式

上面的繪圖方式未免顯得有些麻煩,其實(shí)Core Graphics 內(nèi)部對(duì)創(chuàng)建對(duì)象添加到上下文這兩步操作進(jìn)行了封裝,可以一步完成。另外前面也說(shuō)過(guò)UIKit內(nèi)部其實(shí)封裝了一些以“UI”開(kāi)頭的方法幫助大家進(jìn)行圖形繪制。就拿前面的例子來(lái)說(shuō)我們改進(jìn)一些繪制方法:

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

上面的操作相比前面的方法應(yīng)該說(shuō)已經(jīng)簡(jiǎn)化了不少,除了路徑之外其他矩形、橢圓等都有對(duì)應(yīng)的創(chuàng)建方法。另外上面我們也演示了封閉路徑的方法,大家可以運(yùn)行看一下效果。

其他圖形繪制
相信大家了解了上面的繪制步驟其他圖形繪制并不麻煩,下面以一個(gè)例子簡(jiǎn)單演示一下其他圖形的繪制,包括文字和圖像的繪制。
繪制矩形

在下面的方法中還可以看到UIKit對(duì)繪圖方法的封裝,使用起來(lái)更加簡(jiǎn)單。

-(void)drawRectWithContext:(CGContextRef)context{
    //添加矩形對(duì)象
    CGRect rect=CGRectMake(20, 50, 280.0, 50.0);
    CGContextAddRect(context,rect);
    //設(shè)置屬性
    [[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);
    //設(shè)置屬性
    [[UIColor yellowColor]set];
    //繪制矩形,相當(dāng)于創(chuàng)建對(duì)象、添加對(duì)象到上下文、繪制三個(gè)步驟
    UIRectFill(rect);//繪制矩形(只有填充)
    
    [[UIColor redColor]setStroke];
    UIRectFrame(rect2);//繪制矩形(只有邊框)
}

繪制橢圓

-(void)drawEllipse:(CGContextRef)context{
    //添加對(duì)象,繪制橢圓(圓形)的過(guò)程也是先創(chuàng)建一個(gè)矩形
    CGRect rect=CGRectMake(50, 50, 220.0, 200.0);
    CGContextAddEllipseInRect(context, rect);
    //設(shè)置屬性
    [[UIColor purpleColor]set];
    //繪制
    CGContextDrawPath(context, kCGPathFillStroke);
}

弧形繪制

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

繪制貝塞爾曲線
要繪制規(guī)則圖形在iOS中相當(dāng)簡(jiǎn)單,但是不規(guī)則圖形怎么繪制呢?此時(shí)就要利用路徑。前面我們繪制了直線,它和曲線繪制都屬于路徑繪制。和直線繪制相比曲線繪制就要復(fù)雜一些,但是路徑作為高級(jí)動(dòng)畫(huà)的基礎(chǔ)又是我們必須掌握的,因此這里我們就一起來(lái)熟悉一下曲線繪制。在Quartz 2D中曲線繪制分為兩種:二次貝塞爾曲線和三次貝塞爾曲線。二次曲線只有一個(gè)控制點(diǎn),而三次曲線有兩個(gè)控制點(diǎn),

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

備注:貝塞爾曲線是由法國(guó)數(shù)學(xué)家“貝塞爾”發(fā)現(xiàn)的,他發(fā)現(xiàn):任何一條曲線都能夠由和它相切的直線的兩個(gè)端點(diǎn)來(lái)描述,這種曲線表示方式后來(lái)被廣泛應(yīng)用到計(jì)算機(jī)中,稱為“貝塞爾曲線”

文字繪制

-(void)drawText:(CGContextRef)context{
    //繪制到指定的區(qū)域內(nèi)容
    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];//設(shè)置字體
    UIColor *color=[UIColor redColor];//字體顏色
    NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];//段落樣式
    NSTextAlignment align=NSTextAlignmentLeft;//對(duì)齊方式
    style.alignment=align;
    [str drawInRect:rect withAttributes:@{NSFontAttributeName:font,NSForegroundColorAttributeName:color,NSParagraphStyleAttributeName:style}];
}

圖像繪制

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

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

062332057972566.png

a.線性漸變線:漸變色以直線方式從開(kāi)始位置逐漸向結(jié)束位置漸變

b.徑向漸變:以中心點(diǎn)為圓心從起始漸變色向四周輻射,直到終止?jié)u變色

要做漸變則必須先設(shè)置從開(kāi)始位置到結(jié)束位置的漸變顏色,做過(guò)photoshop的朋友相信對(duì)于漸變色設(shè)置并不陌生,只要在指定位置指定不同的顏色,剩下的事情交給系統(tǒng)處理即可,如下圖在起始位置、3/10位置、結(jié)束位置指定了三種顏色就形成由三種顏色組成的漸變色:

另外,在iOS中繪制漸變還需要注意一點(diǎn)就是指定顏色空間,所謂顏色空間就是不同顏色在不同的維度上取值最終組成一種顏色的過(guò)程。就拿RGB來(lái)說(shuō),如果將紅色、綠色、藍(lán)色看成是x、y、z軸坐標(biāo)系,那么在三個(gè)坐標(biāo)上分別取0~255范圍內(nèi)的不同值則可以組成各類顏色。當(dāng)然,不同顏色空間的“坐標(biāo)系”也是不同的(也就是說(shuō)顏色表示的方式是不同的),常用的顏色空間除了RGB還有CMYK(印刷業(yè)常用這種顏色模式)、Gray。

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

下面的代碼分別演示了兩種漸變方式,具體漸變繪制函數(shù)參數(shù)代碼中已經(jīng)注釋的很清楚了:

-(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:顏色數(shù)組,注意由于指定了RGB顏色空間,那么四個(gè)數(shù)組元素表示一個(gè)顏色(red、green、blue、alpha),
                如果有三個(gè)顏色則這個(gè)數(shù)組有4*3個(gè)元素
     locations:顏色所在位置(范圍0~1),這個(gè)數(shù)組的個(gè)數(shù)不小于components中存放顏色的個(gè)數(shù)
     count:漸變個(gè)數(shù),等于locations的個(gè)數(shù)
     */
    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 開(kāi)始位置之前就進(jìn)行繪制,到結(jié)束位置之后不再繪制,
             kCGGradientDrawsAfterEndLocation開(kāi)始位置之前不進(jìn)行繪制,到結(jié)束點(diǎn)之后繼續(xù)填充
     */
    CGContextDrawLinearGradient(context, gradient, CGPointZero, CGPointMake(320, 300), kCGGradientDrawsAfterEndLocation);
    
    //釋放顏色空間
    CGColorSpaceRelease(colorSpace);
}

#pragma mark 徑向漸變
-(void)drawRadialGradient:(CGContextRef)context{
    //使用rgb顏色空間
    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
    
    /*指定漸變色
     space:顏色空間
     components:顏色數(shù)組,注意由于指定了RGB顏色空間,那么四個(gè)數(shù)組元素表示一個(gè)顏色(red、green、blue、alpha),
     如果有三個(gè)顏色則這個(gè)數(shù)組有4*3個(gè)元素
     locations:顏色所在位置(范圍0~1),這個(gè)數(shù)組的個(gè)數(shù)不小于components中存放顏色的個(gè)數(shù)
     count:漸變個(gè)數(shù),等于locations的個(gè)數(shù)
     */
    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:起始點(diǎn)位置
     startRadius:起始半徑(通常為0,否則在此半徑范圍內(nèi)容無(wú)任何填充)
     endCenter:終點(diǎn)位置(通常和起始點(diǎn)相同,否則會(huì)有偏移)
     endRadius:終點(diǎn)半徑(也就是漸變的擴(kuò)散長(zhǎng)度)
     options:繪制方式,kCGGradientDrawsBeforeStartLocation 開(kāi)始位置之前就進(jìn)行繪制,但是到結(jié)束位置之后不再繪制,
             kCGGradientDrawsAfterEndLocation開(kāi)始位置之前不進(jìn)行繪制,但到結(jié)束點(diǎn)之后繼續(xù)填充
     */
    CGContextDrawRadialGradient(context, gradient, CGPointMake(160, 284),0, CGPointMake(165, 289), 150, kCGGradientDrawsAfterEndLocation);
    //釋放顏色空間
    CGColorSpaceRelease(colorSpace);
}
@end
經(jīng)向漸變
線性漸變

擴(kuò)展--漸變填充

上面我們只是繪制漸變到圖形上下文,實(shí)際開(kāi)發(fā)中有時(shí)候我們還需要填充對(duì)應(yīng)的漸變色,例如現(xiàn)在繪制了一個(gè)矩形,如何填充成漸變色呢?在此可以利用漸變裁切來(lái)完成(當(dāng)然利用層CALayer更加方便但這不在今天的話題討論范圍內(nèi)),特別說(shuō)明一下區(qū)域裁切并不僅僅適用于漸變填充,對(duì)于其他圖形繪制仍然適用,并且注意裁切只能限于矩形裁切。

-(void)drawRectWithLinearGradientFill:(CGContextRef)context{
    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
    
    //裁切處一塊矩形用于顯示,注意必須先裁切再調(diào)用漸變
    //CGContextClipToRect(context, CGRectMake(20, 50, 280, 300));
    //裁切還可以使用UIKit中對(duì)應(yīng)的方法
    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);
}

其他狀態(tài)設(shè)置

常用的圖形上下文狀態(tài)設(shè)置上面基本都用到了,我們不再一一解釋,這里著重說(shuō)一下疊加模式和填充模式,初學(xué)者對(duì)于這兩個(gè)狀態(tài)設(shè)置往往容易產(chǎn)生疑惑。

疊加模式

使用Quartz 2D繪圖時(shí)后面繪制的圖像會(huì)覆蓋前面的,默認(rèn)情況下如果前面的被覆蓋后將看不到后面的內(nèi)容,但是有時(shí)候這個(gè)結(jié)果并不是我們想要的,因此在Quartz 2D中提供了填充模式供開(kāi)發(fā)者配置調(diào)整。由于填充模式類別特別多,因此下面以一個(gè)例子來(lái)說(shuō)明:

-(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);
}

相信大家對(duì)比代碼和顯示效果并不難發(fā)現(xiàn)每種疊加的效果。例子中只是使用UIKit的封裝方法進(jìn)行疊加模式設(shè)置,更一般的方法當(dāng)然是使用CGContextSetBlendMode(CGContextRef context, CGBlendMode mode)方法進(jìn)行設(shè)置。

填充模式

前面的示例中已經(jīng)演示過(guò)純色填充、漸變填充,而有時(shí)我們需要按一定的自定義樣式進(jìn)行填充,這種方式有點(diǎn)類似于貼瓷磚的方式。我們知道如果家里貼地板或瓷磚時(shí),通常我們會(huì)先選擇一種瓷磚樣式,根據(jù)房間面積我們購(gòu)買不同量的瓷磚。但是不管買多少,這些瓷磚的樣式都是一模一樣的。填充模式就是為了達(dá)到這種效果而產(chǎn)生的:我們只需要繪制一個(gè)瓷磚的樣式,然后讓程序自動(dòng)調(diào)用這種樣式填充指定大小的區(qū)域。

Quartz 2D支持兩種填充模式:有顏色填充和無(wú)顏色填充。兩種模式使用起來(lái)區(qū)別很小,有顏色填充就是在繪制瓷磚時(shí)就指定顏色,在調(diào)用填充時(shí)就不用再指定瓷磚顏色;無(wú)顏色填充模式就是繪制瓷磚時(shí)不用指定任何顏色,在調(diào)用填充時(shí)再指定具體填充顏色。相比較無(wú)顏色填充模式而言,有顏色填充模式更加的靈活,推薦使用。

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

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


2.接著需要指定一個(gè)填充的顏色空間,這個(gè)顏色空間跟前面繪制漸變的顏色空間不太一樣,前面創(chuàng)建漸變使用的顏色空間是設(shè)備無(wú)關(guān)的,我們需要基于這個(gè)顏色空間創(chuàng)建一個(gè)顏色空間專門用于填充(注意對(duì)于有顏色填充創(chuàng)建填充顏色空間參數(shù)為NULL,不用基于設(shè)備無(wú)關(guān)的顏色空間創(chuàng)建)。

3.然后我們就可以使用CGPatternCreate方法創(chuàng)建一個(gè)填充模式,創(chuàng)建填充模式時(shí)需要注意其中的參數(shù),在代碼中已經(jīng)做了一一解釋(這里注意對(duì)于有顏色填充模式isColored設(shè)置為true,否則為false)。

4.最后調(diào)用CGContextSetFillPattern方法給圖形上下文指定填充模式(這個(gè)時(shí)候注意最后一個(gè)參數(shù),如果是有顏色填充模式最后一個(gè)參數(shù)為透明度alpa的地址,對(duì)于無(wú)顏色填充模式最后一個(gè)參數(shù)是當(dāng)前填充顏色空間的顏色數(shù)組)。

5.繪制圖形,這里我們繪制一個(gè)矩形。

6.釋放資源。

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

-(void)drawRect:(CGRect)rect{
    CGContextRef context=UIGraphicsGetCurrentContext();
    [self drawBackgroundWithColoredPattern:context];
//    [self drawBackgroundWithPattern:context];
    
}

#pragma mark - 有顏色填充模式
void drawColoredTile(void *info,CGContextRef context){
    //有顏色填充,這里設(shè)置填充色
    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{
    //設(shè)備無(wú)關(guān)的顏色空間
//    CGColorSpaceRef rgbSpace= CGColorSpaceCreateDeviceRGB();
    //模式填充顏色空間,注意對(duì)于有顏色填充模式,這里傳NULL
    CGColorSpaceRef colorSpace=CGColorSpaceCreatePattern(NULL);
    //將填充色顏色空間設(shè)置為模式填充的顏色空間
    CGContextSetFillColorSpace(context, colorSpace);
    
    //填充模式回調(diào)函數(shù)結(jié)構(gòu)體
    CGPatternCallbacks callback={0,&drawColoredTile,NULL};
    /*填充模式
     info://傳遞給callback的參數(shù)
     bounds:瓷磚大小
     matrix:形變
     xStep:瓷磚橫向間距
     yStep:瓷磚縱向間距
     tiling:貼磚的方法
     isClored:繪制的瓷磚是否已經(jīng)指定了顏色(對(duì)于有顏色瓷磚此處指定位true)
     callbacks:回調(diào)函數(shù)
     */
    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;
    //注意最后一個(gè)參數(shù)對(duì)于有顏色瓷磚指定為透明度的參數(shù)地址,對(duì)于無(wú)顏色瓷磚則指定當(dāng)前顏色空間對(duì)應(yīng)的顏色數(shù)組
    CGContextSetFillPattern(context, pattern, &alpha);
    
    UIRectFill(CGRectMake(0, 0, 320, 568));
    
//    CGColorSpaceRelease(rgbSpace);
    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}


#pragma mark - 無(wú)顏色填充模式
//填充瓷磚的回調(diào)函數(shù)(必須滿足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{
    //設(shè)備無(wú)關(guān)的顏色空間
    CGColorSpaceRef rgbSpace= CGColorSpaceCreateDeviceRGB();
    //模式填充顏色空間
    CGColorSpaceRef colorSpace=CGColorSpaceCreatePattern(rgbSpace);
    //將填充色顏色空間設(shè)置為模式填充的顏色空間
    CGContextSetFillColorSpace(context, colorSpace);
    
    //填充模式回調(diào)函數(shù)結(jié)構(gòu)體
    CGPatternCallbacks callback={0,&drawTile,NULL};
    /*填充模式
     info://傳遞給callback的參數(shù)
     bounds:瓷磚大小
     matrix:形變
     xStep:瓷磚橫向間距
     yStep:瓷磚縱向間距
     tiling:貼磚的方法(瓷磚擺放的方式)
     isClored:繪制的瓷磚是否已經(jīng)指定了顏色(對(duì)于無(wú)顏色瓷磚此處指定位false)
     callbacks:回調(diào)函數(shù)
     */
    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};
    //注意最后一個(gè)參數(shù)對(duì)于無(wú)顏色填充模式指定為當(dāng)前顏色空間顏色數(shù)據(jù)
    CGContextSetFillPattern(context, pattern, components);
    //    CGContextSetStrokePattern(context, pattern, components);
    UIRectFill(CGRectMake(0, 0, 320, 568));
  
    CGColorSpaceRelease(rgbSpace);
    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}

這里強(qiáng)調(diào)一點(diǎn),在drawTile回調(diào)方法中不要使用UIKit封裝方法進(jìn)行圖形繪制(例如UIRectFill等),由于這個(gè)方法由Core Graphics內(nèi)部調(diào)用,而Core Graphics考慮到跨平臺(tái)問(wèn)題,內(nèi)部是不允許調(diào)用UIKit方法的。

上下文變換

我們知道在UIKit開(kāi)發(fā)中UIView有一個(gè)transform屬性用于控件的形變,其實(shí)在繪圖中我們也經(jīng)常用到圖形形變,這個(gè)時(shí)候可以借助圖形上下文的形變方法來(lái)完成。在弄清形變之前我們要清楚圖形上下文的坐標(biāo)原點(diǎn),因?yàn)闊o(wú)論是位移還是旋轉(zhuǎn)都是相對(duì)于坐標(biāo)原點(diǎn)進(jìn)行的。其實(shí)Quartz 2D的坐標(biāo)系同UIKit并不一樣,它的坐標(biāo)原點(diǎn)在屏幕左下方,但是為了統(tǒng)一編程方式,UIKit對(duì)其進(jìn)行了轉(zhuǎn)換,坐標(biāo)原點(diǎn)統(tǒng)一在屏幕左上角。注意在設(shè)置圖形上下文形變之前一定要注意保存上下文的初始狀態(tài),在使用完之后進(jìn)行恢復(fù)。否則在處理多個(gè)圖形形變的時(shí)候很容易弄不清楚到底是基于怎樣的坐標(biāo)系進(jìn)行繪圖,容易找不到原點(diǎn)(做過(guò)html5 canvas繪圖的朋友對(duì)這一點(diǎn)應(yīng)該很熟悉,在html5中繪圖也經(jīng)常進(jìn)行狀態(tài)保存和恢復(fù))。下面通過(guò)一個(gè)圖片的變換演示一下圖形上下文的形變(其他圖形也是一樣的,就不再演示):

-(void)drawRect:(CGRect)rect{
    CGContextRef context=UIGraphicsGetCurrentContext();
    [self drawImage:context];
}

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

擴(kuò)展--使用Core Graphics繪制圖像

在前面基本繪圖部分,繪制圖像時(shí)使用了UIKit中封裝的方法進(jìn)行了圖像繪制,我們不妨看一下使用Quartz 2D內(nèi)置方法繪制是什么效果。

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

看起來(lái)整個(gè)圖像是倒過(guò)來(lái)的,原因正是前面說(shuō)的:在Core Graphics中坐標(biāo)系的y軸正方向是向上的,坐標(biāo)原點(diǎn)在屏幕左下角,y軸方向剛好和UIKit中y軸方向相反。而使用UIKit進(jìn)行繪圖之所以沒(méi)有問(wèn)題是因?yàn)閁IKit中進(jìn)行了處理,事實(shí)上對(duì)于其他圖形即使使用Core Graphics繪制也沒(méi)有問(wèn)題,因?yàn)閁IKit統(tǒng)一了編程方式。但是使用Core Graphics中內(nèi)置方法繪制圖像是存在這種問(wèn)題的,如何解決呢?

其實(shí)圖形上下文只要沿著x軸旋轉(zhuǎn)180度,然后向上平移適當(dāng)?shù)母叨燃纯桑ǖ亲⒁獠灰刂鴝軸旋轉(zhuǎn),這樣得不到想要的結(jié)果)??墒峭ㄟ^(guò)前面介紹的CGContextRotateCTM方法只能通過(guò)沿著z軸旋轉(zhuǎn),此時(shí)不妨使用另外一種方法,那就是在y軸方向縮放-1,同樣可以達(dá)到想要的效果:

-(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相當(dāng)于沿著x張旋轉(zhuǎn)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:中繪制的圖形會(huì)在控件顯示的時(shí)候調(diào)用(而且顯示時(shí)會(huì)重繪所有圖形),有時(shí)候我們希望繪制內(nèi)容的顯示是實(shí)時(shí)的,此時(shí)我們就需要調(diào)用繪圖方法重新繪制,但是在iOS開(kāi)發(fā)中不允許開(kāi)發(fā)者直接調(diào)用drawRect:方法,刷新繪制內(nèi)容需要調(diào)用setNeedsDisplay方法。下面以一個(gè)調(diào)整字體大小的界面演示一下視圖的刷新。

KCView.h

#import <UIKit/UIKit.h>

@interface KCView : UIView

@property (nonatomic,copy) NSString *title;

@property (nonatomic,assign) CGFloat fontSize;

@end

KCView.m

#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

#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:@"%@號(hào)字體",_fontSize[row] ];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    _contentView.fontSize=[[_fontSize objectAtIndex:row] intValue];
    
    //刷新視圖
    [_contentView setNeedsDisplay];
}
@end

運(yùn)行效果:

其他圖形上下文

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

上面的示例中一直都是在drawRect:方法中利用UIGraphicsGetCurrentContext()方法取得上下文,要得到位圖或者PDF的上下文可以利用UIGraphicsBeginImageContext(CGSize size)和UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary *pageInfo)方法。位圖圖形上下文和PDF圖形上下文UIKit是不會(huì)負(fù)責(zé)創(chuàng)建的,所以需要用戶手動(dòng)創(chuàng)建,并且在使用完后關(guān)閉它。在使用UIKit中系統(tǒng)創(chuàng)建的圖形上下文的時(shí)候,我們只能在drawRect:方法中使用,由于這兩類圖形上下文是由我們手動(dòng)創(chuàng)建的因此可以放到任何方法中調(diào)用。此外,這兩個(gè)方法開(kāi)啟的圖形上下文并沒(méi)有返回值,如果我們要得到我們創(chuàng)建的圖形上下文只要在創(chuàng)建上下文之后、關(guān)閉之前調(diào)用UIGraphicsGetCurrentContext()方法,此時(shí)取得的上下文即是我們自己創(chuàng)建的圖形上下文。

繪制到位圖
下面利用位圖圖形上下文給一個(gè)圖片添加水印,在下面的程序中我們首先創(chuàng)建上下文,然后在上下文中繪制圖片、直線和文本,最后從當(dāng)前位圖上下文中取得最終形成的新圖片顯示到界面。

#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{
    //獲得一個(gè)位圖圖形上下文
    CGSize size=CGSizeMake(300, 188);//畫(huà)布大小
    UIGraphicsBeginImageContext(size);
    
    UIImage *image=[UIImage imageNamed:@"photo2.png"];
    [image drawInRect:CGRectMake(0, 0, 300, 188)];//注意繪圖的位置是相對(duì)于畫(huà)布頂點(diǎn)而言,不是屏幕
    
    
    //添加水印
    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();
    
    //最后一定不要忘記關(guān)閉對(duì)應(yīng)的上下文
    UIGraphicsEndImageContext();
    
    //保存圖片
//    NSData *data= UIImagePNGRepresentation(newImage);
//    [data writeToFile:@"/Users/kenshincui/Desktop/myPic.png" atomically:YES];
    
    return newImage;
}

@end

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

繪制到PDF

繪制到PDF則要啟用pdf圖形上下文,PDF圖形上下文的創(chuàng)建使用方式跟位圖圖形上下文是類似的,需要注意的一點(diǎn)就是繪制內(nèi)容到PDF時(shí)需要?jiǎng)?chuàng)建分頁(yè),每頁(yè)內(nèi)容的開(kāi)始都要調(diào)用一次IGraphicsBeginPDFPage();方法。下面的示例演示了文本繪制和圖片繪制(其他圖形繪制也是類似的):

#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圖形上下文繪制內(nèi)容到pdf文檔
-(void)drawContentToPdfContext{
    
    //沙盒路徑(也就是我們應(yīng)用程序文件運(yùn)行的路徑)
    NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path=[[paths firstObject] stringByAppendingPathComponent:@"myPDF.pdf"];
    NSLog(@"%@",path);
    //啟用pdf圖形上下文
    /**
     path:保存路徑
     bounds:pdf文檔大小,如果設(shè)置為CGRectZero則使用默認(rèn)值:612*792
     pageInfo:頁(yè)面設(shè)置,為nil則不設(shè)置任何信息
     */
    UIGraphicsBeginPDFContextToFile(path,CGRectZero,[NSDictionary dictionaryWithObjectsAndKeys:@"Kenshin Cui",kCGPDFContextAuthor, nil]);
    
    //由于pdf文檔是分頁(yè)的,所以首先要?jiǎng)?chuàng)建一頁(yè)畫(huà)布供我們繪制
    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)];
    
    //創(chuàng)建新的一頁(yè)繼續(xù)繪制其他內(nèi)容
    UIGraphicsBeginPDFPage();
    UIImage *image3=[UIImage imageNamed:@"applecare_page2.png"];
    [image3 drawInRect:CGRectMake(6, 20, 600, 629)];
    
    //結(jié)束pdf上下文
    UIGraphicsEndPDFContext();
}
@end

生成的pdf文檔:

知識(shí)補(bǔ)充

1.Core Graphics是基于C語(yǔ)言的一套框架,開(kāi)發(fā)時(shí)無(wú)法像使用Obj-C一樣調(diào)用;

2.在Quartz 2D中凡是使用帶有“Create”或者“Copy”關(guān)鍵字方法創(chuàng)建的對(duì)象,在使用后一定要使用對(duì)應(yīng)的方法釋放(由于這個(gè)框架基于C語(yǔ)言編寫無(wú)法自動(dòng)釋放內(nèi)存);

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

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

5.由于Quartz 2D是Core Graphics的一部分,所以API多數(shù)以CG開(kāi)頭;

6.在使用Quartz 2D繪圖API中所有以“Ref”結(jié)尾對(duì)象,在聲明時(shí)都不必聲明為指針類型;

7.在使用Quartz 2D繪圖API時(shí),凡是“UI”開(kāi)頭的相關(guān)繪圖函數(shù),都是UIKit對(duì)Core Graphics的封裝(主要為了簡(jiǎn)化繪圖操作);

Core Image

利用Quartz 2D我們可以繪制各類圖形、圖像,功能確實(shí)強(qiáng)大。用過(guò)photoshop的朋友都知道,使用photoshop可以制作各種濾鏡特效,那么在iOS中能否實(shí)現(xiàn)濾鏡呢?在iOS5.0之前這些算法基本全部要靠程序員編程實(shí)現(xiàn),實(shí)現(xiàn)過(guò)程相當(dāng)復(fù)雜。從iOS5.0開(kāi)始蘋果官方已經(jīng)提供了Core Image框架來(lái)幫助開(kāi)發(fā)者進(jìn)行特效制作。

先來(lái)看一下濾鏡使用過(guò)程中常用的基類對(duì)象:

CIContext:圖像上下文,用于管理整個(gè)圖片處理過(guò)程,不同的圖形上下文將利用不同的圖像處理硬件進(jìn)行圖像處理(在iOS中可以通過(guò)不同的方式創(chuàng)建圖像上下文,例如可以創(chuàng)建基于CPU的圖像上下方、創(chuàng)建基于GPU的圖像上下方以及創(chuàng)建OpenGL優(yōu)化過(guò)的圖像上下文)。

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

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

在使用濾鏡之前我們先要弄清平臺(tái)主要支持哪些濾鏡,以及這些濾鏡的方法和參數(shù)如何設(shè)置,此時(shí)不妨使用下面的方法進(jìn)行打印查看:

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

在iOS7中打印會(huì)發(fā)現(xiàn)有127中濾鏡,如果我們把每種濾鏡都介紹一遍恐怕用幾章內(nèi)容也很難介紹詳細(xì),事實(shí)上也沒(méi)有這個(gè)必要。這些濾鏡使用方法是類似的,只是參數(shù)設(shè)置有所區(qū)別。在iOS文檔中可以搜索“core image filter reference”一節(jié)的內(nèi)容,里面有每種濾鏡的詳細(xì)介紹和圖片使用效果。

使用Core Image框架創(chuàng)建濾鏡效果一般分為以下幾步:

1.創(chuàng)建圖像上下文CIContext

2.創(chuàng)建濾鏡CIFilter

3.創(chuàng)建過(guò)濾原圖片CIImage

4.調(diào)用CIFilter的setValue: forKey:方法為濾鏡指定源圖片

5.設(shè)置濾鏡參數(shù)【可選】

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

大家都知道在美圖秀秀中有一個(gè)“增強(qiáng)”功能,利用它可以調(diào)整照片的飽和度、亮度、對(duì)比度,其實(shí)在Core Image中也有這樣一款濾鏡,下面就以顏色濾鏡來(lái)演示一下Core Image中濾鏡的使用。

#import "KCMainViewController.h"
#define CONSTROLPANEL_FONTSIZE 12

@interface KCMainViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>{
    UIImagePickerController *_imagePickerController;//系統(tǒng)照片選擇控制器
    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;

    //創(chuàng)建圖片顯示控件
    _imageView=[[UIImageView alloc]initWithFrame:CGRectMake(0, 64, 320, 502)];
    _imageView.contentMode=UIViewContentModeScaleAspectFit;
    [self.view addSubview:_imageView];
    
    //上方導(dǎo)航按鈕
    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];
    //飽和度(默認(rèn)為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高度雖然無(wú)法調(diào)整,很多朋友會(huì)說(shuō)高度設(shè)置位0即可,事實(shí)上在iOS7中設(shè)置為0后是無(wú)法拖動(dòng)的
    [controlView addSubview:sldStaturation];
    sldStaturation.minimumValue=0;
    sldStaturation.maximumValue=2;
    sldStaturation.value=1;
    [sldStaturation addTarget:self action:@selector(changeStaturation:) forControlEvents:UIControlEventValueChanged];
    //亮度(默認(rèn)為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];
    //對(duì)比度(默認(rèn)為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
    //創(chuàng)建基于CPU的圖像上下文
    //    NSNumber *number=[NSNumber numberWithBool:YES];
    //    NSDictionary *option=[NSDictionary dictionaryWithObject:number forKey:kCIContextUseSoftwareRenderer];
    //    _context=[CIContext contextWithOptions:option];
    _context=[CIContext contextWithOptions:nil];//使用GPU渲染,推薦,但注意GPU的CIContext無(wú)法跨應(yīng)用訪問(wèn),例如直接在UIImagePickerController的完成方法中調(diào)用上下文處理就會(huì)自動(dòng)降級(jí)為CPU渲染,所以推薦現(xiàn)在完成方法中保存圖像,然后在主程序中調(diào)用
    //    EAGLContext *eaglContext=[[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES1];
    //    _context=[CIContext contextWithEAGLContext:eaglContext];//OpenGL優(yōu)化過(guò)的圖像上下文
    
    //取得濾鏡
    _colorControlsFilter=[CIFilter filterWithName:@"CIColorControls"];

}
#pragma mark 打開(kāi)圖片選擇器
-(void)openPhoto:(UIBarButtonItem *)btn{
    //打開(kāi)圖片選擇器
    [self presentViewController:_imagePickerController animated:YES completion:nil];
}
#pragma mark 保存圖片
-(void)savePhoto:(UIBarButtonItem *)btn{
    //保存照片到相冊(cè)
    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{
    //關(guān)閉圖片選擇器
    [self dismissViewControllerAnimated:YES completion:nil];
    //取得選擇圖片
    UIImage *selectedImage=[info objectForKey:UIImagePickerControllerOriginalImage];
    _imageView.image=selectedImage;
    //初始化CIImage源圖像
    _image=[CIImage imageWithCGImage:selectedImage.CGImage];
    [_colorControlsFilter setValue:_image forKey:@"inputImage"];//設(shè)置濾鏡的輸入圖片
}

#pragma mark 將輸出圖片設(shè)置到UIImageView
-(void)setImage{
    CIImage *outputImage= [_colorControlsFilter outputImage];//取得輸出圖像
    CGImageRef temp=[_context createCGImage:outputImage fromRect:[outputImage extent]];
    _imageView.image=[UIImage imageWithCGImage:temp];//轉(zhuǎn)化為CGImage顯示在界面中
    
    CGImageRelease(temp);//釋放CGImage對(duì)象
}

#pragma mark 調(diào)整飽和度
-(void)changeStaturation:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputSaturation"];//設(shè)置濾鏡參數(shù)
    [self setImage];
}

#pragma mark 調(diào)整亮度
-(void)changeBrightness:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputBrightness"];
    [self setImage];
}

#pragma mark 調(diào)整對(duì)比度
-(void)changeContrast:(UISlider *)slider{
    [_colorControlsFilter setValue:[NSNumber numberWithFloat:slider.value] forKey:@"inputContrast"];
    [self setImage];
}
@end

再次給大家強(qiáng)調(diào)一下:

在上面的代碼中除了使用了基于GPU的圖像上下文(推薦方式),也創(chuàng)建了其他圖像上下文,盡管已經(jīng)被注釋大家還是需要熟悉。
Core Image允許你一次給圖像或視頻幀疊加多種效果,同時(shí)Core Image還能保證強(qiáng)大的處理效率。
和在使用Core Graphics繪圖一樣,UIKit中也封裝了一些方法直接轉(zhuǎn)換為Core Image中的對(duì)象,例如UIImage對(duì)象可以直接調(diào)用CIImage屬性轉(zhuǎn)換為CIImage類型。
原文地址:http://www.cnblogs.com/kenshincui/p/3959951.html#!comments

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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