原文出处: KenshinCui html
概述html5
在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.获取绘图上下文canvas
2.建立并设置路径数组
3.将路径添加到上下文架构
4.设置上下文状态app
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
// // 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并添加到根视图中:
1 2 3 4 5 6 7 |
- (void)viewDidLoad { [super viewDidLoad];
KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds]; view.backgroundColor=[UIColor whiteColor]; [self.view addSubview:view]; } |
运行效果以下:
上面的绘图方式未免显得有些麻烦,其实Core Graphics 内部对建立对象添加到上下文这两步操做进行了封装,能够一步完成。另外前面也说过UIKit内部其实封装了一些以“UI”开头的方法帮助你们进行图形绘制。就拿前面的例子来讲咱们改进一些绘制方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-(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对绘图方法的封装,使用起来更加简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#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 |
运行效果:
1 2 3 4 5 6 7 8 9 10 11 |
#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 |
运行效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-(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中至关简单,可是不规则图形怎么绘制呢?此时就要利用路径。前面咱们绘制了直线,它和曲线绘制都属于路径绘制。和直线绘制相比曲线绘制就要复杂一些,可是路径做为高级动画的基础又是咱们必须掌握的,所以这里咱们就一块儿来熟悉一下曲线绘制。在Quartz 2D中曲线绘制分为两种:二次贝塞尔曲线和三次贝塞尔曲线。二次曲线只有一个控制点,而三次曲线有两个控制点,以下图所示:
固然,在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);
下面就演示一下这两种曲线的绘制方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#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);
} |
运行效果:
备注:贝塞尔曲线是由法国数学家“贝塞尔”发现的,他发现:任何一条曲线都可以由和它相切的直线的两个端点来描述,这种曲线表示方式后来被普遍应用到计算机中,称为“贝塞尔曲线”。
除了绘制图形还能够绘制文本内容。
1 2 3 4 5 6 7 8 9 10 11 |
-(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}]; } |
运行效果:
Quartz 2D还能够将图像绘制到图形上下文。
1 2 3 4 5 6 7 8 9 |
-(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)]; } |
运行效果:
从前面的示例中咱们能够看到如何设置填充颜色,事实上不少时候纯色的填充并不能知足咱们的需求,例若有时候咱们要绘制一些图形可能须要设置一个漂亮的背景,这个时候咱们可能就会选择渐变填充方式。Quartz 2D的渐变方式分为两种:
a.线性渐变线:渐变色以直线方式从开始位置逐渐向结束位置渐变
b.径向渐变:以中心点为圆心从起始渐变色向四周辐射,直到终止渐变色
要作渐变则必须先设置从开始位置到结束位置的渐变颜色,作过photoshop的朋友相信对于渐变色设置并不陌生,只要在指定位置指定不一样的颜色,剩下的事情交给系统处理便可,以下图在起始位置、3/10位置、结束位置指定了三种颜色就造成由三种颜色组成的渐变色:
另外,在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(透明度)。
下面的代码分别演示了两种渐变方式,具体渐变绘制函数参数代码中已经注释的很清楚了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
// // 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 |
运行效果:
上面咱们只是绘制渐变到图形上下文,实际开发中有时候咱们还须要填充对应的渐变色,例如如今绘制了一个矩形,如何填充成渐变色呢?在此能够利用渐变裁切来完成(固然利用层CALayer更加方便但这不在今天的话题讨论范围内),特别说明一下区域裁切并不只仅适用于渐变填充,对于其余图形绘制仍然适用,而且注意裁切只能限于矩形裁切。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-(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); } |
运行效果:
经常使用的图形上下文状态设置上面基本都用到了,咱们再也不一一解释,这里着重说一下叠加模式和填充模式,初学者对于这两个状态设置每每容易产生疑惑。
使用Quartz 2D绘图时后面绘制的图像会覆盖前面的,默认状况下若是前面的被覆盖后将看不到后面的内容,可是有时候这个结果并非咱们想要的,所以在Quartz 2D中提供了填充模式供开发者配置调整。因为填充模式类别特别多,所以下面以一个例子来讲明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
-(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); } |
运行效果:
相信你们对比代码和显示效果并不难发现每种叠加的效果。例子中只是使用UIKit的封装方法进行叠加模式设置,更通常的方法固然是使用CGContextSetBlendMode(CGContextRef context, CGBlendMode mode)方法进行设置。
前面的示例中已经演示过纯色填充、渐变填充,而有时咱们须要按必定的自定义样式进行填充,这种方式有点相似于贴瓷砖的方式。咱们知道若是家里贴地板或瓷砖时,一般咱们会先选择一种瓷砖样式,根据房间面积咱们购买不一样量的瓷砖。可是无论买多少,这些瓷砖的样式都是如出一辙的。填充模式就是为了达到这种效果而产生的:咱们只须要绘制一个瓷砖的样式,而后让程序自动调用这种样式填充指定大小的区域。
Quartz 2D支持两种填充模式:有颜色填充和无颜色填充。两种模式使用起来区别很小,有颜色填充就是在绘制瓷砖时就指定颜色,在调用填充时就不用再指定瓷砖颜色;无颜色填充模式就是绘制瓷砖时不用指定任何颜色,在调用填充时再指定具体填充颜色。相比较无颜色填充模式而言,有颜色填充模式更加的灵活,推荐使用。
下面咱们具体看一下如何按指定模式进行图形填充:
1.在使用填充模式时首先要构建一个符合CGPatternDrawPatternCallback签名的方法,这个方法专门用来建立“瓷砖”。注意:若是使用有颜色填充模式,须要设置填充色。例如咱们定义一个方法drawTile绘制如下瓷砖(有颜色填充):
2.接着须要指定一个填充的颜色空间,这个颜色空间跟前面绘制渐变的颜色空间不太同样,前面建立渐变使用的颜色空间是设备无关的,咱们须要基于这个颜色空间建立一个颜色空间专门用于填充(注意对于有颜色填充建立填充颜色空间参数为NULL,不用基于设备无关的颜色空间建立)。
3.而后咱们就可使用CGPatternCreate方法建立一个填充模式,建立填充模式时须要注意其中的参数,在代码中已经作了一一解释(这里注意对于有颜色填充模式isColored设置为true,不然为false)。
4.最后调用CGContextSetFillPattern方法给图形上下文指定填充模式(这个时候注意最后一个参数,若是是有颜色填充模式最后一个参数为透明度alpa的地址,对于无颜色填充模式最后一个参数是当前填充颜色空间的颜色数组)。
5.绘制图形,这里咱们绘制一个矩形。
6.释放资源。
下面是具体代码(包含两种填充模式代码,能够一一运行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
// // 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 |
运行效果:
这里强调一点,在drawTile回调方法中不要使用UIKit封装方法进行图形绘制(例如UIRectFill等),因为这个方法由Core Graphics内部调用,而Core Graphics考虑到跨平台问题,内部是不容许调用UIKit方法的。
咱们知道在UIKit开发中UIView有一个transform属性用于控件的形变,其实在绘图中咱们也常常用到图形形变,这个时候能够借助图形上下文的形变方法来完成。在弄清形变以前咱们要清楚图形上下文的坐标原点,由于不管是位移仍是旋转都是相对于坐标原点进行的。其实Quartz 2D的坐标系同UIKit并不同,它的坐标原点在屏幕左下方,可是为了统一编程方式,UIKit对其进行了转换,坐标原点统一在屏幕左上角。注意在设置图形上下文形变以前必定要注意保存上下文的初始状态,在使用完以后进行恢复。不然在处理多个图形形变的时候很容易弄不清楚究竟是基于怎样的坐标系进行绘图,容易找不到原点(作过html5 canvas绘图的朋友对这一点应该很熟悉,在html5中绘图也常常进行状态保存和恢复)。下面经过一个图片的变换演示一下图形上下文的形变(其余图形也是同样的,就再也不演示):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// // 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 |
最终运行效果见第四幅截图,下图描绘出了整个程序的运行过程(移动->缩放->旋转):
在前面基本绘图部分,绘制图像时使用了UIKit中封装的方法进行了图像绘制,咱们不妨看一下使用Quartz 2D内置方法绘制是什么效果。
1 2 3 4 5 6 |
-(void)drawImage2:(CGContextRef)context{ UIImage *image=[UIImage imageNamed:@"image2.jpg"]; //图像绘制 CGRect rect= CGRectMake(10, 50, 300, 450); CGContextDrawImage(context, rect, image.CGImage); } |
运行效果:
看起来整个图像是倒过来的,缘由正是前面说的:在Core Graphics中坐标系的y轴正方向是向上的,坐标原点在屏幕左下角,y轴方向恰好和UIKit中y轴方向相反。而使用UIKit进行绘图之因此没有问题是由于UIKit中进行了处理,事实上对于其余图形即便使用Core Graphics绘制也没有问题,由于UIKit统一了编程方式。可是使用Core Graphics中内置方法绘制图像是存在这种问题的,如何解决呢?
其实图形上下文只要沿着x轴旋转180度,而后向上平移适当的高度便可(可是注意不要沿着z轴旋转,这样得不到想要的结果)。但是经过前面介绍的CGContextRotateCTM方法只能经过沿着z轴旋转,此时不妨使用另一种方法,那就是在y轴方向缩放-1,一样能够达到想要的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-(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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// // 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 (){ 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 |
运行效果:
前面咱们也说过,Quartz 2D的图形上下方除了能够绘制到层上还能够绘制到位图、PDF等,这里咱们就介绍一下如何利用Quartz 2D绘制图像到位图及PDF中。
上面的示例中一直都是在drawRect:方法中利用UIGraphicsGetCurrentContext()方法取得上下文,要获得位图或者PDF的上下文能够利用UIGraphicsBeginImageContext(CGSize size)和UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary *pageInfo)方法。位图图形上下文和PDF图形上下文UIKit是不会负责建立的,因此须要用户手动建立,而且在使用完后关闭它。在使用UIKit中系统建立的图形上下文的时候,咱们只能在drawRect:方法中使用,因为这两类图形上下文是由咱们手动建立的所以能够放到任何方法中调用。此外,这两个方法开启的图形上下文并无返回值,若是咱们要获得咱们建立的图形上下文只要在建立上下文以后、关闭以前调用UIGraphicsGetCurrentContext()方法,此时取得的上下文便是咱们本身建立的图形上下文。
下面利用位图图形上下文给一个图片添加水印,在下面的程序中咱们首先建立上下文,而后在上下文中绘制图片、直线和文本,最后从当前位图上下文中取得最终造成的新图片显示到界面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
// // 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 |
运行效果:
注意:上面这种方式绘制的图像除了能够显示在界面上还能够调用对应方法进行保存(代码注释中已经包含保存方法);除此以外这种方法相比在drawRect:方法中绘制图形效率更高,它不用每次展现时都调用全部图形绘制方法。
绘制到PDF则要启用pdf图形上下文,PDF图形上下文的建立使用方式跟位图图形上下文是相似的,须要注意的一点就是绘制内容到PDF时须要建立分页,每页内容的开始都要调用一次IGraphicsBeginPDFPage();方法。下面的示例演示了文本绘制和图片绘制(其余图形绘制也是相似的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
// // 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
- ( |