代碼地址以下:
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大師代發,拒絕轉載,轉載須要做者受權