iOS CoreImage之滤镜简单使用
老骥伏枥,志在千里
前记
最近一直在研究图像处理方面,既上一篇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 Image
和Core Graphics
使用的是左下原点坐标
到此有一个疑问?就是苹果怎么会弄出这么多image
,比如CIImage
、UIImage
、CGImageRef
,有什么区别呢?为了弄清这个问题,我也特别搜寻了一番,下面也记录一下
①UIImage
:管理图片数据,主要用来展现,Image
对象并没有提供直接访问相关的图片数据的操作, 因此你总是通过已经存在的图片数据来创建它
②CGImage
:是基于像素的矩阵,每个点都对应了图片中点的像素信息
③CIImage
:包含了创建图片的所有必要的数据,但其本身没有渲染成图片,它代表的是图像数据或者生成图像数据的流程(如滤镜)。拥有与之关联的图片数据, 但本质上并不是一张图片,你可以CIImage
对象作为一个图片的"配方"。CIImage
对象拥有生成一张图片所具备的所有信息,但Core Image
并不会真正的去渲染一张图片, 除非被要求这么做。
使用方式
- 1 .
CIImage
创建,在使用滤镜之前,你必须要先有一个CIImage
对象,在拥有该对象后结合CIFilter
才能实现我们的滤镜效果。这里需要注意的是,如果直接使用image.cIImage
,那么很遗憾的告诉你,你将得到一个nil
,哈哈
如下:
原因在UIImage
的API
中有介绍 // returns underlying CIImage or nil if CGImageRef based
,应该是说图片可能不是基于CIImage
而创建的
正确的方式为
//得到CIImage
CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
- 2 .
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];
大概效果如下
注意:
1、在设置键值的时候,我们需要有选择性的进行设置,具体怎么选择呢?
比如上面的冲印效果,在官方文档是这么展示的
只有一个必须输入的inputImage
,因此不需要其它参数就可以实现
又比如高斯模糊CIGaussianBlur
,在官方文档中,是这么展示的
如果我们需要控制其模糊半径,可以这么设置
[ciFilter setValue:@(20.f) forKey:@"inputRadius"];
2、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
- 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
的代价是非常高的。并且,CIContext
和 CIImage
对象是不可变的,在线程之间共享这些对象是安全的。所以多个线程可以使用同一个 GPU
或者 CPU
CIContext
对象来渲染 CIImage
对象。所以我们不应该使用 imageWithCIImage
来生成 UIImage
,而应该用上述另外一种方式来获取结果图像。
Core Image 效率
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
。
分析下这个过程:
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) {
//得到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之滤镜简单使用
注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权
iOS CoreImage之滤镜简单使用的更多相关文章
- iOS开发网络篇—简单介绍ASI框架的使用
iOS开发网络篇—简单介绍ASI框架的使用 说明:本文主要介绍网络编程中常用框架ASI的简单使用. 一.ASI简单介绍 ASI:全称是ASIHTTPRequest,外号“HTTP终结者”,功能十分强大 ...
- iOS开发UI篇—简单的浏览器查看程序
iOS开发UI篇—简单的浏览器查看程序 一.程序实现要求 1.要求 2. 界面分析 (1) 需要读取或修改属性的控件需要设置属性 序号标签 图片 图片描述 左边按钮 右边按钮 (2) 需要监听响应事件 ...
- iOS开发UI篇—简单介绍静态单元格的使用
iOS开发UI篇—简单介绍静态单元格的使用 一.实现效果与说明 说明:观察上面的展示效果,可以发现整个界面是由一个tableview来展示的,上面的数据都是固定的,且几乎不会改变. 要完成上面的效果, ...
- iOS开发Swift篇—简单介绍
iOS开发Swift篇—简单介绍 一.简介 Swift是苹果于2014年WWDC(苹果开发者大会)发布的全新编程语言 Swift在天朝译为“雨燕”,是它的LOGO 是一只燕子,跟Objective-C ...
- 李洪强iOS开发之- 实现简单的弹窗
李洪强iOS开发之- 实现简单的弹窗 实现的效果: 112222222222223333333333333333
- iOS开发之WKWebView简单使用
iOS开发之WKWebView简单使用 iOS开发之 WKWebVeiw使用 想用UIWebVeiw做的,但是突然想起来在iOS8中出了一个新的WKWebView,算是UIWebVeiw的升级版. ...
- iOS开发之滤镜的使用技巧(CoreImage)
一.滤镜的内容和效果是比较多并且复杂的 ,学习滤镜需要技巧 如下: 两个输出语句解决滤镜的属性选择问题: 1.查询效果分类中包含什么效果按住command 点击CIFilter 进入接口文件 找到第1 ...
- iOS CoreImage图片处理动态渲染(滤镜)
// // ViewController.m // CoreImageOfDong // // Created by Dong on 15/6/30. // Copyright (c) 201 ...
- IOS 绘制圆饼图 简单实现的代码有注释
今天为大家带来IOS 绘图中圆饼的实现 .h文件 #import <UIKit/UIKit.h> @interface ZXCircle : UIView @end .m文件 #impor ...
随机推荐
- 安装Team Services Agent Win7
现状:项目现时使用的是Team Services,使用Team Services可以控制其中的一台Build Server,从Github提取代码,并在Build Server进入编译打包处理(son ...
- HDU 2824.The Euler function-筛选法求欧拉函数
欧拉函数: φ(n)=n*(1-1/p1)(1-1/p2)....(1-1/pk),其中p1.p2…pk为n的所有素因子.比如:φ(12)=12*(1-1/2)(1-1/3)=4.可以用类似求素数的筛 ...
- Python爬链接
# -*- coding: utf-8 -*- """ Created on Wed Jan 11 17:21:54 2017 @author: PE-Monitor & ...
- 二分图匹配【p2147】课程
Description n个学生去p个课堂,每一个学生都有自己的课堂,并且每个学生只能去一个课堂,题目要求能够安排每一个课堂都有人吗? Input 第一行是测试数据的个数, 每组测试数据的开始分别是p ...
- RPD Volume 168 Issue 4 March 2016 评论2
Influence of the phantom shape (slab, cylinder or Alderson) on the performance of an Hp(3) eye dosem ...
- small test on 5.29 night T1
可以发现题目的重点是在第一个部分,因为只要信心值我们求出来了,那么第二问就是一个简单的最长上升子序列问题了,所以接下来只讲第一问. #include<iostream> #include& ...
- [CF98E]Help Shrek and Donkey
题意:A和B两个卡牌大师玩游戏,A有$n$张牌,B有$m$张牌,桌上有$1$张牌,这$n+m+1$张牌互不相同且A和B都知道这些牌里有什么牌(但他们互相不知道对方有什么牌,两个人也都不知道桌上的那张牌 ...
- 【暴力】Codeforces Round #398 (Div. 2) A. Snacktower
题意不复述. 用个bool数组记录一下,如果某一天,当前剩下的最大的出现了的话,就输出一段. #include<cstdio> using namespace std; int n; bo ...
- 【矩阵乘法】图中长度为k的路径的计数
样例输入 4 2 0 1 1 0 0 0 1 0 0 0 0 1 1 0 0 0 样例输出 6 #include<cstdio> #include<vector> using ...
- 【树套树】bzoj3110 [Zjoi2013]K大数查询
题解很多,实现起来以外地简洁.内层的区间线段树上用了标记永久化. #include<cstdio> using namespace std; #define N 50001 struct ...