Quartz 2D绘图

概况

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替我们完成。

Quartz 2D绘图_第1张图片

3.Quartz 2D 数据类型

除了 Graphics Context 之外,Quartz 2D API还定义一些数据类型。由于这些API就Core Graphics框架的一部分,所以这些数据类型都是以CG开头的。
下面列出了Quartz 2D包含的数据类型:

数据类型 功能
CGPathRef 用于向量图,可创建路径,并进行填充或描画(stroke)
CGImageRef 用于表示bitmap图像和基于采样数据的bitmap图像遮罩
CGLayerRef 用于表示可用于重复绘制(如背景)和幕后(offscreen)绘制的绘画层
CGPatternRef 用于重绘图
CGShadingRef CGGradientRef 用于绘制渐变
CGFunctionRef 用于定义回调函数,该函数包含一个随机的浮点值参数。当为阴影创建渐变时使用该类型
CGColorRef CGColorSpaceRef 用于告诉Quartz如何解释颜色,颜色空间
CGImageSourceRef CGImageDestinationRef 用于在Quartz中移入移出数据
CGPatternRef 用于绘制文本
CGPDFDictionaryRef CGPDFObjectRef CGPDFPageRef CGPDFStream CGPDFStringRef CGPDFArrayRef 用于访问PDF的元数据
CGPDFScannerRef CGPDFContentStreamRef 用于解析PDF元数据
CGPSConverterRef 用于将PostScript转化成PDF。在iOS中不能使用
4.图形状态

Quartz通过修改当前图形状态(current graphics state)来修改绘制操作的结果。图形状态包含用于绘制程序的参数。绘制程序根据这些绘图状态来决定如何渲染结果。
可使用函数CGContextSaveGState来保存图形状态,CGContextRestoreGState来还原图形状态。

5.Quartz 2D 坐标系统

坐标系统定义是被绘制到Page上的对象的位置及大小范围,如下图所示。我们在用户空间坐标系统中指定图形的位置及大小。坐标值是用浮点数来定义的。从下图我们可以看出与我们UIView的坐标系Y轴是相反的,使用时要变换坐标系。

Quartz 2D绘图_第2张图片

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中曲线绘制分为两种:二次贝塞尔曲线和三次贝塞尔曲线。二次曲线只有一个控制点,而三次曲线有两个控制点,如下图所示


Quartz 2D绘图_第3张图片
     /*绘制二次贝塞尔曲线
     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通过组合两个或多个对象来生成一个组合图形。组合图形被看成是单一对象。当需要在一组对象上使用特效时,透明层非常有用,下图显示了在透明层中绘制三个矩形,其中将这三个矩形当成一个整体来渲染阴影.

Quartz 2D绘图_第4张图片

- (void)transparencyMenthod{
    /*
     调用函数CGContextBeginTransparencyLayer
     在透明层中绘制需要组合的对象
     调用函数CGContextEndTransparencyLayer
     */

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetShadowWithColor(context, CGSizeMake(-10, 10), 10, [UIColor grayColor].CGColor);
    
    CGContextBeginTransparencyLayer(context, NULL);
    CGContextAddArc(context, centerX-50, centerY+50, 100, 0, M_PI*2, 0);
    CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
    CGContextDrawPath(context, kCGPathFill);
    
    CGContextAddArc(context, centerX+50, centerY+50, 100, 0, M_PI*2, 0);
    CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
    CGContextDrawPath(context, kCGPathFill);
    
    CGContextAddArc(context, centerX, centerY-50, 100, 0, M_PI*2, 0);
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextDrawPath(context, kCGPathFill);
    CGContextEndTransparencyLayer(context);
}

颜色渐变

Quartz提供了两个不透明数据odgago创建渐变:CGShadingRefCGGradientRef。我们可以使用任何一个来创建轴向(axial)或径向(radial)渐变。一个渐变是从一个颜色到另外一种颜色的填充。

  1. 轴向渐变(也称为线性渐变)沿着由两个端点连接的轴线渐变。所有位于垂直于轴线的某条线上的点都具有相同的颜色值。
  2. 径向渐变也是沿着两个端点连接的轴线渐变,不过路径通常由两个圆来定义。
1.轴向渐变
- (void)drawAxialGradualChange{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGColorSpaceRef coloSpace = CGColorSpaceCreateDeviceRGB();
    
    /*指定渐变色
     space:颜色空间
     components:颜色数组,注意由于指定了RGB颜色空间,那么四个数组元素表示一个颜色(red、green、blue、alpha),
     如果有三个颜色则这个数组有4*3个元素
     locations:颜色所在位置(范围0~1),这个数组的个数不小于components中存放颜色的个数
     count:渐变个数,等于locations的个数
     */
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3] = {0,3/10.0,1};
    CGGradientRef gradient = CGGradientCreateWithColorComponents(coloSpace, compoents, locations, 3);
    
    /*绘制线性渐变
     context:图形上下文
     gradient:渐变色
     startPoint:起始位置
     endPoint:终止位置
     options:绘制方式,kCGGradientDrawsBeforeStartLocation 开始位置之前就进行绘制,到结束位置之后不再绘制,
     kCGGradientDrawsAfterEndLocation开始位置之前不进行绘制,到结束点之后继续填充
     */
    CGContextDrawLinearGradient(context, gradient, CGPointMake(50, 100), CGPointMake(300, 100), kCGGradientDrawsAfterEndLocation);
    
    //释放颜色空间(**使用create和copy创建的对象记得释放**)
    CGColorSpaceRelease(coloSpace);
}

2.径向渐变
- (void)drawRadialGradualChange{
    //获取图形上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //创建RGB颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
    /*指定渐变色
     space:颜色空间
     components:颜色数组,注意由于指定了RGB颜色空间,那么四个数组元素表示一个颜色(red、green、blue、alpha),
     如果有三个颜色则这个数组有4*3个元素
     locations:颜色所在位置(范围0~1),这个数组的个数不小于components中存放颜色的个数
     count:渐变个数,等于locations的个数
     */
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3]={0,0.3,1.0};
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
    
    /*绘制径向渐变
     context:图形上下文
     gradient:渐变色
     startCenter:起始点位置
     startRadius:起始半径(通常为0,否则在此半径范围内容无任何填充)
     endCenter:终点位置(通常和起始点相同,否则会有偏移)
     endRadius:终点半径(也就是渐变的扩散长度)
     options:绘制方式,kCGGradientDrawsBeforeStartLocation 开始位置之前就进行绘制,但是到结束位置之后不再绘制,
     kCGGradientDrawsAfterEndLocation开始位置之前不进行绘制,但到结束点之后继续填充
     */
    CGContextDrawRadialGradient(context, gradient, CGPointMake(160, 284),0, CGPointMake(165, 289), 150, kCGGradientDrawsAfterEndLocation);
    
    //释放颜色空间
    CGColorSpaceRelease(colorSpace);
}
3.渐变填充

上面我们只是绘制渐变到图形上下文,实际开发中有时候我们还需要填充对应的渐变色,例如现在绘制了一个矩形,如何填充成渐变色呢?在此可以利用渐变裁切来完成,特别说明一下区域裁切并不仅仅适用于渐变填充,对于其他图形绘制仍然适用,并且注意裁切只能限于矩形裁切。

- (void)drawGradualFill{
   /*上面我们只是绘制渐变到图形上下文
     实际开发中有时候我们还需要填充对应的渐变色,例如现在绘制了一个矩形,如何填充成渐变色
     可以利用渐变裁切来完成
    */
    
    //裁切处一块矩形用于显示,注意必须先裁切再调用渐变
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextClipToRect(context, CGRectMake(50, 100, kScreenWidth-100, 300));
    
    CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        1.0,1.0,1.0,1.0
    };
    CGFloat locations[3]={0,0.3,1.0};
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
    CGContextDrawLinearGradient(context, gradient, CGPointMake(20, 50), CGPointMake(300, 300), kCGGradientDrawsAfterEndLocation);
    CGColorSpaceRelease(colorSpace);
}

叠加模式

使用Quartz 2D绘图时后面绘制的图像会覆盖前面的,默认情况下如果前面的被覆盖后将看不到后面的内容,但是有时候这个结果并不是我们想要的,因此在Quartz 2D中提供了填充模式供开发者配置调整。由于填充模式类别特别多,因此下面以一个例子来说明:

- (void)superpositionPattern{
    CGRect rect= CGRectMake(0, 130.0, 320.0, 50.0);
    CGRect rect1= CGRectMake(0, 390.0, 320.0, 50.0);
    
    CGRect rect2=CGRectMake(20, 50.0, 10.0, 250.0);
    CGRect rect3=CGRectMake(40.0, 50.0, 10.0, 250.0);
    CGRect rect4=CGRectMake(60.0, 50.0, 10.0, 250.0);
    CGRect rect5=CGRectMake(80.0, 50.0, 10.0, 250.0);
    CGRect rect6=CGRectMake(100.0, 50.0, 10.0, 250.0);
    CGRect rect7=CGRectMake(120.0, 50.0, 10.0, 250.0);
    CGRect rect8=CGRectMake(140.0, 50.0, 10.0, 250.0);
    CGRect rect9=CGRectMake(160.0, 50.0, 10.0, 250.0);
    CGRect rect10=CGRectMake(180.0, 50.0, 10.0, 250.0);
    CGRect rect11=CGRectMake(200.0, 50.0, 10.0, 250.0);
    CGRect rect12=CGRectMake(220.0, 50.0, 10.0, 250.0);
    CGRect rect13=CGRectMake(240.0, 50.0, 10.0, 250.0);
    CGRect rect14=CGRectMake(260.0, 50.0, 10.0, 250.0);
    CGRect rect15=CGRectMake(280.0, 50.0, 10.0, 250.0);
    
    CGRect rect16=CGRectMake(30.0, 310.0, 10.0, 250.0);
    CGRect rect17=CGRectMake(50.0, 310.0, 10.0, 250.0);
    CGRect rect18=CGRectMake(70.0, 310.0, 10.0, 250.0);
    CGRect rect19=CGRectMake(90.0, 310.0, 10.0, 250.0);
    CGRect rect20=CGRectMake(110.0, 310.0, 10.0, 250.0);
    CGRect rect21=CGRectMake(130.0, 310.0, 10.0, 250.0);
    CGRect rect22=CGRectMake(150.0, 310.0, 10.0, 250.0);
    CGRect rect23=CGRectMake(170.0, 310.0, 10.0, 250.0);
    CGRect rect24=CGRectMake(190.0, 310.0, 10.0, 250.0);
    CGRect rect25=CGRectMake(210.0, 310.0, 10.0, 250.0);
    CGRect rect26=CGRectMake(230.0, 310.0, 10.0, 250.0);
    CGRect rect27=CGRectMake(250.0, 310.0, 10.0, 250.0);
    CGRect rect28=CGRectMake(270.0, 310.0, 10.0, 250.0);
    CGRect rect29=CGRectMake(290.0, 310.0, 10.0, 250.0);
    
    
    [[UIColor yellowColor]set];
    UIRectFill(rect);
    
    [[UIColor greenColor]setFill];
    UIRectFill(rect1);
    
    [[UIColor redColor]setFill];
    UIRectFillUsingBlendMode(rect2, kCGBlendModeClear);
    UIRectFillUsingBlendMode(rect3, kCGBlendModeColor);
    UIRectFillUsingBlendMode(rect4, kCGBlendModeColorBurn);
    UIRectFillUsingBlendMode(rect5, kCGBlendModeColorDodge);
    UIRectFillUsingBlendMode(rect6, kCGBlendModeCopy);
    UIRectFillUsingBlendMode(rect7, kCGBlendModeDarken);
    UIRectFillUsingBlendMode(rect8, kCGBlendModeDestinationAtop);
    UIRectFillUsingBlendMode(rect9, kCGBlendModeDestinationIn);
    UIRectFillUsingBlendMode(rect10, kCGBlendModeDestinationOut);
    UIRectFillUsingBlendMode(rect11, kCGBlendModeDestinationOver);
    UIRectFillUsingBlendMode(rect12, kCGBlendModeDifference);
    UIRectFillUsingBlendMode(rect13, kCGBlendModeExclusion);
    UIRectFillUsingBlendMode(rect14, kCGBlendModeHardLight);
    UIRectFillUsingBlendMode(rect15, kCGBlendModeHue);
    UIRectFillUsingBlendMode(rect16, kCGBlendModeLighten);
    
    UIRectFillUsingBlendMode(rect17, kCGBlendModeLuminosity);
    UIRectFillUsingBlendMode(rect18, kCGBlendModeMultiply);
    UIRectFillUsingBlendMode(rect19, kCGBlendModeNormal);
    UIRectFillUsingBlendMode(rect20, kCGBlendModeOverlay);
    UIRectFillUsingBlendMode(rect21, kCGBlendModePlusDarker);
    UIRectFillUsingBlendMode(rect22, kCGBlendModePlusLighter);
    UIRectFillUsingBlendMode(rect23, kCGBlendModeSaturation);
    UIRectFillUsingBlendMode(rect24, kCGBlendModeScreen);
    UIRectFillUsingBlendMode(rect25, kCGBlendModeSoftLight);
    UIRectFillUsingBlendMode(rect26, kCGBlendModeSourceAtop);
    UIRectFillUsingBlendMode(rect27, kCGBlendModeSourceIn);
    UIRectFillUsingBlendMode(rect28, kCGBlendModeSourceOut);
    UIRectFillUsingBlendMode(rect29, kCGBlendModeXOR);
}

Quartz 2D绘图_第5张图片

相信大家对比代码和显示效果并不难发现每种叠加的效果。例子中只是使用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);
}
Quartz 2D绘图_第6张图片

上下文变换

我们知道在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;
}
Quartz 2D绘图_第7张图片
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绘图)