天天看點

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

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

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

Quartz 2D

基本圖形繪制

視圖重新整理

其他圖形上下文

Core Image

--繪圖與濾鏡全面解析

概述

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

在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];
}      

運作效果如下:

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

簡化繪圖方式

上面的繪圖方式未免顯得有些麻煩,其實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      

運作效果:

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

繪制橢圓

#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       
iOS開發系列--打造自己的“美圖秀秀”

繪制弧形

-(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);
}      
iOS開發系列--打造自己的“美圖秀秀”

繪制貝塞爾曲線

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

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

當然,在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);
    
}      
iOS開發系列--打造自己的“美圖秀秀”
備注:貝塞爾曲線是由法國數學家“貝塞爾”發現的,他發現:任何一條曲線都能夠由和它相切的直線的兩個端點來描述,這種曲線表示方式後來被廣泛應用到計算機中,稱為“貝塞爾曲線”。

文字繪制

除了繪制圖形還可以繪制文本内容。

-(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}];
}      
iOS開發系列--打造自己的“美圖秀秀”

圖像繪制

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)];
}      
iOS開發系列--打造自己的“美圖秀秀”

繪制漸變填充

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

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

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

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

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

另外,在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      
iOS開發系列--打造自己的“美圖秀秀”
iOS開發系列--打造自己的“美圖秀秀”

擴充--漸變填充

上面我們隻是繪制漸變到圖形上下文,實際開發中有時候我們還需要填充對應的漸變色,例如現在繪制了一個矩形,如何填充成漸變色呢?在此可以利用漸變裁切來完成(當然利用層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);
}      
iOS開發系列--打造自己的“美圖秀秀”

其他狀态設定

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

疊加模式

使用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);
}      
iOS開發系列--打造自己的“美圖秀秀”

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

填充模式

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

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

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

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

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

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      
iOS開發系列--打造自己的“美圖秀秀”

這裡強調一點,在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      

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

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

擴充--使用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);
}      
iOS開發系列--打造自己的“美圖秀秀”

看起來整個圖像是倒過來的,原因正是前面說的:在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
//  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      
iOS開發系列--打造自己的“美圖秀秀”

前面我們也說過,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      
iOS開發系列--打造自己的“美圖秀秀”

注意:上面這種方式繪制的圖像除了可以顯示在界面上還可以調用對應方法進行儲存(代碼注釋中已經包含儲存方法);除此之外這種方法相比在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文檔:

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

知識補充

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的封裝(主要為了簡化繪圖操作);

利用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      

再次給大家強調一下:

  • 在上面的代碼中除了使用了基于GPU的圖像上下文(推薦方式),也建立了其他圖像上下文,盡管已經被注釋大家還是需要熟悉。
  • Core Image允許你一次給圖像或視訊幀疊加多種效果,同時Core Image還能保證強大的處理效率。
  • 和在使用Core Graphics繪圖一樣,UIKit中也封裝了一些方法直接轉換為Core Image中的對象,例如UIImage對象可以直接調用CIImage屬性轉換為CIImage類型。
iOS開發系列--打造自己的“美圖秀秀”
本作品采用知識共享署名 2.5 中國大陸許可協定進行許可,歡迎轉載,演繹或用于商業目的。但轉載請注明來自崔江濤(KenshinCui),并包含相關連結。

繼續閱讀