iOS Quartz2D

一、简介

Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。我们可以使用Quartz 2D API来实现许多功能,如绘制基本路径、图形,绘制文字、阴影、图层,绘制和生成图片(图像),读取和生成PDF,自定义UI控件。
Quartz 2D在图像中使用了绘画者模型(painter’s model)。在绘画者模型中,每个连续的绘制操作都是将一个绘制层(a layer of ‘paint’)放置于一个画布,我们通常称这个画布为(Page)。 Page上的绘图可以通过额外的绘制操作来叠加更多的绘图。Page上的图形对象只能通过叠加更多的绘图来改变。这个模型允许我们使用小的图形元素来构建复杂的图形。不同的绘制顺序所产生的效果也是不一样的。Page可以是一张纸(如果输出设备是打印机),也可以是虚拟的纸张(如果输出设备是PDF文件),还可以是bitmap图像。这根据实际使用的graphics context而定。

1.图形上下文

Graphics Context是一个数据类型(CGContextRef),用于封装Quartz绘制图像到输出设备的信息,输出设备可以是PDF文件、bitmap或者显示器的窗口上。Graphics Context中的信息包括了Page中的图像的图形绘制参数和设备相关的表现形式。当使用Quartz绘图时,所有设备相关的特性都包含在我们所使用的Graphics Context中。简单地说,给Quartz绘图序列指定不同的Graphics Context,就可将相同的图像绘制到不同的设备上。

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

  • Bitmap Graphics Context
  • PDF Graphics Context
  • Window Graphics Context
  • Layer Context
  • Post Graphics Context

2.数据类型

除了 Graphics Context 之外,Quartz 2D API还定义一些数据类型。由于这些API就是Core Graphics框架的一部分,所以这些数据类型都是以CG开头的。
Quartz 2D使用这些数据类型来创建对象,通过操作这些对象来获取特定的图形。

下面列出了Quartz 2D包含的数据类型:

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

3.坐标系统

坐标系统定义是被绘制到Page上的对象的位置及大小范围。由于不同的设备有不同的图形功能,所以图像的位置及大小依赖于设备。如果在设备级别上定义坐标系统,则在一个设备上绘制的图形无法在其它设备上正常显示。
Quartz通过使用当前转换矩阵(current transformation matrix, CTM)将一个独立的坐标系统(user space)映射到输出设备的坐标系统(device space),以此来解决设备依赖问题。 CTM是一种特殊类型的矩阵(affine transform, 仿射矩阵),通过平移(translation)、旋转(rotation)、缩放(scale)操作将点从一个坐标空间映射到另外一个坐标空间。
UIKit默认的坐标系统与Quartz不同。在UIKit中,原点位于左上角,y轴正方向为向下。UIView通过将修改Quartz的Graphics Context的CTM[原点平移到左下角,同时将y轴反转(y值乘以-1)]以使其与UIView匹配。

4.内存管理

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

如果创建或拷贝一个对象,你将拥有它,因此你必须释放它。通常,如果使用含有”Create”或“Copy”单词的函数获取一个对象,当使用完后必须释放,否则将导致内存泄露。
如果使用不含有”Create”或“Copy”单词的函数获取一个对象,你将不会拥有对象的引用,不需要释放它。
如果你不拥有一个对象而打算保持它,则必须retain它并且在不需要时release掉。可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorspace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。同样,可以使用Core Foundation的CFRetain和CFRelease,但是注意不能传递NULL值给这些函数。

5.部分API

方法 说明
UIGraphicsGetCurrentContext() 获取当前图形上下文
CGContextAddPath(CGContextRef c, CGPathRef path) 将路径添加到图形上下文中
CGContextMoveToPoint(CGContextRef c, CGFloat x, CGFloat y) 在当前路径中从点(x,y)开始创建一个新路径
CGContextAddLineToPoint(CGContextRef c, CGFloat x, CGFloat y) 从当前点添加一个直线段到点(x,y)
CGContextAddRect(CGContextRef,rect) 在上下文路径中添加一个矩形
CGContextAddEllipseInRect(CGContextRef c, CGRect rect) 在上下文路径中添加一个椭圆
CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise) 在上下文路径中添加一个圆弧,点(x,y)为圆心,radius为半径,startAngle为开始角度,endAngle为结束角度,clockwise:伸展方向,为0是顺时针,为1是逆时针
CGContextAddArcToPoint(CGContextRef c,CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius) 以当前点到点(x1,y1),和(x1,y1)到(x2,y2)的两条线段为切线,画一条半径为radius的圆弧
CGContextAddCurveToPoint(CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y) 在当前点添加一个三次贝塞尔曲线,控制点1(cp1x,cp1y),控制点2(cp2x,cp2y),结束点(x,y)
CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y) 在当前点添加一个二次贝塞尔曲线,控制点(cpx,cpy),结束点(x,y)
CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode) 设置绘制模式
CGContextStrokePath(CGContextRef c) 封闭所有路径,将图形上下文的路径描线
CGContextFillPath(CGContextRef c) 封闭所有路径,将图形上下文的路径填充
CGContextClosePath(CGContextRef c) 把当前点和起点连接起来形成封闭路径
CGContextClip(CGContextRef c) 裁剪当前图形上下文中交叉的路径并使用,将所生成的路径作为后续渲染操作的路径
CGContextSetLineWidth(CGContextRef c, CGFloat width) 设置线宽
CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat * lengths, size_t count) 设置虚线(点线模式),phase表示绘制第一条虚线时跳过多少个点,lengths 用来指明虚线如何交替绘制,count 为数组lengths的长度
CGContextSetRGBStrokeColor(CGContextRef c, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) 设置Stroke渲染时颜色,也可以使用UIColor的setStroke方法来设置,set方法同时设置StrokeColor和FillColor
CGContextSetRGBFillColor(CGContextRef, CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) 设置Fill渲染时颜色,也可以使用UIColor的setFill方法来设置,set方法同时设置StrokeColor和FillColor
CGContextSetLineCap(CGContextRef c, CGLineCap cap) 设置线段头尾部样式
CGContextSetLineJoin(CGContextRef c, CGLineJoin join) 设置线段连接点样式
CGContextSetTextDrawingMode(CGContextRef c, CGTextDrawingMode mode) 设置文字绘制模式
CGContextSetBlendMode(CGContextRef c, CGBlendMode mode) 设置混合模式
CGContextSetShadow(CGContextRef c, CGSize offset, CGFloat blur) 设置阴影,offset 为阴影在X,Y方向上的偏移量;blur 为阴影的模糊程度,值越大,阴影越模糊。
CGContextSetShadowWithColor(CGContextRef c, CGSize offset, CGFloat blur, CGColorRef color) 其他参数和上一个方法一样,只是多了个 color ,用来设置阴影颜色
CGContextSaveGState(CGContextRef c) 将当前的图形上下文拷贝一份,保存到图形上下文栈的栈顶,路径不被视为图形状态的一部分,因此不保存
CGContextRestoreGState(CGContextRef c) 将栈顶的图形上下文出栈,替换当前的图形上下文
CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy) 对当前转换矩阵作缩放操作
CGContextRotateCTM(CGContextRef c, CGFloat angle) 对当前转换矩阵作旋转操作
CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty) 对当前转换矩阵作平移操作
CGContextConcatCTM(CGContextRef c, CGAffineTransform transform) 通过 transform 变换矩阵来对 CGContextRef 的 转换矩阵(坐标系统)进行变换
CGAffineTransform CGContextGetCTM(CGContextRef c) 获取 CGContextRef 的坐标系统的变换矩阵

注: 填充一个路径的时候,路径里面的子路径都是独立填充的。假如是重叠的路径,决定一个点是否被填充,有两种规则:

1.nonzero winding number rule:非零绕数规则,假如一个点被从左到右跨过,计数器+1,从右到左跨过,计数器-1,最后,如果结果是0,那么不填充,如果是非零,那么填充。
2.even-odd rule: 奇偶规则,假如一个点被跨过,那么+1,最后是奇数,那么要被填充,偶数则不填充,和方向没有关系。

二、简单使用

1.绘制基本图形

在 - (void)drawRect:(CGRect)rect 方法中调用 Quartz2D 的 API

绘制主要步骤:
1.获得图形上下文
2.绘制并拼接绘图路径
3.将路径添加到图形上下文中
4.渲染图形上下文

① 绘制线段

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 0, 0);
CGContextAddLineToPoint(ctx, 100, 100);
CGContextStrokePath(ctx);

② 绘制三角形

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 0, 0);
CGContextAddLineToPoint(ctx, 100, 100);
CGContextAddLineToPoint(ctx, 120, 80);
CGContextAddLineToPoint(ctx, 0, 0);
//或者 CGContextClosePath(ctx);
CGContextStrokePath(ctx);  //描线
//CGContextFillPath(ctx);  //填充

③ 绘制矩形/正方形

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextStrokePath(ctx);  //描线
//CGContextFillPath(ctx);  //填充

④ 绘制椭圆/圆

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextStrokePath(ctx);  //描线
//CGContextFillPath(ctx);  //填充

⑤ 绘制圆环

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, 100, 100));
CGContextSetLineWidth(ctx, 20); //线宽
CGContextStrokePath(ctx);  

⑥ 绘制圆弧

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddArc(ctx, 100, 100, 50, 0, M_PI, 1);
CGContextStrokePath(ctx);  

⑦ 绘制虚线
CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat * lengths, size_t count),用来设置怎样来绘制虚线。

lengths 指明虚线如何交替绘制,若lengths 的值为{10,10},表示先绘制10个点,在跳过10个点,在绘制10个点,在跳过10个点,如此反复;若lengths的值为{10,20,10},表示先绘制10个点,跳过20个点,绘制10个点,跳过10个点,绘制20个点,跳过10个点,如此反复;数组中的值循环使用。
phase 表示绘制第一条虚线时跳过多少个点,若 phase 为 8,lengths 为 {10,10},则绘制第一条虚线时,先跳过8个点,在绘制(10 - 8)个点,第一条虚线的长度仅有2个点;phase 的值仅影响第一条虚线的绘制;
count 的值为数组lengths的长度。

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat lengths[] = {10.0,10.0}; 
CGContextSetLineDash(ctx, 0, lengths, 2);
CGContextMoveToPoint(ctx, 10, 50);
CGContextAddLineToPoint(ctx, 300, 50);
CGContextStrokePath(ctx);

⑧ 绘制曲线
CGContextAddCurveToPoint(CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y) 用来在当前点的位置开始绘制一条三次贝塞尔曲线,参数为两个控制点 (cp1x,cp1y) 和 (cp2x,cp2y) ,还有一个结束点(x,y);

三次贝塞尔曲线.gif

CGContextAddQuadCurveToPoint(CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y) 用来在当前点的位置开始绘制一条二次贝塞尔曲线,参数为一个控制点(cpx,cpy) 和 一个结束点(x,y);

二次贝塞尔曲线.gif
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 50, 50);
CGContextAddQuadCurveToPoint(ctx, 125, 184, 300, 50);
CGContextAddCurveToPoint(ctx, 200, 100, 10, 300, 200, 500);
CGContextStrokePath(ctx);

⑨ 绘制圆角矩形
自定义一个函数,来绘制圆角矩形。

 void CGContextAddRoundRect(CGContextRef __nullable c, CGRect rect, CGFloat radius){
    CGContextSaveGState(c);
    CGFloat x = rect.origin.x;
    CGFloat y = rect.origin.y;
    CGFloat width = rect.size.width;
    CGFloat height = rect.size.height;

    CGContextMoveToPoint(c, x + radius, y);
    CGContextAddLineToPoint(c, x + width - radius, y);
    CGContextAddArcToPoint(c, x + width, y, x + width, y + radius, radius);
    CGContextAddLineToPoint(c, x + width, y + height - radius);
    CGContextAddArcToPoint(c, x + width, y + height, x + width - radius, y + height, radius);
    CGContextAddLineToPoint(c, x + radius, y + height);
    CGContextAddArcToPoint(c, x, y + height, x, y + height - radius, radius);
    CGContextAddLineToPoint(c, x, y + radius);
    CGContextAddArcToPoint(c, x, y, x + radius, y, radius);
    CGContextRestoreGState(c);
}

⑩ 绘制多角星(限定角数为奇数)
自定义一个函数,来绘制奇数多角形。

void CGContextAddStar(CGContextRef __nullable c, NSInteger n, CGFloat dx, CGFloat dy, CGFloat radius){
    CGContextSaveGState(c);
    CGContextMoveToPoint(c, dx , dy - radius);
    CGFloat angle = 4 * M_PI / n;

    for (int i = 1; i <= n; i++) {
        CGFloat x = dx - sinf(i * angle) * radius;
        CGFloat y = dy - cosf(i * angle) * radius;
        CGContextAddLineToPoint(c, x, y);
    }
    CGContextRestoreGState(c);
}

2.绘制文字

使用NSString的 drawAtPoint:withAttributes: 方法 和 drawInRect:withAttributes: 方法

方法 说明
- (void)drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs 在某个点开始绘制文字
- (void)drawInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs 在一个范围内绘制文字

① 绘制文字

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetTextDrawingMode(ctx, kCGTextStroke);
CGContextSetShadowWithColor(ctx, CGSizeMake(8, -6), 5, [UIColor grayColor].CGColor);
NSString * str = @"Quartz2D";
NSMutableDictionary * attrs = [NSMutableDictionary dictionary];
attrs[NSForegroundColorAttributeName] = [UIColor redColor];
attrs[NSFontAttributeName] = [UIFont systemFontOfSize:18];
[str drawAtPoint:CGPointZero withAttributes:attrs];
[str drawInRect:CGRectMake(0, 0, 100, 100) withAttributes:attrs];

② 在图片上绘制文字(图片水印)

UIImage * oldImage = [UIImage imageNamed:@"image"];
//创建一个基于位图的上下文(context),并将其设置为当前上下文(context),size:大小,opaque:是否透明,scale:缩放比例
UIGraphicsBeginImageContextWithOptions(oldImage.size, NO, 0.0);
 //在指定的点开始绘画图片,相当于在图片上添加了一个空白的图层
[oldImage drawAtPoint:CGPointZero];
NSString * text =  @"quartz2D";
NSDictionary *dict = @{
                       NSFontAttributeName : [UIFont systemFontOfSize:15],
                       NSForegroundColorAttributeName : [UIColor redColor]
                       };
//在图层上绘制文字
[text drawAtPoint:CGPointZero withAttributes:dict];
// 通过当前的图形上下文获取图片
UIImage * newImage =  UIGraphicsGetImageFromCurrentImageContext();
// 关闭图形上下文
UIGraphicsEndImageContext();
imageView.image = newImage;

3.渐变填充

CGGradientRef 用来绘制渐变的对象。

方法 说明
CGGradientCreateWithColors(CGColorSpaceRef space, CFArrayRef colors, const CGFloat * locations) 通过颜色数组来创建CGGradientRef
CGGradientCreateWithColorComponents(CGColorSpaceRef space, const CGFloat * components, const CGFloat * locations, size_t count) 通过自定义颜色组件来创建CGGradientRef
CGGradientRelease(CGGradientRef gradient) 释放渐变对象

CGColorSpaceRef : 用来定义颜色空间,所谓颜色空间,简单的说就是不同颜色在不同的维度上取值最终组成一种颜色的过程。例如RGB,可以看成是一个三维坐标系,x、y、z轴坐标系分别代表红、绿、蓝三种颜色,那么在这三个坐标上分别取0~255范围内的不同值就可以组成各类颜色。当然,不同颜色空间的“坐标系”(颜色模型)是不同的(即颜色表示的方式是不同的),常用的颜色空间有RGB、CMYK、Gray等。
CGColorRef : CoreGaphics中颜色对象

方法 说明
CGColorSpaceCreateDeviceRGB( ) 创建RGB颜色空间
CGColorSpaceCreateDeviceCMYK( ) 创建CMYK颜色空间
CGColorSpaceCreateDeviceGray( ) 创建Gray颜色空间
CGColorSpaceRelease(CGColorSpaceRef space) 释放颜色空间
CGColorCreate(CGColorSpaceRef space, const CGFloat * components) 在颜色空间中根据颜色组件创建一个颜色对象

components : 颜色组件的数组,这个数组中具体如何存储颜色要根据颜色空间而定;如果颜色空间使用的是RGB,那么数组中的元素四个为一组,分别是red(红)、green(绿)、blue(蓝)、alpha(透明度);如果使用CMYK颜色空间,那么数组中的元素五个为一组,分别是cyan(青)、magenta(洋红)、yellow(黄)、black(黑)、alpha(透明度)。

locations : 渐变色所在的位置,默认范围是0.0 ~ 1.0。一个位置对应着一组颜色组件表示的颜色

count : locations数组的长度

注:使用方法名中带有“Create”字样的方法创建出来的对象,在该对象不再使用时,要使用对象的Release方法销毁对象。

绘制渐变填充有两种方式:
① 线性渐变:渐变色以直线方式从开始位置逐渐向结束位置渐变
② 径向渐变:渐变色以中心点为圆心开始向四周辐射,直到终止渐变

方法 说明
CGContextDrawLinearGradient(CGContextRef c, CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint,CGGradientDrawingOptions options) 绘制线性渐变,startPoint : 开始位置,endPoint : 结束位置,options : 绘制方式
CGContextDrawRadialGradient(CGContextRef c, CGGradientRef gradient, CGPoint startCenter, CGFloat startRadius, CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options) 绘制径向渐变,startCenter : 开始位置,startRadius : 起点半径(通常为0,否则在此该半径范围内容无任何填充内容),endCenter : 结束位置(通常和起始点相同,否则会有偏移),endRadius : 终点半径(渐变的扩散圆范围),options : 绘制方式

CGGradientDrawingOptions : 渐变的绘制方式有两种;
kCGGradientDrawsBeforeStartLocation 开始位置之前就进行绘制,到结束位置之后不再绘制;
kCGGradientDrawsAfterEndLocation开始位置之前不进行绘制,到结束点之后继续填充。

① 绘制线性渐变

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef rgbSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef beginColor = CGColorCreate(rgbSpace, (CGFloat[]){0.0, 0.0, 0.0, 1.0});
CGColorRef endColor = CGColorCreate(rgbSpace, (CGFloat[]){1.0, 1.0, 1.0, 1.0});
// 创建颜色数组
CFArrayRef colors = CFArrayCreate(kCFAllocatorDefault, (const void*[]){beginColor, endColor}, 2, nil); 
CGFloat locations[2] = {0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColors(rgbSpace, colors, locations);
CFRelease(colors);
CGColorRelease(beginColor);
CGColorRelease(endColor);
CGContextDrawLinearGradient(ctx, gradient, CGPointMake(0, 0), CGPointMake(0, 500), kCGGradientDrawsAfterEndLocation);
CGColorSpaceRelease(rgbSpace);
CGGradientRelease(gradient);

② 绘制径向渐变

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef rgbSpace = CGColorSpaceCreateDeviceRGB();
CGFloat compoents[12] = {
    1.0, 1.0, 1.0, 1.0,
    0.5, 0.5, 0.5, 1.0,
    0.0, 0.0, 0.0, 1.0
};
CGFloat locations[3] = {0, 0.5, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgbSpace, compoents, locations, 3);
CGPoint startCenter = CGPointMake(200, 320);
CGPoint endCenter = CGPointMake(160, 280);
CGContextDrawRadialGradient(ctx, gradient, startCenter, 10, endCenter, 150, kCGGradientDrawsBeforeStartLocation);
CGColorSpaceRelease(rgbSpace);
CGGradientRelease(gradient);

4.模式填充

使用自定义的一种样式在一个区域上平铺填充,类似于铺瓷砖。这里用“砖块”来代表自定义的样式。Quartz 2D支持两种模式填充:有颜色填充和无颜色填充;有颜色填充就是在绘制砖块时就指定颜色,在调用填充时就不用再指定砖块颜色;无颜色模式就是绘制砖块时不用指定任何颜色,在调用填充时再指定具体填充颜色。推荐使用前者。

需要构建一个符合CGPatternDrawPatternCallback签名的回调函数,遵守 void 函数名 (void * info,CGContextRef context)的签名,这个函数专门用来创建“砖块”。

方法 说明
CGColorSpaceCreatePattern(CGColorSpaceRef baseSpace) 创建模式填充的颜色空间,在有颜色模式填充下,baseSpace的值为NULL;在无颜色模式填充下,baseSpace的值为基于设备的颜色空间。
CGContextSetFillColorSpace(CGContextRef c, CGColorSpaceRef space) 在图形上下文中设置颜色空间
CGPatternCreate(void * info, CGRect bounds, CGAffineTransform matrix, CGFloat xStep, CGFloat yStep, CGPatternTiling tiling, bool isColored, const CGPatternCallbacks * callbacks) 创建CGPatternRef,info : 传递给callback的参数,bounds : “砖块”的大小,matrix : 转换矩阵,xStep : “砖块”横向间距,yStep : “砖块”纵向间距,tiling : 平铺模式,isColored : 是否指定了颜色,callbacks : 回调函数结构体指针。
CGColorCreateWithPattern(CGColorSpaceRef space, CGPatternRef pattern, const CGFloat * components) 通过填充模式颜色组件创建颜色
CGContextSetFillPattern(CGContextRef c, CGPatternRef pattern, const CGFloat * components) 设置填充模式颜色组件
CGContextSetFillColorWithColor(CGContextRef c, CGColorRef color) 设置图形上下文填充颜色
CGContextFillRect(CGContextRef c, CGRect rect) 填充一个区域
CGPatternRelease(CGPatternRef pattern) 释放CGPatternRef对象

① 有颜色模式填充

void ColoredPatternCallback(void * info, CGContextRef context){
    CGFloat side = 20;
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextFillRect(context, CGRectMake(0, 0, side, side));
    CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextFillRect(context, CGRectMake(0, side, side, side));
    CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextFillRect(context, CGRectMake(side, 0, side, side));
    CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextFillRect(context, CGRectMake(side, side, side, side));
}

- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern(NULL);
    CGContextSetFillColorSpace(ctx, colorSpace);
    CGPatternCallbacks callback = {0,ColoredPatternCallback,NULL};
    CGPatternRef pattern = CGPatternCreate(NULL, CGRectMake(0, 0, 40, 40), CGAffineTransformIdentity, 40, 40, kCGPatternTilingNoDistortion, true, &callback);
    CGFloat alpha = 1;
    CGColorRef color = CGColorCreateWithPattern(colorSpace, pattern, &alpha);
    CGContextSetFillColorWithColor(ctx, color);
    CGContextFillRect(ctx, CGRectMake(0, 0, 320, 500));
    CGColorRelease(color);
    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}

② 无颜色模式填充

void PatternCallback(void * info,CGContextRef context){
    CGContextFillRect(context, CGRectMake(0, 0, 20, 20));
    CGContextFillRect(context, CGRectMake(20, 20, 20, 20));
}

- (void)drawRect:(CGRect)rect{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef rgbSpace = CGColorSpaceCreateDeviceRGB();
    CGColorSpaceRef colorSpace = CGColorSpaceCreatePattern(rgbSpace);
    CGContextSetFillColorSpace(ctx, colorSpace);
    CGContextSetStrokeColorSpace(ctx, colorSpace);
    CGPatternCallbacks callback = {0, PatternCallback, NULL};
    CGPatternRef pattern = CGPatternCreate(NULL, CGRectMake(0, 0, 40, 40), CGAffineTransformIdentity, 40, 40, kCGPatternTilingNoDistortion, false, &callback);
    CGFloat components[] = {1.0, 0.0, 0.0, 1.0};
    CGContextSetFillPattern(ctx, pattern, components);
    CGContextSetStrokePattern(ctx, pattern, components);
    CGContextFillRect(ctx, CGRectMake(20, 20, 200, 200));
    CGContextStrokeRectWithWidth(ctx, CGRectMake(20,240, 200, 200), 5);
    CGColorSpaceRelease(rgbSpace);
    CGColorSpaceRelease(colorSpace);
    CGPatternRelease(pattern);
}

5.绘制位图

方法 说明
UIGraphicsBeginImageContext(CGSize size) 开启一个绘制位图的图形上下文,size 为区域大小
UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) 开启一个绘制位图的图形上下文,size 为区域大小,opaque 设置透明,为YES代表透明,NO代表不透明,scale代表缩放,0.0 代表不缩放,可以用[UIScreen mainScreen].scale来获取
UIGraphicsGetImageFromCurrentImageContext() 从图形上下文中获取当前绘制的图形,该方法返回一个UIImage对象
UIGraphicsEndImageContext() 关闭图形上下文
UIRectFill(CGRect rect) 在当前绘图环境所创建的内存中的图片上填充一个矩形
UIRectFillUsingBlendMode(CGRect rect, CGBlendMode blendMode) 在当前绘图环境所创建的内存中的图片上填充一个矩形,绘制时使用指定的混合模式
UIRectFrame(CGRect rect) 在当前绘图环境所创建的内存中的图片上绘制一个矩形边框
UIRectFrameUsingBlendMode(CGRect rect, CGBlendMode blendMode) 在当前绘图环境所创建的内存中的图片上绘制一个矩形边框,绘制时使用指定的混合模式
UIRectClip(CGRect rect) 裁剪当前图形上下文中的区域

UIImage 本身也提供了一些方法:

方法 说明
- (void)drawAtPoint:(CGPoint)point 将该图片本身绘制到当前绘图CGContextRef的指定点,point 为该图片的绘制点
- (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha 将该图片本身绘制到当前绘图CGContextRef的指定点,point 为该图片的绘制点,同时还指定绘制图片的叠加模式 blendMode 和透明度 alpha
- (void)drawInRect:(CGRect)rect 将该图片本身绘制到当前绘图CGContextRef的指定区域内,rect 为该图片的绘制区域
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha 将该图片本身绘制到当前绘图CGContextRef的指定区域内,rect 为该图片的绘制区域,同时还指定绘制图片的叠加模式 blendMode 和透明度 alpha
- (void)drawAsPatternInRect:(CGRect)rect 将该图片本身采用填充模式绘制到当前绘图CGContextRef的指定区域内

① 绘制图片

UIImage * image = [UIImage imageNamed:@"Quartz2D"];
[image drawAtPoint:CGPointZero];
[image drawInRect:CGRectMake(0, 0, 100, 100)]; //图片不够大则拉伸填充
[image drawAsPatternInRect:CGRectMake(0, 0, 100, 100)]; //图片不够大则平铺填充

② 合成图片

UIImage * bigImage = [UIImage imageNamed:@"big.jpg"];
UIImage * smallImage = [UIImage imageNamed:@"small.jpg"];

UIGraphicsBeginImageContextWithOptions(bigImage.size, NO, 0.0);
[bigImage drawInRect:CGRectMake(0, 0, bigImage.size.width, bigImage.size.height)];
[smallImage drawInRect:CGRectMake(0, 0, smallImage.size.width, smallImage.size.height)];

UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

③ 裁剪图片

UIImage * oldImage = [UIImage imageNamed:@"Quartz2D"];
UIGraphicsBeginImageContextWithOptions(oldImage.size, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect circleRect = CGRectMake(0, 0, oldImage.size.width, oldImage.size.height);
CGContextAddEllipseInRect(ctx, circleRect);
CGContextClip(ctx);
[oldImage drawInRect:circleRect];
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

④ 屏幕截图

UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * screenImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

//给UIImage 添加一个类方法
+ (UIImage *)captureView:(UIView *)targetView{
    UIGraphicsBeginImageContextWithOptions(targetView.frame.size, NO, 0.0);
    [targetView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容