iOS绘图之Quartz

2016-10-27

iOS支持两套图形API框架:Core Graphics/QuartZ 2D 和OpenGL ES。本系列文章将向大家介绍这两套API框架

Core Graphics Framework/QuartZ 2D

Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎。它提供了低级别、轻量级、高保真度的2D渲染。该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。

QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分

点我进入苹果官方文档

OpenGL ES

OpenGL ES是跨平台的图形API,属于OpenGL的一个简化版本。OpenGL ES是应用程序编程接口,该接口描述了方法、结构、函数应具有的行为以及应该如何被使用的语义。

因为OpenGL ES只是定义了应用程序编程接口,具体如何去实现,需要开发者具有扎实计算机图形变换的扎实基础。

使用QuartZ 2D

使用原理

在iOS上,所有的绘制图形都发生在UIView对象的所表示的矩形区域内。如果您使用系统提供的视图,绘制工作会自动得到处理。然而,如果您定义自己的定制视图,则必须自行提供绘制代码。例如,咱们定一个类是继承于UIView,那么我们会发现有一个被注释掉的 drawRect 方法,如图:

如果我们不将这个注释方法打开,那么系统将会按照自己的逻辑绘制视图;倘若开发者需要自己定制视图,那么需要在 drawRect 方法里提供绘制代码。

什么时候会触发 `drawRect` 方法?
1. 该视图对象初始化的时候
2. 显式调用视图的setNeedsDisplay或者setNeedsDisplayInRect:方法。
开始绘制
  • 基本绘制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CGContextRef context = UIGraphicsGetCurrentContext();//获得图形上下文
    //两个函数是构建描绘路径
    CGContextMoveToPoint (context, 75, 10);
    CGContextAddLineToPoint (context, 10, 150);
    CGContextAddLineToPoint (context, 160, 150);
    //函数是闭合描绘路径
    CGContextClosePath(context);
    // And stroke the path
    [[UIColor blackColor] setStroke]; //设置描边的颜色
    //CGContextStrokePath(context);//闭合路径描边
    [[UIColor redColor] setFill]; //设置要填充颜色
    CGContextDrawPath(context, kCGPathFillStroke);//设置描绘路径方式
    //kCGPathFillStroke,kCGPathFill,kCGPathStroke
  • 贝塞尔曲线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    CGContextRef cgContext = UIGraphicsGetC urrentContext();
    //CGContextBeginPath(cgContext);
    CGContextMoveToPoint(cgContext, 333, 0);
    CGContextAddCurveToPoint(cgContext, 333, 0, 332, 26, 330, 26);
    CGContextAddCurveToPoint(cgContext, 330, 26, 299, 20, 299, 17);
    CGContextAddLineToPoint(cgContext, 296, 17);
    CGContextAddCurveToPoint(cgContext, 296, 17, 296, 19, 291, 19);
    CGContextAddLineToPoint(cgContext, 250, 19);
    CGContextAddCurveToPoint(cgContext, 250, 19, 241, 24, 238, 19);
    CGContextAddCurveToPoint(cgContext, 236, 20, 234, 24, 227, 24);
    CGContextAddCurveToPoint(cgContext, 220, 24, 217, 19, 216, 19);
    CGContextAddCurveToPoint(cgContext, 214, 20, 211, 22, 207, 20);
    CGContextAddCurveToPoint(cgContext, 207, 20, 187, 20, 182, 21);
    CGContextAddLineToPoint(cgContext, 100, 45);
    CGContextAddLineToPoint(cgContext, 97, 46);
    CGContextAddCurveToPoint(cgContext, 97, 46, 86, 71, 64, 72);
    CGContextAddCurveToPoint(cgContext, 42, 74, 26, 56, 23, 48);
    CGContextAddLineToPoint(cgContext, 9, 47);
    CGContextAddCurveToPoint(cgContext, 9, 47, 0, 31, 0, 0);
    CGContextStrokePath(cgContext);
    }
  • 绘制文本

    1
    2
    3
    4
    5
    6
    7
    8
    NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"dog" ofType:@"png"];
    UIImage* myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath];
    //[myImageObj drawAtPoint:CGPointMake(0, 0)];
    [myImageObj drawInRect:CGRectMake(0, 0, 320, 480)];
    NSString *s = @"我的小狗";
    [s drawAtPoint:CGPointMake(100, 0) withFont:[UIFont systemFontOfSize:34.0]];
  • 绘制图像

    需要注意的是,在QuartZ 2D中遵循的是笛卡尔坐标系,因此绘制图形时需要转换坐标系

    CGContextSaveGState是将当前图形状态要入到图形堆栈。
    CGContextDrawImage(context, touchRect, image)在上下文中绘制图形。
    CGContextRestoreGState回复当前图形状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    NSString *path = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"jpg"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];
    CGImageRef image = img.CGImage;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    CGContextRestoreGState(context);
  • 变换

    变换(transformation)修改了图形上下文中绘制图形的方式。可以通过移动、旋转或缩放实现变换。

    Quartz提供了多种形式的变换,其中主要:CTM(当前变换矩阵)变换和仿射(affine)变换。

    • CTM(current transformation matrix)变换,这种变换比较简单,函数有:

      • CGContextRotateCTM,旋转坐标
      • CGContextScaleCTM,缩放坐标
      • CGContextTranslateCTM,移动原点
    • 仿射变换

      • CGAffineMakeRotation,创建旋转矩阵仿射对象

      • CGAffineMakeScale,创建缩放矩阵仿射对象

      • CGAffineMakeTranslation,创建移动矩阵仿射对象

      • CGAffineTransformRotate,旋转矩阵仿射对象

      • CGAffineTransformScale,缩放矩阵仿射对象

      • CGAffineTransformTranslate,移动矩阵仿射对象

      • CGContextConcatCTM,连接到CTM变换

        移动变换

        1
        CGContextTranslateCTM (myContext, 100, 50)

        旋转变换

        1
        2
        3
        4
        5
        6
        7
        static inline double radians (double degrees) {
        return degrees * M_PI/180;
        }
        CGContextRotateCTM (myContext, radians(–45));
        //从对象角度:
        //在Quartz坐标下正数为逆时针旋转,负数为顺时针旋转。
        //在UIKit坐标下正数为顺时针旋转,负数为逆时针旋转。

        缩放变换

        1
        2
        CGContextScaleCTM (myContext, .5, .75);
        //从对象角度:所有x坐标缩小0.5,所有y坐标缩小0.75。
  • CTM变换示例代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    NSString *path = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"jpg"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];
    CGImageRef image = img.CGImage;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGContextRotateCTM(context, M_PI);
    CGContextTranslateCTM(context, -img.size.width, -img.size.height);
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    CGContextRestoreGState(context);
  • 仿射变换示例代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    NSString *path = [[NSBundle mainBundle] pathForResource:@"cat" ofType:@"jpg"];
    UIImage *img = [UIImage imageWithContentsOfFile:path];
    CGImageRef image = img.CGImage;
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);
    CGAffineTransform myAffine = CGAffineTransformMakeRotation(M_PI);
    myAffine = CGAffineTransformTranslate(myAffine, -img.size.width, -img.size.height);
    CGContextConcatCTM(context, myAffine);
    CGContextRotateCTM(context, M_PI);
    CGContextTranslateCTM(context, -img.size.width, -img.size.height);
    CGRect touchRect = CGRectMake(0, 0, img.size.width, img.size.height);
    CGContextDrawImage(context, touchRect, image);
    CGContextRestoreGState(context);