核心绘图——Core Graphics

一. Core Graphics简介

  1. Core Graphics是一个基于C的绘图专用的API族,它经常被称为QuartZ或QuartZ 2D,是一个二维绘图引擎,同时支持iOS和Mac系统。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变。
    提示:
    引擎:经过包装的函数库,方便开发者使用。QuartZ 2D是苹果帮封装的一套绘图的函数库。
    同时支持iOS和Mac系统:用QuartZ 2D写的同一份代码,既可以运行在iphone上又可以运行在mac上,可以跨平台开发。

  2. QuartZ 2D在开发中比较常用的是截屏/裁剪/自定义UI控件,QuartZ 2D在iOS开发中的主要价值是自定义UI控件

  3. QuartZ 2D能完成的工作
    (1).绘制图形: 线条\三角形\矩形\圆\弧等
    (2).绘制文字
    (3).绘制\生成图片(图像)
    (4).读取\生成PDF
    (5).截图\裁剪图片
    (6).自定义UI控件

二. 图形上下文(Graphics Context)

  1. 图形上下文是一个CGContextRef类型的数据,图形上下文相当于画板,用于封装绘图信息(画了什么)和绘图状态(线条大小、颜色等),它决定绘制的输出目标(绘制到什么地方去),目标可以是PDF文件、bitmap或者显示器的窗口。

  2. 相同的一套绘图序列,指定不同的图形上下文,就可将相同的图像绘制到不同的目标上,目标可以是PDF文件、bitmap或者显示器的窗口。

  3. QuartZ 2D提供了 5 种类型的图形上下文:Bitmap Graphics Context、PDF Graphics Context、Window Graphics Context、Layer Context、Post Graphics Context。

  4. 在UIView中,系统会默认创建一个Layer Graphics Context,它对应UIView的layer属性,该图形上下文可以在drawRect:方法中获取,开发者只能获取,不能自己重新创建,在该图层上下文中绘制的图形,最终会通过CALayer显示出来。因此,View之所以能显示东西,完全是因为它内部的layer。

三. 图形上下文的图形状态堆栈

图形上下文中包含一个保存过的图形状态堆栈。在QuartZ 2D创建图形上下文时,该堆栈是空的。
CGContextSaveGState()函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState()函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。

四. UIView和CALayer的区别

  1. 对于继承
    UIView —> UIResponder —> NSObject
    CALayer —> NSObject

  2. 对于响应用户事件
    UIView继承自UIResponder,UIResponder是用来响应事件的,所以UIView可以响应事件
    CALayer直接继承于NSObject,所以CALayer不能响应事件

  3. 对于所属框架
    UIView是在/System/Library/Frameworks/UIKit.framework中定义,UIKit主要是用来构建用户界面,并且是可以响应事件的
    CALayer是在/System/Library/Frameworks/QuartzCore.framework定义,2D图像绘制都是通过QuartzCore.framework实现的

  4. 对于基本属性
    UIView:position、size、transform
    CALayer:position、size、transform、animations

  5. 总结
    UIView相比CALayer最大区别是UIView可以响应用户事件,而CALayer不可以。UIView侧重于对显示内容的管理,CALayer侧重于对内容的绘制。对于UIView所管理的内容,显示任何图形都会受到CALayer的影响。UIView依赖于CALayer提供的内容,CALayer依赖于UIView提供的容器来显示绘制的内容。UIView与CALayer都有树状层级结构,CALayer内部有SubLayers,UIView内部也有SubViews。

五. UIView的显示原理

因为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:方法中绘图

六. 绘图的步骤

方法一:最原始的绘图方式

  1. 获取当前控件的图形上下文
  2. 描述绘画内容
    a. 创建图形路径
    b. 创建图形起始点
    c. 添加图形的终点
  3. 把绘画内容添加到图形上下文
  4. 设置图形上下文的状态(线宽、颜色等)
  5. 渲染图形上下文
- (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.可以创建多个路径,多个路径可以绘制多个图形,但是使用原始方法绘图不能单独管理图形的状态(线条、颜色等)。

方法二:使用上下文直接绘图

注意:不用创建路径,也不需要把绘图内容添加到图形上下文,因为图形上下文封装了这些步骤。

  1. 获取当前控件的图形上下文
  2. 描述绘画内容
    a. 创建图形起始点
    b. 添加图形的终点
  3. 设置图形上下文的状态(线宽、颜色等)
  4. 渲染图形上下文
- (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);  
}

方法三:贝瑟尔路径(UIBezierPath)绘图

提示:

  1. UIKit已经封装了一些绘图的功能:UIBezierPath,里面封装了很多东西,可以帮我画一些基本的线段,矩形,圆等等,一般开发中用贝塞尔路径绘图。
  2. CGPath转换:UIKit框架转CoreGraphics直接 .CGPath 就能转换
    优点: 用UIBezierPath 画多根不连接的线,可以管理各个线的状态
    缺点: UIBezierPath 不能画曲线

注意:
使用贝瑟尔路径绘图只能在drawRect里进行,因为底层要用到上下文,图形上下文只能在drawRect里获取,不能在其他方法里面绘图,比如:不能在awakeFromNib里绘图!

  1. 创建贝瑟尔路径
  2. 描述绘画内容
    a. 创建图形起始点(moveToPoint)
    b. 添加图形的终点(addLineToPoint)
  3. 设置路径状态
  4. 绘制路径
- (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];

}

七. 绘制曲线

方法一:最原始绘图方法

  1. 获取当前控件的图形上下文
  2. 描述绘画图形内容
    a. 获取图形路径
    b. 添加起始点
    c. 添加控制点和终点
  3. 把绘制图形内容添加到图形上下文
  4. 设置图形上下文状态
  5. 渲染图形上下文
- (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);
}

方法二:使用上下文直接绘图

  1. 获取当前控件的图形上下文
  2. 描述绘画图形内容
    a. 创建图形起始点
    b. 添加控制点和终点
  3. 设置图形上下文状态
  4. 渲染图形上下文
- (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);

}

十一. 图片水印

  1. 开启位图上下文
  2. 描述绘画内容
    a. 绘制图片
    b. 绘制文字
    C. 绘制图形等
  3. 从位图上下文获取生成的图片
  4. 关闭位图上下文
- (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上下文不一样,位图上下文需要手动创建

十二. 图片裁剪

  1. 开启位图上下文
  2. 设置裁剪区
  3. 绘制图片
  4. 从位图上下文获取生成的图片
  5. 关闭位图上下文
- (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();

}

十三. 屏幕截屏

  1. 开启一个位图上下文
  2. 获取位图上下文
  3. 把屏幕上的图层渲染到图形上下文
  4. 从位图上下文获取图片
  5. 关闭上下文
  6. 存储图片
- (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];

}

十四. 提高描画的性能

  1. 合成不透明的视图所需要的开销比合成部分透明的视图要少得多。一个不透明的视图必须不包含任何透明的内容,且视图的opaque属性必须设置为YES。
  2. 如果一个PNG图像的每个像素都是不透明的,则将其alpha通道删除可以避免对包含该图像的图层进行融合操作,从而很大程度上简化了该图像的合成,提高描画的性能。
  3. 在每个更新周期中,您应该只更新视图中真正发生变化的部分。如果您使用UIView的drawRect:方法来进行描画,则要通过传给该方法的更新矩形来限制描画的范围。对于基于OpenGL的描画,您必须自行跟踪更新区域。
  4. 应该避免在滚动过程种创建新的视图。创建新视图的开销会减少用于更新屏幕的时间,因而导致滚动不平滑。
  5. 缺省情况下,在调用drawRect:方法对视图的某个区域进行更新之前,UIKit会清除该区域对应的上下文缓冲区。如果您对视图的滚动事件进行响应,则在滚动过程中反复清除缓冲区的开销是很大的。为了禁止这种行为,可以将clearsContextBeforeDrawing属性设置为NO。
  6. 改变图形状态需要窗口服务器的参与。如果您要描画的内容使用类似的图形状态,则尽可能将这些内容一起描画,以减少需要改变的状态。

十五. 保持图像的质量

  1. 为用户界面提供高品质的图像应该是设计工作中的重点之一。图像是一种合理而有效的显示复杂图形的方法,任何合适的地方都可以使用。在为应用程序创建图像的时候,请记住下面的原则:
  2. 使用PNG格式的图像。PNG格式可以提供高品质的图像内容,是iPhone OS系统上推荐的图像格式。另外,iPhone OS对PNG图像的描画路径是经过优化的,通常比其它格式具有更高的效率。
  3. 创建大小合适的图像,避免在显示时调整尺寸。如果您计划使用特定尺寸的图像,则在创建图像资源时,务必使用相同的尺寸。不要创建一个大的图像,然后再缩小,因为缩放需要额外的CPU开销,而且需要进行插值。如果您需要以不同的尺寸显示图像,则请包含多个版本的图像,并选择与目标尺寸相对接近的图像来进行缩放。

十六. CGContextDrawImage使用提示

使用CGContextDrawImage()函数来直接描画位图,则在缺省情况下,图像数据会上下倒置,因为Quartz图像假定坐标系统的原点在左下角,且坐标轴的正向是向上和向右。虽然您可以在描画之前对其进行转换,但是将Quartz图像包装为一个
UIImage

你可能感兴趣的:(iOS-UI界面设计,ios,core,graphics,图形上下文,QuartZ-2D,绘图)