转载自:http://blog.csdn.net/pre_eminent/article/details/39430943
http://www.tuicool.com/articles/NRnU3a
Quartz 2D是一个二维图形绘制引擎,支持iOS环境和Mac OS X环境。
使用Quartz 2D API可实现许多功能,如 基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问 。
在需要的时候,Quartz 2D还可以借助 图形硬件 的功能。
在Mac OS X中,Quartz 2D可以与其它图形图像技术混合 使用 ,
如 Core Image、Core Video、OpenGL、QuickTime。
例如,通过使用 QuickTime的GraphicsImportCreateCGImage函数,可以用 Quartz从一个 QuickTime图形导入器中创建一个图像。
Quartz 2D在图像中使用了 绘画者模型(painter’s model) 。
在 绘画者模型 中,每个连续的绘制操作都是将一个绘制层(a layer of ‘paint’)放置于一个画布(‘canvas’),我们通常称这个画布为(Page)。
Page上的绘图可以通过额外的绘制操作来叠加更多的绘图。
Page上的图形对象只能通过 叠加更多的绘图 来改变。
Page可以是一张纸(如果输出设备是打印机),也可以是虚拟的纸张(如果输出设备是PDF文件),还可以是bitmap图像。
这根据实际使用的 图形上下文graphics context 而定。
绘制目标:Graphics Context【图形上下文】
Graphics Context是一个数据类型(CGContextRef),用于封装Quartz绘制图像到输出设备的信息。
设备可以是PDF文件、bitmap或者 显示 器的窗口上。
Graphics Context中的信息包括在Page中的图像的图形绘制参数和设备相关的表现形式。
Quartz中所有的对象都是绘制到一个 【图形上下文】
Graphics Context中。
可以将 【图形上下文】 Graphics Context想像成绘制目标,
Quartz提供了以下五种类型的 【图形上下文】 Graphics Context
- 【位图】Bitmap Graphics Context
- 【PDF】PDF Graphics Context
- 【窗口】Window Graphics Context
- 【图层】Layer Context
- 【打印】Post Graphics Context
Quartz 2D 数据类型
除了 【图形上下文】Graphics Context 之外,
Quartz 2D API还定义一些数据类型。
由于这些API就Core Graphics框架的一部分,所以这些数据类型都是以 CG开头 的。
下面列出了Quartz 2D包含的数据类型:
- CG Path Ref:用于向量图(矢量),可创建路径,并进行填充或描画( stroke )
- CG Image Ref:用于表示bitmap图像和基于采样数据的bitmap图像遮罩。
- CG Layer Ref:用于表示可用于 重复绘制 (如背景)和幕后(offscreen)绘制的绘画层
- CG Pattern Ref:用于重绘图(底纹)
- CG Shading Ref、CGGradientRef:用于绘制 渐变
- CG Function Ref:用于定义 回调 函数,该函数包含一个随机的浮点值参数。当 为阴影创建渐变 时使用该类型
- CG Color Ref, CG ColorSpace Ref:用于告诉Quartz如何使用颜色
- CG ImageSource Ref,CG ImageDestination Ref:用于在Quartz中指明数据来源和目的地
- CG Font Ref:用于绘制文本时的字体
- CG PDFDictionary Ref, CG PDFObject Ref, CG PDFPage Ref, CG PDFStream , CG PDFString Ref, and CG PDFArray Ref:用于访问PDF的 元数据
- CG PDFScanner Ref, CG PDFContentStream Ref:用于解析PDF元数据
- CG PSConverter Ref:用于将Mac OS中的PostScript转化成PDF。在iOS中不能使用。
当前图形状态
Quartz通过修改当前图形状态(current graphics state)来修改绘制操作的结果。
图形状态包含用于绘制程序的参数。绘制程序根据这些绘图状态来决定如何渲染结果。
例如,当你调用设置填充颜色的函数时,你将改变存储在当前绘图状态中的颜色值。
Graphics Context包含一个绘图状态栈。
当Quartz创建一个【图形上下文】Graphics Context时,栈为空。
当保存图形状态时,Quartz将当前图形状态的一个副本压入栈中。
当还原图形状态时,Quartz将栈顶的图形状态出栈。
出栈的状态成为当前图形状态。
可使用函数 CGContextSaveGState 来保存图形状态, CGContextRestoreGState
来还原图形状态。
注意:并不是当前绘制环境的所有方面都是图形状态的元素。
如,图形状态不包含当前路径(current path)。
下面列出了图形状态相关的参数:
- Current transformation matrix (CTM) :当前转换矩阵
- Clipping area :裁剪区域
- Line : 线
- Accuracy of curve estimation (flatness) :曲线平滑度
- Anti-aliasing setting :反锯齿设置
- Color : 颜色
- Alpha value (transparency) :透明度
- Rendering intent :渲染目标
- Color space : 颜色空间
- Text : 文本
- Blend mode :混合模式
Quartz 2D 坐标系统(笛卡尔坐标系)
原点在左下方,向右为X,向上为Y
坐标系统定义是被绘制到Page上的对象的位置及大小范围 由于不同的设备有不同的图形功能,所以图像的位置及大小 依赖于设备。
例如,一个显示设备可能每英寸只能显示少于96个像素,而打印机可能每英寸能显示300个像素。
如果在设备级别上定义坐标系统,则在一个设备上绘制的图形无法在其它设备上正常显示。
因此,Quartz通过使用 当前转换矩阵(current transformation matrix, CTM) 将一个独立的坐标系统(user space) 映射 到输出设备的坐标系统(device space),以此来解决设备依赖 问题。
CTM是一种特殊类型的矩阵(affine transform, 仿射矩阵),通过平移(translation)、旋转(rotation)、缩放(scale)操作 将点从一个坐标空间映射到另外一个坐标空间 。
CTM还有另外一个目的:
允许你通过转换来决定对象如何被绘制。
例如,为了绘制一个旋转了45度的盒子,我们可以在绘制盒子之前旋转Page的坐标系统。Quartz使用旋转过的坐标系统来将盒子绘制到输出设备中。
用户空间的点用坐标对(x, y)来表示,(0, 0)表示坐标原点。Quartz中 默认的坐标系统是:笛卡尔坐标系
沿着x轴从左到右坐标值逐渐增大;
沿着y轴从下到上坐标值逐渐增大。
有一些技术在设置它们的graphics context时使用了不同于Quartz的默认坐标系统。
相对于Quartz来说,这些坐标系统是修改的坐标系统(modified coordinate system),
当在这些坐标系统中显示Quartz绘制的图形时,必须进行转换。
最常见的一种修改的坐标系统是: UIKit坐标系
即: 原点位于左上角,而沿着y轴向下 逐渐增大。
我们可以在如下一些地方见到这种坐标系统:
- 在Mac OS X中,重写过isFlipped方法以返回yes的NSView类的子类
- 在IOS中,由 UIView返回的绘图上下文
- 在IOS中,通过调用 UIGraphicsBeginImageContextWithOptions 函数返回的绘图上下文
如果 应用 程序想以相同的绘制程序在一个UIView对象和PDF Graphics Context上进行绘制,
需要做一个变换,以使PDF Graphics Context使用与UIView相同的坐标系。
要达到这一目的,只需要对PDF的上下文的原点做一个平移(移到左上角)、
用-1对y坐标值进行反向
如果你想要一个 图片 或PDF正确的绘制到一个【图形上下文】Graphics Context中,
你的应用程序可能需要 临时调整Graphics Context的CTM 。
在IOS中,如果使用UIImage对象来创建CGImage对象,则不需要修改CTM。
UIImage将自动进行补偿以适用UIKit的坐标系统。
内存管理:对象所有权
Quartz使用Core Foundation内存管理模型(引用计数)。所以,对象的创建与销毁与通常的方式是一样的。在Quartz中,需要记住如下一些规则:
- 如果创建create或拷贝copy一个对象,你将拥有它,因此你必须释放它。
- 通常,如果使用含有”Create”或“Copy”单词的函数获取一个对象,
- 当使用完后必须释放,否则将导致内存泄露。
- 如果使用不含有”Create”或“Copy”单词的函数获取一个对象,你将不会拥有对象的引用,不需要释放它。
- 如果你不拥有一个对象而打算保持它,则必须retain它并且在不需要时release掉。
- 可以使用Quartz 2D的函数来指定retain和release一个对象。
- 例如,如果创建了一个CGColorspace对象,
- 则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。
- 同样,可以使用Core Foundation的CFRetain和CFRelease,但是注意不能传递NULL值给这些函数。
一、 CATransform3D是 什么?
CATransform3D这个结构体表示 三维的齐次坐标变换矩阵 .
齐次坐标 是一种坐标的表示方法,
n维空间的坐标需要用 n+1个 元素的 坐标元组 来表示,
在 Quartz 2D Transform 中就有关于齐次坐标的应用,
那里是关于二维空间的变换,
其某点的 齐次坐标的最后一个元素 始终设置为1 。
二、为什么需要齐次坐标
之所以要使用齐次坐标,而不是简单的数学坐标
目的是:为了方便图形进行 仿射变换 ,
仿射变换 可以通过 仿射变换矩阵 来实现,
3D的仿射变换功能强大,它可以实现诸如:
平移(translation),旋转(rotation),缩放(scaling),切变(shear) 等
如果不用 齐次坐标 那么进行坐标变换可能就涉及到两种运算了:
加法(平移)和乘法(旋转,缩放)
然而,使用 齐次坐标 以及 齐次坐标变换矩阵 后,问题就简单多了
只需要 矩阵乘法 就可以完成一切了。
以下需要线性代数、图形变换的相关知识,进行矩阵的乘法。
三、 CATransform3D中的最关键的m34
iOS中的CALayer的3D本质上并不能算真正的3D
因为其视点即观察点或者所谓的照相机的位置是无法变换的,
而仅仅只是3D在二维平面上的投影,
投影平面就是: 手机屏幕
也就是xy轴组成的UIKit坐标平面(注意UIKit中为左手坐标系),
那么 视点的位置 是如何确定的呢?
可以通过 CATransform3D中的m34 来间接指定:
m34是相当常用的一个属性
m34 = -1/z,
其中z为观察点在z轴上的值,
下面代码是XCode文档中摘取的
1 CATransform3D aTransform = CATransform3DIdentity; 2 // the value of zDistance affects the sharpness of the transform. 3 zDistance = 850 ; 4 aTransform.m34 = 1.0
/ -zDistance;
而Layer的z轴的位置则是通过anchorPoint来指定的,
所谓的anchorPoint(锚点)就是在变换中保持不变的点,
也就是某个Layer在变换中的 原点 ,
xyz三轴相交于此原点(锚点) 。
在iOS中,Layer的anchorPoint使用 单位坐标系 来描述,
单位坐标系,范围是0~1
无需指定具体真实的坐标点,
而是使用layer bounds中的 相对位置 ,
下图展示了一个Layer中的几个特殊的锚点, 默认是(0.5,0.5)
在上面的:
m34 = -1/z中,
当z值为 正 的时候(如上面的850),
是我们人眼观察现实世界的效果,
即在投影平面上表现出 近大远小 的效果,
z越靠近原点则这种效果越明显,越远离原点则越不明显,
当z为正无穷大的时候,则失去了近大远小的效果,
此时投影线垂直于投影平面,也就是视点在无穷远处,
CATransform3D中m34的默认值为0,即视点在无穷远处.
特别注意的是:
齐次坐标到数学坐标的转换
通用的齐次坐标为 (a, b, c, h),
其转换成数学坐标则为 (a/h, b/h, c/h).
四.代数解释
假设一个Layer anchorPoint为默认的 (0.5, 0.5 ),
其三维空间中一个A点 (6, 0, 0),z=1000
m34 = -1/z = -1/1000.0,
则此点往z轴负方向移动10个单位之后,
则在投影平面上看到的点的坐标是多少呢?
上面的两个矩阵相乘则会得到最终的变换矩阵
所以一个矩阵就可以完成 变换和投影 。
将A点坐标乘上最终的变换矩阵,
则得到 {6, 0 , -10, 1.01},
转换成数学坐标点为 {6/1.01, 0, 10/1.01},
则可以知道其在投影平面上的投影点为 {6/1.01, 0, 0}
也就是我们看到的变换后的点。
其比之前较靠近原点。
越往z轴负方向移动,则在投影平面上越靠近原点。
五.几何作图解释
将上面的例子使用几何的方式来进行解释分析,
当人的眼睛 沿着y轴的正方向向下看时候,可以得到如下的景象
此时y轴垂直于屏幕指向人的眼晴
虚线为投影线,其和x轴的交点即为A点的投影点。
由相似三角形的定理算出投影的点,
1000/(1000 + 10) = x/6,则x = 6*1000/1010 = 6/1.01
iOS设备给用户视觉反馈其实都是通过QuartzCore框架来进行的,用户最终看到的显示界面都是图层合成的结果,
而图层即是QuartzCore中的CALayer。
通常我们所说的视图即UIView,并不是直接显示在屏幕上,
而是在创建UIView对象的时候会自动创建一个CALayer 层(根层),
而视图对象把要显示的东西绘制在层上,待到需要显示时硬件将所有的层拷贝,
最后,按 Z轴的高低 合成最终的显示结果。
CALayer本质上是 一块包含一幅位图的缓冲区 ,由视图创建的根层为隐式层,而手动创建的层称为显示层。
所有的动画效果都是通过CAAnimation类的子类来完成的。
(因为CAAnimation是抽象类)
CAAnimation类的子类包括了三个:
CAAnimationGroup,CAPropertyAnimation,CATransition,
而CAPropertyAniamtion(同为抽象类)也衍生了两个子类:
CABasicAnimation和CAKeyframeAnimation。
用UIView的animation实现的动画本质上也是通过CALayer来实现的,
iOS系统中CALayer的很多属性都是隐含有动画效果的,
如果不想要隐式动画或者想要显示动画效果,也可以通过CATransaction来设置是否显示动画效果。
同时,在CATransaction内可同时修改多个属性,然后再一并同时渲染,
此外,CATransaction还是可嵌套的。
CABasicAnimation是一个最多只能有 两个关键帧 的动画,fromValue和toValue
而CAKeyframeAnimation除了可含有 多个关键帧 ,而且还可以修改每个关键帧的速度。
CATransition能够为层提供移出以及移入屏幕的效果。
api只开放了其中的四种,当然你可以调用未公开的api,但是假如苹果以后出于安全还是什么原因调整接口的话,就不一定能用了,所以最好还是不要调用私有api,
例如 @"flip”,@"pageCurl”这些type是属于未公开的 ,
一般来说不应该调用这些做动画,下面的这句
1 [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:containerView cache:YES];
同样也可实现翻转的效果。
还可以通过设置@"transform.rotation.y"这个keypath来旋转,
或者直接通过CATransform3D来设置@"transform”属性。
总而言之, 没必要用未公开的api ,会留下安全隐患 。
变换过程中需要注意的是:
该CALayer的anchorPoint,也就是 旋转缩放基准点 ,默认情况下是(0.5, 0.5)也就是在layer的中心。
基准点如果变了,旋转或者缩放所得到的的结果将会不同。
position 和 anchorPoint 共同决定了CALayer在父层中的frame。
此外, CATransform3D 的一个很常用到的数值,那就是 m34 这个值,
下面这段代码是从Xcode文档中得到的
1 CATransform3D aTransform = CATransform3DIdentity;
2 // the value of zDistance affects the sharpness of the transform.
3 zDistance = 850;
4 aTransform.m34 = 1.0 / -zDistance;
默认情况下, m34的值为0,即zDistance为无穷大 ,
而(0,0,zDistance)点表示照相机(透视点)所在的位置 ,
从照相机位置看到的影像在xy平面上的投影即是我们可以在手机屏幕中所看到的影像 ,
动画的速度控制函数 CAMediaTimingFunction
这个函数是 用于描述 时间和距离 之间的关系 ,
距离=toValue-fromValue,
时间=duration,
将它们标准化为1×1的空间,
x,t取值在[0, 1]区间内。
假设在时刻t(时间过去了duration*t)时,
动画目标的位置应为 fromValue+x*(toValue-fromValue)。
系统提供的四种函数 ,
kCAMediaTimingFunctionLinear为匀速运动,
kCAMediaTimingFunctionEaseIn为加速运动,
kCAMediaTimingFunctionEaseOut为减速运动,
kCAMediaTimingFunctionEaseInEaseOut为先加速后减速运动。
当然,系统也提供了可自定义速度控制函数,
通过 initWithControlPoints:x1:y1:x2:y2 可 自定义两个插值点 ,
然后所得的函数曲线为 由(0,0),(x1,y1),(x2,y2),(1,1)这四个点为控制点的Bezier曲线 。
自行绘制UI,而自行绘制UI就需要用到CoreGraphics这个框架( 甚至 OpenGL)
CGContext类,相当于 Android里面的Canvas ,
使用UIGraphicsGetCurrentContext()获取当前CGContext的引用CGContextRef。
我们 在每一次的绘制之前都应该保存下原来的状态,待绘制完成后再恢复回原来的状态 。
CGContextSaveGState (ctx)
CGContextRestoreGState (ctx)
都应该成对的出现,在他们之间的是绘制UI的代码。
CGPath类用于描述绘制的区域或者路线。
在CGContext中addLine,addRect,其实都是在添加path,
在添加完成path以后我们可以选择是 fill 该path,还是 stroke 该path。
设置相应的颜色以及线宽即可。
如果我们只需要在某个区域中绘制,
我们可以在描述完path以后使用 CGContextClip (ctx)来裁剪当前画布。
CGAffineTransform是一个 仿射变换 的结构体,相当于一个矩阵,
用于进行二维平面的几何变换( 平移,旋转,缩放等 ),
而且这些几何变换都已经有封装好的函数可调用, 变换的顺序就是 矩阵连乘 的顺序,
切不可把矩阵连乘的顺序放反,否则得到的结果可能不一样 。
CGColorSpace 类是用于界面颜色显示,通常颜色组成为RGBA(红,绿,蓝,透明度)四种,
使用 CGColorSpaceCreateDeviceRGB 获取CGColorSpace的引用CGColorSpaceRef,
需要注意的是,在CoreGraphics中,使用 create 方法 调用 release 方法释放掉 ,如CGColorSpaceCreateDeviceRGB()对应的就是CGColorSpaceRelease(rgb)。
在用CoreGraphics绘制时,我们应该注意的是:
当使用CGContext ShowTextAtPoint 绘制文字时,如果不进行几何变换,得到的是文字的 倒影 ,
所以我们需要使用CGContext SetTextMatrix (ctx, myTextTransform);
其中的transform为关于x轴对称的变换矩阵。
当然也可以不用这么做,只需要把原来CGContext的坐标 先关于x轴对称 ,然后 再平移 即可,
CGContext ConcatCTM (ctx, CGAffineTransform Concat (myTextTransform, CGAffineTransformMakeTranslation(140.0f, 30.0f)));
其中的transform为关于x轴对称的变换矩阵。
同样的,使用如下语句 绘制图片时,得到的也将是图片的倒影:
CGContext DrawImage (ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
所以同样的也需要进行 关于x轴的坐标变换 。
但是使用[image drawInRect :CGRectMake(0.0f, 0.0f, image.size.width, image.size.height)];
绘制图片时就不需要关于x轴对称,默认已经是使用UIKit坐标系了。
总结,绘制文字以及图片时会得到倒影,需做一下相关的变换即可 。