导语:Core Graphics是iOS和Mac OS X的2D绘图引擎,本文简单介绍Core Graphics绘图相关概念
一、概述
1、iOS的绘图系统
iOS主要的绘图系统有UIKit、Core Graphics、Core Animation、Core Image、OpenGL ES.结构图如下:
iOS的绘图系统 | 主要作用 | API缩写前缀 |
---|---|---|
UIKit | 使用频率最高,高级别的OC图形接口,它能够访问绘图、动画、字体、图片等内容。 | 缩写前缀为UI |
Core Animation | 使用频率较高,提供了强大的2D和3D动画 | 缩写前缀为CA |
Core Graphics | 使用频率中,基于C实现的iOS和Mac OS X的2D绘图引擎, | 缩写前缀为CG |
Core Image | 使用频率低,图片的滤镜处理,比如高斯模糊、锐化等 | 缩写前缀为CL |
OpenGL ES | 使用频率低,OpenGL针对嵌入式设备的简化版本,用以绘制高性能的2D和3D图形 | 缩写前缀为GL |
说明:图像的滤镜效果建议使用优秀的第三方库 GPUImage,iOS支持两套图形API族:Core Graphics 和OpenGL ES,它们都是基于C的API框架。
2、视图绘制 VS 视图布局
视图绘制:调用UIView中的drawRect方法。如果一个视图调用setNeedsDisplay(或setNeedsDisplayInRect:)方法,它就被标记为重新绘制,并且会在下一次绘图周期中调用drawRect方法重新绘制。
调用setNeedsDisplay或者setNeedsDisplayInRect:方法,只是告诉系统该视图需要重新绘制了,真正的绘制发生在下一个绘制周期,所以可以一次性对多个视图调用setNeedsDisplay/setNeedsDisplayInRect,等到它们在下一个周期被调用drawRect方法更新视图。
即时不使用setNeedsDisplay/setNeedsDisplayInRect:方法,如果当遮挡你的视图的其他视图被移动或删除操作的时,设置视图的hidden属性,改变视图的显示状态,视图滚出屏幕,然后再重新回到屏幕上。等等都会触发视图重新绘制。
-
视图布局:调用UIView中的layoutSubviews方法。如果视图中的子视图布局发生变化,需要重新排列,UIKit会自动调用setNeedsLayout方法,也就是对于发生变化的视图逐层次调用layoutSubviews方法。比如frame发生变化、滚动视图等。
说明:因为绘制的工作大部分是使用CPU完成的,处理速度不如GPU, 尽量避免绘制,多使用布局。视图在第一次创建的时候,绘制和布局的都会调用的。如果子视图因为一些条件的改变,造成布局的改变,这个时候,系统会自动调用layoutSubviews方法。
3、UIKit相关绘图API
-
填充、 描边和路径
UIRectFill(CGRect rect); //填充矩形函数 UIRectFrame(CGRect rect); //矩形描边函数 UIBezierPath //绘制常见路径类,但是对于线段、渐变、阴影、反锯齿等高级特性支持还需要使用Core Graphics
-
UIImage类中绘制图像主要的方法
- (void) drawAtPoint:(CGPoint)point; //设置绘制定点。 - (void) drawInRect:(CGRect)rect; //图片绘制在指定的矩形里。 - (void) drawAsPatternInRect:(CGRect)rect; //在指定的矩形里平铺绘制图片,如果图片大小超出了指定的矩形,形式上与-drawAtPoint:方法 // 类似,如果图片大小小于指定的矩形,就会有平铺的效果。
-
NSString类中绘制文本主要的方法:
- (void) drawAtPoint:(CGPoint)point withAttributes:(NSDictionary *)attrs;文本在指定点绘制; - (void) drawInRect:(CGRect)rect withAttributes:(NSDictionary *)attrs;文本在指定的矩形里绘制;
二、Core Graphics绘图
Core Graphics中的绘图操作都是在在一个上下文中进行,在绘图之前必须获取该上下文,这是绘图任务的第一步。
1、概述
Core Graphics的图形上下文是CGContextRef对象,相当于一块画布,以堆栈形式存放,只有在栈顶的图形上下文上绘制才有效;图形上下文负责存储绘画状态(如画笔颜色、线条粗细等)和绘制内容所处的内存空间。获取上下文的方式有:
iOS有PDF图形上下文、图片处理的图形上下文等,而通过UIGraphicsBeginImageContextWithOptions函数或在UIView的drawRect:方法中,使用 UIGraphicsGetCurrentContext函数获取的是,当前图片处理的图形上下文。
Core Graphics中带有 Ref 后缀的类,其 实例对象 可能有指向其他 Core Graphics “对象”的强引用指针,但是不能被ARC管理,所以创建了这些对象,使用完之后记得手动释放,否则会有内存泄漏的问题。
内存管理规则:凡使用名称中带有create 或者 copy 的函数创建了一个 Core Graphics “对象”,就必须调用对应的Release函数并传入该对象的指针,将其释放。
2、UIKit封装的图形上下文操作函数
1)UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
功能:将旧的上下文入栈、为新上下文分配内存、创建新的上下文、翻转坐标系统,并将其设置为当前上下文。(简记:创建了一个图形处理的上下文,并将其设置为当前上下文)
参数说明:size是新创建的位图的尺寸。opaque代表透明通道的开关,指定所生成图片的背景是否为不透明,YES表示不透明,图片背景是黑色,NO表示透明;scale表明缩放比例,传入0表示让图片的缩放因子等于屏幕的分辨率。
2)UIGraphicsBeginImageContext(CGSize size)
- 创建一个基于位图的上下文,并将其设置为当前上下文。效果相当于UIGraphicsBeginImageContextWithOptions(size,NO,1.0)
3)UIGraphicsGetImageFromCurrentImageContext(void)
- 从当前上下文中获取一个UIImage对象,一般用于获取最终绘制结果。
4)UIGraphicsEndImageContext(void)
- 关闭图形上下文,一般在调用UIGraphicsGetImageFromCurrentImageContext之后调用。
5)UIGraphicsGetCurrentContext(void)
- 获得当前的图形上下文,可以在UIGraphicsBeginImageContextWithOptions/UIGraphicsBeginImageContext或在UIView的drawRect:使用,以获取当前的图形上下文。
3、绘图状态切换 和 上下文切换
绘图中,有绘图状态切换 和 上下文切换,对应两组不同函数,要注意区分使用。
1) 绘图状态切换
在创建Core Graphics图形上下文时,图形上下文中持有一个绘图状态堆栈,这时的绘图状态堆栈是空的,通过CGContextSaveGState与CGContextRestoreGState可以推入和弹出绘图状态,实现了绘图状态切换,但是没有改变其图形上下文。
CGContextSaveGState将当前图形上下文的之前绘图状态(Graphics State )压入绘图状态堆栈,而CGContextRestoreGState是将绘图状态堆栈顶部的状态弹出,返回到之前的图形状态。
-
在CGContextSaveGState之前,绘图状态是A,在CGContextSaveGState和CGContextRestoreGState之间,可以改变绘制状态成为B,实现不同的效果,调用了CGContextRestoreGState之后,绘图状态又回到了A。
//绘图状态A CGContextSaveGState(context) //绘图状态B CGContextRestoreGState(context) //绘图状态A
这种推入和弹出的方式是回到之前绘图状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
2) 上下文切换
通过UIGraphicsPushContext与UIGraphicsPopContext函数可以实现图形上下文切换
UIGraphicsPushContext将图形上下文压入图形上下文堆栈;当需要新建一个上下文做一些绘制工作的话,使用UIGraphicsPushContext保存当前的上下文,在新的绘制图形上下文完成绘制工作后,使用UIGraphicsPopContext恢复会之前的图形上下文。
-
在UIGraphicsPushContext之前,在绘制图形X,在UIGraphicsPushContext和UIGraphicsPopContext之间绘制图形Y,在UIGraphicsPopContext之后,又回到了绘制图像X。
//绘制图形X UIGraphicsPushContext(context) //绘制图形Y UIGraphicsPopContext(context) //绘制图形X
说明:这种推入和弹出的方式是切换上下文比较好的方式。
三、Core Graphics在项目中的使用场景
在项目中,为了满足设计需要,利用Core Graphics完成一些绘制工作,如绘制圆角(Round Corner)、高斯模糊(Gaussian Blur) 和 阴影(Shadow)。这些绘制工作要考虑性能和内存消耗,尽可能将绘制内容缓存下来,避免重复绘制。
1、圆角(Round Corner)
在硬件产品和 软件用户界面的设计上,圆角被大量采用;一是因为圆角在视觉过程中更易被认知,二是因为圆角也同时使得信息更易被处理,总之,大家认为“圆角看起来很漂亮”。
在项目中常见圆角图片。虽然iOS 9之后,对使用加载png图片做了优化,不会触发离屏渲染 ,但网络图片未必都是png格式,使用Core Graphics来处理圆角,是个不错的选择。
关于圆角处理,参考QSImageProcess项目,具体介绍可见iOS实录17:网络图片的优化显示
2、高斯模糊(Gaussian Blur)
模糊(Blur)可以理解成每一个像素都取周边像素的平均值;如果简单地平均,显然不很合理,因为图像都是连续的,越靠近的点关系越密切,越远离的点关系越疏远。理想的想法是,利用加权平均,距离越近的点权重越大,距离越远的点权重越小。
高斯模糊算法其实是利用高斯分布(正态分布)来处理周边像素权重的问题;高斯分布是一种钟形曲线,越接近中心,取值越大,越远离中心,取值越小。在计算平均值的时候,我们只需要将"中心点"作为原点,其他点按照其在正态曲线上的位置,分配权重,就可以得到一个加权平均值。
模糊半径越大,图像就越模糊。从数值角度看,就是数值越平滑。
虽然利用UIVisualEffectView和UIToolBar可以实现模糊效果,但是不够灵活。如果设计需要更加复杂的效果,使用Core Graphics处理也是不错的选择。
3、阴影(Shadow)
大多数阴影效果设置,利用shadow****属性都可以完成。除非设计有特别的阴影需求,才使用Core Graphics(谨记)。
-
网上很多博客中说:通过shadow****设置阴影,会触发离屏渲染,这句话并不严谨。只有在不设置shadowPath(阴影路径)的情况下,设置阴影效果(如shadowColor、shadowOpacity、shadowOffset和shadowRadius等),才会触发。如下代码是不会触发离屏渲染的,
imageView.layer.shadowOpacity = 0.8; //阴影透明度 imageView.layer.shadowOffset = CGSizeMake(2, 2); ;// 阴影偏移量 imageView.layer.shadowRadius = 10; //阴影的圆角 imageView.layer.shadowColor = [UIColor grayColor].CGColor; // 阴影的颜色 UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.bounds]; imageView.layer.shadowPath = path.CGPath; //阴影的路径(避开离屏渲染的关键)
说明1:设置了shadowPath属性,图层在创建阴影的时候,不需要遍历sublayer的alpha通道,而是直接使用指定的路径作为阴影的轮廓,不发生离屏渲染。反之,CoreAnimation需要在每一帧绘制阴影的时候,遍历所有sublayer的alpha通道,以精确的计算出阴影的轮廓,发生离屏渲染,消耗了性能。
说明2:设置shadowPath属性后,当图层的bounds产生变化后,需要更新shadowPath才能让其适配新的bounds。
End
参考资料
iOS绘图系统(一) UIKit与CoreGraphics
iOS绘图框架CoreGraphics分析
CoreGraphics之CGContextSaveGState与UIGraphicsPushContext我是南华coder,一名北漂的初级iOS程序猿。iOS札记是我的一点学习笔记,不足之处,望批评指正。