GPUImage作為iOS相當(dāng)老牌的圖片處理三方庫(kù)已經(jīng)有些日子了(2013年發(fā)布第一個(gè)版本),至今甚至感覺(jué)要離我們慢慢遠(yuǎn)去(2015年更新了最后一個(gè)release)??赡墁F(xiàn)在分享這個(gè)稍微有點(diǎn)晚,再加上落影大神早已發(fā)布過(guò)此類文章,但是還是想從自己的角度來(lái)分享一下對(duì)其的理解和想法。
本文集所有內(nèi)容皆為原創(chuàng),嚴(yán)禁轉(zhuǎn)載。
? ? ? ? 正如之前兩章中對(duì)GPUImage的理解,它的處理流程就像一個(gè)鏈或者管道,官方叫法為Pipeline。有管道就一定存在源頭,也就是使用GPUImage時(shí)需要做的第一件事就是把源頭準(zhǔn)備好,才能進(jìn)行接下來(lái)的濾鏡也好調(diào)整也好的各種操作,最后把結(jié)果從管道中導(dǎo)出。所以理解GPUImage對(duì)輸入源到底做了哪些事情對(duì)于理解整個(gè)處理過(guò)程十分重要。因?yàn)橹笆褂肎PUImage基本是針對(duì)靜態(tài)圖片的處理,所以本章就針對(duì)靜態(tài)圖片作為輸入源的具體操作做介紹??赡苤篑R上也會(huì)涉及到視頻輸入源,那這些就之后再補(bǔ)充。
? ? ? ? 上篇文章中官方文檔有提到,GPUImage提供了四種可以作為輸入源的資源類型,再加上我補(bǔ)充的一種。其中GPUImagePicture、GPUImageRawDataInput這兩種可作為靜態(tài)圖片資源類型的輸入源初始化類型。但在具體說(shuō)明者兩個(gè)類之前,要先重點(diǎn)了解一下GPUImage中所有可作為輸入源類型的父類:GPUImageOutput。
GPUImageOutput
? ? ? 此類的介紹在該類的頭文件注釋中,大概意思是:圖片或者視頻幀可通過(guò)繼承了GPUImageOutput的資源對(duì)象進(jìn)行加載,資源對(duì)象把圖片或者視頻幀加載到OpenGL ES的紋理中,處理完成后再把這些紋理傳遞處理管道中的下一個(gè)對(duì)象。
? ? ? ? 簡(jiǎn)單的說(shuō),就是繼承了這個(gè)類的子類對(duì)象都可以:1.紋理信息傳遞給下一位;2.紋理信息導(dǎo)出成圖片。因此,在此類中的實(shí)現(xiàn)就差不多分為三部分:1.自身數(shù)據(jù)的管理;2.數(shù)據(jù)傳遞對(duì)象的管理。3.導(dǎo)出圖片。以下根據(jù)這三點(diǎn)分別介紹相應(yīng)方法及屬性。
1.自身數(shù)據(jù)的管理:
?//這個(gè)就是所有管道中每個(gè)節(jié)點(diǎn)傳遞的內(nèi)容。GPUImage或者說(shuō)OpenGL ES處理圖片或者視頻過(guò)程中,會(huì)將需要處理的圖像內(nèi)容放置到Buffer中處理。Framebuffer顧名思義,就是用來(lái)渲染單張圖片或者一幀圖像內(nèi)容的對(duì)象,也就是傳說(shuō)中的FBO在GPUImage中的對(duì)應(yīng)??梢钥闯?,在OpenGL ES中也把其對(duì)象化,但本身基于C的api限制,在使用的時(shí)候并不能體現(xiàn)面向?qū)ο蟮奶卣鳎虼薌PUImage把它嚴(yán)格意義上的變成了一個(gè)對(duì)象類型。我對(duì)FrameBuffer的理解是一個(gè)帶有圖片原始內(nèi)容(texture)+各種紋理參數(shù)的類。
GPUImageFramebuffer *outputFramebuffer;
? ? ? ? 說(shuō)到FrameBuffer其實(shí)還需要單獨(dú)一章來(lái)說(shuō)明其具體作用和GPUImage對(duì)此類對(duì)象的特殊管理方式,那就放到之后,現(xiàn)在先把它理解成一個(gè)GPUImageOutput類型的對(duì)象A必定有一個(gè)outputFramebuffer對(duì)象的屬性,A從管道的上一個(gè)節(jié)點(diǎn)得到數(shù)據(jù)后進(jìn)行處理完的數(shù)據(jù)內(nèi)容會(huì)存在自身的outputFramebuffer中,然后再把outputFramebuffer傳遞給下一個(gè)節(jié)點(diǎn)。
? ? ? ?GPUImageOutput中并沒(méi)有涉及到outputFramebuffer屬性的初始化部分,原因在于針對(duì)不同的數(shù)據(jù)內(nèi)容再加上上面提到的GPUImage對(duì)frameBuffer的特殊管理,outputFramebuffer的初始化部分都在GPUImageOutput的各個(gè)子類中進(jìn)行。
//這個(gè)方法就是用來(lái)把自身的outputFramebuffer傳遞給下一個(gè)目標(biāo)節(jié)點(diǎn),也就是方法中的target。另外可以注意到方法還有第二個(gè)參數(shù)inputTextureIndex,簡(jiǎn)單理解就是,每個(gè)節(jié)點(diǎn)的輸入源不一定只有一個(gè)。簡(jiǎn)單的處理流程一般都為一張圖片加一個(gè)濾鏡輸出,但要實(shí)現(xiàn)對(duì)于把兩張或者多張圖片進(jìn)行合并后輸出這種需要兩個(gè)以上輸入源的需求時(shí)就需要增加這個(gè)參數(shù)來(lái)。
- (void)setInputFramebufferForTarget:(id)target atIndex:(NSInteger)inputTextureIndex;
{
[target setInputFramebuffer:[self framebufferForOutput] atIndex:inputTextureIndex];
}
2.數(shù)據(jù)傳遞對(duì)象的管理
//targets用于記錄自身對(duì)象添加過(guò)的目標(biāo)節(jié)點(diǎn),所以對(duì)于一個(gè)管道來(lái)說(shuō),并不是只有一條路,每個(gè)節(jié)點(diǎn)都可以產(chǎn)生多個(gè)分支。例如,一張圖片作為輸入源,可以分別進(jìn)行加馬賽克和圖片銳化操作,最終導(dǎo)出兩張圖片。
//targetTextureIndices 用于記錄targets數(shù)組中對(duì)應(yīng)下標(biāo)的某個(gè)目標(biāo)節(jié)點(diǎn)的輸入順序。這個(gè)順序?qū)τ谛枰鄠€(gè)輸入源的目標(biāo)節(jié)點(diǎn)非常重要,決定了這個(gè)目標(biāo)節(jié)點(diǎn)自己的frameBuffer的各種樣式還有處理后的效果。值的范圍在對(duì)應(yīng)目標(biāo)節(jié)點(diǎn)需要的輸入源個(gè)數(shù)內(nèi),例如target0對(duì)象需要2個(gè)輸入源,那targetTextureIndices中對(duì)應(yīng)下標(biāo)的元素值為0或者1。
NSMutableArray *targets, *targetTextureIndices;
//字面意思:當(dāng)前目標(biāo)對(duì)象是否要忽略。忽略的意思就是在處理管道中要不要選擇不做這個(gè)目標(biāo)對(duì)象的處理。
@property(readwrite, nonatomic) BOOL shouldIgnoreUpdatesToThisTarget;
//以下兩個(gè)方法就是用來(lái)設(shè)置下一個(gè)目標(biāo)節(jié)點(diǎn)的方法。如果下個(gè)target需要兩個(gè)輸入源,那么這兩個(gè)輸入源需按順序調(diào)用addTarget,先add的對(duì)象的textureLocation為0,另一個(gè)則為1。方法中有兩個(gè)操作比較重要:1.調(diào)用- (void)setInputFramebufferForTarget:(id)target atIndex:(NSInteger)inputTextureIndex;方法,在添加時(shí)就把自己所屬的frameBuffer傳遞給了剛添加好的target。2.把添加的target以及自己在這個(gè)target中所占的下標(biāo)保存管理。再會(huì)發(fā)現(xiàn),這些操作都在一個(gè)定義好的隊(duì)列中實(shí)現(xiàn),如下下:
- (void)addTarget:(id)newTarget;
- (void)addTarget:(id)newTarget atTextureLocation:(NSInteger)textureLocation;
在GPUImageContext有對(duì)這個(gè)自定義隊(duì)列進(jìn)行初始化的操作,可以看到,當(dāng)前運(yùn)行的硬件環(huán)境決定了這個(gè)隊(duì)列是串行還是并發(fā)。所有的任務(wù)都是同步的形式添加到隊(duì)列中,目的是保證整個(gè)管道每一步的操作都能按順序執(zhí)行,這樣才能保證管道中每個(gè)target得到并處理的內(nèi)容都是存在且正確的。
_contextQueue = dispatch_queue_create("com.sunsetlakesoftware.GPUImage.openGLESContextQueue", GPUImageDefaultQueueAttribute());
dispatch_queue_attr_t GPUImageDefaultQueueAttribute(void)
{
#if TARGET_OS_IPHONE
if ([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] != NSOrderedAscending)
{
return dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0);
}
#endif
return nil;
}
//有添加就有移除,當(dāng)當(dāng)前管道中某些target的處理效果不需要或者需要更改的時(shí)候,你可以把這些節(jié)點(diǎn)從它的上一站移除,這個(gè)移除的操作就需要它的上一節(jié)點(diǎn)對(duì)象調(diào)用以下方法實(shí)現(xiàn)。如果作為中間節(jié)點(diǎn),還需要把自己所添加的target移除。就像火車中的中間一截車廂,需要把自己與上一截和下一截的鏈接都去掉,才可以完全移除。當(dāng)然在ARC下,全部銷毀的時(shí)候不需要做這些移除工作。
- (void)removeTarget:(id)targetToRemove;
- (void)removeAllTargets;
3.導(dǎo)出圖片
GPUImageOutput類其中一個(gè)非常獨(dú)一無(wú)二的作用就是可以直接從該類處理完成后的數(shù)據(jù)內(nèi)容中生成并導(dǎo)出圖片。但是GPUImageOutput畢竟只是一個(gè)共有的基類,包括上方說(shuō)到兩點(diǎn)以及導(dǎo)出圖片的具體方法的實(shí)現(xiàn)部分并沒(méi)有在本類中體現(xiàn),由其子類重寫(xiě)。
- (void)useNextFrameForImageCapture;
{
}
- (CGImageRef)newCGImageFromCurrentlyProcessedOutput;
{
return nil;
}
不過(guò)在- (CGImageRef)newCGImageByFilteringCGImage:(CGImageRef)imageToFilter方法中,告訴了大家如何實(shí)現(xiàn)導(dǎo)出圖片的具體步驟:
1.其中stillImageSource是一個(gè)輸入源對(duì)象,把自己添加到當(dāng)前target是肯定要做的;
2.調(diào)用當(dāng)前將要導(dǎo)出圖片的target的processImage之前一定要先調(diào)用useNextFrameForImageCapture方法;
3.調(diào)用當(dāng)前將要導(dǎo)出圖片的target的processImage方法。
[self useNextFrameForImageCapture];? ?
?[stillImageSource addTarget:(id)self];
[stillImageSource processImage];
CGImageRef processedImage = [self newCGImageFromCurrentlyProcessedOutput];
以上是GPUImageOutput類中的重要三個(gè)部分的介紹,但是像中間涉及到的frameBuffer、處理隊(duì)列以及未提到的其他屬性和方法并未描述完全,完全是因?yàn)樽髡叩脑O(shè)計(jì)思路太狡猾,把幾乎圖像處理過(guò)程中會(huì)涉及到的點(diǎn)都進(jìn)行了封裝和準(zhǔn)備。這些內(nèi)容詳細(xì)介紹同樣放之后補(bǔ)上,如果有疑問(wèn)或?qū)ξ乙陨系睦斫庥挟愖h,歡迎私信討論。