一、前言
quartz2d的api是纯c语言的,它是一个二维绘图引擎,同时支持ios和mac系统。quartz2d的api来自于core graphics框架,数据类型和函数基本都以cg作为前缀。通常,我们可以使用系统提供的控件去完成大部分ui,但是有些ui界面极其复杂、而且比较个性化,用普通的ui控件无法实现,这时可以利用quartz2d技术将控件内部的结构画出来,类似自定义控件。其实,ios中大部分控件的内容都是通过quartz2d画出来的,因此,quartz2d在ios开发中很重要的一个价值是:自定义view(自定义ui控件)。
quartz 2d能完成的工作:
- 绘制图形 : 线条三角形矩形圆弧等;
- 绘制文字;
- 绘制生成图片(图像);
- 读取生成pdf;
- 截图裁剪图片;
- 自定义ui控件;
- … …
二、图形上下文(graphics context)
图形上下文(graphics context):是一个cgcontextref类型的数据。
图形上下文的作用:
(1)保存绘图信息、绘图状态
(2)决定绘制的输出目标(绘制到什么地方去?)
(输出目标可以是pdf文件、bitmap或者显示器的窗口上)
相同的一套绘图序列,指定不同的graphics context,就可将相同的图像绘制到不同的目标上。
quartz2d提供了以下几种类型的graphics context:
(1)bitmap graphics context
(2)pdf graphics context
(3)window graphics context
(4)layer graphics context
(5)printer graphics context
将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”):
1
|
void cgcontextsavegstate(cgcontextref c) |
将栈顶的上下文出栈,替换掉当前的上下文(清空之前对于上下文设置):
1
|
void cgcontextrestoregstate(cgcontextref c) |
三、使用quartz2d自定义view
1、quartz2d自定义view
如何利用quartz2d自定义view?(自定义ui控件)如何利用quartz2d绘制东西到view上?
首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。
其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。
自定义view的步骤:
(1)新建一个类,继承自uiview
(2)实现- (void)drawrect:(cgrect)rect
方法,然后在这个方法中
(a)取得跟当前view相关联的图形上下文
1
|
cgcontextref ctx = uigraphicsgetcurrentcontext(); |
(b)绘制相应的图形内容
例如:画1/4圆(扇形)
1
2
3
4
5
6
7
8
9
10
|
cgcontextmovetopoint(ctx, 100, 100); cgcontextaddlinetopoint(ctx, 100, 150); cgcontextaddarc(ctx, 100, 100, 50, -m_pi_2, m_pi, 1); cgcontextclosepath(ctx); [[uicolor redcolor] set]; |
(3)利用图形上下文将绘制的所有内容渲染显示到view上面
1
|
cgcontextfillpath(ctx); |
注:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//m_pi的含义:π //m_pi * 2的含义:2π //m_pi_2的含义:π/2 //m_pi / 2的含义:π/2 // 画的图形路径 //bezierpathwitharccenter:弧所在的圆心 //radius:圆的半径 //startangle:开始角度,圆的最右侧为0度 //endangle:截至角度,向下为正,向上为负. //clockwise:时针的方向,yes:顺时针 no:逆时针 uibezierpath *path = [uibezierpath bezierpathwitharccenter:self.center radius:radius startangle:starta endangle:enda clockwise:no]; |
完整代码:
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
|
// // myview.m // quartz2dtest // // created by 李峰峰 on 2017/2/6. // copyright © 2017年 李峰峰. all rights reserved. // #import "myview.h" @implementation myview - (instancetype)initwithframe:(cgrect)frame { self = [super initwithframe:frame]; if (self) { self.backgroundcolor = [uicolor whitecolor]; //设置背景为白色,为了便于观察绘制后的效果 } return self; } - ( void )drawrect:(cgrect)rect { cgcontextref ctx = uigraphicsgetcurrentcontext(); cgcontextmovetopoint(ctx, 100, 100); cgcontextaddlinetopoint(ctx, 100, 150); cgcontextaddarc(ctx, 100, 100, 50, -m_pi_2, m_pi, 1); cgcontextclosepath(ctx); [[uicolor redcolor] set]; cgcontextfillpath(ctx); } @end |
运行效果:
2、核心方法drawrect:
为什么要实现drawrect:方法才能绘图到view上?
因为在drawrect:方法中才能取得跟view相关联的图形上下文
drawrect:方法在什么时候被调用?
当view第一次显示到屏幕上时(被加到uiwindow上显示出来)
调用view的setneedsdisplay或者setneedsdisplayinrect:时.
注意4点:
- 手动调用drawrect:方法,不会自动创建跟view相关联的上下文。应该调用setneedsdisplay方法,系统底层会自动调用drawrect,告诉系统重新绘制view.这样,系统底层会自动创建跟view相关联的上下文
- setneedsdisplay底层会调用drawrect,并不是立马调用的.只是设了一个调用的标志.调用时刻是等下一次屏幕刷新时才去调用drawrect。屏幕每一秒刷新30-60秒次,所以1秒调用drawrect方法大概30-60次,速度非常快哦
- view内部有个layer(图层)属性,drawrect:方法中取得的是一个layer graphics context,因此,绘制的东西其实是绘制到view的layer上去了
- view之所以能显示东西,完全是因为它内部的layer
3、quartz2d绘图的代码步骤
第一步:获得图形上下文:
1
|
cgcontextref ctx = uigraphicsgetcurrentcontext(); |
第二步:拼接路径(下面代码是绘制一条线段):
1
2
|
cgcontextmovetopoint(ctx, 10, 10); cgcontextaddlinetopoint(ctx, 100, 100); |
第三步:绘制路径:
1
|
cgcontextstrokepath(ctx); // cgcontextfillpath(ctx); |
四、quartz2d重要函数
1、常用拼接路径函数
新建一个起点
1
|
void cgcontextmovetopoint(cgcontextref c, cgfloat x, cgfloat y) |
添加新的线段到某个点
1
|
void cgcontextaddlinetopoint(cgcontextref c, cgfloat x, cgfloat y) |
添加一个矩形
1
|
void cgcontextaddrect(cgcontextref c, cgrect rect) |
添加一个椭圆
1
|
void cgcontextaddellipseinrect(cgcontextref context, cgrect rect) |
添加一个圆弧
1
2
3
|
void cgcontextaddarc(cgcontextref c, cgfloat x, cgfloat y, cgfloat radius, cgfloat startangle, cgfloat endangle, int clockwise) |
2、常用绘制路径函数
一般以cgcontextdraw、cgcontextstroke、cgcontextfill开头的函数,都是用来绘制路径的。
mode参数决定绘制的模式
1
|
void cgcontextdrawpath(cgcontextref c, cgpathdrawingmode mode) |
绘制空心路径
1
|
void cgcontextstrokepath(cgcontextref c) |
绘制实心路径
1
|
void cgcontextfillpath(cgcontextref c) |
3、矩阵操作函数
利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化。
缩放:
1
|
void cgcontextscalectm(cgcontextref c, cgfloat sx, cgfloat sy) |
旋转:
1
|
void cgcontextrotatectm(cgcontextref c, cgfloat angle) |
平移:
1
|
void cgcontexttranslatectm(cgcontextref c, cgfloat tx, cgfloat ty) |
4、其他常用函数
设置线段宽度
1
|
cgcontextsetlinewidth(ctx, 10); |
设置线段头尾部的样式
1
|
cgcontextsetlinecap(ctx, kcglinecapround); |
设置线段转折点的样式
1
|
cgcontextsetlinejoin(ctx, kcglinejoinround); |
设置颜色
1
|
cgcontextsetrgbstrokecolor(ctx, 1, 0, 0, 1); |
五、quartz2d的内存管理
关于quartz2d内存管理,有以下原则:
(1)使用含有“create”或“copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露。
(2)使用不含有“create”或“copy”的函数获取的对象,则不需要释放
(3)如果retain了一个对象,不再使用时,需要将其release掉。
(4)可以使用quartz 2d的函数来指定retain和release一个对象。例如,如果创建了一个cgcolorspace对象,则使用函数cgcolorspaceretain和cgcolorspacerelease来retain和release对象。
(5)也可以使用core foundation的cfretain和cfrelease。注意不能传递null值给这些函数。
六、quartz2d使用案例
1、画矩形、正方形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- ( void )drawrect:(cgrect)rect { //1.获取上下文 cgcontextref ctx = uigraphicsgetcurrentcontext(); //2.描述路径 uibezierpath *path = [uibezierpath bezierpathwithrect:cgrectmake(50, 50, 200, 200)]; //3.把路径添加到上下文 cgcontextaddpath(ctx, path.cgpath); [[uicolor redcolor] set]; // 路径的颜色 //4.把上下文的内容渲染到view的layer. //cgcontextstrokepath(ctx);// 描边路径 cgcontextfillpath(ctx); // 填充路径 } |
运行效果:
2、画扇形
除上面“二、使用quartz2d自定义view”中的方法外,也可以使用oc中自带画图方法实现,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- ( void )drawrect:(cgrect)rect { cgpoint center = cgpointmake(rect.size.width * 0.5, rect.size.height * 0.5); cgfloat radius = rect.size.width * 0.5 - 10; cgfloat starta = 0; cgfloat enda = -m_pi_2; // 画弧的路径 uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:no]; // 添加一根线到圆心 [path addlinetopoint:center]; // 闭合路径 [path closepath]; // 路径颜色 [[uicolor redcolor] set]; // 填充路径 [path fill]; // 描边路径 //[path stroke]; } |
运行效果:
注:
判断一个点是否在一个矩形框内
1
|
cgrectcontainspoint(rect,point); //判断point这个点是否在rect这个矩形框内 |
3、画圆形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect { //1.获取上下文 cgcontextref ctx = uigraphicsgetcurrentcontext(); //2.描述路径 // cornerradius:圆角半径。矩形的宽高都为200,如果圆角为100,那么两个角之间弧线上任意一点到矩形中心的距离都为100,所以为圆形 uibezierpath *path = [uibezierpath bezierpathwithroundedrect:cgrectmake(50, 50, 200, 200) cornerradius:100]; //3.把路径添加到上下文 cgcontextaddpath(ctx, path.cgpath); [[uicolor redcolor] set]; // 路径的颜色 //4.把上下文的内容渲染到view的layer. // cgcontextstrokepath(ctx);// 描边路径 cgcontextfillpath(ctx); // 填充路径 } |
运行效果:
4、画圆角矩形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect { //1.获取上下文 cgcontextref ctx = uigraphicsgetcurrentcontext(); //2.描述路径 // cornerradius:圆角半径。 uibezierpath *path = [uibezierpath bezierpathwithroundedrect:cgrectmake(50, 50, 200, 200) cornerradius:50]; //3.把路径添加到上下文 cgcontextaddpath(ctx, path.cgpath); [[uicolor redcolor] set]; // 路径的颜色 //4.把上下文的内容渲染到view的layer. cgcontextstrokepath(ctx); // 描边路径 //cgcontextfillpath(ctx);// 填充路径 } |
运行效果:
5、画直线
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
|
- ( void )drawrect:(cgrect)rect { //1.获取跟view相关联的上下文(uigraphics开头) cgcontextref ctx = uigraphicsgetcurrentcontext(); //2.描述路径 //一条路径可以绘制多条线 路径:path 路径绘制多条线:path使用了两次(2次的起点到终点),都是将线添加到某个点 uibezierpath *path = [uibezierpath bezierpath]; //设置起点 [path movetopoint:cgpointmake(50, 150)]; //添加一根线line到某个点 [path addlinetopoint:cgpointmake(250, 50)]; //画第二根线 [path movetopoint:cgpointmake(50, 250)]; [path addlinetopoint:cgpointmake(250, 100)]; //设置线宽 cgcontextsetlinewidth(ctx, 20); //设置线的连接样式 cgcontextsetlinejoin(ctx, kcglinejoinbevel); //设置线的顶角样式 cgcontextsetlinecap(ctx, kcglinecapround); // 圆角线条 //设置线条颜色 [[uicolor redcolor] set]; //3.把路径添加到上下文 cgcontextaddpath(ctx, path.cgpath); //4.把上下文当中绘制的内容渲染到跟view关联的layer cgcontextstrokepath(ctx); } |
运行效果:
6、画曲线
本塞尔曲线原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
- ( void )drawrect:(cgrect)rect { //1.获取跟view相关联的上下文.】 cgcontextref ctx = uigraphicsgetcurrentcontext(); //2.描述路径 uibezierpath *path = [uibezierpath bezierpath]; //画曲线,设置起点.还有一个控制点(用来控制曲线的方向跟弯曲程度) //设置起点 [path movetopoint:cgpointmake(10, 150)]; //添加一要曲线到某个点 [path addquadcurvetopoint:cgpointmake(200, 150) controlpoint:cgpointmake(150, 10)]; //3.把路径添加到上下文当中 cgcontextaddpath(ctx, path.cgpath); //4.把上下文的内容渲染view上 cgcontextstrokepath(ctx); } |
运行效果:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
- ( void )drawrect:(cgrect)rect { nsarray *dataarray = @[@25,@25,@50]; // 画弧 cgpoint center = cgpointmake(rect.size.width * 0.5, rect.size.height * 0.5); // 半径 cgfloat radius = rect.size.width * 0.5 - 10; cgfloat starta = 0; cgfloat angle = 0; cgfloat enda = 0; for (nsnumber *num in dataarray) { starta = enda; // 遍历出第一个对象25,angle =25/100 *2π,即angle = π/2,所以为1/4圆, angle = num.intvalue / 100.0 * m_pi * 2; // 截至角度= 开始的角度+ 遍历出的对象所占整个圆形的角度 enda = starta + angle; // 顺势针画贝塞尔曲线 uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes]; // 设置随机颜色 [[self randomcolor] set]; // 添加一根线到圆心 [path addlinetopoint:center]; // 填充路径 [path fill]; // 描边路径 // [path stroke]; } } /** 生成随机颜色 @return uicolor */ -(uicolor *)randomcolor{ cgfloat redlevel = rand () / ( float ) rand_max; cgfloat greenlevel = rand () / ( float ) rand_max; cgfloat bluelevel = rand () / ( float ) rand_max; return [uicolor colorwithred: redlevel green: greenlevel blue: bluelevel alpha: 1.0]; } |
运行效果:
方法二:
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
|
- ( void )drawrect:(cgrect)rect { cgpoint center = cgpointmake(self.bounds.size.width * 0.5, self.bounds.size.height * .5); cgfloat radius = self.bounds.size.width * 0.5 - 10; cgfloat starta = 0; cgfloat enda = 25 / 100.0 * m_pi * 2; uibezierpath *path = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes]; [[uicolor redcolor] set]; //添加一根线到圆心 [path addlinetopoint:center]; [path fill]; //第二个扇形 starta = enda; cgfloat angle = 25 / 100.0 * m_pi * 2; enda = starta + angle; uibezierpath *path2 = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes]; [[uicolor greencolor] set]; //添加一根线到圆心 [path2 addlinetopoint:center]; [path2 fill]; starta = enda; angle = 50 / 100.0 * m_pi * 2; enda = starta + angle; uibezierpath *path3 = [uibezierpath bezierpathwitharccenter:center radius:radius startangle:starta endangle:enda clockwise:yes]; [[uicolor bluecolor] set]; //添加一根线到圆心 [path3 addlinetopoint:center]; [path3 fill]; } /** 生成随机颜色 @return uicolor */ -(uicolor *)randomcolor{ cgfloat redlevel = rand () / ( float ) rand_max; cgfloat greenlevel = rand () / ( float ) rand_max; cgfloat bluelevel = rand () / ( float ) rand_max; return [uicolor colorwithred: redlevel green: greenlevel blue: bluelevel alpha: 1.0]; } |
注:
如果想实现点击以下变换颜色可以加上如下代码:
1
2
3
4
5
6
|
- ( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { //重绘 [self setneedsdisplay]; } |
8、绘制文字
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
|
- ( void )drawrect:(cgrect)rect { nsstring *str = @ "李峰峰博客:http://www.imlifengfeng.com/" ; nsmutabledictionary *dict = [nsmutabledictionary dictionary]; //设置字体 dict[nsfontattributename] = [uifont systemfontofsize:30]; //设置颜色 dict[nsforegroundcolorattributename] = [uicolor redcolor]; //设置描边 dict[nsstrokecolorattributename] = [uicolor bluecolor]; dict[nsstrokewidthattributename] = @3; //设置阴影 nsshadow *shadow = [[nsshadow alloc] init]; shadow.shadowcolor = [uicolor greencolor]; shadow.shadowoffset = cgsizemake(-2, -2); shadow.shadowblurradius = 3; dict[nsshadowattributename] = shadow; //设置文字的属性 //drawatpoint不会自动换行 //[str drawatpoint:cgpointmake(0, 0) withattributes:dict]; //drawinrect会自动换行 [str drawinrect:self.bounds withattributes:dict]; } |
运行效果:
9、加水印
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
48
49
50
51
|
// // viewcontroller.m // quartz2dtest // // created by 李峰峰 on 2017/2/6. // copyright © 2017年 李峰峰. all rights reserved. // #import "viewcontroller.h" #import "myview.h" @interface viewcontroller () @end @implementation viewcontroller - ( void )viewdidload { [super viewdidload]; uiimageview *myimageview = [[uiimageview alloc]initwithframe:cgrectmake(0, 0, [uiscreen mainscreen].bounds.size.width, [uiscreen mainscreen].bounds.size.height)]; [self.view addsubview:myimageview]; //生成一张图片 //0.加载图片 uiimage *oriimage = [uiimage imagenamed:@ "test" ]; //1.创建位图上下文(size:开启多大的上下文,就会生成多大的图片) uigraphicsbeginimagecontext(oriimage.size); //2.把图片绘制到上下文当中 [oriimage drawatpoint:cgpointzero]; //3.绘制水印(虽说uilabel可以快速实现这种效果,但是我们也可以绘制出来) nsstring *str = @ "李峰峰博客" ; nsmutabledictionary *dict = [nsmutabledictionary dictionary]; dict[nsfontattributename] = [uifont systemfontofsize:20]; dict[nsforegroundcolorattributename] = [uicolor redcolor]; [str drawatpoint:cgpointzero withattributes:dict]; //4.从上下文当中生成一张图片 uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext(); //5.关闭位图上下文 uigraphicsendimagecontext(); myimageview.image = newimage; } @end |
运行效果:
10、屏幕截图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
-( void )touchesbegan:(nsset<uitouch *> *)touches withevent:(uievent *)event { //生成图片 //1.开启一个位图上下文 uigraphicsbeginimagecontext(self.view.bounds.size); //2.把view的内容绘制到上下文当中 cgcontextref ctx = uigraphicsgetcurrentcontext(); //uiview内容想要绘制到上下文当中, 必须使用渲染的方式 [self.view.layer renderincontext:ctx]; //3.从上下文当中生成一张图片 uiimage *newimage = uigraphicsgetimagefromcurrentimagecontext(); //4.关闭上下文 uigraphicsendimagecontext(); //把图片转成二进制流 //nsdata *data = uiimagejpegrepresentation(newimage, 1); nsdata *data = uiimagepngrepresentation(newimage); [data writetofile:@ "/users/lifengfeng/downloads/imlifengfeng.jpg" atomically:yes]; } |
运行效果:
截图而已,就跟普通截图一样,自己试。
总结
以上就是关于quartz2d一些常用的案例,quartz2d还可以实现更多效果,具体的根据需求去实现。希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。
原文链接:http://www.imlifengfeng.com/blog/?p=514