iOS中實現模糊效果教程

51CTO發表於2014-11-28

iOS 7在視覺方面有許多改變,其中非常吸引人的功能之一就是在整個系統中巧妙的使用了模糊效果。許多第三方應用程式已經採用了這樣的設計細節,並以各種奇妙的和具有創造性的方式使用它。

本文將通過幾種不同的技術來實現iOS 7中的模糊效果,當然,這一切都利用了一個名為GPUImage的框架。

GPUImage是由Brad Larson建立的,它利用GPU,使在圖片和視訊上應用不同的效果和濾鏡變得非常的容易,同時它還擁有出色的效能,並且它的效能要比蘋果內建的相關APIs出色。

注意:本文需要一臺物理裝置來編譯並執行示例程式(在模擬器上無法使用)。同樣還需要一個iOS開發者賬號。如果你還沒有開發者賬號的 話,可以來[這裡](https://developer.apple.com/)註冊一個。註冊為開發者之後,會有許多福利喲,例如可以使用物理裝置來 開發程式,提前獲得蘋果的相關測試版程式,以及大量的開發資源。

iOS中利用GPUImage實現模糊效果

iOS中利用GPUImage實現模糊效果

下面我們先來看看本文的目錄結構:

  • 開始
  • 為什麼要是用模糊效果
    • 深度引導
    • 上下文
    • 關注度
  • 新增靜態的模糊效果
    • 建立截圖Category
    • 利用斷點測試截圖圖片
    • 顯示截圖圖片
    • 設定contentsRect
    • 重置模糊濾鏡
    • 對其背景圖片
  • 實時模糊
  • 執行緒中簡潔的分支
  • 一些潛在的實時模糊方案
  • 一個折中的方法——對視訊實時模糊
    • 利用GPUImage對視訊進行模糊處理
  • 何去何從?

 

開始

首先先來這裡下載本文的starter工程,並將其解壓出來。

用Xcode開啟Video Blurring.xcodeproj,並將工程執行到裝置中。此時看到程式的效果如下所示:

點選螢幕左上角的選單(三條橫紋),可以看到介面中出現兩個選項:錄製視訊和播放已有視訊。

請注意,現在所有的使用者介面都有一個灰色的背景,是不是感覺有點沉悶呢,本文我們就利用iOS 7中的模糊效果來替換掉這些沉悶的灰色背景。

 

為什麼要是用模糊效果

除了外觀看起來很棒以外,模糊效果還可以讓程式給使用者帶來3個重要的概念:深度引導、上下文和關注度。

深度引導

在使用者介面上,模糊效果可以給使用者提供一個深度引導效果,並且有利於使用者對程式導航的理解。在之前的iOS版本中的深度引導效果是通過:三維斜面 (three-dimensional bevels)和有關澤的按鈕(反映出一個模擬的光源),而在iOS 7中是通過模糊和視差(parallax)來實現的。

這裡說的視差效果,可以很明顯的觀察出來:在裝有iOS 7的裝置中,將裝置從一側傾斜至另一側,會發現裝置中的圖示在移動(會獨立於背景)。這樣可以給使用者做出一個提示:介面是由不同的層構成的,並且重要的界 面元素是在最前面的——這也涉及到下面將要介紹的一個概念:上下文。

上下文

上下文可以讓使用者在程式內獲得一種軸承的感覺。動畫的過度效果就提供了一種非常優秀的上下文,當使用者點選一個按鈕時,在兩個view之間利用動畫效 果來切換畫面(而不是直接顯示一個新的view),可以讓使用者知道新的view是從哪裡出現的,並且可以讓使用者很容易知道如何回到上一個view。

模糊效果可以將上一個view當做背景顯示出來,儘管上一個view已經失去焦點了,不過可以給使用者提供更多的上下文:剛剛是在哪裡。通知中心就是一個非常棒的例子:當拉下通知中心時,我們可以在背景中看到原來的view(即使現在正在處於通知中心介面)。

關注度

讓介面更加關注於某些選擇項上,而移除不需要的內容,讓使用者可以更加快捷的進行導航。使用者可以本能的忽略那些被模糊的介面元素,而將注意力集中到某些介面元素中。

通過本文,你將學到兩種模糊型別的實現方法:靜態模糊和動態模糊。靜態模糊代表著快照的時間點,它並不能反映被模糊介面元素的變化。大多數情況下,使用靜態模糊效果就足夠了。相反,動態模糊則是對需要模糊的背景做出實時更新。

相信看到具體的效果才是最好的,下面我們就來看看模糊效果的具體實現吧!

 

新增靜態的模糊效果

建立一個靜態模糊效果首先是將當前螢幕中的view轉換為一幅圖片。獲得圖片之後,只需要對圖片做模糊處理就可以了。將view轉換為一幅圖片(截圖)蘋果已經提供了一些非常棒的APIs了,並且在iOS 7中又有了新的方法可以讓截圖更加快速。

這些新的方法屬於截圖APIs中的一部分,截圖APIs不僅可以對某個view截圖,還能把整個view層次截圖,如果你希望對某個view截圖,那麼可以把view中的按鈕、標籤、開關等各種view也進行截圖。

此處我們將截圖的邏輯實現到UIView的一個category中。這樣一來,我們就可以很方便快捷的將任意的view(以及view中的內容)轉換為一個圖片——也算是程式碼的重用吧。

建立截圖Category

開啟File/New/File…,然後選擇iOS/Cocoa Touch/Objective-C category,如下圖所示:

將這個category命名為Screenshot,並將它的category選為UIView,如下圖所示:

將下面這個方法宣告到UIView+Screenshot.h中:

-(UIImage *)convertViewToImage;

接著將如下方法新增到 UIView+Screenshot.m 中:

-(UIImage *)convertViewToImage 
{ 
    UIGraphicsBeginImageContext(self.bounds.size); 
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES]; 
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    return image; 
}

上面的方法中,首先呼叫了UIGraphicsBeginImageContext(),最後呼叫的是UIGraphicsEndImageContext(),這兩行程式碼可以理解為圖形上下文的一個事物處理過程。一個上下文可以理解為不同的概念,例如螢幕,或者此處可以理解為一幅圖片。這裡的兩行程式碼起到一個離屏畫布的作用——可以將view繪製上去。

drawViewHierarchyInRect:afterScreenUpdates:方法利用view層次結構並將其繪製到當前的上下文中。

最後,UIGraphicsGetImageFromCurrentImageContext()從圖形上下文中獲取剛剛生成的UIImage。

現在,我們已經完成了category的實現,接著我們只需要在使用到的地方將其import一下即可。

如下程式碼所示,將程式碼新增到DropDownMenuController.m頂部:

#import "UIView+Screenshot.h"

同時,將如下方法新增到相同的檔案中:

-(void)updateBlur 
{ 
    UIImage *image = [self.view.superview convertViewToImage]; 
}

上面的程式碼確保是對superview進行截圖,而不僅僅是當前的view。不這樣做的話,截圖獲得的圖片只是menu本身。

利用斷點測試截圖圖片

為了測試截圖的效果,我們在convertViewToImage呼叫的下面一行新增一個斷點。這樣當命中斷點時,程式會在斷點中暫停執行,這樣我們就可以看到截圖的圖片,以此確保截圖程式碼的正確性:

在測試之前還有一件事情需要做:呼叫上面這個方法。

找到show方法,並在addToParentViewController下面直接呼叫一下updateBlur:

-(void)show { 
    [self addToParentViewController]; 

    [self updateBlur]; // Add this line 

    CGRect deviceSize = [UIScreen mainScreen].bounds; 

    [UIView animateWithDuration:0.25f animations:^(void){ 
        _blurView.frame = CGRectMake(0, 0, deviceSize.size.height, MENUSIZE); 
        _backgroundView.frame = CGRectMake(0, 0, _backgroundView.frame.size.width, MENUSIZE); 
    }]; 
}

編譯並執行程式,點選選單按鈕,可以看到Xcode在斷點出停止了,如下所示:

在debugger左下角hand pane中選擇image,然後單擊快速查詢圖示按鈕,就可以預覽剛剛的截圖啦:

如上圖所示,正是我們所預期的。

顯示截圖圖片

將擷取到的圖片顯示到選單的背景中就是小菜一碟啦。

一般來說我們都會利用UIImageView來顯示一幅圖片,而由於我們要利用GPUImage來模糊圖片,所以需要使用GPUImageView。

在這裡的工程中,已經新增好了GPUImage框架,我們只需要將標頭檔案import一下即可。

將下面的程式碼新增到DropDownMenuController.m頂部:

#import <GPUImage/GPUImage.h>

注意:GPUImage被包含在一個框架中,所以在import語句中,需要利用尖括弧,而不是雙引號。

此時,有一個_blurView,型別為UIView——是選單的灰色背景。將UIView修改為GPUImageView,如下所示:

@implementation DropDownMenuController { 
    GPUImageView *_blurView; 
    UIView *_backgroundView; 
}

修改之後,Xcode會報一個warning:大意是你利用UIView進行例項化,而不是預期的GPUImageView。

可以通過下面的方法消除這個警告,在viewDidLad中修改做如下修改:

_blurView = [[GPUImageView alloc] initWithFrame:CGRectMake(0, 0, deviceSize.size.height, 0)];

緊隨其後,將如下兩行程式碼新增進去,並移除設定背景色的程式碼:

_blurView.clipsToBounds = YES; 
_blurView.layer.contentsGravity = kCAGravityTop;

clipToBounds屬性設定為YES,把超出_blurView範圍的子view隱藏起來,而contentsGravity確保圖片出現在image view的頂部。

由於_blurView已經用於背景了,所以此處不需要額外設定了。

接著,我們還需要宣告一個用於模糊效果的過濾器。

將如下程式碼新增到DropDownMenuController.m:檔案的@implementation中

GPUImageiOSBlurFilter *_blurFilter;

找到之前新增的斷點,右鍵單擊,並選中Delete Breakpoint:

下面是非常重要的一步了——初始化模糊濾鏡。將如下程式碼新增到DropDownMenuController.m中:

-(void)updateBlur 
{ 
    if(_blurFilter == nil){ 
        _blurFilter = [[GPUImageiOSBlurFilter alloc] init]; 
         _blurFilter.blurRadiusInPixels = 1.0f; 

    } 

    UIImage *image = [self.view.superview convertViewToImage]; 
}

注意:上面將模糊半徑設定為一個畫素,這裡暫時將這個值設定低一點,這樣可以確保圖片的正確定位,當一切ok之後,再增加模糊半徑即可。

下面是時候將圖片顯示到GPUImageView中了。不過並不是簡單的例項化一個UIImage,並將其新增到GPUImageView中。首先需建立一個GPUImagePicture。

將如下程式碼新增到updateBlur方法的底部:

GPUImagePicture *picture = [[GPUImagePicture alloc] initWithImage:image];

至此,我們獲得了一個圖片,模糊濾鏡和iamge view。

接著再將如下程式碼新增到updateBlur底部:

[picture addTarget:_blurFilter]; 
[_blurFilter addTarget:_blurView]; 

[picture processImage];

上面這幾行程式碼,就像膠水一樣,將所有的事情關聯起來。將濾鏡當做target新增到圖片中,然後將image view當做濾鏡的target。

上面程式碼對圖片的處理全程發生在GPU上,也就是說當進行模糊計算和顯示時,並不會影響到使用者介面。當處理結束時,會把圖片顯示到image view上面。

編譯並執行程式,點選選單按鈕,可以看到如下類似畫面:

上面的圖片看起來是不是有點奇怪?看到的圖片被縮放到適配到選單檢視中了。要對此做出修正,我們需要指定圖片的哪一部分需要顯示在GPUImageView中——也就是處理截圖檢視的上半部分。

設定contentsRect

按照如下程式碼所示修改DropDownMenuController.m檔案中的show方法:

-(void)show 
{ 
    [self addToParentViewController]; 

    [self updateBlur]; 

    CGRect deviceSize = [UIScreen mainScreen].bounds; 

    [UIView animateWithDuration:0.25f animations:^(void){ 
        _blurView.frame = CGRectMake(0.0f, 0.0f, deviceSize.size.height, MENUSIZE); 
        _backgroundView.frame = CGRectMake(0.0f, 0.0f, _backgroundView.frame.size.width, MENUSIZE); 
        _blurView.layer.contentsRect = CGRectMake(0.0f, 0.0f, 1.0f, MENUSIZE / 320.0f); // Add this line! 
    }]; 
}

通過指定_blurView.layer.contentsRect來定義一個矩形,在單元座標空間(unit coordinate space)中,表示只使用layer content的一部分。

編譯並執行程式,點選選單按鈕,會看到如下圖所示效果:

雖然已經使用了圖片的一部分,看起來還是不正確,這是因為它的縮放比例還不適合!此處還缺少對正確內容的縮放。

將下面這行程式碼新增到show方法中動畫block的尾部:

_blurView.layer.contentsScale = (MENUSIZE / 320.0f) * 2;

contentsScale屬性宣告瞭layer在邏輯座標空間(以點為單位)和物理座標空間(以畫素為單位)之間的對映關係。更高比例因子表示在渲染layer時,一個點代表著多個畫素點。

編譯並執行程式,點選選單按鈕,可以看到縮放比例已經正常了:

沒錯——看起來好多了!現在關閉程式,然後重新開啟,ou~~發生了什麼?如下圖所示:

看起來這還是有點問題。如果在對view進行animation之前將contentScale設定回2.0,會解決half bar的問題。

將如下程式碼新增到DropDownMenuController.m中show方法裡面的animation block上面:

_blurView.layer.contentsScale = 2.0f;

編譯並執行程式,然後點選選單,接著關閉選單,再開啟選單,此時選單開起來如下所示:

現在半個尺寸的黑色box已經沒有問題了——但是現在是全尺寸的黑色box!

重置模糊濾鏡

上面問題產生的原因是由於進行了二次模糊計算。解決的方法是移除模糊濾鏡中的所有target。如果不這樣做的話,之後對濾鏡的呼叫不會輸出任何的內容——進而引起黑色box的問題。

按照如下程式碼更新一下updateBlur方法:

-(void)updateBlur 
{ 
    if(_blurFilter == nil){ 
        _blurFilter = [[GPUImageiOSBlurFilter alloc] init]; 
        _blurFilter.blurRadiusInPixels = 1.0f; 
    } 

    UIImage *image = [self.view.superview convertViewToImage]; 

    GPUImagePicture *picture = [[GPUImagePicture alloc] initWithImage:image]; 
    [picture addTarget:_blurFilter]; 
    [_blurFilter addTarget:_blurView]; 

    [picture processImageWithCompletionHandler:^{ 
        [_blurFilter removeAllTargets]; 
    }]; 
}

上面的程式碼用processImageWithCompletionHandler:替換了processImage方法。這個新的方法有一個completion block,當image 處理結束時,會執行這個block。一旦image處理結束,我們就可以安全的將濾鏡中的target全部移除。

編譯並執行程式,點選選單,檢查一下黑色box問題是不是已經解決掉了:

多次開啟和關閉選單,確保之前的那個bug已經解決掉啦!

現在仔細觀察一下開啟選單的模糊效果——有些東西看起來不正確。為了更加明顯的觀察到問題,我們減慢動畫的時間,讓其慢慢的移動。

在show方法中,將animation bloc的持續時間修改為10.0f。

編譯並執行程式,點選選單,然後觀察一下選單出場的慢動作:

恩,現在可能你已經發現問題了。被模糊的圖片從頂部往下滑動——而我們的本意是希望模糊效果從上往下滑(並不是圖片本身)。

對其背景圖片

此處我們需要對靜態模糊效果使用一些技巧。當出現選單時,我們需要利用背景來將模糊效果對其。所以在這裡我們不是對image view做移動處理,而是需要對image view做擴充套件處理,從0開始擴充套件至image view的全尺寸。這樣就可以確保選單開啟時,圖片依然保留在原位。

在show方法中,我們已經將選單開啟至全尺寸了,所以現在只需要將contentRect的高度設定為0即可(當image view首次建立並隱藏的時候)。

將下面的程式碼新增至DropDownMenuController.m檔案的viewDidLoad方法中——在_blurView初始化的下方:

_blurView.layer.contentsRect = CGRectMake(0.0f, 0.0f, 1.0f, 0.0f);

同時,在相同的一個檔案中,將下面的程式碼新增到animation block的尾部:

_blurView.layer.contentsRect = CGRectMake(0.0f, 0.0f, 1.0f, 0.0f);

contentRect屬性是可以動畫方式設定的。因此在動畫期間會rect會自動的插補上。

編譯並執行程式。可以看到,問題已經解決:

這樣看起來自然多了。現在我們已經有一個具有模糊背景的滑動選單了。

現在是時候把動畫所需時間調整一下了(為了更好的效果,其實之前設定的值是為了測試所用):設定為0.25秒,接著在updateBlur方法中將_blurFilter.blurRadiusInPixels設定為4.0f。

編譯並執行程式,多次開啟選單,看看效果如何:

 

實時模糊

實時模糊涉及到的技術具有一定的難度,有些難點需要解決才行。為了有效的進行實時模糊,我們需要不停(每秒60幀)的截圖、模糊計算和顯示。使用GPUImage每秒中處理60張圖片(模糊並顯示圖片)一點問題都沒有。

真正棘手的問題是什麼呢?如何實時的擷取螢幕圖片,信不信由你!

由於擷取的螢幕是主使用者介面,所有必須使用CPU的主執行緒來截圖,並將其轉換為一幅圖片。

提醒:如果事物每秒鐘的變化速度在46幀以上,那麼人眼就無法識別出來了。這對於開發者來說也是一種解脫——現代處理器在各幀之間可以完成更多的大量工作。

 

執行緒中簡潔的分支

當執行程式時,會執行大量的指令列表。每一個指令列表都執行在各自的執行緒中,而我們又可以在多個執行緒中併發執行各自的指令列表。一個程式在主執行緒中 開始執行,然後會根據需要,建立新的執行緒,並在後臺執行執行緒。如果之前你並沒有管理過多執行緒,你可能在寫程式的時候總是在主執行緒中執行指令。

主執行緒主要處理與使用者的互動,以及介面的更新。確保主執行緒的響應時間是非常關鍵的。如果在主執行緒上做了太多的任務,你會明顯的感覺到主介面響應遲鈍。

如果你曾經使用過Twitter貨Facebook,並滾動操作過它裡面的內容,你可能已經感覺到後臺執行緒在執行操作了——在滾動的過程中,並不是所有的個人圖片立即顯示出來,滾動過程中,程式會啟動後臺執行緒來獲取圖片,當圖片獲取成功之後,再顯示到螢幕中。

如果不使用後臺執行緒,那麼table view的滾動過程中,如果在主執行緒上去獲取個人圖片,會感覺到table view被凍結住了。由於圖片的獲取需要一些時間,所以最好將這樣耗時的操作讓後臺執行緒來做,這樣就能對使用者介面做平滑的操作和響應了。

那麼對本文的程式有什麼影響呢?之間介紹了,UIView的截圖APIs操作必須在主執行緒中執行。這就意味著每次截圖時,整個使用者介面都會被凍結中。

對於靜態模糊效果時,由於這個截圖操作很快,你不會感覺到介面的凍結。並且只需要截圖一次。然而在實時模糊效果中需要每秒中截圖60次。如果在主執行緒中做這樣頻繁的截圖操作,那麼animation和transition會變得非常的遲鈍。

更糟糕的時,如果使用者介面複雜度增加,那麼在截圖過程中就需要消耗更多的時間,那麼就會導致整個程式無法使用了!

那麼怎麼辦呢!

 

一些潛在的實時模糊方案

這裡有一個關於實時模糊方案:原始碼開源的live blur libraries,它通過降低截圖的速度來實現實時模糊效果,並不是使用每秒截圖60次,可能是20、30或者40次。即使看起來沒有多大區別,但是你的眼睛還是能發現一定的遲鈍——模糊效果並沒有跟程式的其它部分同步起來——這樣一來,介面看起會沒有模糊效果更加的糟糕。

實際上蘋果在它們自己的一些程式中處理實時模糊並不存在類似的問題——但是蘋果並沒有公開相關的API。在iOS 7中UIView的截圖方法,相比於舊方法,效能有了很大的提升,但還是不能滿足實時模糊的需求。

一些開發者利用UIToolbar的模糊效果來做一些不好的操作。沒錯,這是有效果的,但是強烈建議不要在程式中使用它們。雖然這不是私有API,但是這並不算是一種可行的方法,蘋果也可能會reject你的程式。也就是說在,在之後的iOS 7版本中,並不能保證還能正常使用。

蘋果可以在任何時候對UIToolBar做出修改,或許你的程式就有問題了。在iOS 7.0.3更新中,蘋果的修改已經影響到UIToolbar和UINavigationBar了,有些開發者也因此報告出利用相關模糊效果已經失效了!所以最好不要陷入這樣潛在的陷阱裡面!

 

一個折中的方法——對視訊實時模糊

OK,此時你可能在想,要想在程式中做到實時模糊是不可能的了。那麼還有什麼方法可以突破限制,做到實時模糊效果呢?

在許多場景中,靜態模糊是可以接受的。上一節中,我們對view做適當的修改,讓使用者看起來是對背景圖做的實際模糊處理。當然,這對於靜止不動的背景是合適的,並且還可以在模糊背景上實現一些不錯的效果。

我們可以做一些實驗,看看能不能找到一些效果來實現之前無法做到的實時模糊效果呢?

有一個方法可以試試:對實時視訊做模糊處理,雖然截圖是一個非常大的瓶頸,但是GPUImage非常的強大,它能夠對視訊進行模糊(無論是來自攝像頭的視訊或者已經錄製好的視訊,都沒問題)。

利用GPUImage對視訊進行模糊處理

利用GPUImage對視訊的模糊處理與圖片的模糊處理類似。針對圖片,我們例項化一個GPUImagePicture,然後將其傳送給GPUImageiOSBlurFilter,接著再將其傳送給GPUImageView。

類似的方法,對於視訊,我們使用GPUImageVideoCamera或GPUImageMovie,將後將其傳送給GPUImageiOSBlurFilter,接著再將其傳送給一個GPUImageView。GPUImageVideoCamera用於裝置中的實時攝像頭,而GPUImageMovie用於已經錄製好的視訊。

在我們的starter工程中,已經例項化並配置好了GPUImageVideoCamera。現在的任務是將播放和錄製按鈕的灰色背景替換為視訊的實時濾鏡效果。

首先是將此處提供的灰色背景例項UIView替換為GPUImageView。完成之後,我們需要調整每個view的contentRect(基於view的frame)。

這聽起來對每個view都需要做大量的工作。為了讓任務變得簡單,我們建立一個GPUImageView的子類,並把自定義的程式碼放進去,以便重用。

開啟File/New/File…,然後選擇iOS/Cocoa Touch/Objective-C class,如下所示:

將類命名為BlurView,繼承自GPUImageView,如下圖所示:

開啟ViewController.m檔案,將下面的import新增到檔案頂部:

#import "BlurView.h"

還是在ViewController.m中,在@implementation中找到_recordView和_controlView的宣告,將其修改為BlurView型別,如下所示:

BlurView *_recordView; //Update this! 
UIButton *_recordButton; 
BOOL _recording; 

BlurView *_controlView; //Update this too! 
UIButton *_controlButton; 
BOOL _playing;

然後按照如下程式碼修改viewDidLoad方法:

_recordView = [[BlurView alloc] initWithFrame: 
                CGRectMake(self.view.frame.size.height/2 - 50, 250, 110, 60)]; //Update this! 
//_recordView.backgroundColor = [UIColor grayColor]; //Delete this! 

_recordButton = [UIButton buttonWithType:UIButtonTypeCustom]; 
_recordButton.frame = CGRectMake(5, 5, 100, 50); 
[_recordButton setTitle:@"Record" forState:UIControlStateNormal]; 
[_recordButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 
[_recordButton setImage:[UIImage imageNamed:@"RecordDot.png"] forState:UIControlStateNormal] ; 
[_recordButton addTarget:self 
                  action:@selector(recordVideo) 
        forControlEvents:UIControlEventTouchUpInside]; 

[_recordView addSubview:_recordButton]; 
_recording = NO; 

_recordView.hidden = YES; 
[self.view addSubview:_recordView]; 

_controlView = [[BlurView alloc] initWithFrame: 
                 CGRectMake(self.view.frame.size.height/2 - 40, 230, 80, 80)]; //Update this! 
//_controlView.backgroundColor = [UIColor grayColor]; //Delete this!

接著,需要建立模糊圖片,將其顯示到上面構建的image view中。回到@implementation中,將下面的兩個宣告新增進去:

GPUImageiOSBlurFilter *_blurFilter; 
GPUImageBuffer *_videoBuffer;

現在你已經知道GPUImageiOSBlurFilter的作用了,那麼GPUImageBuffer的作用是什麼呢?它的任務是獲取視訊的輸出,並獲取每一幀,這樣我們就可以方便的對其做模糊處理。一個額外的好處就是它可以提升程式的效能!

一般來說,視訊輸出的內容會通過模糊濾鏡處理,然後傳送到背景檢視中(被顯示出來)。不過,在這裡使用buffer的話,傳送到buffer的視訊輸出內容,會被分為背景檢視和模糊濾鏡。這樣可以對視訊的輸出顯示做到平滑處理。

將下面的程式碼新增到viewDidLoad方法的頂部(在super呼叫的後面):

_blurFilter = [[GPUImageiOSBlurFilter alloc] init]; 

_videoBuffer = [[GPUImageBuffer alloc] init]; 
[_videoBuffer setBufferSize:1];

還是在同一個檔案中,將如下高亮顯示的語句新增到useLiveCamera方法中:

-(void)useLiveCamera 
{ 
    if (![UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera]) { 
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No camera detected" 
                                                        message:@"The current device has no camera" 
                                                       delegate:self 
                                              cancelButtonTitle:@"Ok" 
                                              otherButtonTitles:nil]; 
        [alert show]; 
        return; 
    } 

    _liveVideo = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720 
                                                     cameraPosition:AVCaptureDevicePositionBack]; 
    _liveVideo.outputImageOrientation = UIInterfaceOrientationLandscapeLeft; 

    [_liveVideo addTarget:_videoBuffer];           //Update this 
    [_videoBuffer addTarget:_backgroundImageView]; //Add this 
    [_videoBuffer addTarget:_blurFilter];          //And this 
    [_blurFilter addTarget:_recordView];           //And finally this 

    [_liveVideo startCameraCapture]; 

    _recordView.hidden = NO; 
    _controlView.hidden = YES; 
}

上面的模糊背景是用於錄製按鈕的。對於播放按鈕也要做類似的處理。

將下面的程式碼新增到loadVideoWithURL:方法中(在_recordedVideo.playAtActualSpeed = YES;之後):

[_recordedVideo addTarget:_videoBuffer]; 
[_videoBuffer addTarget:_backgroundImageView]; 
[_videoBuffer addTarget:_blurFilter]; 
[_blurFilter addTarget:_controlView];

編譯並執行程式,開啟錄製操作,看看情況如何:

好訊息是看起來基本正常!壞訊息是整個螢幕被縮放到錄製按鈕中去了。這個問題跟之前遇到的類似。我們需要給BlurView這是適當的contentRect。

開啟BlurView.m,用下面的程式碼替換掉initWithFrame:方法:

- (id)initWithFrame:(CGRect)frame 
{ 
    self = [super initWithFrame:frame]; 
    if (self) { 
        CGRect deviceSize = [UIScreen mainScreen].bounds; 
        self.layer.contentsRect = CGRectMake(frame.origin.x/deviceSize.size.height, 
                                             frame.origin.y/deviceSize.size.width, 
                                             frame.size.width/deviceSize.size.height, 
                                             frame.size.height/deviceSize.size.width); 
        self.fillMode = kGPUImageFillModeStretch; 
    } 
    return self; 
}

contentRect的每個引數必須在0.0f和1.0f之間。在這裡只需要利用view的位置除以螢幕的size,得到的值即可。

編譯並執行程式,看看效果如何:

恭喜!至此已經完成了靜態模糊和實時視訊模糊的實現。現在你已經完全可以在程式中新增iOS 7的模糊效果啦!

 

何去何從?

可以在這裡下載到完整的工程。

本文不僅指導你在程式中使用iOS 7的模糊效果,還介紹瞭如何使用GPUImage框架,這個框架也是我非常希望你能看到的東西。重要的是,本文指出了為什麼要使用模糊,什麼時候使用模糊效果是合適的,這在iOS 7的新設計語言中是一個關鍵的概念。當然也希望在未來的版本中,蘋果能夠將相關APIs提供給開發者使用,不過在那之前,GPUImage是一個不錯的替代品。

相關文章