路径(Paths)

路径定义了一个或多个形状或子路径。子路径可以由直线,曲线或两者组成。它可以打开或关闭。子路径可以是简单的形状,例如线条,圆形,矩形或星形,也可以是更复杂的形状,如山脉或抽象涂鸦的轮廓。图3-1显示了一些可以创建的路径。直线(在图的左上方)可以是虚线的;线条也可以是实心的。弯曲的路径(在中间顶部)由多条曲线组成,并且是一条开放的路径。同心圆被描边,但未被填充。加利福尼亚州是一条封闭的路径,由许多曲线组成,道路既被描边又被填充。星星说明了填充路径的两种选择,本章后面将介绍这些选项。

图3-1 Quartz支持基于路径的绘图:

路径创建和路径绘画

路径创建和路径绘制是独立的任务。首先你创建一条路径。当你想渲染一条路径时,你需要Quartz来绘制它。如图3-1所示,您可以选择描边路径,填充路径,或者同时描边和填充路径。您还可以使用路径来限制创建实际上剪切区域的路径边界内的其他对象的绘制。

图3-2显示了已绘制的路径,其中包含两个子路径。左边的子路径是一个矩形,而右边的子路径是由直线和曲线组成的抽象形状。每个子路径都被填充并且它的轮廓被描边。

图3-2 包含两个形状或子路径的路径

图3-3 显示了独立绘制的多个路径。每条路径都包含一条随机生成的曲线,其中一些曲线被填充,另一些则被描边。绘图被剪辑区域限制为圆形区域。

图3-3 裁剪区域限制绘图

构建块

子路径由线条(lines),弧线(arcs)和曲线(curves)构建而成。Quartz 还提供了很多方便的函数,用一个函数调用添加矩形和椭圆。点也是重要的路径构建块,因为点定义了形状的起始位置和结束位置。

点(Points)

点是指定用户空间中位置的x和y坐标。您可以调用函数CGContextMoveToPoint来指定新子路径的起始位置。Quartz会跟踪当前点,这是用于路径构建的最后一个位置。例如,如果您调用函数CGContextMoveToPoint在(10,10)处设置位置,则会将当前点移动到(10,10)。如果你画一条50单位长的水平线,那么线上的最后一点,即(60,10)就成为当前点。直线(Lines),圆弧(arcs)和曲线(curves)总是从当前点开始绘制。

大多数情况下,您通过传递Quartz函数的两个浮点值来指定x和y坐标来指定点。某些函数要求您传递一个CGPoint数据结构,该结构包含两个浮点值。

线(Lines)

一条线由其端点定义。它的起点总是被假定为当前点,所以当你创建一条线时,你只能指定它的结束点(endpoint)。您可以使用函数CGContextAddLineToPoint将单行(line)附加到子路径。

通过调用函数CGContextAddLines,可以将一系列连接线添加到路径中。第一点必须是第一条线的起点;剩余的点是结束点。Quartz在第一个点开始一个新的子路径,并将一条直线段连接到每个结束点。

弧(Arcs)

圆弧是圆形段。Quartz提供了两个创建弧的函数。函数CGContextAddArc从圆中创建一个曲线段。您可以指定圆的中心,半径和径向角(以弧度表示)。您可以通过指定2 pi的径向角度来创建一个完整的圆。图3-4显示了独立绘制的多个路径。每个路径包含一个随机生成的圆;一些被填充,另一些被描边。

图3-4 多条路径;每个路径都包含一个随机生成的圆

CGContextAddArcToPoint函数是您想要圆角矩形的角的理想选择。Quartz使用您提供的端点来创建两条相切线。您还可以提供Quartz切割圆弧的圆的半径。圆弧的中心点是两个半径的交点,每个半径都垂直于两条切线中的一条。圆弧的每个端点(endpoint)都是其中一条切线上的切点,如图3-5所示。圆的红色部分是实际绘制的。

图3-5用两条切线和一个半径定义弧

如果当前路径已经包含子路径,则Quartz将从当前点到弧的起点追加一条直线段。如果当前路径为空,则Quartz会在圆弧的起点处创建一个新的子路径,并且不会添加最初的直线段。

曲线(Curves)

二次方和三次方贝塞尔(Bézier)曲线是代数曲线,可以指定任意数量的有趣的曲线形状。通过将多项式应用于起点和终点以及一个或多个控制点来计算这些曲线上的点。以这种方式定义的形状是矢量图形的基础。一个公式比一系列位存储更紧凑,并且具有曲线可以在任何分辨率下重新创建的优点。

图3-6显示了通过独立绘制多条路径创建的各种曲线。每条路径都包含随机生成的曲线;一些被填充,另一些被描边。

图3-6多条路径;每条路径都包含随机生成的曲线

给出二次方和三次Bézier曲线的多项式以及如何从公式生成曲线的细节在很多描述计算机图形的数学文本和在线资源中讨论。这些细节不在这里讨论。

使用CGContextAddCurveToPoint函数可以使用控制点和指定的端点从当前点追加三次方Bézier曲线。图3-7显示了由图中显示的当前点,控制点和端点产生的三次Bézier曲线。两个控制点的位置决定曲线的几何形状。如果控制点都在起点和终点之上,则曲线向上拱起。如果控制点都低于起点和终点,则曲线向下拱起。

图3-7贝塞尔三次曲线使用两个控制点:

您可以通过调用函数CGContextAddQuadCurveToPoint并指定控制点和端点(endpoint),从当前点追加二次方Bézier曲线。图3-8显示了使用相同端点但控制点不同的两条曲线。控制点确定曲线的方向。用二次Bézier曲线创建尽可能多的有趣形状,因为二次曲线只能使用一个控制点。例如,使用单个控制点创建分频器是不可能的。

图3-8 二次方贝塞尔曲线使用一个控制点

关闭子路径(Closing a Subpath)

要关闭当前子路径,应用程序应调用CGContextClosePath。此函数添加从当前点到子路径起点的线段并关闭子路径。以子路径起点为终点的直线,圆弧和曲线实际上并不关闭子路径。您必须显式调用CGContextClosePath来关闭子路径。

一些Quartz函数将路径的子路径视为由应用程序关闭。这些命令对待每个子路径,就像应用程序调用CGContextClosePath关闭它一样,隐式地将一个线段添加到子路径的起始点。

在关闭一个子路径后,如果你的应用程序进行了额外的调用来将线,弧或曲线添加到路径中,Quartz将从刚刚关闭的子路径的起点开始一个新的子路径。

椭圆(Ellipses)

椭圆本质上是一个压扁的圆。您可以通过定义两个焦点创建一个,然后绘制位于某个距离的所有点,以便将椭圆上任意一点到一个焦点的距离与该点到另一个焦点的距离相加得到的值始终相同。图3-9显示了独立绘制的多个路径。每个路径包含一个随机生成的椭圆;一些被填充,另一些被描边。

图3-9多条路径;每个路径包含一个随机生成的椭圆

您可以通过调用CGContextAddEllipseInRect函数向当前路径添加一个椭圆。您提供了一个定义椭圆边界的矩形。Quartz用一系列贝塞尔曲线近似椭圆。椭圆的中心是矩形的中心。如果矩形的宽度和高度相等(即正方形),则椭圆是圆形的,其半径等于矩形宽度(或高度)的一半。如果矩形的宽度和高度不相等,则它们定义椭圆的长轴和短轴。

添加到路径的椭圆开始于移至操作,并以关闭子路径操作结束,所有移动均以顺时针方向。

矩形(Rectangles)

您可以通过调用CGContextAddRect函数向当前路径添加一个矩形。您提供了一个CGRect结构,其中包含矩形的原点及其宽度和高度。

添加到路径的矩形以移动操作开始,以结束子路径操作结束,所有移动均以逆时针方向定向。

通过调用函数CGContextAddRects并提供CGRect结构数组,可以将多个矩形添加到当前路径。图3-10显示了独立绘制的多个路径。每个路径包含一个随机生成的矩形;一些被填充,另一些被描边。

图3-10 多条路径;每个路径都包含一个随机生成的矩形

创建一条路径

当你想在图形上下文中构建一个路径时,你可以通过调用CGContextBeginPath函数来发信号通知Quartz。接下来,通过调用函数CGContextMoveToPoint来设置路径中第一个形状或子路径的起点。在建立第一个点后,可以将线条,弧线和曲线添加到路径中,请注意以下事项:

  • 在开始一个新路径之前,调用函数CGContextBeginPath

  • 直线,圆弧和曲线从当前点开始绘制。空路径没有当前点;您必须调用CGContextMoveToPoint来为第一个子路径设置起始点,或者调用一个为您隐式执行此操作的便利函数。

  • 如果要关闭路径中的当前子路径,请调用函数CGContextClosePath将一个段连接到子路径的起始点。后续路径调用会开始一个新的子路径,即使您没有明确设置新的起点。

  • 绘制弧线时,Quartz会在当前点与弧线的起点之间绘制一条线。

  • 添加椭圆和矩形的Quartz例程为路径添加一个新的封闭子路径。

  • 您必须调用绘画函数来填充或描边路径,因为创建路径不会绘制路径。

绘制路径后,将从图形上下文中刷新路径。您可能不想轻易失去自己的路径,特别是如果它描绘了一个想要反复使用的复杂场景。出于这个原因,Quartz提供了两种用于创建可重用路径的数据类型 - CGPathRefCGMutablePathRef。你可以调用CGPathCreateMutable函数来创建一个可变的CGPath对象,你可以在其中添加直线,弧线,曲线和矩形。Quartz提供了一组CGPath函数,它们与Building Block中讨论的函数并行。路径函数在CGPath对象上运行,而不是在图形上下文中运行。这些功能是:

  • CGPathCreateMutable,取代CGContextBeginPath

  • CGPathMoveToPoint,取代CGContextMoveToPoint

  • CGPathAddLineToPoint,取代CGContextAddLineToPoint

  • CGPathAddCurveToPoint,取代CGContextAddCurveToPoint

  • CGPathAddEllipseInRect,取代CGContextAddEllipseInRect

  • CGPathAddArc,取代CGContextAddArc

  • CGPathAddRect,取代CGContextAddRect

  • CGPathCloseSubpath,取代CGContextClosePath

当你想把路径附加到图形上下文时,你可以调用函数CGContextAddPath。路径保持在图形上下文中,直到Quartz描绘它。您可以通过调用CGContextAddPath再次添加路径。

绘制一条路径

您可以通过描边或填充或两者来绘制当前路径。描边(Stroking)绘制一条路径线。填充(Filling)绘制路径中包含的区域。Quartz具有可以让您描边路径,填充路径,或者同时描边和填充路径的函数。描边线(宽度,颜色等)的特征,填充颜色和Quartz用来计算填充区域的方法都是图形状态的一部分。

影响描边的参数

您可以通过修改表3-1中列出的参数来影响路径的描边。这些参数是图形状态的一部分,这意味着您为参数设置的值会影响所有后续的描边,直到您将参数设置为其他值。

表3-1 影响Quartz如何描边当前路径的参数:

  • 线宽(Line width):CGContextSetLineWidth

  • Line join:CGContextSetLineJoin

  • Line cap:CGContextSetLineCap

  • 斜接限制(Miter limit):CGContextSetMiterLimit

  • Line dash pattern:CGContextSetLineDash

  • 描边颜色空间(Stroke color space):CGContextSetStrokeColorSpace

  • 描边颜色(Stroke color):CGContextSetStrokeColorCGContextSetStrokeColorWithColor

  • 描边模式(Stroke pattern):CGContextSetStrokePattern

线宽是线的总宽度,以用户空间为单位表示。

线连接(line join)指定Quartz如何绘制连接线段之间的连接点。Quartz支持表3-2中描述的线连接样式。默认样式是斜接连接。

表3-2 线路连接样式:

  • Miter join

  • Round join

  • Bevel join

线帽指定CGContextStrokePath用于绘制线的端点的方法。

Quartz支持表3-3中描述的线帽样式。

  • Butt cap
  • Round cap

  • Projecting square cap

闭合的子路径将起始点视为连接的线段之间的连接点;起点使用选定的线连接(line-join)方法呈现。相反,如果通过添加连接到起点的线段来关闭路径,则路径的两端都将使用选定的线头方法绘制。

线 dash pattern 允许您沿着描边路径绘制分段线。您可以通过指定破折号数组(dash array)和破折号相位(dash phase)作为CGContextSetLineDash的参数来控制沿着该线的破折号段(dash segments)的大小和位置:

void CGContextSetLineDash (
    CGContextRef ctx,
    CGFloat phase,
    const CGFloat lengths[],
    size_t count
);

length 参数的元素指定了 dashes 的宽度,在线条的涂漆和未涂漆部分之间交替。phase 参数指定 dash pattern 的起点。图3-11显示了一些线条 dash patterns。

描边颜色空间确定Quartz解释描边颜色值的方式。您还可以指定封装颜色和颜色空间的Quartz颜色(CGColorRef数据类型)。

用于描边路径的函数

Quartz提供了表3-4中所示的用于描边当前路径的函数。一些是用于描边矩形或椭圆的便利函数。

表3-4 描边路径的功能

  • CGContextStrokePath:描边当前路径。

  • CGContextStrokeRect:描边指定的矩形。

  • CGContextStrokeRectWithWidth:使用指定的线条宽度描边指定的矩形。

  • CGContextStrokeEllipseInRect:描边一个适合指定矩形内的椭圆。

  • CGContextStrokeLineSegments:描边一系列线条。

  • CGContextDrawPath:如果您传递常量kCGPathStroke,则会描边当前路径。

函数CGContextStrokeLineSegments等同于以下代码:

CGContextBeginPath (context);
for (k = 0; k < count; k += 2) {
    CGContextMoveToPoint(context, s[k].x, s[k].y);
    CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
}
CGContextStrokePath(context);
填充路径

填充当前路径时,Quartz的行为就好像路径中包含的每个子路径都已关闭。然后它使用这些闭合的子路径并计算要填充的像素。Quartz可以用两种方法计算填充区域。椭圆和矩形等简单路径具有明确的区域。但是如果你的路径是由重叠的段组成的,或者如果路径包含多个子路径,例如图3-12中显示的同心圆,则可以使用两条规则来确定填充区域。

默认填充规则称为非零卷数规则(nonzero winding number rule)。要确定是否应绘制特定点,请从该点开始绘制超出图形边界的线。从0开始计数,每当路径段从左到右越过该行时加1,每当路径段从右向左越过该行时减1。如果结果为0,则该点不被绘制。否则,该点被涂上。路径段的绘制方向会影响结果。图3-12显示了使用非零卷数规则填充的两组内外圆。当每个圆圈以相同的方向绘制时,两个圆圈都被填充。当圆圈的方向相反时,内圆不填充。

您可以选择使用偶奇数规则(even-odd rule)。要确定是否应绘制特定点,请从该点开始绘制超出图形边界的线。计算线路交叉的路径段的数量。如果结果很奇数,则该点被绘制。如果结果是偶数,那么这个点就不会被绘制。路径段的绘制方向不会影响结果。如图3-12所示,绘制每个圆圈的方向并不重要,填充将始终如图所示。

图3-12 使用不同的填充规则填充的同心圆

Quartz提供了填充当前路径的表3-5中所示的函数。一些用于描边矩形或椭圆的便利函数。

表3-5 填充路径的函数

  • CGContextEOFillPath:使用偶奇数规则填充当前路径。
  • CGContextFillPath:使用非零卷积数规则填充当前路径。

  • CGContextFillRect:填充适合指定矩形内的区域。

  • CGContextFillRects:填充适合指定矩形内的区域。

  • CGContextFillEllipseInRect:填充适合指定矩形内的椭圆。

  • CGContextDrawPath:如果传入 kCGPathFill(非零卷积数规则)或kCGPathEOFill(偶奇数规则),则填充当前路径。

设置混合模式

混合模式指定Quartz如何应用于背景上的绘制。Quartz默认使用普通混合模式,它使用以下公式将前景绘画与背景绘画相结合:

result = (alpha * foreground) + (1 - alpha) * background

您可以通过调用函数CGContextSetBlendMode来设置混合模式以获得各种效果,并传递适当的混合模式常量。请记住,混合模式是图形状态的一部分。如果在更改混合模式之前使用函数CGContextSaveGState,则调用函数CGContextRestoreGState会将混合模式重置为normal。

图3-13 在前景中绘制的矩形

图3-14 在背景中绘制的矩形

正常混合模式(kCGBlendModeNormal):

乘法混合模式(kCGBlendModeMultiply):

屏幕混合模式(kCGBlendModeScreen):

重叠混合模式(kCGBlendModeOverlay):

(还有其他12种混合模式)

剪切到一条路径

当前裁剪区域是从用作遮罩的路径创建的,允许您阻止不想绘制的页面部分。例如,如果您有非常大的位图图像并且只想显示其中的一小部分,则可以将剪辑区域设置为仅显示要显示的部分。

当图形上下文最初创建时,剪贴区域包括上下文的所有可绘制区域(例如,PDF上下文的媒体框)。您可以通过设置当前路径并使用裁剪功能而不是绘图功能来改变裁剪区域。裁剪功能将当前路径的填充区域与现有裁剪区域相交。因此,您可以交叉裁剪区域,缩小图片的可见区域,但不能增加裁剪区域的面积。

裁剪区域是图形状态的一部分。要将裁剪区域恢复到以前的状态,可以在裁剪之前保存图形状态,并在裁剪完成后恢复图形状态。清单3-1显示了一个代码片段,它以圆形的形式设置裁剪区域。

清单3-1 设置一个圆形裁剪区域

CGContextBeginPath (context);
CGContextAddArc (context, w/2, h/2, ((w>h) ? h : w)/2, 0, 2*PI, 0);
CGContextClosePath (context);
CGContextClip (context);

表3-6 裁剪图形上下文的函数

  • CGContextClip
  • CGContextEOClip

  • CGContextClipToRect

  • CGContextClipToRects

  • CGContextClipToMask

参考资料

  1. Paths

results matching ""

    No results matching ""