iOS開發系列--打造自己的“美圖秀秀”

KenshinCui發表於2014-09-06

--繪圖與濾鏡全面解析

概述

在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型別。

相關文章