Quartz 2D绘图

作者: _誌念 | 来源:发表于2017-08-26 15:48 被阅读49次

    概况

    Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。我们可以使用Quartz 2D API来实现许多功能,如基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问。
    下面来了解一下几个基本概念:

    1.Page

    Quartz 2D在图像中使用了绘画者模型(painter’s model)。在绘画者模型中,每个连续的绘制操作都是将一个绘制层(a layer of ‘paint’)放置于一个画布(‘canvas’),我们通常称这个画布为Page。 Page上的绘图可以通过额外的绘制操作来叠加更多的绘图。不同的绘制顺序所产生的效果不一样。

    2.绘制目标:Graphics Context

    Graphics Context是一个数据类型CGContextRef,用于封装Quartz绘制图像到输出设备的信息。设备可以是PDF文件、bitmap或者显示器的窗口上。Graphics Context中的信息包括在Page中的图像的图形绘制参数和设备相关的表现形式。Quartz中所有的对象都是绘制到一个Graphics Context中。

    我们可以将Graphics Context想像成绘制目标,如下图所示。当用Quartz绘图时,所有设备相关的特性都包含在我们所使用的Graphics Context中。换句话说,我们可以简单地给Quartz绘图序列指定不同的Graphics Context,就可将相同的图像绘制到不同的设备上。我们不需要处理任何设备相关的计算;这些都由Quartz替我们完成。

    6.内存管理:对象所有权

    Quartz使用Core Foundation内存管理模型(引用计数)。所以,对象的创建与销毁与通常的方式是一样的。在Quartz中,需要记住如下一些规则:

    1. 如果创建或拷贝一个对象,你将拥有它,因此你必须释放它。通常,如果使用含有CreateCopy单词的函数获取一个对象,当使用完后必须释放,否则将导致内存泄露。
    2. 如果你不拥有一个对象而打算保持它,则必须retain它并且在不需要时release掉。可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorspace对象,则使用函数CGColorSpaceRetainCGColorSpaceRelease来retain和release对象。

    基本图形的绘制

    在UIKit中默认已经为我们准备好了一个图形上下文对象,在UI控件的drawRect:方法(这个方法在loadView、viewDidLoad方法后执行)中我们可以通过UIKit封装函数UIGraphicsGetCurrentContext()方法获得这个图形上下文,绘制步骤分为以下四部:

    1. 获取图形上下文
    2. 绘制路径
    3. 设置图形上下文属性
    4. 绘制路径
    1.标准图形绘制
       //绘制矩形
       CGContextRef context = UIGraphicsGetCurrentContext();
       CGContextAddRect(context, CGRectMake(50, 50, 100, 70));
       CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
       CGContextDrawPath(context, kCGPathStroke);
       
       //绘制椭圆
       CGContextAddEllipseInRect(context, CGRectMake(50, 150, 100, 70));
       CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
       CGContextDrawPath(context, kCGPathStroke);
       
       /*绘制弧形,圆形
        x:中心点x坐标
        y:中心点y坐标
        radius:半径
        startAngle:起始弧度
        endAngle:终止弧度
        closewise:是否逆时针绘制,0则顺时针绘制
        */
       CGContextAddArc(context, 100, 300, 50, 0, M_PI_2*3, 0);
       CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
       CGContextDrawPath(context, kCGPathFillStroke);
    
    2.绘制贝塞尔曲线

    Quartz 2D中曲线绘制分为两种:二次贝塞尔曲线和三次贝塞尔曲线。二次曲线只有一个控制点,而三次曲线有两个控制点,如下图所示


         /*绘制二次贝塞尔曲线
         c:图形上下文
         cpx:控制点x坐标
         cpy:控制点y坐标
         x:结束点x坐标
         y:结束点y坐标
         */
        CGContextMoveToPoint(context, 50, 450);
        CGContextAddQuadCurveToPoint(context, 150, 300, 250, 450);
        CGContextDrawPath(context, kCGPathStroke);
        
    
        /*绘制三次贝塞尔曲线
         c:图形上下文
         cp1x:第一个控制点x坐标
         cp1y:第一个控制点y坐标
         cp2x:第二个控制点x坐标
         cp2y:第二个控制点y坐标
         x:结束点x坐标
         y:结束点y坐标
         */
        CGContextMoveToPoint(context, 50, 500);
        CGContextAddCurveToPoint(context, 150, 350, 250, 500, 300, 300);
        CGContextDrawPath(context, kCGPathStroke);
    
    3.文字绘制

    除了绘制图形还可以绘制文本内容。

     /*绘制到指定的区域内容*/
     NSString *str=@"这是一段文字";
       CGRect rect = CGRectMake(20, 50, kScreenWidth-40, kScreenHeight-100);
        [str drawInRect:rect withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13],NSForegroundColorAttributeName:[UIColor blueColor]}];
    
    4.图像绘制

    Quartz 2D还可以将图像绘制到图形上下文

    - (void)drawImage{
        UIImage *image = [UIImage imageNamed:@"IMG_1909"];
        /*从某一点开始绘制*/
        [image drawAtPoint:CGPointMake(50, 50)];
    
        /*绘制在指定的矩形区域内,如果大小不合适会进行拉伸*/
        [image drawInRect:CGRectMake(50, 50, kScreenWidth-100, 200)];
        
        /*平铺图片*/
        [image drawAsPatternInRect:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
    }
    

    Core Graphics中坐标系的y轴正方向是向上的,坐标原点在屏幕左下角,y轴方向刚好和UIKit中y轴方向相反。而使用UIKit进行绘图之所以没有问题是因为UIKit中进行了处理,事实上对于其他图形即使使用Core Graphics绘制也没有问题,因为UIKit统一了编程方式。

    但是使用Core Graphics中内置方法绘制图像是存在这种问题的,其实图形上下文只要沿着x轴旋转180度,然后向上平移适当的高度即可(但是注意不要沿着z轴旋转,这样得不到想要的结果)。可是CGContextRotateCTM方法只能通过沿着z轴旋转,此时不妨使用另外一种方法,那就是在y轴方向缩放-1,同样可以达到想要的效果:

    /*使用Core Graphics绘制图像*/
    - (void)CoreGraphicsDrawImage{
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSaveGState(context);
        UIImage *image = [UIImage imageNamed:@"IMG_1909"];
        CGSize size=[UIScreen mainScreen].bounds.size;
        CGFloat height=200*776/400.0,y=50;
    
        //上下文形变
        CGContextScaleCTM(context, 1.0, -1.0);//在y轴缩放-1相当于沿着x张旋转180
        CGContextTranslateCTM(context, 0, -(size.height-(size.height-2*y-height)));//向上平移
        //图像绘制
        CGRect rect= CGRectMake(10, y, 200, height);
        CGContextDrawImage(context, rect, image.CGImage);
        CGContextRestoreGState(context);
    }
    
    5.绘制虚线
    - (void)drawDashLine{
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextMoveToPoint(context, 50, 100);
        CGContextAddLineToPoint(context,kScreenWidth-50, 100);
        /*phase值为13,则首先绘制【15减去13】,再跳过5,绘制15,反复绘制*/
        CGFloat length[2] = {15,5};
        CGContextSetLineDash(context,13,length,2);
        CGContextSetLineWidth(context, 3);
        CGContextDrawPath(context, kCGPathStroke);
        
        CGContextMoveToPoint(context, 50, 200);
        CGContextAddLineToPoint(context, kScreenWidth - 50, 200);
        /*表示填充线和非填充线相交*/
        CGFloat lenght[3] = {5,10,5};
        CGContextSetLineDash(context, 0, lenght, 3);
        CGContextStrokePath(context);
        
        CGPoint p[2] = {CGPointMake(50, 150),CGPointMake(kScreenWidth - 50, 150)};
        CGContextStrokeLineSegments(context,p, 2);
    }
    
    6.绘制阴影

    阴影有三个属性:

    1. x偏移值,用于指定阴影相对于图片在水平方向上的偏移值。
    2. y偏移值,用于指定阴影相对于图片在竖直方向上的偏移值。
    3. 模糊(blur)值,用于指定图像是有一个硬边,blur的值越大,阴影的虚化层度越大。
    /*绘制阴影*/
    - (void)drawShadowMenthod{
        /*1.保存图形状态
         2.调用函数CGContextSetShadow,传递相应的值
         3.使用阴影绘制所有的对象
         4.恢复图形状态*/
    
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextAddArc(context, centerX, centerY, 100, 0, M_PI*2, 0);
        /*blur的值越大,阴影的虚化层度越大*/
        CGContextSetShadow(context, CGSizeMake(10,10), 10);
        CGContextSetShadowWithColor(context, CGSizeMake(10,10), 10, [UIColor redColor].CGColor);
        CGContextSetFillColorWithColor(context, [UIColor grayColor].CGColor);
        CGContextDrawPath(context, kCGPathFill);
        
        CGContextSetShadowWithColor(context, CGSizeMake(10, 10), 10, [UIColor blueColor].CGColor);
        CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
        CGContextFillRect(context, CGRectMake(100, 50, 200, 100));
    }
    

    透明层TransparencyLayers通过组合两个或多个对象来生成一个组合图形。组合图形被看成是单一对象。当需要在一组对象上使用特效时,透明层非常有用,下图显示了在透明层中绘制三个矩形,其中将这三个矩形当成一个整体来渲染阴影.


    相信大家对比代码和显示效果并不难发现每种叠加的效果。例子中只是使用UIKit的封装方法进行叠加模式设置,更一般的方法当然是使用CGContextSetBlendMode(CGContextRef context, CGBlendMode mode)方法进行设置。

    填充模式

    前面的示例中已经演示过纯色填充渐变填充,而有时我们需要按一定的自定义样式进行填充,这种方式有点类似于贴瓷砖的方式。我们知道如果家里贴地板或瓷砖时,通常我们会先选择一种瓷砖样式,根据房间面积我们购买不同量的瓷砖。但是不管买多少,这些瓷砖的样式都是一模一样的。填充模式就是为了达到这种效果而产生的:我们只需要绘制一个瓷砖的样式,然后让程序自动调用这种样式填充指定大小的区域。

    Quartz 2D支持两种填充模式:有颜色填充和无颜色填充。

    1. 有颜色填充就是在绘制瓷砖时就指定颜色,在调用填充时就不用再指定瓷砖颜色
    2. 无颜色填充模式就是绘制瓷砖时不用指定任何颜色,在调用填充时再指定具体填充颜色。
    /*构建一个符合CGPatternDrawPatternCallback签名的方法,这个方法专门用来创建“瓷砖”*/
    void drawColoredTitle(void *info,CGContextRef context){
         //有颜色填充,在这里设置颜色,创建瓷砖
        CGContextSetRGBFillColor(context, 254.0/255.0, 52.0/255.0, 90.0/255.0, 1);
        CGContextFillRect(context, CGRectMake(0, 0, TILE_SIZE, TILE_SIZE));
        CGContextFillRect(context, CGRectMake(TILE_SIZE, TILE_SIZE, TILE_SIZE, TILE_SIZE));
        
        CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextFillRect(context, CGRectMake(0, TILE_SIZE, TILE_SIZE, TILE_SIZE));
        CGContextFillRect(context, CGRectMake(TILE_SIZE, 0, TILE_SIZE, TILE_SIZE));
    }
    
    /*有颜色填充模式*/
    - (void)coloredFillPattern{
        //获得图形上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        //指定一个填充的颜色空间,模式填充颜色空间,注意对于有颜色填充模式,这里传null
        CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern(NULL);
        
        //将填充色颜色空间,设置为模式填充的颜色空间
        CGContextSetFillColorSpace(context, colorSpace);
        
        //填充模式回调函数结构体
        CGPatternCallbacks callback = {0,&drawColoredTitle,NULL};
        
        /*填充模式
         info://传递给callback的参数
         bounds:瓷砖大小
         matrix:形变
         xStep:瓷砖横向间距
         yStep:瓷砖纵向间距
         tiling:贴砖的方法
         isClored:绘制的瓷砖是否已经指定了颜色(对于有颜色瓷砖此处指定位true)
         callbacks:回调函数
         */
        CGPatternRef patternRef = CGPatternCreate(NULL, CGRectMake(0, 0, 2*TILE_SIZE, 2*TILE_SIZE), CGAffineTransformIdentity, 2*TILE_SIZE, 2*TILE_SIZE, kCGPatternTilingNoDistortion, YES, &callback);
        
        //注意最后一个参数对于有颜色瓷砖指定为透明度的参数地址,对于无颜色瓷砖则指定当前颜色空间对应的颜色数组
        CGFloat alpha=1;
        CGContextSetFillPattern(context, patternRef, &alpha);
        
        CGContextFillRect(context, CGRectMake(0, 0, kScreenWidth, kScreenHeight));
        
        //释放资源
        CGColorSpaceRelease(colorSpace);
        CGPatternRelease(patternRef);
    }
    

    上下文变换

    我们知道在UIKit开发中UIView有一个transform属性用于控件的形变,其实在绘图中我们也经常用到图形形变,这个时候可以借助图形上下文的形变方法来完成。

    注意在设置图形上下文形变之前一定要注意保存上下文的初始状态,在使用完之后进行恢复。否则在处理多个图形形变的时候很容易弄不清楚到底是基于怎样的坐标系进行绘图,容易找不到原点。

    - (void)transformMenthod{
        CGContextRef context = UIGraphicsGetCurrentContext();
        //保存图形上下文
        CGContextSaveGState(context);
        
        //形变第一步,向右平移50,向下平移50
        CGContextTranslateCTM(context, 50, 50);
        
        //形变第二步,缩放0.5
        CGContextScaleCTM(context, 0.7, 0.7);
        
        //形变第三部,旋转以左上角为端点进行顺时针旋转)
        CGContextRotateCTM(context, M_PI_4*0.5);
        
        UIImage *image = [UIImage imageNamed:@"IMG_1909"];
        [image drawAtPoint:CGPointMake(0, 0)];
        
        //恢复到初始状态
        CGContextRestoreGState(context);
    }
    

    其他图形上下文绘制

    上面的示例中一直都是在drawRect:方法中利用UIGraphicsGetCurrentContext()方法取得上下文,要得到位图或者PDF的上下文可以利用UIGraphicsBeginImageContext(CGSize size)UIGraphicsBeginPDFPageWithInfo(CGRect bounds, NSDictionary *pageInfo)方法。位图图形上下文和PDF图形上下文UIKit是不会负责创建的,所以需要用户手动创建,并且在使用完后关闭它。在使用UIKit中系统创建的图形上下文的时候,我们只能在drawRect:方法中使用,由于这两类图形上下文是由我们手动创建的因此可以放到任何方法中调用。此外,这两个方法开启的图形上下文并没有返回值,如果我们要得到我们创建的图形上下文只要在创建上下文之后、关闭之前调用UIGraphicsGetCurrentContext()方法,此时取得的上下文即是我们自己创建的图形上下文。

    1.绘制到位图

    下面利用位图图形上下文给一个图片添加水印

    - (UIImage *)drawBitmapWithName:(NSString *)imageName{
        //获得一个位图上下文
        UIGraphicsBeginImageContext(CGSizeMake(300, 300));
        
        //注意绘图的位置是相对于画布顶点而言,不是屏幕
        UIImage *image = [UIImage imageNamed:imageName];
        [image drawInRect:CGRectMake(0, 0, 300, 300)];
        CGFloat height = image.size.height;
        CGFloat width = image.size.width;
        
        //添加水印
        CGContextRef context = UIGraphicsGetCurrentContext();
        NSString *str = @"xueLiang_cao";
        [str drawInRect:CGRectMake(width-100, height - 70, 100, 30) withAttributes:@{NSFontAttributeName : [UIFont fontWithName:@"Marker Felt" size:20],NSForegroundColorAttributeName : [UIColor blueColor]}];
        CGContextMoveToPoint(context, width-100, height-70+30);
        CGContextAddLineToPoint(context, width, height-70+30);
        CGContextSetLineWidth(context, 3);
        CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
        CGContextDrawPath(context, kCGPathStroke);
        
        //返回绘制的新图形
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        
        // 存储图片到"相机胶卷"
        UIImageWriteToSavedPhotosAlbum(newImage, self, NULL, nil);
        
        //最后一定要关闭图形上下文
        UIGraphicsEndImageContext();
        return newImage;
    }
    
    2.绘制到PDF

    绘制到PDF则要启用pdf图形上下文,PDF图形上下文的创建使用方式跟位图图形上下文是类似的,需要注意的一点就是绘制内容到PDF时需要创建分页,每页内容的开始都要调用一次IGraphicsBeginPDFPage();方法。

    /*利用pdf图形上下文绘制内容到pdf文档*/
    - (void)drawPDFMenthod{
      /*绘制内容到PDF时需要创建分页,每页内容的开始都要调用一次UIGraphicsBeginPDFPage();方法*/
        
        //获取沙盒路径
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *path = [[paths lastObject] stringByAppendingPathComponent:@"myPDF.pdf"];
        NSLog(@"%@",path);
        /**
         path:保存路径
         bounds:pdf文档大小,如果设置为CGRectZero则使用默认值:612*792
         pageInfo:页面设置,为nil则不设置任何信息
         */
        UIGraphicsBeginPDFContextToFile(path, CGRectZero, [NSDictionary dictionaryWithObjectsAndKeys:@"xueliang cao",kCGPDFContextAuthor, nil]);
        
        //由于pdf是分页的,所以首先要创建一页画布供我们绘制
        UIGraphicsBeginPDFPage();
        NSString *title=@"Welcome to Apple Support";
        NSMutableParagraphStyle *style=[[NSMutableParagraphStyle alloc]init];
        NSTextAlignment align=NSTextAlignmentCenter;
        style.alignment=align;
        [title drawInRect:CGRectMake(26, 20, 300, 50) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:18],NSParagraphStyleAttributeName:style}];
        NSString *content=@"Learn about Apple products, view online manuals, get the latest downloads, and more. Connect with other Apple users, or get service, support, and professional advice from Apple.";
        NSMutableParagraphStyle *style2=[[NSMutableParagraphStyle alloc]init];
        style2.alignment=NSTextAlignmentLeft;
        [content drawInRect:CGRectMake(26, 56, 300, 255) withAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15],NSForegroundColorAttributeName:[UIColor grayColor],NSParagraphStyleAttributeName:style2}];
        
        UIImage *image=[UIImage imageNamed:@"IMG_1909.png"];
        [image drawInRect:CGRectMake(316, 20, 300, 300*776/400.0)];
        
        
        //创建新的一页继续绘制其他内容
        UIGraphicsBeginPDFPage();
        UIImage *image2 = [UIImage imageNamed:@"IMG_1909.png"];
        [image2 drawInRect:CGRectMake(6, 20, 400, 776)];
        
        //结束PDF上下文
        UIGraphicsEndPDFContext();
    }
    

    参考资料

    南峰子的技术博客
    Demo地址

    相关文章

      网友评论

        本文标题:Quartz 2D绘图

        本文链接:https://www.haomeiwen.com/subject/feomdxtx.html