首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 操作系统 >

iOS绘图课程

2013-12-09 
iOS绘图教程图1 UIImage平移处理  缩放操作:下面代码展示了如何对UIImage进行缩放操作:图2 UIImage缩放处

iOS绘图教程

图1 UIImage平移处理

  缩放操作:下面代码展示了如何对UIImage进行缩放操作:

图2 UIImage缩放处理

  UIImage没有提供截取图片指定区域的功能。但通过创建一个较小的图形上下文并移动图片到一个适当的图形上下文坐标系内,指定区域内的图片就会被获取。

  裁剪操作:下面代码展示了如何获取图片的右半边:

图3 UIImage裁剪原理

  CGImage常用的绘图操作

  UIImage的Core Graphics版本是CGImage(具体类型是CGImageRef)。两者可以直接相互转化: 使用UIImage的CGImage属性可以访问Quartz图片数据;将CGImage作为UIImage方法imageWithCGImage:initWithCGImage:的参数创建UIImage对象。

  一个CGImage对象可以让你获取原始图片中指定区域的图片(也可以获取指定区域外的图片,UIImage却办不到)。

  下面的代码展示了将图片拆分成两半,并分别绘制在上下文的左右两边:

CIContext* c = [CIContext contextWithOptions:nil];

CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent];

UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation];

CGImageRelease(moi3);

iOS绘图课程

??图4 图片合成快照 

  这个例子可能没有什么吸引人的地方,因为所有一切都可以使用Core Graphics完成。除了Core Image是使用GPU处理,可能有点吸引人。Core Graphics也可以做到径向渐变并使用混合模式合成图片。但Core Image要简单得多,特别是当你有多个图片输入想重用一个滤镜链时。并且Core Image的颜色调整功能比Core Graphics更加强大。对了,Core Image还能实现自动人脸识别哦!

  绘制一个UIView

  绘制一个UIVIew最灵活的方式就是由它自己完成绘制。实际上你不是绘制一个UIView,你只是子类化了UIView并赋予子类绘制自己的能力。当一个UIVIew需要执行绘图操作的时,?drawRect:方法就会被调用。覆盖此方法让你获得绘图操作的机会。当drawRect方法被调用,当前图形上下文也被设置为属于视图的图形上下文。你可以使用Core Graphics或UIKit提供的方法将图形画到该上下文中。

  你不应该手动调用drawRect方法!如果你想调用drawRect:方法更新视图,只需发送setNeedsDisplay方法。这将使得drawRect:方法会在下一个适当的时间调用。当然,不要覆盖drawRect方法除非你知道这样做绝对合法。比方说,在UIImageView子类中覆盖drawRect方法是不合法的,你将得不到你绘制的图形。

?????? 在UIView子类的drawRect方法中无需调用super,因为本身UIView的drawRect方法是空的。为了提高一些绘图性能,你可以调用setNeedsDisplayInRect方法重新绘制视图的子区域,而视图的其他部分依然保持不变。

?????? 一般情况下,你不应该过早的进行优化。绘图代码可能看上去非常的繁琐,但它们是非常快的。并且iOS绘图系统自身也是非常高效,它不会频繁调用drawRect方法,除非迫不得已(或调用了setNeedsDisplay方法)。一旦一个视图已由自己绘制完成,那么绘制的结果会被缓存下来留待重用,而不是每次重头再来。(苹果公司将缓存绘图称为视图的位图存储回填(bitmap backing store))。你可能会发现drawRect方法中的代码在整个应用程序生命周期内只被调用了一次!事实上,将代码移到drawRect方法中是提高性能的普遍做法。这是因为绘图引擎直接对屏幕进行渲染相对于先是脱屏渲染然后再将像素拷贝到屏幕要来的高效。

?????? 当视图的backgroundColor为nil并且opaque属性为YES,视图的背景颜色就会变成黑色。

  Core Graphics上下文属性设置

  当你在图形上下文中绘图时,当前图形上下文的相关属性设置将决定绘图的行为与外观。因此,绘图的一般过程是先设定好图形上下文参数,然后绘图。比方说,要画一根红线,接着画一根蓝线。那么首先需要将上下文的线条颜色属性设定为为红色,然后画红线;接着设置上下文的线条颜色属性为蓝色,再画出蓝线。表面上看,红线和蓝线是分开的,但事实上,在你画每一条线时,线条颜色却是整个上下文的属性。无论你用的是UIKit方法还是Core Graphics函数。

  因为图形上下文在每一时刻都有一个确定的状态,该状态概括了图形上下文所有属性的设置。为了便于操作这些状态,图形上下文提供了一个用来持有状态的栈。调用CGContextSaveGState函数,上下文会将完整的当前状态压入栈顶;调用CGContextRestoreGState函数,上下文查找处在栈顶的状态,并设置当前上下文状态为栈顶状态。

  因此一般绘图模式是:在绘图之前调用CGContextSaveGState函数保存当前状态,接着根据需要设置某些上下文状态,然后绘图,最后调用CGContextRestoreGState函数将当前状态恢复到绘图之前的状态。要注意的是,CGContextSaveGState函数和CGContextRestoreGState函数必须成对出现,否则绘图很可能出现意想不到的错误,这里有一个简单的做法避免这种情况。代码如下:

图5 CGContextClearRect函数的应用

??? 如图5,在左边的蓝色正方形被挖去部分留为黑色,然而在右边的蓝色正方形也被挖去部分留为透明。但这两个正方形都是UIView子类的实例,采用相同的绘图代码!不同之处在于视图的背景颜色,左边的正方形的背景颜色在nib文件中

  但是这却完全改变了CGContextClearRect函数的效果。UIView子类的drawRect:方法看起来像这样:

图6 一个简单的路径绘图

图7 左右圆角矩形

  裁剪

  路径的另一用处是遮蔽区域,以防对遮蔽区域进一步绘图。这种用法被称为裁剪。裁剪区域外的图形不会被绘制到。默认情况下,一个图形上下文的裁剪区域是整个图形上下文。你可在上下文中的任何地方绘图。

  总的来说,裁剪区域是上下文的一个特性。与已存在的裁剪区域相交会出现新的裁剪区域。所以如果你应用了你自己的裁剪区域,稍后将它从图形上下文中移除的做法是使用CGContextSaveGStateCGContextRestoreGState函数将代码包装起来。

??? 为了便于说明这一点,我使用裁剪而不是使用混合模式在箭头杆子上打孔的方法重写了生成箭头的代码。这样做有点小复杂,因为我们想要裁剪区域不在三角形内而在三角形外部。为了表明这一点,我们使用了一个三角形和一个矩形组成了一个组合路径。

??? 当填充一个组合路径并使用它表示一个裁剪区域时,系统遵循以下两规则之一:

  环绕规则(Winding rule)

  如果边界是顺时针绘制,那么在其内部逆时针绘制的边界所包含的内容为空。如果边界是逆时针绘制,那么在其内部顺时针绘制的边界所包含的内容为空。

  奇偶规则

  最外层的边界代表内部都有效,都要填充;之后向内第二个边界代表它的内部无效,不需填充;如此规则继续向内寻找边界线。我们的情况非常简单,所以使用奇偶规则就很容易了。这里我们使用CGContextEOCllip设置裁剪区域然后进行绘图。(如果不是很明白,可以参见这篇文章:五种方法绘制有孔的2d形状)

?

图8 箭头杆子渐变

图9 模板填充?

  如你所见,实际的模板绘图代码是非常简单的。唯一的复杂点在于CGPatternCreate函数必须与模板绘图函数的矩形元尺寸相同。我们知道矩形元的尺寸为4*4,所以我们用红色填充它,并接着填充它的下半部分为绿色。当这些矩形元被水平垂直平铺时,我们得到了如图8所示的条纹图案。

  注意,最后图形上下文遗留下了一个不可取的状态,即填充颜色空间被设置为了一个模板颜色空间。如果稍后尝试设置填充颜色为常规颜色,就会引起错误。通常的解决方案是,使用CGContextSaveGStateCGContextRestoreGState函数将代码包起来。

  你可能观察到图8的平铺效果并不与箭头的三角形内部相符合:最底部的似乎只平铺了一半蓝色。这是因为一个模板的定位并不关心你填充(描边)的形状,总的来说它只关心图形上下文。我们可以调用CGContextSetPatternPhase函数改变模板的定位。

  图形上下文变换

  就像UIView可以实现变换,同样图形上下文也具备这项功能。然而对图形上下文应用一个变换操作不会对已在图形上下文上的绘图产生什么影响,它只会影响到在上下文变换之后被绘制的图形,并改变被映射到图形上下文区域的坐标方式。一个图形上下文变换被称为CTM,意为“当前变换矩阵“(current transformation matrix)。

  完全利用图形上下文的CTM来免于即使是简单的计算操作是很常见的。你可以使用CGContextConcatCTM函数将当前变换乘上任何CGAffineTransform,还有一些便利函数可对当前变换应用平移、缩放,旋转变换。?

  当你获得上下文的时候,对图形上下文的基本变换已经设置好了;这就是系统能映射上下文绘图坐标到屏幕坐标的原因。无论你对当前变换应用了什么变换,基本变换变换依然有效并且绘图继续工作。通过将你的变换代码封装到CGContextSaveGState和CGContextRestoreGState函数调用中,对基本变换应用的变换操作可以被还原。?

  举个例子,对于我们迄今为止使用代码绘制的向上箭头来说,已知的放置箭头的方式仅仅只有一个位置:箭头矩形框的左上角被硬编码在坐标{80,0}。这样代码很难理解、灵活性差、且很难被重用。最明智的做法是通过将所有代码中的x坐标值减去80,让箭头矩形框左上角在坐标{0,0}。事先应用一个简单的平移变换,很容易将箭头画在任何位置。为了映射坐标到箭头的左上角,我们使用下面代码:

图10 使用CTM旋转变换

  变换有多个方法解决我们早期使用CGContextDrawImage函数遇到的倒置问题。相对于逆向绘图,我们选择逆向我们绘图的上下文。实质上,我们对上下文坐标系统应用了一个“倒置”变换。你自上而下移动上下文,接着你通过应用一个让y坐标乘以-1的缩放变换逆向y坐标的方向。

图11 阴影效果

  点与像素

  一个点是由xy坐标描述的一个无穷小量的位置。通过指定点实现在图形上下文中的绘图。我们并没有关心设备的分辨率,因为Core Graphics已经精细地将绘图映射到物理输出设备(基于CTM、反锯齿和平滑技术)。因此,文章之前的讨论只关心图形上下文的点,不关注点与屏幕像素的关系。

  然而像素是真实存在的。一个像素是真实世界中一个具有完整物理尺寸的显示单元。整数的点实际上介于像素之间。在单分辨率设备上,这可能会让人感到迷惑。比方说,如果使用线宽为1的线条对一个整数坐标的垂直路径描边,那么线条将会被分为两半,分别落在路径的两侧。所以在单分辨率设备上线宽会变成2px(因为设备无法表示半个像素)。

?iOS绘图课程

图12 整数的点坐标与偏移0.5点的坐标对应的描边处理

? ? ? ?当你遇到显示效果不佳的时,可能会被建议通过对坐标增减0.5让它在像素中居中。这个建议可能有效,如图11。但它只是做了一些头脑简单的假设。一个复杂的做法是获得UIView的contentScaleFactor属性。这个值为1.0或2.0,所以你可以除以这个属性值得到从像素到点的转换。还可以想想用最精确的方式绘制一条水平或垂直的线条的方式不是描边路径,而是填充路径。使用这种方法UIView的子类代码将可以在任何设备上绘制一条完美的1px宽的垂线,代码如下:

图13 内容自动伸展

  可是早晚drawRect:方法会被调用,绘图将按照drawRect:方法中的代码被刷新。代码不会将箭头绘制在相对于视图边界的高度。它是在一个固定的高度。因此箭头会伸展,而且会在以后某个时间返回到原始的尺寸。

  通常我们的视图的contentMode属性需要与视图绘制自己的方式一致。假设我们的drawRect:方法中的代码让箭头的尺寸和位置相对于视图的边界原点,即它的左上方。所以我们可以设置它的contentModeUIViewContentModeTopLeft。又或者,我们可以将contentMode设置为UIVIewContentModeRedraw,这将引起缓存内容的自动缩放和重定位被关闭,最终结果是视图的setNeedsDisplay方法将被调用,触发drawRect:方法重绘视图内容。

  在另一方面,如果一个视图只是暂时被调整大小。假设是作为动画的一部分,那么伸缩行为正是你所想要的。假设我们的动画是想要让视图变大然后还原回原始大小以达到作为吸引用户的一种手段。这就需要视图伸缩的时候视图的内容也跟着伸缩,正确的contentMode的值是UIViewContentModeScaleToFill,被伸缩的内容仅仅是视图内容的一副缓存图片,所以它运行起来十分的高效。?

  完。

  本文由海水的味道翻译,转载请注明译者和出处,请勿用于商业用途!

热点排行