QuartZ 2D个人总结

这两天集中学习了一下使用QuartZ 2D画图的知识,现自己梳理备忘一下:

原文地址:http://blog.csdn.net/Le_Wrynn/article/details/50734231

QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分。Core Graphics API所有的操作都在上下文中进行。所以在绘图之前需要获取该上下文并传入执行渲染的函数内。获得一个图形上下文是完成绘图任务的第一步,可以将图形上下文理解为一块画布。如果你没有得到这块画布,那么你就无法完成任何绘图操作。获得一个图形上下文有两种方法:

第一种方法就是创建一个图片类型的上下文。调用UIGraphicsBeginImageContextWithOptions函数就可获得用来处理图片的图形上下文。利用该上下文,你就可以在其上进行绘图,并生成图片。调用UIGraphicsGetImageFromCurrentImageContext函数可从当前上下文中获取一个UIImage对象。记住在你所有的绘图操作后别忘了调用UIGraphicsEndImageContext函数关闭图形上下文。

第二种方法是子类化了一个UIView并重写drawRect:方法后,一旦drawRect:方法被调用,Cocoa就会为你创建一个图形上下文,此时你对图形上下文的所有绘图操作都会显示在这个UIView上。补充:在UIview的drawRect:方法中获取到的上下文就是layer的上下文

drawRect:方法在什么时候被调用?
当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
调用view的setNeedsDisplay或者setNeedsDisplayInRect:时

还有一种是在UIView子类的drawLayer:inContext:方法中实现绘图任务。

判断一个上下文是否为当前图形上下文需要注意的几点:

  • UIGraphicsBeginImageContextWithOptions函数不仅仅是创建了一个适用于图形操作的上下文,并且该上下文也属于当前上下文。
  • drawRect方法被调用时,UIView的绘图上下文属于当前图形上下文。
  • 回调方法所持有的context:参数并不会让任何上下文成为当前图形上下文。此参数仅仅是对一个图形上下文的引用罢了。
上代码:
第一种方法,实现了画一个圆:
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), false, 0)
        let context = UIGraphicsGetCurrentContext();
        CGContextAddEllipseInRect(context, CGRectMake(0,0,100,100));
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor);
        CGContextFillPath(context);
        let image = UIGraphicsGetImageFromCurrentImageContext()
        let imageV = UIImageView(image: image)
        imageV.frame = CGRectMake(0, 0, 100, 100)
        self.view.addSubview(imageV)
        UIGraphicsEndImageContext();
第二种方法:
    override func drawRect(rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        CGContextSetLineWidth(context, 2)
        CGContextAddRect(context, CGRectMake(50, 50, 50, 50))
        CGContextSetStrokeColorWithColor(context, UIColor.yellowColor().CGColor)
        CGContextDrawPath(context, CGPathDrawingMode.Stroke)
        CGContextStrokeRect(context,CGRectMake(100, 120, 10, 10))
    }
第三种方法:
    override func drawLayer(layer: CALayer, inContext ctx: CGContext) {
        UIGraphicsPushContext(ctx)
        let path = UIBezierPath.init(ovalInRect: CGRectMake(0, 0, 100, 100))
        UIColor.blueColor().setFill()
        path.fill()
        UIGraphicsPopContext()
    }
注:(1).使用第一种方法一定要有begin就要有end。 UIGraphicsBeginImageContextWithOptions 函数参数的含义:第一个参数表示所要创建的图片的尺寸;第二个参数用来指定所生成图片的背景是否为不透明,如上我们使用YES而不是NO,则我们得到的图片背景将会是黑色,显然这不是想要的;第三个参数指定生成图片的缩放因子,这个缩放因子与UIImage的scale属性所指的含义是一致的。传入0则表示让图片的缩放因子根据屏幕的分辨率而变化,所以我们得到的图片不管是在单分辨率还是视网膜屏上看起来都会很好。
       (2).使用第三种方法已经有了一个context参数,使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住别忘了调用UIGraphicsPopContext函数恢复上下文环境。
       (3).个人测试在调用自定义的View后,在走了
drawLayer(layer: CALayer, inContext ctx: CGContext)方法后,不会再走drawRect方法。
绘图图形属性的设置总结:

       线条的宽度

  CGContextSetLineWidth

扫描二维码关注公众号,回复: 17212328 查看本文章

       线条的虚线样式

       CGContextSetLineDash

  线帽和线条联接点样式

  CGContextSetLineCap(线帽)、CGContextSetLineJoin(连接转角)、CGContextSetMiterLimit

  线条颜色和线条模式

  CGContextSetRGBStrokeColor、CGContextSetGrayStrokeColor、CGContextSetStrokeColorWithColor、CGContextSetStrokePattern

  对应的有填充颜色和模式

  CGContextSetRGBFillColor,CGContextSetGrayFillColor,CGContextSetFillColorWithColor, CGContextSetFillPattern

  阴影

  CGContextSetShadow、CGContextSetShadowWithColor

  混合模式

  CGContextSetBlendMode(决定你当前绘制的图形与已经存在的图形如何被合成)

  整体透明度

  CGContextSetAlpha(个别颜色也具有alpha成分)

  文本属性

  CGContextSelectFont、CGContextSetFont、CGContextSetFontSize、CGContextSetTextDrawingMode、CGContextSetCharacterSpacing

  是否开启反锯齿和字体平滑

  CGContextSetShouldAntialias、CGContextSetShouldSmoothFonts

另外一些属性设置:

  裁剪区域:在裁剪区域外绘图不会被实际的画出来。

  变换(或称为“CTM“,意为当前变换矩阵): 改变你随后指定的绘图命令中的点如何被映射到画布的物理空间。

  许多这些属性设置接下来我都会举例说明。

路径与绘图的方法:

    定位当前点

    CGContextMoveToPoint

    描画一条线

    CGContextAddLineToPoint、CGContextAddLines

    描画一个矩形

    CGContextAddRect、CGContextAddRects

    描画一个椭圆或圆形

    CGContextAddEllipseInRect

    描画一段圆弧

    CGContextAddArcToPoint、CGContextAddArc(参数解读:1、上下文,2、x,y圆弧圆心,3、半径,4、开始弧度5、结束弧度6、方向:0或1对应顺时针、逆时针)

    通过一到两个控制点描画一段贝赛尔曲线

    CGContextAddQuadCurveToPoint(cpx、cpy:控制点 x、y:结束点)、CGContextAddCurveToPoint

    关闭当前路径

    CGContextClosePath 这将从路径的终点到起点追加一条线。如果你打算填充一段路径,那么就不需要使用该命令,因为该命令会被自动调用。

    描边或填充当前路径

    CGContextStrokePath、CGContextFillPath、CGContextEOFillPath、CGContextDrawPath对当前路径描边或填充会清除掉路径。如果你只想使用一条命令完成描边和填充任务,可以使用CGContextDrawPath命令,因为如果你只是使用CGContextStrokePath对路径描边,路径就会被清除掉,你就不能再对它进行填充了。

    创建路径并描边路径或填充路径只需一条命令就可完成的函数:CGContextStrokeLineSegments、CGContextStrokeRect、CGContextStrokeRectWithWidth、CGContextFillRect、CGContextFillRects、CGContextStrokeEllipseInRect、CGContextFillEllipseInRect。

    一段路径是被合成的,意思是它是由多条独立的路径组成。举个例子,一条单独的路径可能由两个独立的闭合形状组成:一个矩形和一个圆形。当你在构造一条路径的中间过程(意思是在描画了一条路径后没有调用描边或填充命令,或调用CGContextBeginPath函数来清除路径)调用CGContextMoveToPoint函数,就像是你拾起画笔,并将画笔移动到一个新的位置,如此来准备开始一段独立的相同路径。如果你担心当你开始描画一条路径的时候,已经存在的路径和新的路径会被认为是已存在路径的一个合成部分,你可以调用CGContextBeginPath函数指定你绘制的路径是一条独立的路径;苹果的许多例子都是这样做的,但在实际开发中我发现这是非必要的。

    CGContextClearRect函数的功能是擦除一个区域。这个函数会擦除一个矩形内的所有已存在的绘图;并对该区域执行裁剪。结果像是打了一个贯穿所有已存在绘图的孔。

    CGContextClearRect函数的行为依赖于上下文是透明还是不透明。当在图形上下文中绘图时,这会尤为明显和直观。如果图片上下文是透明的(UIGraphicsBeginImageContextWithOptions第二个参数为NO),那么CGContextClearRect函数执行擦除后的颜色为透明,反之则为黑色。

CGContextRef con = UIGraphicsGetCurrentContext();

// 绘制一个黑色的垂直黑色线,作为箭头的杆子

CGContextMoveToPoint(context, 100, 100)

CGContextAddLineToPoint(context, 100, 19)

CGContextSetLineWidth(context, 20)

CGContextStrokePath(context)

// 绘制一个红色三角形箭头

CGContextSetFillColorWithColor(context, UIColor.redColor().CGColor

CGContextMoveToPoint(context, 80, 25)

CGContextAddLineToPoint(context, 100, 0)

CGContextAddLineToPoint(context, 120, 25)

CGContextFillPath(context)

// 从箭头杆子上裁掉一个三角形,使用清除混合模式

CGContextMoveToPoint(context, 90, 101)

CGContextAddLineToPoint(context, 100, 90)

CGContextAddLineToPoint(context, 110, 101);

CGContextSetBlendMode(context, CGBlendMode.Clear);

CGContextFillPath(context);

为了以防万一,我们应该在绘图代码周围使用CGContextSaveGState CGContextRestoreGState 函数。但对于一些情况并不会有区别。

如果一段路径需要重用或共享,你可以将路径封装为CGPath(具体类型是CGPathRef)。你可以创建一个新的CGMutablePathRef对象并使用多个类似于图形的路径函数的CGPath函数构造路径,或者使用CGContextCopyPath函数复制图形上下文的当前路径。有许多CGPath函数可用于创建基于简单几何形状的路径(CGPathCreateWithRect、CGPathCreateWithEllipseInRect)或基于已存在路径(CGPathCreateCopyByStrokingPath、CGPathCreateCopyDashingPath、CGPathCreateCopyByTransformingPath)。

  UIKit的UIBezierPath类包装了CGPath。它提供了用于绘制某种形状路径的方法,以及用于描边、填充、存取某些当前上下文状态的设置方法。类似地,UIColor提供了用于设置当前上下文描边与填充的颜色。因此我们可以重写我们之前绘制箭头的代码(自己写的坐标没对o(╯□╰)o):

        let path = UIBezierPath()
        path.moveToPoint(CGPointMake(120, 120))
        path.addLineToPoint(CGPointMake(120, 200))
        path.lineWidth = 5
        path.stroke()
        UIColor.blueColor().set()
        path.removeAllPoints()
        path.moveToPoint(CGPointMake(80, 80))
        path.addLineToPoint(CGPointMake(80, 160))
        path.addLineToPoint(CGPointMake(120, 20))
        path.fill()
        path.removeAllPoints()
        path.moveToPoint(CGPointMake(90, 70))
        path.addLineToPoint(CGPointMake(90, 110))
        path.addLineToPoint(CGPointMake(110, 110))
        path.fillWithBlendMode(CGBlendMode.Clear, alpha: 1)

  在这种特殊情况下,完成同样的工作并没有节省多少代码,但是UIBezierPath仍然还是有用的。如果你需要对象特性,UIBezierPath提供了一个便利方法:bezierPathWithRoundedRect:cornerRadius:,它可用于绘制带有圆角的矩形,如果是使用Core Graphics就相当冗长乏味了。还可以只让圆角出现在左上角和右上角。

- (void)drawRect:(CGRect)rect {

  CGContextRef ctx = UIGraphicsGetCurrentContext();

  CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);

  CGContextSetLineWidth(ctx, 3);

  UIBezierPath *path;

  path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerTopRight) cornerRadii:CGSizeMake(10, 10)];

  [path stroke];

裁剪(有点懵 w(゚Д゚)w)

  路径的另一用处是遮蔽区域,以防对遮蔽区域进一步绘图。这种用法被称为裁剪。裁剪区域外的图形不会被绘制到。默认情况下,一个图形上下文的裁剪区域是整个图形上下文。你可在上下文中的任何地方绘图。

  总的来说,裁剪区域是上下文的一个特性。与已存在的裁剪区域相交会出现新的裁剪区域。所以如果你应用了你自己的裁剪区域,稍后将它从图形上下文中移除的做法是使用CGContextSaveGStateCGContextRestoreGState函数将代码包装起来。

    为了便于说明这一点,我使用裁剪而不是使用混合模式在箭头杆子上打孔的方法重写了生成箭头的代码。这样做有点小复杂,因为我们想要裁剪区域不在三角形内而在三角形外部。为了表明这一点,我们使用了一个三角形和一个矩形组成了一个组合路径。

    当填充一个组合路径并使用它表示一个裁剪区域时,系统遵循以下两规则之一:

  环绕规则(Winding rule)

  如果边界是顺时针绘制,那么在其内部逆时针绘制的边界所包含的内容为空。如果边界是逆时针绘制,那么在其内部顺时针绘制的边界所包含的内容为空。

  奇偶规则

  最外层的边界代表内部都有效,都要填充;之后向内第二个边界代表它的内部无效,不需填充;如此规则继续向内寻找边界线。我们的情况非常简单,所以使用奇偶规则就很容易了。这里我们使用CGContextEOCllip设置裁剪区域然后进行绘图。(如果不是很明白,可以参见这篇文章:五种方法绘制有孔的2d形状

        // 在上下文裁剪区域中挖一个三角形状的孔
        CGContextMoveToPoint(context, 90, 100)
        CGContextAddLineToPoint(context, 100, 90)
        CGContextAddLineToPoint(context, 110, 100)
        CGContextClosePath(context)
        CGContextAddRect(context, CGContextGetClipBoundingBox(context))
        // 使用奇偶规则,裁剪区域为矩形减去三角形区域
        CGContextEOClip(context)
        // 绘制垂线
        CGContextMoveToPoint(context, 100, 100)
        CGContextAddLineToPoint(context, 100, 19)
        CGContextSetLineWidth(context, 20)
        CGContextStrokePath(context)
        // 画红色箭头
        CGContextSetFillColorWithColor(context, UIColor.redColor().CGColor)
        CGContextMoveToPoint(context, 80, 25)
        CGContextAddLineToPoint(context, 100, 0)
        CGContextAddLineToPoint(context, 120, 25)
        CGContextFillPath(context)

渐变

渐变可以很简单也可以很复杂。一个简单的渐变(接下来要讨论的)由一端点的颜色与另一端点的颜色决定,如果在中间点加入颜色(可选),那么渐变会在上下文的两个点之间线性的绘制或在上下文的两个圆之间放射状的绘制。不能使用渐变作为路径的填充色,但可使用裁剪限制对路径形状的渐变。

  以下代码重写了绘制箭头的代码,箭杆使用了线性渐变。

        CGContextSaveGState(context)
        // 在上下文裁剪区域挖一个三角形孔
        CGContextMoveToPoint(context, 90, 100)
        CGContextAddLineToPoint(context, 100, 90)
        CGContextAddLineToPoint(context, 110, 100)
        CGContextClosePath(context)
        CGContextAddRect(context, CGContextGetClipBoundingBox(context))
        CGContextEOClip(context)
        //绘制一个垂线,让它的轮廓形状成为裁剪区域
        CGContextMoveToPoint(context, 100, 100)
        CGContextAddLineToPoint(context, 100, 19)
        CGContextSetLineWidth(context, 20)
        // 使用路径的描边版本替换图形上下文的路径
        CGContextReplacePathWithStrokedPath(context)
        // 对路径的描边版本实施裁剪
        CGContextClip(context)
        // 绘制渐变
        let locations:[CGFloat] = [ 0.0, 0.5, 1.0 ]
        let colors:[CGFloat] = [
            0.3,0.3,0.3,0.8, // 开始颜色,透明灰
            0.0,0.0,0.0,1.0, // 中间颜色,黑色
            0.3,0.3,0.3,0.8 // 末尾颜色,透明灰
        ]
        let colorSpace = CGColorSpaceCreateDeviceGray()
        let gradient = CGGradientCreateWithColorComponents(colorSpace,colors, locations, 3)
        CGContextDrawLinearGradient(context, gradient, CGPointMake(89,0), CGPointMake(111,0), .DrawsAfterEndLocation)
        CGContextRestoreGState(context) // 完成裁剪
        // 绘制红色箭头
        CGContextSetFillColorWithColor(context, UIColor.redColor().CGColor)
        CGContextMoveToPoint(context, 80, 25)
        CGContextAddLineToPoint(context, 100, 0)
        CGContextAddLineToPoint(context, 120, 25)
        CGContextFillPath(context)

待续。。。
绘制文字:




猜你喜欢

转载自blog.csdn.net/qizd0802/article/details/51003016