iOS成长之路

Core Graphics 之 Transforms (六)

2018-08-27  本文已影响0人  Niko_peng
Transforms

Quartz 2D绘图模型定义了两个完全独立的坐标空间:表示文档页面的用户空间和表示设备本机分辨率的设备空间。用户空间坐标是与设备空间像素分辨率无关的浮点数。当您想打印或显示文档时,Quartz会将用户空间坐标映射到设备空间坐标。因此,您不必重写应用程序或编写额外的代码来调整应用程序的输出以在不同设备上进行最佳显示。

您可以通过操作当前转换矩阵(CTM)来修改默认用户空间。在创建图形上下文之后,CTM是单位矩阵。您可以使用Quartz转换函数来修改CTM,从而在用户空间中修改绘图。

这一章:

提供可用于执行转换的函数的概述
演示如何修改CTM
描述如何创建仿射变换
演示如何确定两个转换是否等效
描述如何获得从用户到设备的空间转换
讨论仿射变换背后的数学原理

About Quartz Transformation Functions

您可以使用Quartz 2D内置转换函数轻松地转换、缩放和旋转绘图。只需几行代码,您就可以以任意顺序和任意组合应用这些转换。图5-1展示了缩放和旋转图像的效果。应用的每个转换都会更新CTM。CTM总是表示用户空间和设备空间之间的当前映射。此映射确保应用程序的输出在任何显示屏幕或打印机上看起来都很好。


屏幕快照 2018-08-27 下午2.02.50.png

Quartz 2D API提供了五个函数,允许您获取、修改CTM。你可以旋转,平移,缩放CTM,你可以连接一个仿射变换矩阵与CTM。

Quartz还允许您在决定将转换应用到CTM之前创建不会在用户空间上操作的仿射转换。您可以使用另一组函数来创建仿射变换,然后可以将其与CTM连接起来。看到创建仿射变换。

你可以使用任意一组函数而不需要了解矩阵数学。但是,如果您想了解调用转换函数之一Quartz会做什么,请阅读矩阵背后的数学原理。

Modifying the Current Transformation Matrix

在绘制图像之前,您可以操纵CTM旋转、缩放或转换页面,从而转换将要绘制的对象。在转换CTM之前,需要保存图形状态,以便在绘制之后恢复它。您还可以使用仿射变换(参见创建仿射变换)连接CTM。这四个操作(转换、旋转、缩放和连接)中的每一个都将在本节中与执行每个操作的CTM函数一起描述。

以下代码行绘制图像,假设您提供了一个有效的图形上下文、一个指向绘制图像的矩形的指针和一个有效的CGImage对象。代码绘制图像,例如图5-2所示的示例公鸡图像。在阅读本节的其余部分时,您将看到在应用转换时图像是如何变化的。

CGContextDrawImage (myContext, rect, myImage);
屏幕快照 2018-08-27 下午2.06.08.png

平移将坐标空间的原点移动到指定的x轴和y轴上。您可以调用函数CGContextTranslateCTM,以指定数量修改每个点的x和y坐标。图5-3显示了在x轴上翻译100个单位和y轴上翻译50个单位的图像,代码如下:

CGContextTranslateCTM (myContext, 100, 50);
屏幕快照 2018-08-27 下午2.07.12.png

旋转以指定的角度移动坐标空间。您可以调用函数cgcontext - trotatectm来指定以弧度表示的旋转角度。图5-4显示了一个围绕原点旋转-45度的图像,该原点位于窗口的左下方,使用以下代码行:

CGContextRotateCTM (myContext, radians(–45.));

由于旋转将图像的一部分移动到上下文之外的位置,因此将对图像进行剪切。你需要用弧度来表示旋转角度。

如果您计划执行多次旋转,那么编写一个弧度例程是很有用的。

#include <math.h>
static inline double radians (double degrees) {return degrees * M_PI/180;}
屏幕快照 2018-08-27 下午2.08.29.png

缩放可以通过指定的x和y因子改变坐标空间的大小,有效地拉伸或收缩图像。x和y因子的大小决定了新坐标的大小。另外,通过使x因子为负,你可以沿着x轴翻转坐标;类似地,你可以沿着y轴水平翻转坐标,让y因子为负。您可以调用函数CGContextScaleCTM来指定x和y缩放因子。图5-5显示了一个图像,其x值被缩放了0.5,y值被缩放了0.75,使用以下代码行:

CGContextScaleCTM (myContext, .5, .75);
屏幕快照 2018-08-27 下午2.09.25.png

串联通过将两个矩阵相乘组合在一起。您可以将几个矩阵串联起来,形成一个包含矩阵累积效应的单个矩阵。您可以调用函数CGContextConcatCTM来将CTM与仿射变换结合起来。在创建仿射变换时,我们讨论了仿射变换以及创建仿射变换的函数。

另一种实现累积效果的方法是执行两个或多个转换,而不恢复转换调用之间的图形状态。图5-6显示了一个图像,它是通过翻译图像并旋转图像得到的,使用以下代码行:

CGContextTranslateCTM (myContext, w,h);
CGContextRotateCTM (myContext, radians(-180.));

Figure 5-7 shows an image that is translated, scaled, and rotated, using the following lines of code:

CGContextTranslateCTM (myContext, w/4, 0);
CGContextScaleCTM (myContext, .25,  .5);
CGContextRotateCTM (myContext, radians ( 22.));
屏幕快照 2018-08-27 下午2.10.37.png

执行多个转换的顺序很重要;如果颠倒顺序会得到不同的结果。反转用于创建图5-7的转换顺序,您将得到图5-8所示的结果,该结果由以下代码生成:

CGContextRotateCTM (myContext, radians ( 22.));
CGContextScaleCTM (myContext, .25,  .5);
CGContextTranslateCTM (myContext, w/4, 0);
屏幕快照 2018-08-27 下午2.11.26.png
Creating Affine Transforms

Quartz中的仿射变换函数作用于矩阵,而不是CTM。您可以使用这些函数构造一个矩阵,稍后通过调用CGContextConcatCTM函数应用到CTM。仿射变换函数要么对CGAffineTransform数据结构进行操作,要么返回。您可以构造可重用的简单或复杂仿射转换。

仿射变换函数执行与CTM函数相同的操作—平移、旋转、缩放和连接。表5-1列出了执行这些操作的函数及其使用信息。注意,每个平移、旋转和缩放操作都有两个函数。


屏幕快照 2018-08-27 下午2.12.33.png

Quartz还提供了一个仿射变换函数,用于反转矩阵CGAffineTransformInvert。倒置通常用于提供被转换对象内的点的反向转换。当您需要恢复一个被一个矩阵转换过的值时,反转可能是有用的:反转矩阵,然后将这个值乘以反转矩阵,结果就是原始值。您通常不需要反向转换,因为您可以通过保存和恢复图形状态来逆转转换CTM的效果。
在某些情况下,您可能不想转换整个空间,而只想转换一个点或大小。通过调用函数CGPointApplyAffineTransform对CGPoint结构进行操作。通过调用函数CGSizeApplyAffineTransform来操作CGSize结构。您可以通过调用函数CGRect结构来操作CGRect结构。这个函数返回最小的矩形,其中包含传递给它的矩形的转角点。如果对矩形进行操作的仿射变换只执行缩放和平移操作,返回的矩形与由四个转换角构成的矩形重合。

您可以通过调用CGAffineTransformMake函数来创建一个新的仿射变换,但与其他进行新仿射变换的函数不同,这个函数需要提供矩阵项。要有效地使用这个函数,您需要了解矩阵数学。看看矩阵背后的数学原理。

Evaluating Affine Transforms

您可以通过调用CGAffineTransformEqualToTransform函数来确定一个仿射变换是否等于另一个仿射变换。如果传递给该函数的两个转换相等,则该函数返回true,否则返回false。

CGAffineTransformIsIdentity是用于检查转换是否为恒等转换的有用函数。身份转换不执行平移、缩放或旋转。对输入坐标应用这个转换总是返回输入坐标。石英常数cgaffine实体表示身份变换。

Getting the User to Device Space Transform

通常,当您使用Quartz 2D绘制时,您只在用户空间中工作。Quartz为您负责用户和设备空间之间的转换。如果您的应用程序需要获得Quartz用于在用户和设备空间之间进行转换的仿射变换,您可以调用函数CGContextGetUserSpaceToDeviceSpaceTransform。

Quartz提供了一些方便的功能,可以在用户空间和设备空间之间转换下列几何图形。您可能会发现这些函数比应用从CGContextGetUserSpaceToDeviceSpaceTransform函数返回的仿射变换更容易使用。

1、点。CGContextConvertPointToDeviceSpace和CGContextConvertPointToUserSpace函数将CGPoint数据类型从一个空间转换为另一个空间。
2、大小。CGContextConvertSizeToDeviceSpace和CGContextConvertSizeToUserSpace函数将CGSize数据类型从一个空间转换为另一个空间。
3、矩形。CGContextConvertRectToDeviceSpace和CGContextConvertRectToUserSpace函数将CGRect数据类型从一个空间转换为另一个空间。

The Math Behind the Matrices

唯一需要理解矩阵数学的石英2D函数是CGAffineTransformMake函数,它从一个3x3矩阵中的6个关键元素进行仿射变换。即使您从未计划从头构建仿射变换矩阵,您可能会发现转换函数背后的数学原理很有趣。如果没有,你可以跳过这一章的其余部分。
3×3变换矩阵a、b、c、d、tx和ty的6个临界值如下表所示:


屏幕快照 2018-08-27 下午2.16.06.png

注意:矩阵最右边的列总是包含常量0,0,1。从数学上讲,这第三列是允许连接的,本节稍后将对此进行解释。它出现在本节只是为了数学正确性。
给定上述3×3变换矩阵,Quartz使用这个方程将一个点(x, y)转换为一个合成点(x ', y '):


屏幕快照 2018-08-27 下午2.17.00.png
结果是在一个不同的坐标系中,这个坐标系是由变换矩阵中的变量值所变换的。下面的方程是前一个矩阵变换的定义:
屏幕快照 2018-08-27 下午2.17.30.png
下面的矩阵是单位矩阵。它不执行平移、缩放或旋转。将这个矩阵乘以输入坐标总是返回输入坐标。
屏幕快照 2018-08-27 下午2.18.05.png

使用前面讨论的公式,您可以看到这个矩阵将生成一个新的点(x ', y '),它与旧点(x, y)相同:


屏幕快照 2018-08-27 下午2.18.45.png
这个矩阵描述了一个平移操作:
屏幕快照 2018-08-27 下午2.19.21.png
这些是Quartz用来应用转换的结果方程:
屏幕快照 2018-08-27 下午2.19.52.png
这个矩阵描述了点(x, y)的缩放操作:
屏幕快照 2018-08-27 下午2.20.22.png
这些是Quartz用于缩放坐标的结果方程:
屏幕快照 2018-08-27 下午2.21.00.png
这个矩阵描述了一个旋转操作,将点(x, y)逆时针旋转一个角度a:
屏幕快照 2018-08-27 下午2.21.35.png
这些是Quartz用来应用旋转的结果方程:
屏幕快照 2018-08-27 下午2.22.07.png

这个方程将一个旋转操作和一个平移操作连接起来:


屏幕快照 2018-08-27 下午2.22.35.png

这些是Quartz用来应用转换的结果方程:


屏幕快照 2018-08-27 下午2.23.07.png

注意,连接矩阵的顺序是重要的——矩阵乘法不是可交换的。也就是说,矩阵A乘以矩阵B的结果不一定等于矩阵B乘以矩阵A的结果。

如前所述,连接是仿射变换矩阵包含第三列的原因,该列的值为0,0,1。要将一个矩阵与另一个矩阵相乘,一个矩阵的列数必须与另一个矩阵的行数相匹配。这意味着一个2x3矩阵不能与一个2x3矩阵相乘。因此,我们需要包含常量值的额外列。

反转操作从转换后的坐标产生原始坐标。给定坐标(x, y),这个坐标(x, y)已经被一个给定的矩阵a转换成新的坐标(x ', y '),将坐标(x ', y ')转换成矩阵a的逆会得到原始坐标(x, y),当一个矩阵乘以它的逆,结果是单位矩阵。

上一篇 下一篇

猜你喜欢

热点阅读