Core Graphics是一个基于C的绘图专用的API族,它经常被称为QuartZ或QuartZ 2D,是一个二维绘图引擎,同时支持iOS和Mac系统。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变。
提示:
引擎:经过包装的函数库,方便开发者使用。QuartZ 2D是苹果帮封装的一套绘图的函数库。
同时支持iOS和Mac系统:用QuartZ 2D写的同一份代码,既可以运行在iphone上又可以运行在mac上,可以跨平台开发。
QuartZ 2D在开发中比较常用的是截屏/裁剪/自定义UI控件,QuartZ 2D在iOS开发中的主要价值是自定义UI控件
QuartZ 2D能完成的工作
(1).绘制图形: 线条\三角形\矩形\圆\弧等
(2).绘制文字
(3).绘制\生成图片(图像)
(4).读取\生成PDF
(5).截图\裁剪图片
(6).自定义UI控件
图形上下文是一个CGContextRef类型的数据,图形上下文相当于画板,用于封装绘图信息(画了什么)和绘图状态(线条大小、颜色等),它决定绘制的输出目标(绘制到什么地方去),目标可以是PDF文件、bitmap或者显示器的窗口。
相同的一套绘图序列,指定不同的图形上下文,就可将相同的图像绘制到不同的目标上,目标可以是PDF文件、bitmap或者显示器的窗口。
QuartZ 2D提供了 5 种类型的图形上下文:Bitmap Graphics Context、PDF Graphics Context、Window Graphics Context、Layer Context、Post Graphics Context。
在UIView中,系统会默认创建一个Layer Graphics Context,它对应UIView的layer属性,该图形上下文可以在drawRect:方法中获取,开发者只能获取,不能自己重新创建,在该图层上下文中绘制的图形,最终会通过CALayer显示出来。因此,View之所以能显示东西,完全是因为它内部的layer。
图形上下文中包含一个保存过的图形状态堆栈。在QuartZ 2D创建图形上下文时,该堆栈是空的。
CGContextSaveGState()函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState()函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
对于继承
UIView —> UIResponder —> NSObject
CALayer —> NSObject
对于响应用户事件
UIView继承自UIResponder,UIResponder是用来响应事件的,所以UIView可以响应事件
CALayer直接继承于NSObject,所以CALayer不能响应事件
对于所属框架
UIView是在/System/Library/Frameworks/UIKit.framework中定义,UIKit主要是用来构建用户界面,并且是可以响应事件的
CALayer是在/System/Library/Frameworks/QuartzCore.framework定义,2D图像绘制都是通过QuartzCore.framework实现的
对于基本属性
UIView:position、size、transform
CALayer:position、size、transform、animations
因为UIView依赖于CALayer提供的内容,而CALayer又依赖于UIView提供的容器来显示绘制的内容,所以UIView的显示可以说是CALayer要显示绘制的图形。当要显示时,CALayer会准备好一个CGContextRef(图形上下文),然后调用它的delegate(这里就是UIView)的drawLayer:inContext:方法,并且传入已经准备好的CGContextRef对象,在drawLayer:inContext:方法中UIView又会调用自己的drawRect:方法。
我们可以把UIView的显示比作“在电脑上使用播放器播放U盘上得电影”,播放器相当于UIView,视频解码器相当于CALayer,U盘相当于CGContextRef,电影相当于绘制的图形信息。不同的图形上下文可以想象成不同接口的U盘。
注意:当我们需要绘图到根层上时,一般在drawRect:方法中绘制,不建议在drawLayer:inContext:方法中绘图
- (void)drawRect:(CGRect)rect {
// 1. 获取当前控件的图形上下文
// CG:表示这个类在CoreGraphics框架里 Ref:引用
CGContextRef context = UIGraphicsGetCurrentContext();
// 2. 描述绘画内容
// a. 创建图形路径
CGMutablePathRef path = CGPathCreateMutable();
// b. 创建图形起始点
CGPathMoveToPoint(path, NULL, 50, 50);
// c. 添加图形的终点
CGPathAddLineToPoint(path, NULL, 200, 50);
// 3. 把绘画内容添加到图形上下文
CGContextAddPath(context, path);
// 4. 设置图形上下文的状态(线宽、颜色等)
CGContextSetLineWidth(context, 5);
CGContextSetRGBStrokeColor(context, 0, 1, 0, 1);
// 5. 渲染图形上下文
CGContextStrokePath(context);
}
解析:
1.drawRect方法是在当前控件要显示的时候才去调用,并且drawRect方法只能系统调用,不能手动调用,手动调用setNeedDispplay方法或setNeedsDisplayInRect:方法,系统才会去调用drawRect。
系统默认:viewWillAppear —> drawRect —> viewDidAppear
手动调用:setNeedDispplay —> drawRect
2.- (void)drawRect:(CGRect)rect;中的rect的值是当前控件的bounds值。
3.描述绘画内容相当于描述图形的路径,其实也就是设置图形的各个关键点,但是还没有真正画线,只有起点和终点绘制的图形是一条直线,画线是在view要显示的时候才进行,但路径添加到上下文后必须渲染上下文,不然图形不会显示在view上。
获取图形上下文 —> 创建图形路径 —> 设置路径 —> 设置上下文状态 —> 路径添加到上下文 —> 渲染上下文 —> 在view上显示
4.一条路径连续多次调用addLineToPoint函数添加点,它的路径是拼接的,默认下一条线的起点是上一条线的终点。
5.当要画多根不连续的线,方法一:创建不同的路径再添加线段需要的点,方法二:同一个路径,按步骤画完第一条线,第二条线是重新设置起始点再设置需要的点。
6.可以创建多个路径,多个路径可以绘制多个图形,但是使用原始方法绘图不能单独管理图形的状态(线条、颜色等)。
注意:不用创建路径,也不需要把绘图内容添加到图形上下文,因为图形上下文封装了这些步骤。
- (void)drawRect:(CGRect)rect {
// 1. 获取当前控件的图形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 2. 描述绘画内容
// a. 创建图形起始点
CGContextMoveToPoint(context, 20, 50);
// b. 添加图形的终点
CGContextAddLineToPoint(context, 200, 150);
CGContextAddLineToPoint(context, 200, 55);
// 3. 设置图形上下文的状态(线宽、颜色等)
CGContextSetLineWidth(context, 5);
CGContextSetCMYKStrokeColor(context, 0, 1, 1, 0, 1);
// 4. 渲染图形上下文
CGContextStrokePath(context);
}
提示:
- UIKit已经封装了一些绘图的功能:UIBezierPath,里面封装了很多东西,可以帮我画一些基本的线段,矩形,圆等等,一般开发中用贝塞尔路径绘图。
- CGPath转换:UIKit框架转CoreGraphics直接 .CGPath 就能转换
优点: 用UIBezierPath 画多根不连接的线,可以管理各个线的状态
缺点: UIBezierPath 不能画曲线
注意:
使用贝瑟尔路径绘图只能在drawRect里进行,因为底层要用到上下文,图形上下文只能在drawRect里获取,不能在其他方法里面绘图,比如:不能在awakeFromNib里绘图!
- (void)drawRect:(CGRect)rect {
// 1. 创建贝瑟尔路径
UIBezierPath *path = [UIBezierPath bezierPath];
// 2. 设置起点
[path moveToPoint:CGPointMake(20, 20)];
// 3. 设置终点
[path addLineToPoint:CGPointMake(80, 150)];
// 4. 设置路径状态
// 设置颜色
[[UIColor redColor] set];
// 设置线宽
[path setLineWidth:5];
// 4. 绘制路径
[path stroke];
// 1. 创建贝瑟尔路径
UIBezierPath *path1 = [UIBezierPath bezierPath];
// 2. 设置起点
[path1 moveToPoint:CGPointMake(50, 20)];
// 3. 设置拐点
[path1 addLineToPoint:CGPointMake(200, 100)];
// 3. 设置终点
[path1 addLineToPoint:CGPointMake(50, 230)];
// 4. 设置路径状态
// 设置颜色
[[UIColor blueColor] set];
// 设置线宽
[path1 setLineWidth:15];
// 设置连接样式
[path1 setLineJoinStyle:kCGLineJoinRound];
// 设置顶角样式
[path1 setLineCapStyle:kCGLineCapRound];
// 4. 绘制路径
[path1 stroke];
}
- (void)drawRect:(CGRect)rect {
// 1. 获取当前控件的图形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 2. 描述绘画图形内容
// a. 获取图形路径
CGMutablePathRef path = CGPathCreateMutable();
// b. 添加起始点
CGPathMoveToPoint(path, NULL, 0, 0);
// C. 添加控制点和终点,控制点(150,150)、终点(0,250)
CGPathAddQuadCurveToPoint(path, NULL, 250, 150, 0, 250);
// 3. 把绘制图形内容添加到图形上下文
CGContextAddPath(context, path);
// 4. 设置图形上下文状态
// 设置颜色
[[UIColor redColor] set];
// 设置线宽
CGContextSetLineWidth(context, 10);
// 5. 渲染图形上下文
CGContextStrokePath(context);
}
- (void)drawRect:(CGRect)rect {
// 1. 获取当前控件的图形上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 2. 描述绘画图形内容
// a. 创建图形起始点
CGContextMoveToPoint(context, 0, 0);
// b. 添加控制点和终点,控制点(300,200),终点(0,250)
CGContextAddQuadCurveToPoint(context, 300, 200, 0, 250);
// 3. 设置图形上下文状态
// 设置颜色
[[UIColor redColor] set];
// 设置线宽
CGContextSetLineWidth(context, 10);
// 4. 渲染图形上下文
CGContextStrokePath(context);
}
常见问题:
1. 关闭路径closePath:从路径的终点连接到起点
2. 填充路径CGContextFillPath:有了封闭的路径就能填充。
3. 设置填充颜色 [[UIColor blueColor] setFill];
4. 设置描边颜色 [[UIColor redColor] setStroke];
5. 不显示描边颜色,为什么?没有设置线宽
6. 设置线宽,还是不显示,为什么?因为绘制路径不对。
7. 即填充又描边CGContextDrawPath:kCGPathFillStroke。
8. 圆的起点在圆右边水平线
// RoundedRect: 坐标与宽高
// cornerRadius: 角半径
UIBezierPath *oblongPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 150) cornerRadius:100];
// 颜色
[[UIColor redColor] set];
// 填充
[oblongPath fill];
// 扇形
// Center:圆心
// startAngle:弧度
// clockwise:YES:顺时针 NO:逆时针
CGPoint center = CGPointMake(100, 100);
UIBezierPath *sectorPath = [UIBezierPath bezierPathWithArcCenter:center radius:50 startAngle:0 endAngle:M_PI clockwise:YES];
// 从终点连线到圆心
[sectorPath addLineToPoint:center];
// 颜色
[[UIColor blueColor] set];
// // 关闭路径,从终点连线到起始点
// [sectorPath closePath];
// // 描边
// [sectorPath stroke];
// 填充:必须是一个完整的封闭路径,默认就会自动关闭路径
[sectorPath fill];
- (void)drawRect:(CGRect)rect {
// 获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 描述图形内容
// 第一根线
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(50, 10)];
[path1 addLineToPoint:CGPointMake(50, 150)];
// 把第一根添加到上下文
CGContextAddPath(context, path1.CGPath);
// 保存一份初始的上下文状态
CGContextSaveGState(context);
// 设置上下文状态
[[UIColor redColor] set];
CGContextSetLineWidth(context, 5);
// 渲染上下文
CGContextStrokePath(context);
// 第二根线
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(100, 10)];
[path2 addLineToPoint:CGPointMake(100, 150)];
// 添加到上下文
CGContextAddPath(context, path2.CGPath);
// 还原上下文状态
CGContextRestoreGState(context);
// 渲染上下文
CGContextStrokePath(context);
}
- (void)drawRect:(CGRect)rect {
// 获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 创建路径
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 50, 100)];
// 矩阵操作
// 移动
CGContextTranslateCTM(context, 100, 100);
// 缩放
CGContextScaleCTM(context, 1, 2);
// 旋转
CGContextRotateCTM(context, M_PI_4);
// 把路径添加到上下文
CGContextAddPath(context, path.CGPath);
// 设置颜色
[[UIColor blueColor] set];
// 渲染上下文
CGContextStrokePath(context);
}
- (void)viewDidLoad {
[super viewDidLoad];
// 创建图片
UIImage *logoImage = [UIImage imageNamed:@"小黄人"];
// 1. 开启位图上下文
// 注意: 位图上下文跟view无关联,所以不需要在drawRect中获取上下文
// size: 位图上下文的尺寸(绘制出新图片的尺寸)
// opaque: 是否透明,YES:不透明 NO:透明,通常设置成透明的上下文
// scale: 缩放上下文,取值0表示不缩放,通常不需要缩放上下文
UIGraphicsBeginImageContextWithOptions(logoImage.size, NO, 0);
// 2. 描述绘画内容
// 绘制原生图片
[logoImage drawAtPoint:CGPointZero];
// 绘制文字
NSString *logo = @"小黄人";
// 创建字典属性
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[NSForegroundColorAttributeName] = [UIColor redColor];
dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
[logo drawAtPoint:CGPointMake(51, 27) withAttributes:dict];
// 绘制图形
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(30, 30, 20, 20)];
[[UIColor redColor] set];
[path fill];
// 3. 从上下文获取生成的图片
_logoImage.image = UIGraphicsGetImageFromCurrentImageContext();
// 4. 关闭位图上下文
UIGraphicsEndImageContext();
}
提示:
1. 位图上下文跟view无关联,所以不需要在drawRect中获取上下文
2. UIGraphicsGetCurrentContext()函数可以获取的不同类型的上下文,CGContextRef变量可以指向不同类型上下文
3. 在位图上下文上绘制图形,必须获取位图上下文生成的图片,再显示图片才可以看见绘制的图形
4. 位图上下文的获取方式跟layer上下文不一样,位图上下文需要手动创建
- (void)viewDidLoad {
[super viewDidLoad];
// 创建图片
UIImage *image = [UIImage imageNamed:@"阿狸头像"];
// 1. 开启上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
// 2. 设置裁剪区
// 创建图形路径
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
// 把图形路径设置为裁剪区
[path addClip];
// 3. 绘制图形
[image drawAtPoint:CGPointZero];
// 4. 从位图上下文获取图片
_SJMImage.image = UIGraphicsGetImageFromCurrentImageContext();
// 5. 关闭上下文
UIGraphicsEndImageContext();
}
- (void)viewDidLoad {
// 1. 开启一个位图上下文
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
// 2. 获取位图上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 3. 把屏幕上的图层渲染到图形上下文
[self.view.layer renderInContext:ctx];
// 4. 从位图上下文获取图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 5. 关闭上下文
UIGraphicsEndImageContext();
// 6. 存储图片
// image转data
// compressionQuality:图片质量 1:最高质量
NSData *data = UIImageJPEGRepresentation(image,1);
[data writeToFile:@"/Users/SJM/Desktop/view.png" atomically:YES];
}
使用CGContextDrawImage()函数来直接描画位图,则在缺省情况下,图像数据会上下倒置,因为Quartz图像假定坐标系统的原点在左下角,且坐标轴的正向是向上和向右。虽然您可以在描画之前对其进行转换,但是将Quartz图像包装为一个
UIImage