代码地址如下:
http://www.demodashi.com/demo/11605.html

老骥伏枥,志在千里

前记

最近一直在研究图像处理方面,既上一篇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尽可能将滤镜合并,以最大限度地减少应用滤镜的计算量。

涉及API
  • CIImage :这是一个模型对象,它保存能构建图像的数据,可以是图像的Data,可以是一个文件,也可以是CIFilter输出的对象。
  • CIContext :上下文,是框架真正工作的地方,它需要分配必要的内存,并编译和运行滤镜内核来执行图像处理。建立一个上下文是非常昂贵的,所以你会经常想创建一个反复使用的上下文。
  • CIFilter :滤镜对象,主要是对图像进行处理的类。通过设置一些键值来控制滤镜的具体效果

注:Core ImageCore Graphics使用的是左下原点坐标

到此有一个疑问?就是苹果怎么会弄出这么多image,比如CIImageUIImageCGImageRef,有什么区别呢?为了弄清这个问题,我也特别搜寻了一番,下面也记录一下

UIImage:管理图片数据,主要用来展现,Image对象并没有提供直接访问相关的图片数据的操作, 因此你总是通过已经存在的图片数据来创建它

CGImage:是基于像素的矩阵,每个点都对应了图片中点的像素信息

CIImage:包含了创建图片的所有必要的数据,但其本身没有渲染成图片,它代表的是图像数据或者生成图像数据的流程(如滤镜)。拥有与之关联的图片数据, 但本质上并不是一张图片,你可以CIImage对象作为一个图片的”配方”。CIImage对象拥有生成一张图片所具备的所有信息,但Core Image并不会真正的去渲染一张图片, 除非被要求这么做。


使用方式
  • 1 . CIImage创建,在使用滤镜之前,你必须要先有一个CIImage对象,在拥有该对象后结合CIFilter才能实现我们的滤镜效果。这里需要注意的是,如果直接使用image.cIImage,那么很遗憾的告诉你,你将得到一个nil,哈哈
    如下:


原因在UIImageAPI中有介绍// returns underlying CIImage or nil if CGImageRef based,应该是说图片可能不是基于CIImage而创建的
正确的方式为

 //得到CIImageCIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
  • 2 . CIFilter创建方式大概有下面三种
+(nullable CIFilter *) filterWithName:(NSString *) name
+(nullable CIFilter *)filterWithName:(NSString *)namekeysAndValues:key0, ... NS_REQUIRES_NIL_TERMINATION NS_SWIFT_UNAVAILABLE("");
+(nullable CIFilter *)filterWithName:(NSString *)namewithInputParameters:(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];

大概效果如下


注意:
1、在设置键值的时候,我们需要有选择性的进行设置,具体怎么选择呢?
比如上面的冲印效果,在官方文档是这么展示的


只有一个必须输入的inputImage,因此不需要其它参数就可以实现
又比如高斯模糊CIGaussianBlur,在官方文档中,是这么展示的


如果我们需要控制其模糊半径,可以这么设置

[ciFilter setValue:@(20.f) forKey:@"inputRadius"];

2、CIFilter 并不是线程安全的,这意味着 一个 CIFilter对象不能在多个线程间共享。如果你的操作是多线程的,每个线程都必须创建自己的 CIFilter 对象,而CIContextCIImage对象都是不可修改的, 意味着它们可以在线程之间安全的共享。多个线程可以使用同样的GPU或者CPUCIContext对象来渲染CIImage对象

CIFilter类中,还有一些其他函数,可能是我们需要用到的,这里也简单说明下

//输入的键值信息
NSArray<NSString *> *inputKeys;
//输出的键值信息
NSArray<NSString *> *outputKeys;
//返回滤镜的属性描述信息
NSDictionary<NSString *,id> *attributes;
//将所有输入键值的值设为默认值(曾经乱用,导致我的滤镜效果完全没有任何反应,差点怀疑人生...)
- (void)setDefaults;
//根据滤镜的key查找其下面的所以子类效果
+ (NSArray<NSString *> *)filterNamesInCategory:(nullable NSString *)category

  • 3 . 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的代价是非常高的。并且,CIContextCIImage 对象是不可变的,在线程之间共享这些对象是安全的。所以多个线程可以使用同一个 GPU 或者 CPU
CIContext对象来渲染 CIImage 对象。所以我们不应该使用 imageWithCIImage 来生成UIImage,而应该用上述另外一种方式来获取结果图像。

Core Image 效率

Core Image在处理图像的时候,可以有两种选择GPUCPU,在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

分析下这个过程:
1、将图像上传到GPU,然后进行滤镜处理
2、得到CGImageRef cgImage的时候,又将图像复制到了CPU
3、在赋值给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) {//得到CIImageCIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];CGRect inRect = [self imageBoundsForContentModeWithFromRect:inputCIImage.extenttoRect:CGRectMake(0, 0, self.drawableWidth, self.drawableHeight)];if (_filter) {[_filter setValue:inputCIImage forKey:kCIInputImageKey];//根据filter得到输出图像if (_filter.outputImage) {//渲染开始[_ciContext drawImage:_filter.outputImageinRect:inRectfromRect:inputCIImage.extent];}}else{[_ciContext drawImage:inputCIImageinRect:inRectfromRect:inputCIImage.extent];}}
}
项目文件截图:


如此之后,我们就能提高滤镜的效率,特别是一些复杂的。
关于滤镜,能写的就只要这么多了,在学习中,也确实发现这是一个好东西,可以做很多炫酷的东西出来,为此,特意做了一个简单的[Demo],目前还未完善,希望各位勿喷。iOS CoreImage之滤镜简单使用

代码地址如下:
http://www.demodashi.com/demo/11605.html

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

iOS CoreImage之滤镜简单使用相关推荐

  1. iOS开发UI篇—简单介绍静态单元格的使用

    iOS开发UI篇-简单介绍静态单元格的使用 一.实现效果与说明 说明:观察上面的展示效果,可以发现整个界面是由一个tableview来展示的,上面的数据都是固定的,且几乎不会改变. 要完成上面的效果, ...

  2. 李洪强iOS开发之- 实现简单的弹窗

     李洪强iOS开发之- 实现简单的弹窗 实现的效果:  112222222222223333333333333333

  3. iOS开发-ZFPlayer的简单使用 播放单个网络视频

    iOS开发-ZFPlayer的简单使用 播放单个网络视频 前言 开发准备 代码 注意 前言 关于ZFPlayer播放单个网络视频案例,它的网络列表视频案例在gitHub上面很多. 开发准备 podfi ...

  4. ios射击类游戏简单代码射击

    ios射击类游戏简单代码射击 之后会持续的更新,学习的同学请关注,共同学习 main.c #import <Foundation/Foundation.h> #import "S ...

  5. Socket在iOS客户端上的简单实现 - 利用GCAsyncSocket框架

    Socket在iOS客户端上的简单实现 - 利用GCAsyncSocket框架 GCAsyncSocket 这是一个2003的开发出来的一个开源框架 首先把GCDAsyncSocket的.h和.m文件 ...

  6. 思科IOS软件命名规则简单介绍:

    思科IOS软件命名规则简单介绍: AAAAA-BBBB-CC-DDDD.EE 1.  AAAAA    这组字符是说明文件所适用的硬件平台, 2.  BBBB       这组字符是说明这个IOS中所 ...

  7. html放射性背景怎么做,Photoshop使用几个滤镜简单制作放射性背景教程

    当你想要突出某物的时候,在画面上我们普遍都会认同放射状背景的作用,而自制背景看上去似乎是繁琐的任务,放到PS里,几个滤镜就能轻松解决.下面小编就为大家详细介绍Photoshop使用几个滤镜简单制作放射 ...

  8. html 滤镜制作线条,PS滤镜简单制作炫酷的线条效果

    今天为大家分享利用PS滤镜制作炫酷线条效果方法,教程很不错,值得大家学习,推荐过来,喜欢的朋友快快来学习吧! 步骤 启动PS软件 ctrl+n新建一空白文档,尺寸为 800x600px,背景色设置为黑 ...

  9. ios分屏_其实iOS远没那么简单!这些好用的隐藏技巧,希望你不是第一次用

    其实iOS远没那么简单!这些好用的隐藏技巧,希望你不是第一次用 苹果iOS系统一项以简洁著称,每次更新系统,都会在系统更新里面,说的非常清楚明白,一些使用技巧.并且相较于安卓系统,苹果iOS系统看起来 ...

最新文章

  1. 【CVPR2020-中科院计算所】多模态GNN:在视觉信息和场景文字上联合推理
  2. 赛门铁克公布Q3财报 亏损68亿美元
  3. 考前自学系列·计算机组成原理·存储器画图
  4. 【转载】JAVAEE之内置对象和属性范围
  5. Android语言国际化values资源文件命名规则
  6. insert ... on duplicate key update产生death lock死锁原理
  7. C++解析(31):自定义内存管理(完)
  8. YUV420查表法高效、无失真的转换为RGB32格式
  9. 一位程序员妹纸讲述她是如何拿到美团offer的?
  10. 调用地图JS/API只显示一个省
  11. windows更改redis配置文件
  12. LaTeX字符加的各种帽子
  13. xls与csv文件区别
  14. 生物医学文献知识图创建的关系提取
  15. 【终极之战】基于Vue3+Vant3造一个网页版的类掘金app项目 - 个人主页
  16. 计算机绘图图框实验报告,制图基础及计算机绘图实验报告.doc
  17. git版本控制常用指令
  18. 3D深度相机---结构光
  19. 今天看到一篇文章,摘抄几句。愿,与君共勉!
  20. 菜谱更新:平菇烧豆腐。

热门文章

  1. nsga2代码解读python_代码资料
  2. su生成面域插件_插件玩的溜,SU不用愁
  3. MQTT进阶:web端远程控制LED灯
  4. enum 有什么好处_高新技术企业认定四个核心评分标准是什么?软著能加分吗?...
  5. [C++] - 创建对象时 () 和 {} 的区别
  6. android bitmap大小计算,android 之Bitmap 内存大小计算
  7. matlab在电磁学中的应用,MATLAB在电磁学中的应用
  8. 【小项目】学生信息登记系统
  9. Spring Data Elasticsearch案例详解
  10. icomoon.io生成字体图标