移动开发(IOS) – Quartz 2D绘图
1.Quartz 2D
1.1.Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。
1.2.Quartz 2D API可以实现许多功能,如基于路径的绘图、透明度、阴影、颜色管理、反锯齿、PDF文档生成和PDF元数据访问等。
1.3.Quartz 2D API是Core Graphics框架的一部分,因此其中的很多数据类型和方法都是以CG开头的。会经常见到Quartz 2D(Quartz)和Core Graphics两个术语交互使用。
1.4.Quartz 2D与分辨率和设备无关,因此在使用Quartz 2D绘图时,无需考虑最终绘图的目标设备。
1.5.Quartz中默认的坐标系统是:原点(0, 0)在左下角。沿着X轴从左到右坐标值逐渐增大;沿着Y轴从下到上坐标值逐渐增大。
1.6.坐标系的转换:
CGContextRotateCTM(CGContextRef c, CGFloat angle) | 相对原点旋转上下文坐标系 |
CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty) | 相对原点平移上下文坐标系 |
CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy) | 缩放上下文坐标系 |
1.6.1.转换坐标系前,使用 CGContextSaveGState(CGContextRef c) 保存当前上下文状态
1.6.2.坐标系转换后,使用 CGContextRestoreGState(CGContextRef c) 可以恢复之前保存的上下文状态
1.7.Quartz 2D的绘图是有顺序的,后面画的可以覆盖前面画的。
2.Core Graphics
2.1.Core Graphic 框架是一组基于 C 的 API,当使用 UIKit 创建按钮、标签或者其他 UIView 的子类时,UIKit 会用 Core Graphics 将这些元素绘制在屏幕上。此外,UIEvent ( UIKit 中的事件处理类)也会使用 Core Graphics,用来帮助确定触摸事件在屏幕上所处的位置。
2.2.因为 UIKit 依赖于 Core Graphics,所以当引入 <UIKit/Uikit.h> 时,Core Graphics 框架会被自动引入,即 UIKit 内部已经引入了 Core Graphics 框架的主头文件: <CoreGraphics/CoreGraphics.h>。
2.3.UIKit 内部封装了 Core Graphics 的一些 API,可以快速生成通用的界面元素。
3.Graphics Context
3.1.Graphics Context 是一个数据类型 ( CGContextRef ),封装了 Quartz 绘制图像到输出设备的信息。输出设备可以是 PDF 文件、 Bitmap 或者显示器的窗口。
3.2.Quartz 中所有的对象都是绘制到一个 Graphics Context 中。
3.3.当用 Quartz 绘图时,所有设备相关的特性都包含在 Graphics Context 中。换句话说,我们可以简单地给Quartz 绘图序列指定不同的 Graphics Context,就可将相同的图像绘制到不同的设备上。而不需要任何设备相关的计算,这些都由 Quartz 替我们完成。
3.4.Quartz 提供了以下几种类型的 Graphics Context:
3.4.1.Bitmap Graphics Context
3.4.2.PDF Graphics Context
3.4.3.Window Graphics Context
3.4.4.Layer Graphics Context
3.4.6.Printer Graphics Context
3.5.一个 Graphics Context 表示一个绘制目标。它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息。
3.6.Graphics Context 定义了基本的绘制属性,如颜色、裁减区域、线条宽度和样式信息、字体信息、混合模式等。
3.7.在 iOS 应用程序中,如果要在屏幕上进行绘制,需要创建一个 UIView 对象,并实现它的 drawRect: 方法。视图的 drawRect: 方法在视图显示在屏幕上及它的内容需要更新时被调用。
3.8.在调用自定义的 drawRect: 后,视图对象自动配置绘图环境以便能立即执行绘图操作。
3.9.作为配置的一部分,视图对象将为当前的绘图环境创建一个 Graphics Context 。通过调用 UIGraphicsGetCurrentContext() 方法可以获取当前的 Graphics Context。
3.10.UIView 中的 UIGraphicsGetCurrentContext 方法返回的图形上下文的坐标系统的原点位于左上角,而沿着 Y 轴从上到下坐标值逐渐增大。于是,在绘图时无需进行坐标转换。
4.利用 Quartz 2D 绘制 UIView
4.1.当在 UIView 子类中重写 drawRect: 方法时,iOS 会自动准备好一个图形上下文,可以通过调用 UIGraphicsGetCurrentContext() 来获取。
4.2.只要一个 UIView 需要被刷新或者重绘,drawRect: 方法就会被调用,所以 drawRect: 的调用频率很高。
4.3.重绘时应该调用 setNeedsDisplay ,而不能直接调用 drawRect:, setNeedsDisplay 会自动调用 drawRect: 。
4.4.drawRect: 注意事项:
4.4.1.drawRect: 是在 UIViewController 的 loadView 和 viewDidLoad 两方法之后调用的。
4.4.2.如果在 UIView 初始化时没有设置 CGRect,drawRect: 将不会被自动调用。
4.4.3.如果设置 UIView 的 contentMode 属性值为 UIViewContentModeRedraw,那么将在每次更改 frame 时自动调用 drawRect: 。
4.4.4.如果使用 UIView 绘图,只能在 drawRect: 方法中获取相应的 CGContextRef 并绘图。而在其他方法中获取的 CGContextRef 不能用于绘图。
5.Quartz内存管理
5.1.使用含有 “Create” 或 “Copy” 的函数创建的对象,使用完后必须释放,否则将导致内存泄露。使用不含有“Create”或“Copy”的函数获取的对象,则不需要释放。
5.2.如果 retain 了一个对象,不再使用时,需要将其 release 掉。可以使用 Quartz 2D 的函数来指定 retain 和 release 一个对象。例如,如果创建了一个 CGColorSpace 对象,则使用函数 CGColorSpaceRetain 和 CGColorSpaceRelease 来 retain 和 release 对象。也可以使用 Core Foundation 的 CFRetain 和 CFRelease 。注意不能传递 NULL 值给这些函数。
6.基本绘图
6.1.绘制直线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
// 1. 获取上下文(UIView对应的上下文)
CGContextRef context = UIGraphicsGetCurrentContext();
//使用Ref声明的对象,不需要用*
// 2. 创建可变的路径并设置路径 (当我们开发动画的时候,通常需要指定对象运动的路线,然后由动画方法负责实现动画效果)
CGMutablePathRef path = CGPathCreateMutable();
// 画线
// 1) 设置起始点
CGPathMoveToPoint(path,
NULL
, 50, 50);
// 2) 设置目标点
CGPathAddLineToPoint(path,
NULL
, 200, 200);
CGPathAddLineToPoint(path,
NULL
, 50, 200);
// 3) 封闭路径
// a) 直接指定目标点
CGPathAddLineToPoint(path,
NULL
, 50, 50);
// b) 使用关闭路径方法
CGPathCloseSubpath(path);
// 3. 将路径添加到上下文
CGContextAddPath(context, path);
// 4. 设置上下文属性
//在使用rgb颜色设置时,最好不要同时指定 rgb 和 alpha,否则会对性能造成一定影响
//默认线条和填充颜色都是黑色
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextSetRGBFillColor(context, 0.0, 1.0, 0.0, 1.0);
// 设置线条宽度
CGContextSetLineWidth(context, 5.0);
// 设置线条的顶点样式
CGContextSetLineCap(context, kCGLineCapRound);
// 设置线条的连接点样式
CGContextSetLineJoin(context, kCGLineJoinRound);
// 设置线条的虚线样式
/*
* 虚线的参数
* context 上下文对象
* phase 相位,虚线起始的位置,通常使用0即可,从头开始画虚线
* lengths 长度的数组
* count lengths数组的个数
*/
CGFloat lengths[2] = {20.0, 10.0};
CGContextSetLineDash(context, 0.0, lengths, 2);
// 5. 绘制路径
/*
* kCGPathStroke: 画线(空心)
* kCGPathFill: 填充(实心)
* kCGPathFillStroke: 即画线又填充
*/
CGContextDrawPath(context, kCGPathFillStroke);
// 6. 释放路径
CGPathRelease(path);
|
使用默认的 context 进行绘制直线:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 1. 获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 2. 设置当前上下文的路径
// 1) 设置起始点
CGContextMoveToPoint(context, 50, 50);
// 2) 增加点
CGContextAddLineToPoint(context, 200, 200);
CGContextAddLineToPoint(context, 50, 200);
// 3) 关闭路径
CGContextClosePath(context);
// 3 设置属性
/*
* UIKit默认会导入Core Graphics框架,UIKit对常用的很多CG方法做了封装
* UIColor setStroke 设置边线颜色
* UIColor setFill 设置填充颜色
* UIColor set 设置边线和填充颜色
*/
// 设置边线
// [[UIColor redColor]setStroke];
// 设置填充
// [[UIColor blueColor]setFill];
// 设置边线和填充
[[UIColor greenColor]set];
// 4 绘制路径,虽然没有直接定义路径,但是第2步操作,就是为上下文指定路径
CGContextDrawPath(context, kCGPathFillStroke);
|
6.2.绘制矩形
1
2
3
4
5
6
|
CGRect rect = CGRectMake(50, 50, 200.0, 200.0);
[[UIColor redColor]set];
// 绘制实心矩形
UIRectFill(rect);
// 绘制空心矩形
UIRectFrame(CGRectMake(50, 300, 100, 100));
|
6.3.绘制圆形
1
2
3
4
5
6
7
8
|
// 1. 取出上下文
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(50, 50, 200, 100);
// 2. 设置路径
UIRectFrame(rect);
CGContextAddEllipseInRect(context, rect);
// 3. 绘制路径
CGContextDrawPath(context, kCGPathFillStroke);
|
6.4.绘制圆弧
1
2
3
4
5
6
7
8
9
10
11
12
|
// 1. 设置路径
/*
* CGContextAddArc(context, x, y, radius, startAngle, endAngle, clockwise);
* context 上下文
* x,y 是圆弧所在圆的中心点坐标
* radius 半径,所在圆的半径
* startAngle endAngle 起始角度和截止角度 单位是弧度
* clockwise 顺时针 0 或者逆时针 1
*/
CGContextAddArc(context, 160, 230, 100, -M_PI_4, M_PI_4, 0);
// 2. 绘制圆弧
CGContextDrawPath(context, kCGPathFill);
|
6.5.绘制文字
1
2
3
4
5
6
7
8
9
10
11
|
NSString
*string =
@"Hello world"
;
// 获取字体 ( [UIFont familyNames] )
UIFont *font = [UIFont fontWithName:
@"Marker Felt"
size:20];
// 在指定点绘制字符串
[string drawAtPoint:CGPointMake(50, 50) withFont:font];
// 如果在UILabel中,可以将numbersOfLine设置为0,并且指定足够的高度即可
CGRect rect = CGRectMake(50, 50, 210, 360);
[[UIColor lightGrayColor]set];
UIRectFill(rect);
[[UIColor redColor]set];
[string drawInRect:rect withFont:font lineBreakMode:
NSLineBreakByWordWrapping
alignment:
NSTextAlignmentCenter
];
|
6.6.绘制图像
1
2
3
4
5
6
7
8
|
UIImage *image = [UIImage imageNamed:
@"image.png"
];
// 绘制之后,就无法改变位置,也没有办法监听手势识别
// 在指定点绘制图像
[image drawAtPoint:CGPointMake(50, 50)];
// 会在指定的矩形中拉伸绘制
[image drawInRect:CGRectMake(0, 0, 320, 460)];
// 在指定矩形区域中平铺图片
[image drawAsPatternInRect:CGRectMake(0, 0, 320, 460)];
|
7.绘制渐变
7.1.径向渐变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 1. 创建颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 2. 创建渐变
/*
* colorSpace 颜色空间 RGB
* components 数组,每4个一组,表示一个颜色 {r, g, b, a, r, g, b, a}
* locations 表示渐变开始的位置
*/
CGFloat components[8] = {1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0};
CGFloat locations[2] = {0.3, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
// 渐变的区域剪裁 (整个渐变实际上是完整绘制在屏幕上的,通过裁剪区域,可以让指定范围内显示渐变效果)
// 3. 设置裁剪区域范围
// 4. 绘制渐变
/*
* context 上下文
* gradient 渐变
* startCenter 起始中心点
* startRadius 起始半径,如果指定为0,就从圆心开始渐变,否则,会空出指定半径的位置,不填充
* endCenter 截止点(通常和起始中心点重合,即便偏移,也不会太大)
* endRadius 截止半径
* 渐变填充方式
*/
CGContextDrawRadialGradient(context, gradient, CGPointMake(160, 230), 10, CGPointMake(0, 0), 150, kCGGradientDrawsAfterEndLocation);
// 5. 释放对象
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
|
7.2.线性渐变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// 1. 创建颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 2. 创建渐变
/*
* colorSpace 颜色空间 rgb
* components 数组,每4个一组,表示一个颜色 {r, g, b, a, r, g, b, a}
* location 表示渐变开始的位置
*/
CGFloat components[8] = {1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0};
CGFloat locations[2] = {0.0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
// 渐变的区域剪裁 (整个渐变实际上是完整绘制在屏幕上的,通过裁剪区域,可以让指定范围内显示渐变效果)
CGContextClipToRect(context, CGRectMake(0, 360, 200, 100));
// 3. 设置裁剪区域范围
CGRect rects[5] = {CGRectMake(0, 0, 100, 100),
CGRectMake(200, 0, 100, 100),
CGRectMake(100, 100, 100, 100),
CGRectMake(200, 200, 100, 100),
CGRectMake(0, 200, 100, 100)};
CGContextClipToRects(context, rects, 5);
// 4. 绘制渐变
CGContextDrawLinearGradient(context, gradient, CGPointMake(0.0, 0.0), CGPointMake(320.0, 460.0), kCGGradientDrawsAfterEndLocation);
// 5. 释放对象
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
|
8.生成PDF文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 1. 创建PDF上下文
/*
* path 保存 PDF 文件的路径
* bounds 大小如果指定 CGRectZero,则建立612 * 792大小的页面
* documentInfo 文档信息
*/
NSArray
*array =
NSSearchPathForDirectoriesInDomains
(
NSDocumentDirectory
,
NSUserDomainMask
,
YES
);
NSString
*path = [array[0] stringByAppendingPathComponent:
@"test.pdf"
];
UIGraphicsBeginPDFContextToFile(path, CGRectZero,
NULL
);
// 2. 创建PDF内容
/*
* PDF 中是分页的,要一个页面一个页面的创建
* 使用 UIGraphicsBeginPDFPage 方法可以创建一个 PDF 的页面
*/
for
(
NSInteger
i = 0; i < 6; i++) {
// 1) 创建PDF页面,每个页面的装载量是有限的
if
(i % 2 == 0) {
UIGraphicsBeginPDFPage();
}
// 2) 将Image添加到PDF文件 (一个页面装2张图片)
NSString
*fileName = [
NSString
stringWithFormat:
@"NatGeo%02d.png"
, i + 1];
UIImage *image = [UIImage imageNamed:fileName];
[image drawInRect:CGRectMake(0, (i % 2) * 396, 612, 396)];
}
// 3. 关闭PDF上下文
UIGraphicsEndPDFContext();
|
9.Quantz 2D 基本绘图方法
函数 | 方法 |
CGContextBeginPath | 开始一个新路径 |
CGContextMoveToPoint | 设置路径的起点 |
CGContextClosePath | 关闭路径 |
CGContextAddPath | 添加路径 |
CGContextAddLineToPoint | 在指定点添加线 |
CGContextAddLines | 添加多条线 |
CGContextAddRect | 添加矩形 |
CGContextAddRects | 添加多个矩形 |
CGContextAddEllipseInRect | 在矩形区域中添加椭圆 |
CGContextAddArc | 添加圆弧 |
CGContextAddArcToPoint | 在指定点添加圆弧 |
CGContextAddCurveToPoint | 在指定点添加曲线 |
CGContextDrawPath | 绘制路径 |
CGContextFillPath | 实心路径 |
CGContextFillRect | 实心矩形 |
CGContextFillRects | 多个实心矩形 |
CGContextFillEllipseInRect | 在矩形区域中绘制实心椭圆 |
CGContextStrokePath | 空心路径 |
CGContextStrokeRect | 空心矩形 |
CGContextStrokeRectWithWidth | 使用宽度绘制空心矩形 |
CGContextStrokeEllipseInRect | 在矩形区域中绘制空心椭圆 |
CGContextSetLineWidth | 设置线宽 |
CGContextSetBlendMode | 设置混合模式 |
CGContextSetShouldAntialias | 设置抗锯齿效果 |
CGContextSetLineCap | 设置线条收尾点样式 |
CGContextSetLineJoin | 设置线条连接点样式 |
CGContextSetLineDash | 设置虚线 |