本会会详细介绍ios中的我所理解的绘图知识,希望对CGContext的坐标和变换不太熟练的朋友有所帮助。所谓难者不会,会者不难,对这方面精通的朋友轻喷。
要搞懂CGContext的坐标和变换,先要知道屏幕坐标和CGContext的数据结构。
可以这样理解,屏幕上的像素是一个二维数组(专业叫矩阵),行数代表屏幕像素高度,列数代表屏幕像素宽度。它的第一行(对应坐标x=0)在最上边,第一列(对应y=0)在屏幕最左边。
屏幕坐标系在UIView的drawRect中绘图时,屏幕要展示的内容由CGContext来指定,那么CGContext中应该也会有对应的一个二维数组(矩阵),数组中的一个元素代表一个RGBA, 指定屏幕中对应位置的像素应该展示的颜色。CGContext中的二维数组的第一行(对应x=0)在最下边,第一列(对应y=0)在最左边。
(我是CGContext坐标系)CGContext的二维数组和屏幕中的像素二维数组有一个对应关系(也叫映射), CGContext数组的第一行指定屏幕像素数组的第一行要显示的像素,CGContext数组的第二行指定屏幕像素数组的第二行要显示的像素,以此类推。虽然CGContext在绘制的时候,坐标支持浮点数,但是最终生成的坐标都是整数。
举个例子,我们使用CGContext绘制一条直线,比如从(100,100)到(200,200),屏幕刷新后展示的图像如下图
示例代码:
调用MoveToPoint和AddLineToPoint后,会将直线的数据写入CGContext的二维数组里,考虑到UIScreen的scale,这条直线的起点应该是100*scale行, 100*scale列,终点是200*scale行,200*scale列。(起点与终点之间的颜色数据由系统的CGContext指定,我们不用管)
都是些基础的东西,大家都是聪明人,一看就明白了。但是基础的东西很重要,CGContext中的数据结构和其与屏幕像素的映射关系,是一切图形变换的基础,所有的图形变换,都是先作用在CGContext的二维数组,再映射到屏幕上的。
二维的图像变换比较简单,常用的有平移,旋转和缩放, quartz2d给我们提供了对应的接口,CGContextTranslateCTM,CGContextRotateCTM和CGContextScaleCTM,(在其他的系统框架下,也会有图形变换的功能,如CALayer的transform属性,这里只讨论CGContext的绘图变换),下边分别介绍各个变换的执行过程。
1. 平移变换。
为了方便说明问题,假设UIScreen的scale为1。 以(100,100)-(200,200)的直线图形为例,向右平移100,即x=x+100
直线向右平移100示例代码:
指定向右平移100的变换矩阵,调用MoveToPoint和AddLineTooPoint,会向CGContext的二维数组中写入直线的数据。直线的起点为
(100+100,100),终点为(200+100,100),这个很好理解。(在底层的变换计算中,平移矩阵应该是作用到了图元的每一个点上,即组成这个图元的所有点的x都变成了x+100,下边的旋转和缩放同理)
2. 旋转变换
以(100,100)-(200,200)的直线图形为例,在CGContext的数据结构中逆时针旋转M_PI_2/4(M_PI_2是弧度,代表角度90,即一个直角),那么在屏幕上显示的就是顺时针旋转M_PI_2/4。旋转变换是以坐标原点为中心,在图元初始位置进行旋转。
示例代码:
在向CGContext的二维数组中写入直线数据时,直线的起点变成了(100*√2*cos(M_PI_2*3/4),100*√2*sin(M_PI_2*3/4)),直线的终点变成了
(200*√2*cos(M_PI_2*3/4),200*√2*sin(M_PI_2*3/4)),我们不用管旋转变换的具体计算过程,CGContext的api会帮我们计算好。
3. 缩放变换
以(100,100)-(200,200)的直线图形为例,x轴方向不变,即缩放倍数为1.0,y轴方向缩放为原来的0.5
示例代码:
直线的起点变成了(100,100*0.5),终点变成了(200,200*0.5)。
这些基础变换单独使用的时候非常容易理解,但是组合在一起的时候要怎么理解呢?
举个例子,我需要绘制一个围绕自己中心点旋转45度(M_PI_2/2)的正方形,代码如下:
如果按照代码执行的顺序去对图元进行变换,那么我们得到的结果会和实际想要的结果大相径庭,那么该怎么理解矩阵的组合变换呢?
为了说明这个问题,我们假设组成图元的数据为G,平移变换为T,旋转变换为R,缩放变换为S,那么上边的示例的执行过程可以写成
TRTG,可以这样理解,最先作用到图元G的变换矩阵是离它最近的T,即第一步的变换过程是TR(TG),第二步执行旋转变换,此时为
T(RG1),G1为TG运算后的结果,最后执行TG2,G2为RG1的执行结果,也就是说,组合变换的实际执行顺序可以理解为和代码的顺序相反。图例如下(变换都是先作用于CGContext中的图元,这个要记清楚):
初始图元:
CGContext中的数据执行CGContextTranslateCTM(context, -150, -150)
CGContext中的数据执行CGContextRotateCTM(context, M_PI_2/2)后(绘图软件不支持旋转,自己用CGContext绘制的)
CGContext中的数据最后执行CGContextTranslateCTM(context, 150, 150);
CGContext中的数据这只是CGContext的二维数组中保存的图元数据,最终映射到屏幕上
在屏幕坐标系下显示的图形到这里,我们应该理解了CGContext相关的组合变换。(想继续深入了解图形变换知识的朋友,可以去看看计算机图形学,本文只是普及一下基础知识)
组合变换一般在绘制图片和Core Text的时候用的比较多,下边分别介绍这两种情况。
1. 图片的绘制
在UIView的drawRect中,使用CGContextDrawImage直接绘制从硬盘加载(从硬盘加载,从硬盘加载,重要的说三遍)的图片,会发现这个图片在屏幕上是上下颠倒的,如下图:
这时,我们需要使用矩阵变换把它翻转过来。
在屏幕上是翻转的,那么在CGContext的二维数组中,这个图片应该是正常的,如下图
CGContext如何让图片在屏幕上是正常的呢?聪明的朋友应该想到了,在CGContext中使用变换把图片倒转,显示到屏幕上的时候就是倒转的倒转,就会变正常了。
示例代码如下
使用的正是我们前边介绍的矩阵变换的知识,可以自己想象一下变换执行的过程(一定要记得变换的作用顺序和代码的顺序是不一样的哦)。翻转图片的变换不止一种,这里只介绍最容易理解的。
其实还有一个不使用矩阵变换的方法,就是自己先使用ImageContext 绘制一张要显示的图片,再在drawRect中使用自己绘制的图片绘制,此时图片在屏幕上显示也是正常的,示例代码如下:
具体是为什么在这里就不详细讲了(可以提示一下,图片的数据也是一个二维数组,第一行在最上边,和屏幕一样),这个要创建额外的context,不是性能最优的方案。
2. 在Core Text中的简单运用
这里只探讨矩阵变换在Core Text中的运用,更多Core Text的知识请大家去看别的文章(ps:我是core text小白)
示例代码:
在屏幕上显示的结果:
文字都是翻转的,我们需要使用矩阵变换将文字翻转过来。原理和图片翻转类似。
从屏幕显示结果可以猜测,在CGContext中的数据应该如下图所示:
我们发现,textRect指定的original不起作用,Core Text内部使用的画布区域应该如图中红框所示为(0,0,100,200),并且文字的第一行是从画布的顶部开始
我们使用类似翻转图片的方法
添加图中红色框框的变换,缩放和平移变换后,CGContext中的数据如下:
再映射到屏幕上,就是我们想要的效果了,文字的具体位置,可以使用translate去控制,默认的origianl应该是(0,0)
本文涵盖了坐标变换的大部分基础知识点,希望对不熟悉这方面知识的朋友有所帮助。
最后总结一下绘图的流程:指定变换矩阵->绘制图元->CGContext二维数组数据计算->映射到屏幕。
如果只想对单独的图元进行变换,在绘制完图元后去掉变换的作用,可以在变换之前Save当前的CGContext,在绘制完后
Restore CGContext就可以了
网友评论