美文网首页iOS 图像处理
ios CGContext与屏幕坐标映射关系和矩阵变换

ios CGContext与屏幕坐标映射关系和矩阵变换

作者: 淡清风 | 来源:发表于2018-12-24 19:37 被阅读0次

本会会详细介绍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就可以了

相关文章

网友评论

    本文标题:ios CGContext与屏幕坐标映射关系和矩阵变换

    本文链接:https://www.haomeiwen.com/subject/xqpakqtx.html