iOS 绘画学习(3)


Graphics Context Settings (图形上下文设置)

一个图形上下文状态有很多个图形上下文设置构成,这个状态决定了在这个时刻绘画的行为和外观。下面列举了Core Graphics 函数,同时跟着对应的UIKit 提供的封装好的方便的方法:

  • 线的粗细和虚线样式

    CGContextSetLineWidth, CGContextSetLineDash (and UIBezierPath lineWidth, setLineDash:count:phase:)

  • 线末端样式和连接样式

    CGContextSetLineCap, CGContextSetLineJoin, CGContextSetMiterLimit (and UIBezierPath lineCapStyle, lineJoinStyle, miterLimit)

  • 线颜色和模式

    CGContextSetRGBStrokeColor, CGContextSetGrayStrokeColor, CGContextSet- StrokeColorWithColor, CGContextSetStrokePattern (and UIColor setStroke)

  • 填充颜色和模式

    CGContextSetRGBFillColor, CGContextSetGrayFillColor, CGContextSetFill- ColorWithColor, CGContextSetFillPattern (and UIColor setFill)

  • 阴影

    CGContextSetShadow, CGContextSetShadowWithColor

  • 整体透明度和组合

    CGContextSetAlpha, CGContextSetBlendMode

  • 反锯齿

    CGContextSetShouldAntialias

  • 其它额外的设置

    • 裁剪区域

      在裁剪区域外进行绘制,其实不会真的被绘制

    • 转换(或者“CTM”,当前转换矩阵)

      可以改变你指定的点在随后的绘制命令中,如何对应到画布的实际空间上。


Paths and Shapes (路径和形状)

下面是一些可能会用到的路径绘制命令:

  • 当前点的位置

    CGContextMoveToPoint

  • 描摹一条线

    CGContextAddLineToPoint, CGContextAddLines

  • 描摹一个矩形

    CGContextAddRect, CGContextAddRects

  • 描摹一个椭圆或者圆形

    CGContextAddEllipseInRect

  • 描摹一个圆弧

    CGContextAddArcToPoint, CGContextAddArc

  • 描摹有一个或者两个控制点的贝塞尔曲线

    CGContextAddQuadCurveToPoint, CGContextAddCurveToPoint

  • 关闭当前路径

    CGContextClosePath 这个会在路径的最后一个点和第一个点之间添加一条线,如果你是打算要填充这个路径,那么不需要调用这个方法,填充操作会自动帮我们完成。

  • 描边或者填充当前路径

    CGContextStrokePath, CGContextFillPath, CGContextEOFillPath, CGContext- DrawPath. 描边或者填充操作都会清除这个路径。如果你想既描边又填充路径,可以使用CGContextDrawPath。 如果你仅仅先通过 CGContextStrokePath 描边,你就不可能再填充这个路径了,因为这个路径已经被清除了。

    当然还有很多其它更方便的函数来创建一个路径,然后描边或者填充:CGContextStrokeLineSegments, CGContextStrokeRect, CGContextStrokeRectWithWidth, CGContextFillRect, CGContextFillRects, CGContextStrokeEllipseInRect, CGContextFillEllipseInRect.

一个路径可以是混合而成的,意味着这个路径由多个独立的块组成。例如,一个路径可以包括两个分开的闭合的形状:一个矩形和一个圆形。 当你在构造一个路径的某个时刻(也就是在描摹出一个路径之后,同时没有描边,填充来清除路径,或者调用 CGContextBeginPath)调用 CGContextMoveToPoint,你提起画笔,然后移动到一个新的位置,接着准备开始一个独立的一块同样的路径。如果你担心之前绘制的路径会被清除,可以调用CGContextBeginPath 来指定你开始一段新的不同的路径,在苹果的很多例子中都会这么做,但是我在实际中通常发现不需要。

为了说明上面路径绘制的命令,我会生成一个向上的箭头,如下图所示。这可能不是创建一个箭头的最好方式,同时我也会故意不使用一个方便的函数,但是过程是清晰的:

// obtain the current graphics context
CGContextRef con = UIGraphicsGetCurrentContext();
// draw a black (by default) vertical line, the shaft of the arrow
CGContextMoveToPoint(con, 100, 100);
CGContextAddLineToPoint(con, 100, 19);
CGContextSetLineWidth(con, 20);
CGContextStrokePath(con);
// draw a red triangle, the point of the arrow
CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);
CGContextMoveToPoint(con, 80, 25);
CGContextAddLineToPoint(con, 100, 0);
CGContextAddLineToPoint(con, 120, 25);

CGContextFillPath(con);
// snip a triangle out of the shaft by drawing in Clear blend mode
CGContextMoveToPoint(con, 90, 101);
CGContextAddLineToPoint(con, 100, 90);
CGContextAddLineToPoint(con, 110, 101);
CGContextSetBlendMode(con, kCGBlendModeClear);
CGContextFillPath(con);

如果你想复用或者分享一个路径,可以把这个路径包装成 CGPath,实际上就是CGPathRef。你也可以创建一个新的CGMutablePathRef ,像CGContext 中的路径构造方法一样,使用不同的CGPath 方法来构造路径,或者你可以使用CGContextCopyPath 来复制图形上下文中当前的路径。还有一些CGPath方法用来基于简单的几何图形来创建一个路径(CGPathCreateWithRect, CGPathCreateWithEllipseInRect)或者基于一个已存在的路径(CGPathCreateCopyByStrokingPath, CGPathCreateCopyByDashing- Path, CGPathCreateCopyByTransformingPath)。

UIKit 中的UIBezierPath封装了CGPath,同样也提供了一些路径构造方法,例如 moveToPoint:, addLineToPoint:, bezierPathWithRect:, bezierPathWithOvalInRect:, addArcWithCenter:radius:startAngle:endAngle:clockwise:, addQuadCurveToPoint:controlPoint:, addCurveToPoint:controlPoint1:controlPoint2:, closePath。同样,UIBezierPath也提供了一个非常有用的方法:bezierPathWithRoundedRect:cornerRadius: —— 仅仅使用Core Graphics 方法来绘制圆角矩形是非常繁琐的。当你调用UIBezierPath 实例方法 fill 或者 stroke (或者 fillWithBlendMode:alpha: 或者 strokeWithBlend- Mode:alpha:)时,当前图形上下文状态会被保存,被封装好的CGPath 路径会变成当前上下文的绘制路径,然后描边或者填充该路径,最后当前上下文会被恢复原来状态。

下面我们使用UIKit 提供的UIBezierPath 来重写上面的箭头:

UIBezierPath* p = [UIBezierPath bezierPath];
[p moveToPoint:CGPointMake(100,100)];
[p addLineToPoint:CGPointMake(100, 19)];
[p setLineWidth:20];
[p stroke];
[[UIColor redColor] set];
[p removeAllPoints];
[p moveToPoint:CGPointMake(80,25)];
[p addLineToPoint:CGPointMake(100, 0)];
[p addLineToPoint:CGPointMake(120, 25)];
[p fill];
[p removeAllPoints];
[p moveToPoint:CGPointMake(90,101)];
[p addLineToPoint:CGPointMake(100, 90)];
[p addLineToPoint:CGPointMake(110, 101)];
[p fillWithBlendMode:kCGBlendModeClear alpha:1.0];

Clipping (裁剪)

路径的另一种使用方式是遮罩某一块区域,防止在未来的绘制中被改变。这种就是裁剪。默认,一个图形上下文的裁剪区域就是整一个图形上下文,你可以在上下文中任意绘制。

裁剪区域是整个上下文的一个功能,任何新的裁剪区域会被应用到现有的裁剪区域,相交而成。如果你应用了自己的裁剪区域,在后面想移除这个裁剪区域的办法就只能把你的处理包含在 CGContextSaveGState和CGContextRestoreGState方法之间。

为了说明这一点,我将使用裁剪重写生成我们原来的箭头符号,而不是使用混合模式在箭头尾部“切出”三角缺口。这样会有些棘手,因为我们想要的不是裁剪三角形内部的区域,而是外面的区域。为了解析这个,我们使用包含超过一个闭合区域的混合路径 —— 一个三角形,以及整个绘制区域(我们可以使用CGContextGetClipBoundingBox 获取)

当填充一个混合路径时,或者使用路径来表示一个裁剪区域时,系统遵循两个准则:

  • 非零环绕数规则(Winding rule)

    环绕(winding)就是一个路径环绕的方向,分顺时针(正方向)和逆时针(负方向)。在图形学中判断一个点是否在多边形内,若多边形不是自相交的,那么可以简单的判断这个点在多边形内部还是外部;若多边形是自相交的,那么就需要根据非零环绕数规则和奇-偶规则判断。这里推荐一篇文章:http://blog.csdn.net/freshforiphone/article/details/8273023。

  • 奇-偶规则(Odd-even Rule)

    从任意位置p作一条射线,若与该射线相交的多边形边的数目为奇数,则p是多边形内部点,否则是外部点。

这里我们使用简单的奇-偶规则,所以我们使用CGContextEOClip 来设定裁剪区域,然后绘制箭头:

// obtain the current graphics context
CGContextRef con = UIGraphicsGetCurrentContext();
// punch triangular hole in context clipping region
CGContextMoveToPoint(con, 90, 100);
CGContextAddLineToPoint(con, 100, 90);
CGContextAddLineToPoint(con, 110, 100);
CGContextClosePath(con);
CGContextAddRect(con, CGContextGetClipBoundingBox(con));
CGContextEOClip(con);
// draw the vertical line
CGContextMoveToPoint(con, 100, 100);
CGContextAddLineToPoint(con, 100, 19);
CGContextSetLineWidth(con, 20);
CGContextStrokePath(con);
// draw the red triangle, the point of the arrow
CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);
CGContextMoveToPoint(con, 80, 25);
CGContextAddLineToPoint(con, 100, 0);
CGContextAddLineToPoint(con, 120, 25);
CGContextFillPath(con);

UIBezierPath 的裁剪命令是 usesEvenOddFillRule 和 addClip。


Gradients (渐变)

渐变可以很简单,也可以很负责,这里只讲解简单的。一个简单的渐变是由一个起始点的颜色和一个结束点的颜色,再加上(可选的)一个中间点的颜色值决定,然后就会在上下文中在两个指定的点以线性或者反射形式绘制。

你不能使用渐变来作为路径的填充颜色,但是你可以使用裁剪来规定一个路径形状的渐变。为了说明这一点,我会重画我们的箭头,使用一个线性渐变:

// obtain the current graphics context
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextSaveGState(con);
// punch triangular hole in context clipping region
CGContextMoveToPoint(con, 90, 100);
CGContextAddLineToPoint(con, 100, 90);
CGContextAddLineToPoint(con, 110, 100);
CGContextClosePath(con);
CGContextAddRect(con, CGContextGetClipBoundingBox(con));
CGContextEOClip(con);
// draw the vertical line, add its shape to the clipping region
CGContextMoveToPoint(con, 100, 100);
CGContextAddLineToPoint(con, 100, 19);
CGContextSetLineWidth(con, 20);
CGContextReplacePathWithStrokedPath(con);
CGContextClip(con);
// draw the gradient
CGFloat locs[3] = { 0.0, 0.5, 1.0 };
CGFloat colors[12] = {
    0.3,0.3,0.3,0.8, // starting color, transparent gray
    0.0,0.0,0.0,1.0, // intermediate color, black
    0.3,0.3,0.3,0.8 // ending color, transparent gray
};
CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();
CGGradientRef grad =
    CGGradientCreateWithColorComponents (sp, colors, locs, 3);
CGContextDrawLinearGradient (
    con, grad, CGPointMake(89,0), CGPointMake(111,0), 0);
CGColorSpaceRelease(sp);
CGGradientRelease(grad);
CGContextRestoreGState(con); // done clipping
// draw the red triangle, the point of the arrow
CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);
CGContextMoveToPoint(con, 80, 25);
CGContextAddLineToPoint(con, 100, 0);
CGContextAddLineToPoint(con, 120, 25);
CGContextFillPath(con);

调用CGContextReplacePathWithStrokedPath 这个方法来防止描边当前的路径。

posted @ 2014-07-10 14:33  剑尖  阅读(819)  评论(0编辑  收藏  举报