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);
网友评论