Quartz 2D编程指南(5)
用户坐标空间用浮点数表示坐标,与设备空间的像素分辨率没有关系。
当我们须要一个点或者显示文档时, Quartz会将用户空间坐标系统映射到设备空间坐标系统。因此,我们不须要重写应用程序或加入额外的代码来调整应用程序的输出以适应不同的设备。
我们能够通过操作CTM(current transformation matrix)来改动默认的用户空间。在创建图形上下文后,CTM是单位矩阵。我们能够使用 Quartz的变换函数来改动CTM,从而改动用户空间中的绘制操作。 本章内容包含:- 变换操作函数概览
- 怎样改动CTM
- 怎样创建一个仿射变换
- 怎样选择两个同样的变换
- 怎样获取user-to-device-space变换
我们可能使用Quartz内置的变换函数方便的平移、旋转和缩放我们的画图。仅仅须要短短几行代码,我们便能够按顺序应用变换或结合使用变换。图5-1显示了缩放和旋转一幅图片的效果。
我们使用的每一个变换操作都更新了CTM。CTM总是用于表示用户空间和设备空间的当前映射关系。
这样的映射确保了应用程序的输出在不论什么显示器或打印机上看上去都非常棒。
Quartz 2D API提供了5个函数,以同意我们获取和改动CTM。我们能够旋转、平移、缩放CTM。我们还能够联结一个仿射变换矩阵。
有时我们能够不想操作用户空间。直到我们决定将变换应用到CTM时。 Quartz 为此同意我们创建应用于此的仿射矩阵。我们能够使用另外一组函数来创建仿射变换,这些变换能够与CTM联结在一起。
我们能够不须要了解矩阵的数学含义而使用这些函数。
改动CTM
我们在绘制图像前操作CTM来旋转、缩放或平移page,从而变换我们将要绘制的对象。以变换CTM之前,我们须要保存图形状态,以便绘制后能恢复。我们相同能用仿射矩阵来联结CTM。
在本节中,我们将介绍与CTM函数相关的四种操作--平移、旋转、缩放和联结。
如果我们提供了一个可用的图形上下文、一个指向可绘制图像的矩形的指针和一个可用的CGImage对象。则以下一行代码绘制了一个图像。该行代码能够绘制如图5-2所看到的的图片。
在阅读了本节余下的部分后,我们将看到怎样将变换应用于图像。
|
平移变换依据我们指定的x, y轴的值移动坐标系统的原点。
我们通过调用CGContextTranslateCTM函数来改动每一个点的x, y坐标值。如图5-3显示了一幅图片沿x轴移动了100个单位,沿y轴移动了50个单位。详细代码例如以下:
|
旋转变换依据指定的角度来移动坐标空间。
我们调用CGContextRotateCTM函数来指定旋转角度(以弧度为单位)。图5-4显示了图片以原点(左下角)为中心旋转45度。代码所下所看到的:
|
因为旋转操作使图片的部分区域置于上下文之外。所以区域外的部分被裁减。我们用弧度来指定旋转角度。假设须要进行旋转操作,以下的代码将会非常实用
|
x, y因子的大小决定了新的坐标空间是否比原始坐标空间大或者小。另外,通过指定x因子为负数。能够倒转x轴,相同能够指定y因子为负数来倒转y轴。
通过调用CGContextScaleCTM函数来指定x, y缩放因子。图5-5显示了指定x因子为0.5,y因子为0.75后的缩放效果。代码例如以下:
|
联合变换将两个矩阵相乘来联接现价变换操作。
我们能够联接多个矩阵来得到一个包括全部矩阵累积效果矩阵。通过调用CGContextConcatCTM来联接CTM和仿射矩阵。
第二种得到累积效果的方式是运行两个或多个变换操作而不恢复图形状态。
图5-6显示了先平移后旋转一幅图片的效果。代码例如以下:
|
图5-7显示了平移、缩放和旋转一幅图片。代码例如以下:
|
变换操作的顺序会影响到终于的效果。
假设调换顺序,将得到不同的结果。
调换上面代码的顺序将得到如图5-8所看到的的效果,代码例如以下:
|
创建仿射变换
仿射变换操作在矩阵上,而不是在CTM上。我们能够使用这些函数来构造一个之后用于CTM(调用函数CGContextConcatCTM)的矩阵。
仿射变换函数使用或者返回一个CGAffineTransform数据对象。我们能够构建简单或复杂的仿射变换。
仿射变换函数能实现与CTM函数同样的操作--平移、旋转、缩放、联合。表5-1列出了仿射变换函数及其用途。
注意每种变换都有两个函数。
表5-1 仿射变换函数
函数 | 用途 |
CGAffineTransformMakeTranslation | 通过指定x, y值来创建一个平移矩阵 |
CGAffineTransformTranslate | 在已存在的矩阵中使用平移 |
CGAffineTransformMakeRotation | 通过指定角度来创建一个旋转矩阵 |
CGAffineTransformRotate | 在已存在的矩阵中使用旋转 |
CGAffineTransformMakeScale | 通过指定x, y缩放因子来创建一个缩放矩阵 |
CGAffineTransformScale | 在已存在的矩阵中使用缩放 |
倒置操作通经常使用于在变换对象中提供点的倒置变换。当我们须要恢复一个被矩阵变换的值时。能够使用倒置操作。
将值与倒置矩阵相乘。就可得到原先的值。我们通常不须要倒置操作,由于我们能够通过保存和恢复图形状态来倒置CTM的效果。
在一些情况下,我们可能不须要变换整修空间,而仅仅是一个点或一个大小。我们通过调用CGPointApplyAffineTransform在CGPoint结构上运行变换操作。调用CGSizeApplyAffineTransform在CGSize结构上运行变换操作。
调用CGRectApplyAffineTransform在CGRect结构上运行变换操作。
CGRectApplyAffineTransform返回一个最小的矩形,该矩形包括了被传递给CGRectApplyAffineTransform的矩形对象的角点。
假设矩形上的仿射变换操作仅仅有缩放和平移操作,则返回的矩形与四个变换后的角组成的矩形是一致的。
能够通过调用函数CGAffineTransformMake来创建一个新的仿射变换,但与其他函数不同的是。它须要提供一个矩阵实体。
评价仿射变换
我们能够通过调用CGAffineTransformEqualToTransform函数来决定一个仿射变换是否与还有一个同样。假设两个变换同样,则返回true;否则返回false。
函数CGAffineTransformIsIdentity用于确认一个变换是否是单位变换。
单位变换没有平移、缩放和旋转操作。
Quartz 常量CGAffineTransformIdentity表示一个单位变换。获取用户空间到设备空间的变换
当使用 Quartz 2D时。我们仅仅是在用户空间下工作。 Quartz 为我们处理用户空间和设备空间的转换。假设我们的应用程序须要获取 Quartz 转换用户空间和设备空间的仿射变换,我们能够调用函数CGContextGetUserSpaceToDeviceSpaceTransform。
Quartz 提供了一系列的函数来转换用户空间和设备空间的几何体。我们会发现这些函数使用赶来比使用CGContextGetUserSpaceToDeviceSpaceTransform函数返回的仿射变换更好用。
- 点:函数CGContextConvertPointToDeviceSpace和CGContextConvertPointToUserSpace将一个CGPoint数据结构从一个空间变换到还有一个空间。
- 大小:函数CGContextConvertSizeToDeviceSpace和CGContextConvertSizeToUserSpace将一个CGSize数据结构从一个空间变换到还有一个空间。
- 矩形:函数CGContextConvertRectToDeviceSpace和CGContextConvertRectToUserSpace将一个CGPoint数据结构从一个空间变换到还有一个空间。
首先要说的是CALayers 是屏幕上的一个具有可见内容的矩形区域。每一个UIView都有一个根CALayer,其全部的绘制(视觉效果)都是在这个layer上进行的。(译者注:为验证这点,我写下了例如以下代码:
1 2 3 4 5 6 7 8 9 10 | UILabel* lable = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 30)]; lable.text = @"test"; [self.view addSubview: lable]; lable.backgroundColor = [UIColor clearColor]; [lable release]; // 设定CALayer self.view.layer.backgroundColor =[UIColor orangeColor].CGColor; self.view.layer.cornerRadius =20.0; self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20); |
请注意。我创建的UILable始终随着UIView的根CALayer的缩放而改变位置。)
其次,CALayer的能够影响其外观的特性有:
- 层的大小尺寸
- 背景色
- 内容(比方图像或是使用绘制的内容)
- 是否使用圆角
- 是否使用阴影
- 等等
须要说明的是CALayer的大部分属性都能够用来实现动画效果。
另外,你能够直接使用CALayer,也能够使用其子类,如CAGradientLayer,CATextLayer, CAShapeLayer等等。
演示样例
首先在Xcode中创建一个View-based App,CALayer是属于QuartzCore framework的,所以须要引入QuartzCore framework,另外在程序中包含QuartzCore.h。
第一个样例是创建一个带圆角的层,在你的ViewController中的ViewDidLoad中增加以下代码:
1 2 3 4 5 6 7 | // Import QuartzCore.h at the top of the file #import // Uncomment viewDidLoad and add the following lines self.view.layer.backgroundColor =[UIColor orangeColor].CGColor; self.view.layer.cornerRadius =20.0; self.view.layer.frame = CGRectInset(self.view.layer.frame, 20, 20); |
然后加入一个带阴影效果的子层。加入下列代码:
1 2 3 4 5 6 7 8 | CALayer *sublayer = [CALayer layer]; sublayer.backgroundColor = [UIColor blueColor].CGColor; sublayer.shadowOffset = CGSizeMake(0, 3); sublayer.shadowRadius = 5.0; sublayer.shadowColor = [UIColor blackColor].CGColor; sublayer.shadowOpacity = 0.8; sublayer.frame = CGRectMake(30, 30, 128, 192); [self.view.layer addSublayer:sublayer]; |
为子层添加内容(图片),你还能够设置层的边框,代码例如以下:
1 2 3 | sublayer.contents =(id)[UIImage imageNamed:@"BattleMapSplashScreen.png"].CGImage; sublayer.borderColor =[UIColor blackColor].CGColor; sublayer.borderWidth =2.0; |
假设你希望子层也是圆角怎么办?你可能说非常easy设置cornerRadius属性即可。
实际上你即算是设置了cornerRadius属性,图片仍然不会显示圆角。
你还须要设置masksToBounds为YES。可是这样做还是不够的,由于假设是这样,这个层的阴影显示就没有了。
简单的实现方法例如以下(通过两个层来实现):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | CALayer *sublayer =[CALayer layer]; sublayer.backgroundColor =[UIColor blueColor].CGColor; sublayer.shadowOffset = CGSizeMake(0, 3); sublayer.shadowRadius =5.0; sublayer.shadowColor =[UIColor blackColor].CGColor; sublayer.shadowOpacity =0.8; sublayer.frame = CGRectMake(30, 30, 128, 192); sublayer.borderColor =[UIColor blackColor].CGColor; sublayer.borderWidth =2.0; sublayer.cornerRadius =10.0; [self.view.layer addSublayer:sublayer]; CALayer *imageLayer =[CALayer layer]; imageLayer.frame = sublayer.bounds; imageLayer.cornerRadius =10.0; imageLayer.contents =(id)[UIImage imageNamed:@"BattleMapSplashScreen.png"].CGImage; imageLayer.masksToBounds =YES; [sublayer addSublayer:imageLayer]; |
最后,还介绍一下自画图型的实现,其要点是要设置所绘制层的delegate。比方在我们的样例中使用ViewController作为delegate,那么就须要在ViewController中实现drawLayer:inContext方法,对层进行绘制工作。另外,还须要调用setNeedsDisplay,来通知层须要进行绘制了,于是层才会通过对delegate的drawLayer:inContext方法进行调用。
代码例如以下:
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 | void MyDrawColoredPattern (void*info, CGContextRef context){ CGColorRef dotColor =[UIColor colorWithHue:0 saturation:0 brightness:0.07 alpha:1.0].CGColor; CGColorRef shadowColor =[UIColor colorWithRed:1 green:1 blue:1 alpha:0.1].CGColor; CGContextSetFillColorWithColor(context, dotColor); CGContextSetShadowWithColor(context, CGSizeMake(0, 1), 1, shadowColor); CGContextAddArc(context, 3, 3, 4, 0, radians(360), 0); CGContextFillPath(context); CGContextAddArc(context, 16, 16, 4, 0, radians(360), 0); CGContextFillPath(context); } -(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { CGColorRef bgColor =[UIColor colorWithHue:0.6 saturation:1.0 brightness:1.0 alpha:1.0].CGColor; CGContextSetFillColorWithColor(context, bgColor); CGContextFillRect(context, layer.bounds); staticconst CGPatternCallbacks callbacks ={ 0, &MyDrawColoredPattern, NULL}; CGContextSaveGState(context); CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL); CGContextSetFillColorSpace(context, patternSpace); CGColorSpaceRelease(patternSpace); CGPatternRef pattern = CGPatternCreate(NULL, layer.bounds, CGAffineTransformIdentity, 24, 24, kCGPatternTilingConstantSpacing, true, &callbacks); CGFloat alpha =1.0; CGContextSetFillPattern(context, pattern, &alpha); CGPatternRelease(pattern); CGContextFillRect(context, layer.bounds); CGContextRestoreGState(context); } |
还须要注意。radians是一个自己定义函数:
1 | static inline double radians (double degrees) { return degrees * M_PI/180; } |
版权声明:本文博主原创文章。博客,未经同意不得转载。