iOS绘图

转载自: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坐标系了。

总结,绘制文字以及图片时会得到倒影,需做一下相关的变换即可 。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值