二维绘图引擎Quartz 2D

作者: 張贺 | 来源:发表于2016-08-19 23:08 被阅读205次
    图片来自500px

    文 || 張贺

    Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统

    Quartz 2D能完成的工作:

    • 绘制图形 : 线条/三角形/矩形/圆/弧等
    • 绘制文字
    • 绘制/生成图片(图像)
    • 读取/生成PDF
    • 截图/裁剪图片
    • 自定义UI控件(重要)

    图形上下文

    图形上下文(Graphics Context):是一个CGContextRef类型的数据

    图形上下文的作用:

    • 保存绘图信息、绘图状态
    • 决定绘制的输出目标(绘制到什么地方去?)
      (输出目标可以是PDF文件、Bitmap或者显示器的窗口上)
      相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上

    Quartz2D提供了以下几种类型的Graphics Context:

    • Bitmap Graphics Context
    • PDF Graphics Context
    • Window Graphics Context
    • Layer Graphics Context
    • Printer Graphics Context

    drawRect:方法

    • 如何利用Quartz2D绘制东西到view上?
      首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去
      其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面

    • 如何利用Quartz2D自定义view?(自定义UI控件)
      1.新建一个类,继承自UIView
      2.实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中取得跟当前view相关联的图形上下文
      3.绘制相应的图形内容
      4.利用图形上下文将绘制的所有内容渲染显示到view上面

    • 为什么要实现drawRect:方法才能绘图到view上?
      因为在drawRect:方法中才能取得跟view相关联的图形上下文

    • drawRect:方法在什么时候被调用?
      当view第一次显示到屏幕上时(被加到UIWindow上显示出来)
      调用view的setNeedsDisplay(重绘)或者setNeedsDisplayInRect:时
      注意:调用setNeedsDisplay方法并不会立马调用drawRect:方法,只是设了一个标志,当屏幕下一次刷新的时候才去调用drawRect:方法。(屏幕每秒刷新60次)

    画直线

    /**
     *   在drawRect:方法中系统已经帮你创建好了一个跟view相关联的上下文(layer上下文),只需要获取就好了
     *   作用:专门用来绘图
     *   什么时候调用:当view显示的时候自动调用
     *   @param rect:当前view的bounds
     *
     */
    - (void)drawRect:(CGRect)rect {
        //1.获取图形上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
    
        //上下文的状态(线的粗细、颜色、链接样式等)
        //设置线的粗细
        CGContextSetLineWidth(ctx, 10);
        //设置链接点的样式
        /**
         typedef CF_ENUM(int32_t, CGLineJoin) {
            kCGLineJoinMiter,
            kCGLineJoinRound,
            kCGLineJoinBevel
         };
         */
        CGContextSetLineJoin(ctx, kCGLineJoinRound);
        //设置线的顶角样式
        /**
         typedef CF_ENUM(int32_t, CGLineCap) {
            kCGLineCapButt,
            kCGLineCapRound,
            kCGLineCapSquare
         };
         */
    
        CGContextSetLineCap(ctx, kCGLineCapRound);
        //设置颜色(注意:这里设置颜色必须跟渲染方式一致,否则设置不上颜色)
    //    [[UIColor redColor]setFill]; //渲染方式为Fill
    //    [[UIColor redColor]setStroke];  //渲染方式为Stroke
        [[UIColor redColor]set];  //两种渲染方式均可,推荐使用
    
        //2. 绘制路径
        UIBezierPath *path = [UIBezierPath bezierPath];
        //2.1 设置起点
        [path moveToPoint:CGPointMake(50, 250)];
        //2.2 添加一根线到终点
        [path addLineToPoint:CGPointMake(250, 50)];
    
        //画第二条直线(新起点)
        [path moveToPoint:CGPointMake(100, 250)];
        [path addLineToPoint:CGPointMake(250, 100)];
    
        //把上一条线的终点当做起点来画第二条线
        [path addLineToPoint:CGPointMake(250, 250)];
    
        //3.把绘制的内容添加到上下文中
        // UIBezierPath:UIKit框架   --->   CGPathRef:CoreGraphics框架
        CGContextAddPath(ctx, path.CGPath);
    
        //4.把上下文的内容显示到view上(渲染到view的layer上)渲染的方式有两种Stroke(描边)、Fill(填充)
        CGContextStrokePath(ctx);
    }
    

    画曲线

    //画曲线
    - (void)drawQuadCurve{
        //1.获取图形上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        //2.绘制图形
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(50, 250)];
        //添加一条曲线
        [path addQuadCurveToPoint:CGPointMake(250, 250) controlPoint:CGPointMake(50, 50)];
    
        //3.把绘制的内容添加到上下文中
        CGContextAddPath(ctx, path.CGPath);
        //4.把上下文的内容渲染到view上
        CGContextStrokePath(ctx);
    }
    
    图片来自苹果官方文档

    画矩形

    - (void)drawRect:(CGRect)rect {
    
        //1.获取图形上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
    
        //设置颜色
        [[UIColor redColor]set];
        //2.绘制路径
        //画矩形
        //    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 100, 100)];
        //画圆角矩形
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 100, 100) cornerRadius:50];
        //3.把路径添加到上下文
        CGContextAddPath(ctx, path.CGPath);
        //4.把上下文渲染到view上
        //描边
        //    CGContextStrokePath(ctx);
        //填充
        CGContextFillPath(ctx);
    
    }
    

    画椭圆

    - (void)drawRect:(CGRect)rect {
    
        //画椭圆
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 50, 100, 50)];
        //画矩形
    //    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 100, 50)];
        //画线
        [path moveToPoint:CGPointMake(50, 250)];
        [path addLineToPoint:CGPointMake(250, 250)];
        //设置颜色
        [[UIColor redColor]set];
        //设置线条的粗细
        [path setLineWidth:10];
        //[path stroke] 底层:
        //1.获取图形上下文 -> 2.绘制路径 -> 3.把路径添加到上下文 -> 4.把上下文渲染到view上
        [path stroke];
        //填充
    //    [path fill];
    }
    

    画弧

    - (void)drawRect:(CGRect)rect {
    
        //画弧
        //ArcCenter:弧所在圆的圆心
        //radius:弧所在圆的半径
        //startAngle:起始角度
        //endAngle:结束角度
        //clockwise: YES:顺时针 NO:逆时针
    
        //不能直接使用self.center :因为self.center坐标是相对于他的父控件
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        CGFloat radius = rect.size.width * 0.5 - 10;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_4 clockwise:YES];
    
        [path stroke];
    
    }
    

    画扇形

    - (void)drawRect:(CGRect)rect {
        //画弧
        //ArcCenter:弧所在圆的圆心
        //radius:弧所在圆的半径
        //startAngle:起始角度
        //endAngle:结束角度
        //clockwise: YES:顺时针 NO:逆时针
    
        //不能直接使用self.center :因为self.center坐标是相对于他的父控件
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        CGFloat radius = rect.size.width * 0.5 - 10;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_4 clockwise:YES];
    
        //画扇形
        //添加一根线到圆心
        [path addLineToPoint:center];
    
        //关闭路径:从路径终点添加一根线到路径的起点
        [path closePath];
    
        //填充之前,会自动关闭路径
        //    [path fill];
        [path stroke];
        }
    

    画饼图

    #import "PieView.h"
    
    @implementation PieView
    
    - (void)drawRect:(CGRect)rect {
    
        NSArray *dataArray = @[@15,@20,@5,@10,@50];
    
        CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
        CGFloat redius = rect.size.width * 0.5 - 10;
        CGFloat startA = 0;
        CGFloat angle = 0;
        CGFloat endA = 0;
    
        for (NSNumber *num in dataArray) {
       
            startA = endA;
            angle = num.intValue / 100.0 * M_PI * 2;
            endA = startA + angle;
            UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:redius startAngle:startA endAngle:endA clockwise:YES];
            //随机颜色
            [[self randomColor]set];
            //弧的终点向圆心画一条线
            [path addLineToPoint:center];
            //填充
            [path fill];
        }
    }
    
    //随机颜色
    - (UIColor *)randomColor{
    
        CGFloat r = arc4random_uniform(256) / 255.0;
        CGFloat g = arc4random_uniform(256) / 255.0;
        CGFloat b = arc4random_uniform(256) / 255.0;
        return [UIColor colorWithRed:r green:g blue:b alpha:1];
    }
    
    @end
    
    饼图

    UIKit绘图

    画文字
    - (void)drawRect:(CGRect)rect {
    // Drawing code

        NSString *str = @"Quartz 2D是一个二维绘图引擎,同时支持iOS和Mac系统";
    
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        //设置文字大小
        dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
        //设置文字颜色
        dict[NSForegroundColorAttributeName] = [UIColor greenColor];
        //设置描边宽度
        dict[NSStrokeWidthAttributeName] = @2;
        //设置描边颜色
        dict[NSStrokeColorAttributeName] = [UIColor blueColor];
        //设置阴影
        NSShadow *shadow = [[NSShadow alloc] init];
        //设置阴影的便宜量
        shadow.shadowOffset = CGSizeMake(10, 10);
        //设置阴影颜色
        shadow.shadowColor = [UIColor greenColor];
        //设置阴影模糊程序
        shadow.shadowBlurRadius = 1;
        dict[NSShadowAttributeName] = shadow;
    
        /**
         AtPoint:文字所画的位置
         withAttributes:描述文字的属性.
         */
        //不会自动换行
        [str drawAtPoint:CGPointZero withAttributes:dict];
        //会自动换行.
        [str drawInRect:self.bounds withAttributes:dict];
    }
    

    画图片
    - (void)drawRect:(CGRect)rect {
    // Drawing code
    //1.加载图片
    UIImage *image = [UIImage imageNamed:@"agt"];

        //绘制出来的图片,是保持原来图片大小
        [image drawAtPoint:CGPointZero];
        //把图片填充到这个rect当中.
        [image drawInRect:rect];
        //添加裁剪区域 .把超区裁剪区域以外都裁剪掉
        UIRectClip(CGRectMake(0, 0, 50, 50));
        //平铺
        [image drawAsPatternInRect:self.bounds];
    
        //快速的画出一个矩形
        [[UIColor blueColor] set];
        UIRectFill(CGRectMake(10, 10, 100, 100));
    }
    

    上下文状态栈

    - (void)drawRect:(CGRect)rect {
        // Drawing code
        //1.获取图形上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        //2.描述路径
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(10, 100)];
        [path addLineToPoint:CGPointMake(150, 100)];
    
        /*******保存当前上下文的状态到上下文状态栈*********/
        CGContextSaveGState(ctx);
    
        //修改状态
        CGContextSetLineWidth(ctx, 10);
        [[UIColor redColor]set];
    
        //3.将路径添加到上下文
        CGContextAddPath(ctx, path.CGPath);
        //4.将上下文渲染到view上
        CGContextStrokePath(ctx);
    
        //2.描述路径
        UIBezierPath *path2 = [UIBezierPath bezierPath];
        [path2 moveToPoint:CGPointMake(100, 10)];
        [path2 addLineToPoint:CGPointMake(100, 150)];
    
        //修改状态
    //    CGContextSetLineWidth(ctx, 1);
    //    [[UIColor blackColor]set];
        /*******从上下文状态栈中取出最上面的状态*********/
        CGContextRestoreGState(ctx);
        //3.将路径添加到上下文
        CGContextAddPath(ctx, path2.CGPath);
        //4.将上下文渲染到view上
        CGContextStrokePath(ctx);
    }
    @end
    
    绘制不同的状态

    图形上下文的矩阵操作

    - (void)drawRect:(CGRect)rect {
        // Drawing code
    
        //1.获取图形上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        //2.绘制路径
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 100, 50)];
    
        //把路径添加到上下文中之前可以做一些矩阵操作
        //平移
        CGContextTranslateCTM(ctx, 100, 100);
        //旋转
        CGContextRotateCTM(ctx, M_PI_4);
        //缩放
        CGContextScaleCTM(ctx, 1.5, 1.5);
    
    
        //3.把路径添加到图形上下文中
        CGContextAddPath(ctx, path.CGPath);
        //4.把上下文渲染到view上
        CGContextFillPath(ctx);
    }
    

    给图片加水印

    #import "ViewController.h"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageV;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //0.加载图片
        UIImage *image = [UIImage imageNamed:@"123"];
        //1.开启一个跟图片大小一样的上下文
        UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
        //2.把图片绘制到上下文中
        [image drawAtPoint:CGPointZero];
        //3.把文字绘制到上下文中
        NSString *watermark = @"張贺";
        [watermark drawAtPoint:CGPointMake(10, 20) withAttributes:nil];
        //4.从上下文中生成一张新的图片(把上下文中绘制的所有内容,生成一张新的图片)
        UIImage *waterMarkImage = UIGraphicsGetImageFromCurrentImageContext();
        //5.关闭上下文
        UIGraphicsEndImageContext();
    
        self.imageV.image = waterMarkImage;
    
    }
    
    @end
    
    给图片加水印

    圆形图片裁剪

    #import "ViewController.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageV;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        //0.加载图片
        UIImage *image = [UIImage imageNamed:@"icon"];
        //1.开启一个跟图片一样大小的上下文
        UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
        //2.设置圆形裁剪区域(超出裁剪区域的部分会被裁剪掉)
        //贝塞尔曲线
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
        [path addClip];
        //3.把图片绘制到上下文中
        [image drawAtPoint:CGPointZero];
        //4.从上下文中取出图片
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        //5.关闭上下文
        UIGraphicsEndImageContext();
    
        self.imageV.image = newImage;
    
    }
    
    @end
    
    裁剪前 裁剪后

    带边框的圆形图片裁剪

    #import "ViewController.h"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageV;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        //0.加载图片
        UIImage *image = [UIImage imageNamed:@"icon"];
        //1.确定边框宽度
        CGFloat border = 10;
        //2.开启一个上下文
        CGSize size = CGSizeMake(image.size.width + 2 * border, image.size.height + 2 * border);
        UIGraphicsBeginImageContextWithOptions(size, NO, 0);
        //3.绘制大圆,显示出来
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)];
        [[UIColor redColor]set];
        [path fill];
        //4.绘制一个小圆,把小圆设置成裁剪区域
        UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(border, border, image.size.width, image.size.height)];
        [clipPath addClip];
        //5.把图片绘制到上下文中
        [image drawAtPoint:CGPointMake(border, border)];
        //6.从上下文中取出图片
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        //7.关闭上下文
        UIGraphicsEndImageContext();
    
        self.imageV.image = newImage;
    
    }
    
    带边框的图片裁剪

    截屏

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        //把控制器的view生成一张图片
        //1.开启一个位图上下文(大小和控制器的view大小一样)
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);
    
        //2.把控制器的view绘制到上下文中
        //想要把view上面的东西绘制到上下文当中,必须使用渲染的方式
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        [self.view.layer renderInContext:ctx];
    
        //3.从上下文中生成一张图片
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        //4.关闭上线文
        UIGraphicsEndImageContext();
    
        //把生成的图片写入到桌面(文件方式进行传输:二进制流NSData)
        //把图片转成二进制流
        //png  默认最高质量
        //UIImagePNGRepresentation(newImage)
        //jpg  第二个参数是截图质量
        NSData *data = UIImageJPEGRepresentation(newImage, 1);
        [data writeToFile:@"/Users/zhanghe/Desktop/截屏.jpg" atomically:YES];
    }
    

    当然,在实际开发中我们不需要自己去画折线图、饼图、各种统计图... ...
    一个很好的第三方库 Charts 是一个简单、面向对象、为设计者和开发者准备的图表绘制工具库

    相关文章

      网友评论

      本文标题:二维绘图引擎Quartz 2D

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