美文网首页iOS DevQuartZ 2D
iOS开发-Quartz 2D基础篇

iOS开发-Quartz 2D基础篇

作者: Eiwodetianna | 来源:发表于2016-10-17 18:59 被阅读1436次

    Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。我们可以使用Quartz 2D API来实现许多功能,如基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问。

    绘制原理

    Quartz 2D在图像绘制中使用了绘画者模型。在绘画者模型中,每个连续的绘制操作都是将一个绘制层放置于一个画布(canvas),我们可以理解为图像的绘制即为在画布上绘画,而绘画操作的顺序同时影响着我们绘制的结果,如图:

    图形上下文(Graphics Context)

    一个Graphics Context表示一个绘制目标。它包含完成绘制任务所需的一些绘制参数和设备相关信息。Graphics Context定义了基本的绘制属性,如颜色、裁减区域、线条宽度和字体信息、混合模式等。

    Quartz 2D 坐标系

    Quartz 2D中默认的坐标系统为:左下角为坐标系统原点(0,0),屏幕水平方向为x轴,沿着x轴从左到右为正方向;屏幕垂直方向为y轴,沿着y轴从下到上为正方向。在实际应用中,不同的Graphics Context可能使用了不同的坐标系统,它们在Quartz 2D默认的坐标系统基础之上做了调整,来适应不同的场景。

    iOS中的视图绘制

    在iOS应用程序中,如果需要在屏幕上进行视图绘制,需要创建一个UIView对象,UIView是定义为在屏幕上的一块矩形区域,用于管理这块区域所呈现的内容,而在这个矩形区域内,我们可以通过重写drawRect:方法来自定义一些需要显示的内容。drawRect:方法在视图显示在屏幕上及它的内容需要更新时被系统自动调用,我们手动调用是无效的,系统提供了两个方法让我们进行间接调用drawRect:来达到重绘视图的目的:

    // 方法一:重新绘制这个view
    - (void)setNeedsDisplay;
    // 方法二:重新绘制view的某个区域
    - (void)setNeedsDisplayInRect:(CGRect)rect;
    

    获取Graphics Context

    在调用自定义的drawRect:后,视图对象自动配置绘图环境以便代码能立即执行绘图操作。作为配置的一部分,视图对象将为当前的绘图环境创建一个Graphics Context。我们可以在drawRect:中使用代码获取这个context:

    CGContextRef context = UIGraphicsGetCurrentContext();
    

    这里需要注意的是,这个context的坐标系是默认原点位于左上角,y轴正方向为向下。这是因为UIKit使用的默认的坐标系统与Quartz 2D默认的坐标系统不同,在UIKit中,默认原点位于左上角,y轴正方向为向下。所以UIView通过修改Quartz的Graphics Context的CTM(ps:一种仿射矩阵,通过平移(translation)、旋转(rotation)、缩放(scale)操作可以将点从一个坐标空间映射到另外一个坐标空间)以使其与UIKit的坐标系匹配。

    创建与绘制路径(Path)

    路径我们可以理解为我们手中的画笔绘制出来的一个或者多个形状(子路径),每一个形状可以是线、圆、矩形、星形等简单的形状,也可以是一些更复杂的自定义形状。如下图显示了一些路径。左上角的直线可以是虚线;直线也可以是实线。上边中间的路径是由多条曲线组成的开放路径;右上角的同心圆填充了颜色,但没有描边;左下角的加利福尼亚州是闭合路径,由许多曲线和直线构成,且对路径进行填充和描边。


    • 创建路径:
      路径的创建和绘制是两个独立的工作,我们可以手动创建一个路径,也可以使用一些便利的函数帮我们隐式的创建一些路径,我们获取到的Quartz Context其实默认会在内部创建一个path用来保存绘图信息。我们以构建一条直线线段路径为例:
        - (void)drawRect:(CGRect)rect {
        
        // 1.获取Quartz Context
        CGContextRef context = UIGraphicsGetCurrentContext();
        // 2.开始构建路径,设置一个起点的坐标,绘制系统会追踪至该点进行绘制
        // 参数一:关联的绘制目标
        // 参数二:起点的x坐标
        // 参数三:起点的y坐标
        CGContextMoveToPoint(context, 100, 100);
        // 3.将当前点与一个点(这里我们举例为(200,100))之间构建一条直线
        // 参数一:关联的绘制目标
        // 参数二:该端点的x坐标
        // 参数三:该端点的y坐标
        CGContextAddLineToPoint(context, 200, 100);
        
        }
    

    让我们共同分析一下上面的代码:
    第一步,我们在drawRect:方法中获取Quartz Context;
    第二步,我们准备开始构建路径,在构建路径时我们使用了CGContextMoveToPoint()函数来确定绘制的一个起始点,context会将该点信息存储至默认生成的path中;
    第三步,我们使用了CGContextAddLineToPoint()函数将两点之间建立直线关系,同样context会将该点信息也存储至默认生成的path中,注意此时,我们只是构建了一个直线路径,并没有绘制,所以运行后不会有效果。
    但是上述代码的可读性并不友好,而且在绘制路径后,系统将清空Quartz Context,我们可能想保留路径,特别是在绘制一些比较复杂场景时,我们需要反复使用,所以我们在实际开发中,通常会手动创建一个path对象,而且这个path对象通常是可变的,使用的函数为CGPathCreateMutable(),数据类型为:CGMutablePathRef,然后我们可以向该对象添加直线、弧、曲线和矩形等。Quartz提供了一个类似于操作图形上下文的CGPath的函数集合。这些路径函数直接操作CGPath对象,而不是Quartz Context。这些函数包括:

    CGPathCreateMutable,取代CGContextBeginPath
    CGPathMoveToPoint,取代CGContextMoveToPoint
    CGPathAddLineToPoint,取代CGContexAddLineToPoint
    CGPathAddCurveToPoint,取代CGContexAddCurveToPoint
    CGPathAddEllipseInRect,取代CGContexAddEllipseInRect
    CGPathAddArc,取代CGContexAddArc
    CGPathAddRect,取代CGContexAddRect
    CGPathCloseSubpath,取代CGContexClosePath

    如果想要添加一个路径或者多个路径到Quartz Context,可以调用CGContextAddPath。路径将保留在Quartz Context中,直到Quartz绘制它。

    • 绘制路径:
      构建路径后我们可以给它描边(Stroke)或者填充(Fill)。
      描边:绘制路径的边框;
      填充:填充是绘制路径包含的区域。
      Quartz提供了关于描边和填充的函数,我们可以设置描边线的属性,如宽度、颜色等,也可以设置填充的颜色以及填充的方式。

    基本图形绘制

    • 线段绘制:
      一条最基本的直线线段需要两个端点,一个起始点一个结束点,下面的代码案例绘制了一条P1(100, 100) 至P2(200, 100)之间的直线线段;
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGMutablePathRef linePath = CGPathCreateMutable();
        // 设置一个起点的坐标,绘制系统会追踪至该点进行绘制
        CGPathMoveToPoint(linePath, NULL, 100, 100);
        // 将当前点与一个点(这里我们举例为(200,100))之间构建一条直线
        CGPathAddLineToPoint(linePath, NULL, 200, 100);
        CGContextAddPath(context, linePath);
        // 设置描边的颜色(两种方式)
        // 方法一:
        // CGContextSetRGBStrokeColor(context, 211.f / 255.f, 106.f / 255.f, 119.f / 255.f, 1.0);
        // 方法二:
        [[UIColor redColor] setStroke];
        // 设置描边的宽度
        CGContextSetLineWidth(context, 10);
        // 绘制路径-仅描边
        CGContextStrokePath(context);
        // 注意内存管理
        CGPathRelease(linePath);
    
    • 多条连续线段绘制:
      如果绘制多个且连续的线段,只需多次调用CGPathAddLineToPoint()函数连接更多的点即可,当然也有更便利的方式提供给我们:
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGMutablePathRef linesPath = CGPathCreateMutable();
        CGPoint pointArray[] = {CGPointMake(100, 100), CGPointMake(200, 100), CGPointMake(200, 200), CGPointMake(100, 200)};
        // 通过数组绘制一条多端点的线
        // 参数一:关联的绘制路径
        // 参数二:仿射变化
        // 参数三:数组
        // 参数四:数组元素个数
        CGPathAddLines(linesPath, NULL, pointArray, sizeof(pointArray) / sizeof(CGPoint));
        CGContextAddPath(context, linesPath);
        // 设置填充的颜色
        // 方法一:
        CGContextSetRGBFillColor(context, 211.f / 255.f, 106.f / 255.f, 119.f / 255.f, 1.0);
        // 方法二:
        [[UIColor redColor] setFill];
        // 闭合绘制路径,会从当前点绘制至起点
        CGContextClosePath(context);
        // 绘制路径-仅描边
        //    CGContextStrokePath(context);
        // 绘制路径-仅填充
        //    CGContextFillPath(context);
        // 绘制路径-既有描边也有填充
        CGContextDrawPath(context, kCGPathFillStroke);
        CGPathRelease(linesPath);
    
    • 弧线绘制:
      弧指的是圆弧段。我们指定一个圆心,半径和放射角(以弧度为单位)。放射角为2*PI时,创建的是一个圆。
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGMutablePathRef arcPath = CGPathCreateMutable();
        // 绘制一条弧
        // 参数一:关联的绘制路径
        // 参数二:仿射变化
        // 参数三:原点的坐标x
        // 参数四:原点的坐标y
        // 参数五:半径
        // 参数六:起始弧度
        // 参数七:结束弧度
        // 参数八:绘制方向(0为顺时针,1为逆时针)
        CGPathAddArc(arcPath, NULL, 100, 100, 50, 0, 135 * (M_PI / 180), 1);
        CGContextAddPath(context, arcPath);
        CGContextStrokePath(context);
        CGPathRelease(arcPath);   
    
    • 贝赛尔(Bezier)曲线绘制:
      Bezier曲线是应用于二维图形的曲线。曲线由顶点和控制点组成,通过改变控制点坐标可以改变曲线的形状。
      一次Bezier曲线是由P0至P1的连续点,描述的一条线段:

    二次Bezier曲线是 P0至P1 的连续点Q0和P1至P2 的连续点Q1 组成的线段上的连续点B(t),描述一条抛物线:

    三次Bezier曲线:

    绘制一条三次Bezier曲线:

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGMutablePathRef curvePath = CGPathCreateMutable();
    CGPathMoveToPoint(curvePath, NULL, 20, 100);
    // 绘制贝赛尔曲线
    // 参数一:关联的绘制路径
    // 参数二:仿射变化
    // 参数三:控制点1的x坐标
    // 参数四:控制点1的y坐标
    // 参数五:控制点2的x坐标
    // 参数六:控制点2的y坐标
    // 参数七:终点的x坐标
    // 参数八:终点的y坐标
    CGPathAddCurveToPoint(curvePath, NULL, 50, 50, 80, 100, 130, 100);
    CGContextAddPath(context, curvePath);
    CGContextStrokePath(context);
    CGPathRelease(curvePath);   
    
    • 矩形绘制:
      我们可以调用CGPathAddRect或者CGContextAddRect来添加一个矩形到当前路径中,并提供一个CGRect结构体(包含矩形的原点及大小)作为参数。
      添加到路径的矩形开始于一个move-to-point操作,结束于一个close-subpath操作,所有的移动方向都是顺时针。
      我们也可能调用CGPathAddRects或者CGContextAddRects函数来添加一系列的矩形到当前路径,并传递一个CGRect结构体的数组。
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        CGMutablePathRef rectPath = CGPathCreateMutable();
        // 绘制矩形
        // 参数一:关联的绘制路径
        // 参数二:仿射变化
        // 参数二:绘制位置及大小
        CGPathAddRect(rectPath, NULL, CGRectMake(100, 100, 100, 100));
        
        CGContextAddPath(context, rectPath);
        CGContextStrokePath(context);
        CGPathRelease(rectPath);
    
    • 椭圆绘制:
      椭圆是一种特殊的圆。椭圆是通过定义两个焦点,在平面内所有与这两个焦点的距离之和相等的点所构成的图形。
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        CGMutablePathRef ellipsePath = CGPathCreateMutable();
        // 绘制椭圆
        // 参数一:关联的绘制路径
        // 参数二:仿射变化
        // 参数三:绘制的位置及大小
        CGPathAddEllipseInRect(ellipsePath, NULL, CGRectMake(100, 100, 200, 200));
        
        CGContextAddPath(context, ellipsePath);
        [[UIColor redColor] setFill];
        CGContextDrawPath(context, kCGPathFillStroke);
        CGPathRelease(ellipsePath);
    

    参考:《Quartz 2D Programming Guide》

    相关文章

      网友评论

      • Bestmer:亮哥,写的很好,Quartz 2D已入门。

      本文标题:iOS开发-Quartz 2D基础篇

      本文链接:https://www.haomeiwen.com/subject/zrlzyttx.html