CoreGraphics(Quartz 2D) | 实战最详解篇:绘制路径(Path),附详细 Demo

很多童鞋知道CoreGraphics,但不见得听过Quartz 2D,二者关系见上一篇(等等,其实就是土豆和马铃薯的区别啦)在“度娘”中搜索CoreGraphics多数文章都是CGPathRef的操作,没有关于API详细的说明和解释,对于一些不太容易理解的地方自然也没有解释。

本篇立足于绘制 Path 基本概念和API,并结合实战完成图形绘制

此文是对于官文的总结和实战。

Paths(路径)原理

路径定义了一个或多个形状或是子路径。一个子路径可由直线,曲线,或者同时由两者构成。它既可以是开放的,也可以是闭合的。

创建路径和绘制路径是分开的任务,1.首先创建一个路径,2.当我们需要渲染一个路径时,我们使用Quartz来绘制它。

创建、绘制路径

子路径是由点、线、圆弧和曲线构成的,Quartz同时也提供了函数用于绘制矩形和椭圆形,其中点是定义了绘制的起点和终点,当然有些图片的绘制仅仅选定了原点即可绘制,比如说圆弧、椭圆形等。

复杂图形多是由n个子路径构成

路径的绘制,Quartz提供了“两”组API(允许我这么理解),分别是是CGPathCGContext

  • CGContext是直接操作上下文,在上下文中绘制子路径,由CGContextClosePath(context)关闭当前的子路径,然后继续绘制下一个路径,那么此时如何需要再继续修改或者绘制上一个路径便不太可能实现了。于是。。
  • CGPath出场了,此时操作的是CGPathRef对象,每此绘制完子路径使用 CGContextAddPath(context, curvePath)添加到当前上下文中,然后绘制下一个子路径,此时所有的子路径都可以保存起来,以被再使用。

所以笔者建议,用CGPath绘制复杂的组合图形,CGContext绘制简单图形。

0. 上下文

//获取上下文,UIKit坐标系。
CGContextRef context = UIGraphicsGetCurrentContext();
..
/* Begin a new path. The old path is discarded. */
/* 所以一定要注意,这里创建的不是子路径,如果采用CGContext绘制路径时,
 * 开始绘制下一个子路径时再次调用CGContextBeginPath(context)会覆盖
 * 上一个路径,所以这个函数只用调用一次即可*/
CGContextBeginPath(context);

1. 点 Point

  CGContextMoveToPoint(context, p1.x, p1.y);
  //或者
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);

2. 线 Lines

  CGContextMoveToPoint(context, p1.x, p1.y);
  CGContextAddLineToPoint(context, x, y);
  CGContextClosePath(context);
  //或者
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
  CGPathAddLineToPoint(path, nil, toPoint.x, toPoint.y);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

3. 圆弧 Arcs

  CGContextAddArc(context, center.x, center.y, radius, radians(0.0f), radians(360.0f), 0);
  CGContextClosePath(context);
  //或者
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathAddArc(path, nil, center.x, center.y, 50, radians(-90.0f), radians((- 180.0f)), YES);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

  //其他画法
  CGContextAddArcToPoint;
  CGContextAddQuadCurveToPoint;

4. 曲线 Curves

  CGContextAddCurveToPoint(context, refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);

  CGContextMoveToPoint(context, fromPoint.x, fromPoint.y);
  CGContextAddQuadCurveToPoint(context, refPoint1.x, refPoint1.y, toPoint.x, toPoint.y);
  CGContextClosePath(context);

  //或者
  CGMutablePathRef path = CGPathCreateMutable();
  CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
  CGPathAddCurveToPoint(path, nil,refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

5. 矩形 Rectangle

  CGContextAddRect(context, rect);
  CGContextClosePath(context);

  //或者
  CGPathAddRect(path, nil, rect);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

  //一起添加多个矩形
   CGContextAddRects(context, rects, 3);

6. 椭圆形 Ellipses

  CGContextAddEllipseInRect(context, rect);
  CGContextClosePath(context);

  //或者
  CGPathAddEllipseInRect(path, nil, rect);
  CGPathCloseSubpath(path);
  CGContextAddPath(context, path);
  CGPathRelease(path);

绘制路径规则总结

  1. 在开始绘制路径前,调用函数CGContextBeginPath;
  2. 直线、弧、曲线开始于当前点。空路径没有当前点;我们必须用CGContext- MoveToPoint来设置第一个子路径的起始点,或者调用一个便利函数来隐式地完成该任务。
  3. 如果要闭合当前子路径,调用函数CGContextClosePath。随后路径将开始一个新的子路径,即使我们不显示设置一个新的起始点。
  4. 当绘制弧时,Quartz将在当前点与弧的起始点间绘制一条直线。
  5. 添加椭圆和矩形的Quartz程序将在路径中添加新的闭合子路径。
  6. 我们必须调用绘制函数来填充或者描边一条路径,因为创建路径时并不会绘制路径。

深度刻画(描边、填充)

影响描边的属性

下表中展示了上下文的一些属性,修改这些的属性会影响后续的进行描边操作。

Parameter Function to set parameter value
Line width CGContextSetLineWidth
Line join CGContextSetLineJoin
Line cap CGContextSetLineCap
Miter limit CGContextSetMiterLimit
Line dash pattern CGContextSetLineDash
Stroke color space CGContextSetStrokeColorSpace
Stroke color CGContextSetStrokeColorCG...
Stroke pattern CGContextSetStrokePattern

描边函数

Quartz提供了下表的函数来描边当前路径。

Function Description
CGContextStrokePath 给当前的path描边
CGContextStrokeRect 注:创建一个矩形子路径,并对其进行描边
CGContextStrokeRectWithWidth 注:创建一个矩形子路径,设置描边宽度,并对其进行描边
CGContextStrokeEllipseInRect 注:创建一个椭圆子路径,并对其进行描边
CGContextStrokeLineSegments 给一组直线进行描边
CGContextDrawPath :在上下文中绘制当前的子路径,需指定CGPathDrawingMode(见下表)

CGPathDrawingMode有以下几种:

  • kCGPathFill, 仅填充
  • kCGPathEOFill, 奇偶填充
  • kCGPathStroke, 仅描边
  • kCGPathFillStroke, 既填充又描边
  • kCGPathEOFillStroke, 奇偶描边

填充路径

Quartz将路径包含的每个子路径都看作是闭合的。然后,使用这些闭合路径并计算填充的像素。 椭圆和矩形这样的路径其区域都很明显。但是如果路径是由几个重叠的部分组成或者路径包含多个子路径,我们则有两种规则来定义填充区域。

  • 非零缠绕数规则(nonzero windingnumber rule):
    为了确定一个点是否需要绘制,我们从该点开始绘制一条直线穿过绘图的边界。从0开始计数,每次路径片断从左到右穿过直线是,计数加1;而从右到左穿过直线时,计数减1。如果结果为0,则不绘制该点,否则绘制。路径片断绘制的方向会影响到结果。

  • 偶数-奇数规则: 为了确定一个点是否被绘制,我们从该点开始绘制一条直线穿过绘图的边界。计算穿过该直线的路径片断的数目。如果是奇数,则绘制该点,如果是偶数,则不绘制该点。路径片断绘制的方向不影响结果。

综上:默认 采用的是非零缠绕数规则。非零缠绕数的填充规则与绘制的方向有关、偶数-奇数规则则与方向无关。
详见:官方文档

Quartz提供了下表的函数来填充当前路径。

Function Description
CGContextEOFillPath 采用奇偶规则给当前的path填充
CGContextFillPath 采用非零缠绕数规则给当前的path填充
CGContextFillRect 注:创建一个矩形子路径,并对其进行填充。
CGContextFillRects 注:创建一组矩形路径,并对其进行填充
CGContextFillEllipseInRect 注:创建一个椭圆形路径,并对其进行填充
CGContextDrawPath :在上下文中绘制当前的子路径,需指定CGPathDrawingMode

设置混合模式

混合模式指定了Quartz如何将绘图绘制到背景上。Quartz默认使用普通混合模式(normal blend mode),该模式使用如下公式来计算前景绘图与背景绘图如何混合:

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

调用 CGContextSetBlendMode(context, kCGBlendModeNormal) 设置回合模式。

你可以在《Quartz 2D Programming Guide》- Paths章节中查看混合模式的具体效果。

注意:CGContext保存和恢复函数

当构建复杂的路径,其由n个子路径构成,每个子路径样式不一致,分别需要对于当前Context上下文属性做了各种不同的修改,为了保证下一个子路径的context参数值保持原有值(即不继承上一个子路径的参数值),我们需要在每绘制一个子路径之前调用CGContextSaveGState(context)保存当前的context状态,并在绘制完当前子路径后恢复设置,即调用CGContextRestoreGState(context)

Paths 实战,绘制图形。

实战中我们来绘制一图形,它是由几个形状各异的子路径构成。

实现 drawRect

为了使用Core Graphics来绘图,最简单的方法就是自定义一个类继承自UIView,并重写子类的drawRect方法。在这个方法中绘制图形。
Core Graphics必须一个画布,才能把东西画在这个画布上。在drawRect方法方法中,我们可以直接获取当前栈顶的上下文(Context)。

- (void)drawRect:(CGRect)rect {

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextBeginPath(context);

    //1.直线绘图
    [self beginLinesDemo:context];

    //2.圆弧绘图
    [self beginArcsDemo:context];

    //3.曲线绘图
    [self beginCurvesDemo:context];

    //4.椭圆绘图
    [self beginEllipsesDemo:context];

    //5.矩形绘图
    [self beginRectangleDemo:context];

    //6.影响描边的属性
//    CGContextSet...
    //7.路径描边的函数
//    CGContextStroke...
    //8.填充路径
//    CGContextFill...
}

直线绘制五角星

本文重点不是如何计算五角星,而是学会使用CG,所以如果你对计算有兴趣的话,我想度娘会给你答案的,继续。

- (void)beginLinesDemo:(CGContextRef)context
{
    //1.Line线
    CGContextSaveGState(context);
    cg_drawPentagramByLine(context, CGPointMake(150, 150), 100);

    //设置描边属性
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetLineWidth(context, 2);

    CGContextDrawPath(context, kCGPathFillStroke);
    CGContextRestoreGState(context);
}

void cg_drawPentagramByLine(CGContextRef context, CGPoint center,CGFloat radius)
{
    CGPoint p1 = CGPointMake(center.x, center.y - radius);
    CGContextMoveToPoint(context, p1.x, p1.y);
    CGFloat angle=4 * M_PI / 5.0;

    for (int i=1; i<=5; i++)
    {
        CGFloat x = center.x -sinf(i*angle)*radius;
        CGFloat y = center.y -cosf(i*angle)*radius;
        CGContextAddLineToPoint(context, x, y);
    }

    CGContextClosePath(context);
}

绘制图如下:

Pentagram.png

圆弧链接五角星顶点

- (void)beginArcsDemo:(CGContextRef)context
{
  //2.Arcs弧线
  CGContextSaveGState(context);
  cd_drawCircleByArc(context, CGPointMake(150, 150), 102);

  //设置描边属性
  CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
  CGContextSetLineWidth(context, 2);

  CGContextDrawPath(context, kCGPathStroke);
  CGContextRestoreGState(context);
}

void cd_drawCircleByArc(CGContextRef context, CGPoint center, CGFloat radius)
{
    CGContextAddArc(context, center.x, center.y, radius, radians(0.0f), radians(360.0f), 0);

    //下面两种用法可以自行研究了
//    CGContextAddArcToPoint;
//    CGContextAddQuadCurveToPoint;
}

绘制图如下:

PentagramCircle.png

曲线绘制大风车

- (void)beginCurvesDemo:(CGContextRef)context
{
    //3.Curve曲线
    CGContextSaveGState(context);
    cd_drawPinwheelByCurve(context,  CGPointMake(300, 100),  CGPointMake(0, 200),  CGPointMake(0, 0),  CGPointMake(300, 300));

    CGContextSetRGBFillColor(context, 246 / 255.0f, 122 / 255.0f , 180 / 255.0f, 0.6);
    CGContextSetStrokeColorWithColor(context, [UIColor orangeColor].CGColor);
    CGContextDrawPath(context, kCGPathFillStroke);

    CGMutablePathRef curvePath = cd_drawPinwheelCurveByPath(CGPointMake(300, 200), CGPointMake(0, 100), CGPointMake(300, 0), CGPointMake(0, 300));
    CGContextAddPath(context, curvePath);
    CGPathRelease(curvePath);

    CGContextDrawPath(context, kCGPathFillStroke);
    CGContextRestoreGState(context);
}
void cd_drawPinwheelByCurve(CGContextRef context,CGPoint refPoint1, CGPoint refPoint2, CGPoint fromPoint, CGPoint toPoint)
{
    CGContextMoveToPoint(context, fromPoint.x, fromPoint.y);
    CGContextAddCurveToPoint(context, refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
    CGContextAddQuadCurveToPoint(context, refPoint1.x, refPoint1.y, toPoint.x, toPoint.y);
}

CGMutablePathRef cd_drawPinwheelCurveByPath(CGPoint refPoint1, CGPoint refPoint2, CGPoint fromPoint, CGPoint toPoint)
{
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathMoveToPoint(path, nil, fromPoint.x, fromPoint.y);
    CGPathAddCurveToPoint(path, nil,refPoint1.x, refPoint1.y, refPoint2.x, refPoint2.y, toPoint.x, toPoint.y);
    CGPathCloseSubpath(path);
    //其他addCurve同context创建curve方法。
    return path;
}

绘制图如下:

Pinwheel.png

至此,关于Quartz中Paths主干相关的东西就写这么多了,还有边角知识点和API这里就不做详细介绍了。

Demo地址

详细查看官文文档
这是CoreGraphic第二篇,实战详细篇,接下来会进入CG美化篇,如果你感兴趣,来个关注。我们下期见。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容