Quartz 2D绘图模型定义了两个完全独立的坐标空间:表示文档页面的用户空间和表示设备本机分辨率的设备空间。 用户空间坐标是与设备空间中像素的分辨率无关的浮点数。 当您打印或显示文档时,Quartz会将用户空间坐标映射到设备空间坐标。 因此,您不必重写应用程序或编写额外的代码来调整应用程序的输出,以便在不同设备上实现最佳显示。
您可以通过对当前转换矩阵或CTM进行操作来修改默认用户空间。 创建图形上下文后,CTM是单位矩阵。 您可以使用Quartz变换函数修改CTM,因此,在用户空间中修改图形。
本章:
- 提供可用于执行转换的功能的概述
- 显示如何修改CTM
- 描述如何创建仿射变换
- 显示如何确定两个变换是否相等
- 介绍如何获取用户到设备空间的转换
- 讨论仿射变换后的数学
关于Quartz变换函数
您可以使用Quartz 2D内置转换函数轻松平移,缩放和旋转图形。 只需几行代码,就可以按任何顺序和任意组合应用这些转换。 图5-1说明了缩放和旋转图像的效果。 您应用的每个转换更新CTM。 CTM始终表示用户空间和设备空间之间的当前映射。 此映射确保您的应用程序的输出在任何显示屏幕或打印机上看起来不错。
Quartz 2D API提供了五个功能,允许您获取和修改CTM。 您可以旋转,平移和缩放CTM,您可以将仿射变换矩阵与CTM连接。 请参阅修改当前转换矩阵。
Quartz还允许创建不对用户空间进行操作的仿射变换,直到您决定将变换应用于CTM。 您使用另一组函数创建仿射变换,然后可以与CTM连接。 请参阅创建仿射变换。
你可以使用任何一组函数,而不了解矩阵数学的任何东西。 然而,如果你想了解什么Quartz在调用一个转换函数时,请阅读矩阵后面的数学。
修改当前转换矩阵
在绘制图像之前,您可以操纵CTM来旋转,缩放或平移页面,从而转换要绘制的对象。 在转换CTM之前,您需要保存图形状态,以便您可以在绘制后恢复它。 您还可以将CTM与仿射变换连接(请参阅创建仿射变换)。 这四个操作(平移,旋转,缩放和连接)中的每一个都与执行每个操作的CTM函数一起在本节中描述。
以下代码行绘制图像,假设您提供了有效的图形上下文,指向要绘制图像的矩形的指针以及有效的CGImage对象。 代码绘制图像,如图5-2所示的示例公鸡图像。 阅读本部分的其余部分时,您将看到在应用转换时图像如何更改。
CGContextDrawImage (myContext, rect, myImage);
平移将坐标空间的原点移动您为x和y轴指定的量。 您调用函数CGContextTranslateCTM以指定的量修改每个点的x和y坐标。 图5-3显示了使用以下代码行在x轴上平移100个单位,在y轴上平移50个单位的图像:
CGContextTranslateCTM (myContext, 100, 50);
旋转以您指定的角度移动坐标空间。 您调用函数CGContextRotateCTM来指定旋转角度(以弧度表示)。 图5-4显示了使用以下代码行绕原点旋转-45度的图像,该原点是窗口的左下角:
CGContextRotateCTM (myContext, radians(–45.));
图像被裁剪,因为旋转将图像的一部分移动到上下文之外的位置。 您需要指定以弧度表示的旋转角度。
如果你计划执行多次旋转,写一个弧度例程是有用的。
#include
static inline double radians (double degrees) {return degrees * M_PI/180;}
缩放通过您指定的x和y因子更改坐标空间的比例,有效地拉伸或缩小图像。 x和y因子的大小决定新坐标是大于还是小于原始坐标。 此外,通过使x因子为负,可以沿着x轴翻转坐标; 类似地,通过使y因子为负,可以沿着y轴水平地翻转坐标。 您调用函数CGContextScaleCTM以指定x和y缩放因子。 图5-5显示了一个图像,其x值由.5缩放,其y值由.75缩放,使用以下代码行:
CGContextScaleCTM (myContext, .5, .75);
连接通过将两个矩阵相乘在一起来组合它们。 您可以连接几个矩阵以形成包含矩阵的累积效应的单个矩阵。 您调用函数CGContextConcatCTM将CTM与仿射变换组合。 仿射变换和创建它们的函数在创建仿射变换中讨论。
实现累积效应的另一种方式是执行两个或多个变换,而不在变换调用之间恢复图形状态。 图5-6显示了通过平移图像然后旋转图像而产生的图像,使用以下代码行:
CGContextTranslateCTM (myContext, w,h);
CGContextRotateCTM (myContext, radians(-180.));
图5-7显示了使用以下代码行平移,缩放和旋转的图像:
CGContextTranslateCTM (myContext, w/4, 0);
CGContextScaleCTM (myContext, .25, .5);
CGContextRotateCTM (myContext, radians ( 22.));
执行多个转换的顺序很重要; 如果逆转顺序,您会得到不同的结果。 反转用于创建图5-7的转换顺序,您将得到如图5-8所示的结果,该结果是使用以下代码生成的:
CGContextRotateCTM (myContext, radians ( 22.));
CGContextScaleCTM (myContext, .25, .5);
CGContextTranslateCTM (myContext, w/4, 0);
创建仿射变换
Quartz中可用的仿射变换函数对矩阵而不是CTM进行操作。 您可以使用这些函数构建一个矩阵,稍后通过调用函数CGContextConcatCTM将其应用于CTM。 仿射变换函数操作或返回CGAffineTransform数据结构。 您可以构造可重用的简单或复杂仿射变换。
仿射变换函数执行与CTM函数相同的操作 - 平移,旋转,缩放和连接。 表5-1列出了执行这些操作的功能及其使用信息。 请注意,每个平移,旋转和缩放操作都有两个函数。
Quartz还提供了一个反转矩阵的仿射变换函数,CGAffineTransformInvert。反转通常用于提供变换对象内的点的逆变换。当需要恢复由矩阵变换的值时,反转可能很有用:反转矩阵,并将值乘以反转矩阵,结果为原始值。您通常不需要反转变换,因为您可以通过保存和恢复图形状态来逆转换CTM的效果。
在某些情况下,您可能不想变换整个空间,而只是一个点或大小。您通过调用CGPointApplyAffineTransform函数在CGPoint结构上操作。您通过调用CGSizeApplyAffineTransform函数在CGSize结构上操作。您可以通过调用CGRectApplyAffineTransform函数对CGRect结构进行操作。此函数返回包含传递给它的矩形的转换角点的最小矩形。如果对矩形操作的仿射变换仅执行缩放和平移操作,则返回的矩形与从四个变换的角构造的矩形重合。
您可以通过调用函数CGAffineTransformMake创建一个新的仿射变换,但与其他创建新仿射变换的函数不同,这需要您提供矩阵条目。要有效地使用此功能,您需要了解矩阵数学。见矩阵的数学。
评估仿射变换
您可以通过调用函数CGAffineTransformEqualToTransform来确定一个仿射变换是否等于另一个。 如果传递给它的两个变换相等,此函数返回true,否则返回false。
函数CGAffineTransformIsIdentity是用于检查变换是否是标识变换的有用函数。 身份转换不执行转换,缩放或旋转。 将此变换应用于输入坐标始终返回输入坐标。 Quartz常量CGAffineTransformIdentity表示身份转换。
使用户到设备空间转换
通常当你使用Quartz 2D绘制时,你只能在用户空间中工作。 Quartz负责为用户和设备空间之间进行转换。 如果您的应用程序需要获得Quartz用于在用户和设备空间之间进行转换的仿射变换,则可以调用函数CGContextGetUserSpaceToDeviceSpaceTransform。
Quartz提供了许多方便的功能来转换用户空间和设备空间之间的以下几何。 您可能会发现这些函数比应用从函数CGContextGetUserSpaceToDeviceSpaceTransform返回的仿射变换更容易使用。
- Points。 函数CGContextConvertPointToDeviceSpace和CGContextConvertPointToUserSpace将CGPoint数据类型从一个空间转换为另一个空间。
- Sizes。 函数CGContextConvertSizeToDeviceSpace和CGContextConvertSizeToUserSpace将CGSize数据类型从一个空间转换为另一个空间。
- Rectangles。 函数CGContextConvertRectToDeviceSpace和CGContextConvertRectToUserSpace将CGRect数据类型从一个空间转换为另一个空间。
矩阵后面的数学
您需要理解矩阵数学的唯一的Quartz 2D函数是函数CGAffineTransformMake,它从3 x 3矩阵中的6个关键条目进行仿射变换。 即使你从来没有计划从头构建仿射变换矩阵,你可能会发现变换函数背后的数学很有趣。 如果没有,您可以跳过本章的剩余部分。
3×3变换矩阵-a,b,c,d,tx和ty-的六个临界值在以下矩阵中示出:
注:矩阵的最右边的列总是包含常量值0,0,1。在数学上,这个第三列是允许级联的,这将在本节后面解释。 它出现在本节仅为了数学正确性。
给定上述3×3变换矩阵,Quartz使用该方程将点(x,y)变换为结果点(x',y'):
结果在不同的坐标系统中,由变换矩阵中的变量值变换的坐标系统。 以下等式是以前矩阵变换的定义:
下矩阵是单位矩阵。 它不执行转换,缩放或旋转。 将该矩阵乘以输入坐标总是返回输入坐标。
使用前面讨论的公式,您可以看到,该矩阵将生成与旧点(x,y)相同的新点(x',y'):
该矩阵描述了平移操作:
这些是Quartz用来应用翻译的结果方程:
该矩阵描述对点(x,y)的缩放操作:
这些是Quartz用来缩放坐标的结果方程:
该矩阵描述了旋转操作,将点(x,y)逆时针旋转角度α:
这些是Quartz用来应用旋转的结果方程:
这个等式将旋转操作与平移操作连接起来:
这些是Quartz用来应用变换的结果方程:
注意,连接矩阵的顺序是重要的 - 矩阵乘法不是可交换的。也就是说,将矩阵A乘以矩阵B的结果不一定等于矩阵B乘以矩阵A的结果。
如前所述,级联是仿射变换矩阵包含具有常数值0,0,1的第三列的原因。为了将一个矩阵与另一个矩阵相乘,一个矩阵的列数必须与另一个矩阵的行数相匹配。这意味着2×3矩阵不能与2×3矩阵相乘。因此,我们需要包含常量值的额外列。
反演操作从变换的坐标产生原始坐标。给定已经由给定矩阵A变换到新坐标(x',y')的坐标(x,y),通过矩阵A的逆变换坐标(x',y')产生原始坐标x,y)。当矩阵乘以其逆时,结果是单位矩阵。