代码地址以下:
http://www.demodashi.com/demo/11605.htmlhtml
老骥伏枥,志在千里api
最近一直在研究图像处理方面,既上一篇iOS Quart2D绘图之UIImage简单使用后,就一直在学习关于CoreImage
图像滤镜处理。中间也看了很多文章,也获得了很多帮助,下面就结合这些知识和我本身的认识,记录一下,方便本身,方便他人安全
Core Graphics
对比:基于Quartz 2D
绘图引擎的绘图API
,经过它能够进行绘图功能、经常使用的剪切裁剪合成等。Core Image
是一个很强大的框架。它可让你简单地应用各类滤镜来处理图像,好比修改鲜艳程度, 色泽, 或者曝光。 它利用GPU
(或者CPU
)来很是快速、甚至实时地处理图像数据和视频的帧。而且隐藏了底层图形处理的全部细节,经过提供的API就能简单的使用了,无须关心OpenGL
或者OpenGL ES
是如何充分利用GPU
的能力的,也不须要你知道GCD
在其中发挥了怎样的做用,Core Image
处理了所有的细节多线程
Core Image
滤镜须要一副输入图像(生成图像的滤镜除外)以及一些定制滤镜行为的参数。被请求时,Core Image
将滤镜应用于输入图像,并提供一副输出图像。在应用滤镜方面,Core Image
的效率极高:仅当输出图像被请求时才应用滤镜,而不是在指定时就应用它们;另外,Core Image
尽量将滤镜合并,以最大限度地减小应用滤镜的计算量。框架
CIImage
:这是一个模型对象,它保存能构建图像的数据,能够是图像的Data
,能够是一个文件,也能够是CIFilter
输出的对象。CIContext
:上下文,是框架真正工做的地方,它须要分配必要的内存,并编译和运行滤镜内核来执行图像处理。创建一个上下文是很是昂贵的,因此你会常常想建立一个反复使用的上下文。CIFilter
:滤镜对象,主要是对图像进行处理的类。经过设置一些键值来控制滤镜的具体效果Core Image
和Core Graphics
使用的是左下原点坐标到此有一个疑问?就是苹果怎么会弄出这么多image
,好比CIImage
、UIImage
、CGImageRef
,有什么区别呢?为了弄清这个问题,我也特别搜寻了一番,下面也记录一下函数
①UIImage
:管理图片数据,主要用来展示,Image
对象并无提供直接访问相关的图片数据的操做, 所以你老是经过已经存在的图片数据来建立它性能
②CGImage
:是基于像素的矩阵,每一个点都对应了图片中点的像素信息学习
③CIImage
:包含了建立图片的全部必要的数据,但其自己没有渲染成图片,它表明的是图像数据或者生成图像数据的流程(如滤镜)。拥有与之关联的图片数据, 但本质上并非一张图片,你能够CIImage
对象做为一个图片的"配方"。CIImage
对象拥有生成一张图片所具有的全部信息,但Core Image
并不会真正的去渲染一张图片, 除非被要求这么作。
*******atom
CIImage
建立,在使用滤镜以前,你必需要先有一个CIImage
对象,在拥有该对象后结合CIFilter
才能实现咱们的滤镜效果。这里须要注意的是,若是直接使用image.cIImage
,那么很遗憾的告诉你,你将获得一个nil
,哈哈
缘由在UIImage
的API
中有介绍// returns underlying CIImage or nil if CGImageRef based
,应该是说图片可能不是基于CIImage
而建立的
正确的方式为线程
//获得CIImage CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
CIFilter
建立方式大概有下面三种+(nullable CIFilter *) filterWithName:(NSString *) name +(nullable CIFilter *)filterWithName:(NSString *)name keysAndValues:key0, ... NS_REQUIRES_NIL_TERMINATION NS_SWIFT_UNAVAILABLE(""); +(nullable CIFilter *)filterWithName:(NSString *)name withInputParameters:(nullable NSDictionary<NSString *,id> *)params NS_AVAILABLE(10_10, 8_0);
方法上都差很少,只是后面两个在初始化的时候加入了一些键值,在API
文档中,能够查到不少键值,这里须要说明下,键值kCIInputImageKey
是咱们必需要设置的,这是为咱们的滤镜对象设置输入图像,图像的值为CIImage
对象,方法以下
[_filter setValue:inputCIImage forKey:kCIInputImageKey];
方法中的name
就是咱们须要用的滤镜效果,具体效果,能够在官网上面进行查询,以下
下面,咱们以冲印效果为例,冲印属于CICategoryColorEffect
中的CIPhotoEffectProcess
//建立滤镜对象 CIFilter *ciFilter = [CIFilter filterWithName:@"CIPhotoEffectProcess" keysAndValues:kCIInputImageKey,ciImage, nil];
大概效果以下
注意:
一、在设置键值的时候,咱们须要有选择性的进行设置,具体怎么选择呢?
好比上面的冲印效果,在官方文档是这么展现的
只有一个必须输入的inputImage
,所以不须要其它参数就能够实现
又好比高斯模糊CIGaussianBlur
,在官方文档中,是这么展现的
若是咱们须要控制其模糊半径,能够这么设置
[ciFilter setValue:@(20.f) forKey:@"inputRadius"];
二、CIFilter
并非线程安全的,这意味着 一个 CIFilter
对象不能在多个线程间共享。若是你的操做是多线程的,每一个线程都必须建立本身的 CIFilter
对象,而CIContext
和CIImage
对象都是不可修改的, 意味着它们能够在线程之间安全的共享。多个线程可使用一样的GPU
或者CPU
的CIContext
对象来渲染CIImage
对象
在CIFilter
类中,还有一些其余函数,多是咱们须要用到的,这里也简单说明下
//输入的键值信息 NSArray<NSString *> *inputKeys; //输出的键值信息 NSArray<NSString *> *outputKeys; //返回滤镜的属性描述信息 NSDictionary<NSString *,id> *attributes; //将全部输入键值的值设为默认值(曾经乱用,致使个人滤镜效果彻底没有任何反应,差点怀疑人生...) - (void)setDefaults; //根据滤镜的key查找其下面的因此子类效果 + (NSArray<NSString *> *)filterNamesInCategory:(nullable NSString *)category
CIContext
在建立结果图片的时候须要用到,刚开始用的时候,出于好奇用了两种不一样的方法来返回结果,本觉得....我会有一个方式获取不处处理后的结果,然而大跌眼镜,竟然有....CIImage *outPutImage = [ciFilter outputImage]; //获取上下文 CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent]; UIImage *filter_image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); // UIImage *filter_image = [UIImage imageWithCIImage:outPutImage];
就是上面屏蔽的代码部分imageWithCIImage
,这就使我纳闷了,因而猜想并查阅资料,原来在调用该方法的时候,实际上是隐式的声明了CIContext
,这样看来,哇!好简单,省了我一堆代码,然而,这却引发另外的问题了,就是每次都会从新建立一个 CIContext
,然而 CIContext
的代价是很是高的。而且,CIContext
和 CIImage
对象是不可变的,在线程之间共享这些对象是安全的。因此多个线程可使用同一个 GPU
或者 CPU
CIContext
对象来渲染 CIImage
对象。因此咱们不该该使用 imageWithCIImage
来生成UIImage
,而应该用上述另一种方式来获取结果图像。
Core Image
在处理图像的时候,能够有两种选择GPU
、CPU
,在Context
中能够对其进行设置,经过设置键值,这里的键值为kCIContextUseSoftwareRenderer
,默认状况下,是为GPU
处理方式,若是将其设置为YES
,则为CPU
处理
以下
//CPU处理 CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];
若是经过GPU
的话,速度就会更快,利用了GPU
硬件的并行优点,可使用 OpenGLES
或者Metal
来渲染图像,这种方式CPU
彻底没有负担,应用程序的运行循环不会受到图像渲染的影响。可是也有个问题,就是若是APP
运行到后台的时候,GPU
就会中止处理,等回到前台的时候又继续,而若是采起CPU
来处理的话,就不会出现这么一种状况,在前面的图中,咱们能够看到CPU
是采用GCD
的方式来对图像进行渲染。因此在使用的时候,仍是须要分状况,若是是处理复杂的操做,好比高斯模糊这样的,建议仍是用GPU
来处理,能够节省CPU
的开销,若是在后台还须要操做的话,可使用CPU
来操做。
// CIImage *outPutImage = [ciFilter outputImage]; //获取上下文 CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent]; UIImage *filter_image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage);
上面的这段代码是经过GPU
的方式来处理图像,而后获得结果UIImage
,最后再赋值给UIImageView
。
分析下这个过程:
一、将图像上传到GPU
,而后进行滤镜处理
二、获得CGImageRef cgImage
的时候,又将图像复制到了CPU
上
三、在赋值给UIImageView
进行显示的时候,又须要经过GPU
处理位图数据,进行渲染
这样的话,咱们就在GPU
-CPU
-GPU
上循环操做,在性能上确定是有必定的损耗的,那么为了不这种问题,咱们该这怎么办呢?
查看API
,咱们能够看到有这么一个函数
+ (CIContext *)contextWithEAGLContext:(EAGLContext *)eaglContext
EAGLContext
:是基于OpenGL ES
的上下文
经过上面的函数,咱们经过OpenGL ES
的上下文建立的Core Image
的上下文就能够实时渲染了,而且渲染图像的过程始终在 GPU
上进行,可是要显示图像,又该怎么办呢?若是仍是用UIImageView
的话,那么势必会回到CPU
上,这里,咱们能够用GLKView
,一个属于GLKIT
中的类,经过GLKView
和其属性@property (nonatomic, retain) EAGLContext *context
来将图像绘制出来,这样的话,就能保证咱们的滤镜,一直在GPU
上进行,大大的提升效率。
针对该方案,我自定义了一个相似UIImageView
的类FilterImageView
//FilterImageView.h #import <GLKit/GLKit.h> @interface FilterImageView : GLKView @property (nonatomic,strong) UIImage *image; @property (nonatomic,strong) CIFilter *filter; @end
.m
文件核心代码
//FilterImageView.m - (id)initWithFrame:(CGRect)frame { EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; self = [super initWithFrame:frame context:context]; if (self) { _ciContext = [CIContext contextWithEAGLContext:context options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:kCIContextUseSoftwareRenderer]]; //超出父视图 进行剪切 self.clipsToBounds = YES; } return self; } - (void)drawRect:(CGRect)rect { if (_ciContext && _image) { //获得CIImage CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image]; CGRect inRect = [self imageBoundsForContentModeWithFromRect:inputCIImage.extent toRect:CGRectMake(0, 0, self.drawableWidth, self.drawableHeight)]; if (_filter) { [_filter setValue:inputCIImage forKey:kCIInputImageKey]; //根据filter获得输出图像 if (_filter.outputImage) { //渲染开始 [_ciContext drawImage:_filter.outputImage inRect:inRect fromRect:inputCIImage.extent]; } }else{ [_ciContext drawImage:inputCIImage inRect:inRect fromRect:inputCIImage.extent]; } } }
如此以后,咱们就能提升滤镜的效率,特别是一些复杂的。
关于滤镜,能写的就只要这么多了,在学习中,也确实发现这是一个好东西,能够作不少炫酷的东西出来,为此,特地作了一个简单的[Demo],目前还未完善,但愿各位勿喷。iOS CoreImage之滤镜简单使用
代码地址以下:
http://www.demodashi.com/demo/11605.html
注:本文著做权归做者,由demo大师代发,拒绝转载,转载须要做者受权