基於iOS SDK 8.0以及Xcode 6 Beta 6。
Core Image是一個很強大的框架。它可以讓你簡單地應用各種濾鏡來處理圖像,比如修改鮮艷程度, 色澤, 或者曝光。 它利用GPU(或者CPU)來非常快速、甚至實時地處理圖像數據和視頻的幀。並且隱藏了底層圖形處理的所有細節,通過提供的API就能簡單的使用了,無須關心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不需要你知道GCD在其中發揮了怎樣的作用,Core Image處理了全部的細節。
Core Image框架給我們提供了這些東西:
內置的圖片濾鏡特征檢測能力(如人臉識別)支持自動改善圖像能利用多個濾鏡組合成一個自定義濾鏡
先上一個簡單的例子。用Single View Application的工程模板建立一個工程,這個工程建好後只有一個AppDelegate和一個ViewController,另外還有一個Main.storyboard,在Main.storyboard裡已經准備好一個ViewController了,我們在這個ViewController裡放置一個UIImageView,調整其frame:
除此之外,因為UIImageView是默認拉伸圖片的,我們不想讓它變形,把它的ContentMode設置為Aspect Fit。
最後在拖兩個按鈕進來,一個用於顯示原圖,一個用於自動改善圖像,整個ViewController看起來像這樣:
接下來在ViewController中增加對應的IBAction方法以及IBOutlet屬性:
class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
lazy var originalImage: UIImage = {
return UIImage(named: Image)
}()
......
一個連接到Storyboard的imageView,還有一個只懶加載一次的originalImage屬性,這個屬性在後面會用到很多次,這裡有我整個工程用到的image。
然後在ViewDidLoad裡像這樣:
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.layer.shadowOpacity = 0.8
self.imageView.layer.shadowColor = UIColor.blackColor().CGColor
self.imageView.layer.shadowOffset = CGSize(width: 1, height: 1)
self.imageView.image = originalImage
}
只做了兩件事:一是給imageView加上了陰影邊框,只是為了好看;二是把originalImage賦值給imageView。
一行代碼顯示原圖:
@IBAction func showOriginalImage() {
self.imageView.image = originalImage
}
下面是自動改善的代碼,我先貼出來,再慢慢說,有一個直觀的效果會更有興趣一些:
@IBAction func autoAdjust() {
var inputImage = CIImage(image: originalImage)
let filters = inputImage.autoAdjustmentFilters() as [CIFilter]
for filter: CIFilter in filters {
filter.setValue(inputImage, forKey: kCIInputImageKey)
inputImage = filter.outputImage
}
self.imageView.image = UIImage(CIImage: inputImage)
}
把IBAction和IBOutlet連接對之後,運行起來應該就能看到以下效果了:點擊自動改善就能看到效果了,可能運行會有點慢,因為我們還沒做優化,如果覺得改善效果不明顯的話,可以多點擊幾次原圖來對比一下。
雖然有些問題,但是已經不妨礙我們繼續探索了。上面的自動改善代碼顯式地用到了兩個類(我為什麼在這裡要用顯式這個詞?):CIImage和CIFilter,其中:
CIImage:只保存能構建圖像的原始數據,是一個模型對象。CIFilter:濾鏡,不同的CIFilter實例能表示不同的濾鏡效果,不同的濾鏡所能設置的參數也不盡相同。另外Core Image的自動改善功能能智能的對圖像的柱狀圖(histogram?)、人臉區域以及元數據進行分析,只需要傳入一個Image作為輸入參數,就能得到一組能使圖像改善的濾鏡。CIImage的實例能通過UIImage來得到,然後通過兩個API:autoAdjustmentFilters和autoAdjustmentFiltersWithOptions:取到能使圖像得到改善的濾鏡數組,在大多數情況下,你可能會用提供Option字典的那個API,因為你能設置:圖片的方向,這對CIRedEyeCorrection、CIFaceBalance等濾鏡來說格外重要,因為Core Image需要精確的對面部進行識別。是否只需要消除紅眼(設置kCIImageAutoAdjustEnhance為false)。是否使用除了消除紅眼以外的所有的濾鏡(設置kCIImageAutoAdjustRedEye為false)。如果你想提供Option字典,那麼可以這樣使用:
NSDictionary *options = @{ CIDetectorImageOrientation : [[image properties] valueForKey:kCGImagePropertyOrientation] }; NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options];在這個例子中,我就不傳入Option字典了,因為不涉及圖片方向的問題。想知道自動改善功能用了哪些濾鏡?只需要把filter對象打印出來即可,一般來說,會是這5個濾鏡:CIRedEyeCorrection:修復因相機的閃光燈導致的各種紅眼CIFaceBalance:調整膚色CIVibrance:在不影響膚色的情況下,改善圖像的飽和度CIToneCurve:改善圖像的對比度CIHighlightShadowAdjust:改善陰影細節大部分情況下這些濾鏡就夠用了。之前也提到了,不同的CIFilter會有不同的參數,如果我們想知道CIFilter具體有哪些參數,可以調用它自己的inputKeys方法,得到它支持的輸入參數的列表,或者調用它的outputKeys方法,得到它的輸出參數的列表(一般我們只用outpuntImage就行了),又或者直接調用它的attributes方法,得到它的所有信息,包括它的名字、所屬的分類、輸入參數、輸出參數、各參數的取值范圍以及默認值等。調用CIFilter的inputKeys方法可以看到它的輸入參數:
for filter: CIFilter in filters {
let inputKeys = filter.inputKeys()
print(filter.name())
println(inputKeys)
...
打印結果:
CIFaceBalance[inputImage, inputOrigI, inputOrigQ, inputStrength, inputWarmth] CIVibrance[inputImage, inputAmount] CIToneCurve[inputImage, inputPoint0, inputPoint1, inputPoint2, inputPoint3, inputPoint4] CIHighlightShadowAdjust[inputImage, inputRadius, inputShadowAmount, inputHighlightAmount]
幾乎所有的濾鏡都有inputImage這個輸入參數,我們可以直接用系統預置的各種key來設置參數(如kCIInputImageKey),系統已經預置了大部分常用的key,如果你發現有的key系統沒有預置,你可以直接使用獲取的key名的字符串來作key,如:
filter.setValue(inputImage, forKey: kCIInputImageKey)
//兩者設置方式完全一樣
filter.setValue(inputImage, forKey: inputImage)
對自動改善圖像功能來說,我們不需要知道太多的細節,設置inputImage就可以了。
接下來填坑。
上面的代碼有兩個問題:一是在每次使用自動改善的過程中感覺到明顯的慢;二是圖像在自動改善後變形了。原圖和改善後的圖像對比:
我把UIImageView的contentMode設置為Aspect Fit,就是不希望圖片發生變形,而是按等比例縮放,如果把UIImageView的背景色設置為紅色的話,在顯示原圖的時候可以看到上下均有紅色,而改善之後的圖片就沒有紅色。由於蘋果表示UIImage是完全支持CIImage的,所以文檔中並沒有指出出現這個問題的原因,我參考了下面這個貼子:
http://stackoverflow.com/questions/15878060/setting-uiimageview-content-mode-after-applying-a-cifilter
上面有說通過UIImage(CIImage:)方法得到的UIImage並不是一個基於CGImage的標准UIImage,所以不能按一般的顯示規則去理解,因此我們要換種方式去得到一個真正的UIImage,解決方法放在下面再說。
在之前介紹CIImage和CIFilter的時候我們用到顯式這個詞,因為在代碼裡可以直觀的看到使用了這兩個類,CIImage提供圖像的信息,CIFilter提供濾鏡,Core Image還需要另一個對象把兩者粘合起來,這個對象就是CIContext。
CIContext是Core Image處理圖像的關鍵,它和Core Graphics的CGContext類似但又與之不同,CIContext可以被重用,不必每次都新建一個,同時在輸出CIImage的時候又必須有一個。在上面的例子中我們沒有使用CIContext,但在調用UIImage(CIImage:)的時候Core Image隱式地在內部使用了CIContext,也就是把我們需要手動做的工作自動地完成了。但是這就有了一個問題,在每次調用UIImage(CIImage:)的時候它都會重新創建一個CIContext對象,這在用完即毀的情況下不會造成很大的影響,但在反復地使用濾鏡的過程中就很影響性能了,為了防止這種情況,我們把CIContext對象重用起來,讓ViewController持有一個懶加載的屬性:
lazy var context: CIContext = {
return CIContext(options: nil)
}()
CIContext在初始化的時候需要一個字典,可以通過kCIContextUseSoftwareRenderer創建一個基於CPU的CIContext對象,默認是創建基於GPU的CIContext對象,不同之處在於GPU的CIContext對象處理起來會更快,而基於CPU的CIContext對象除了支持更大的圖像以外,還能在後台處理。我們傳nil創建基於GPU的CIContext對象就可以了。
有了可重用的CIContext對象,在創建UIImage的時候需要這樣:
@IBAction func autoAdjust() {
var inputImage = CIImage(image: originalImage)
let filters = inputImage.autoAdjustmentFilters() as [CIFilter]
for filter: CIFilter in filters {
filter.setValue(inputImage, forKey: kCIInputImageKey)
inputImage = filter.outputImage
}
//self.imageView.image = UIImage(CIImage: inputImage)
let cgImage = context.createCGImage(inputImage, fromRect: inputImage.extent())
self.imageView.image = UIImage(CGImage: cgImage)
}
雖然在第一次執行自動改善的時候會有點慢(因為要創建CIContext對象),但是在反復執行的情況下性能改善了很多,而且這樣一來還解決了第二個問題,即ContentMode的問題。如果沒有特殊情況,應該總是用這種方式創建一個CGImage,再把CGImage轉成UIImage。
func showFiltersInConsole() {
let filterNames = CIFilter.filterNamesInCategory(kCICategoryBuiltIn)
println(filterNames.count)
println(filterNames)
for filterName in filterNames {
let filter = CIFilter(name: filterName as String)
let attributes = filter.attributes()
println(attributes)
}
}
在這個方法裡傳入的類別參數是kCICategoryBuiltIn,表示會輸出iOS8 Core Image的所有濾鏡:class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
lazy var originalImage: UIImage = {
return UIImage(named: Image)
}()
lazy var context: CIContext = {
return CIContext(options: nil)
}()
var filter: CIFilter!
......
// MARK: - 懷舊
@IBAction func photoEffectInstant() {
filter = CIFilter(name: CIPhotoEffectInstant)
outputImage()
}
// MARK: - 黑白
@IBAction func photoEffectNoir() {
filter = CIFilter(name: CIPhotoEffectNoir)
outputImage()
}
// MARK: - 色調
@IBAction func photoEffectTonal() {
filter = CIFilter(name: CIPhotoEffectTonal)
outputImage()
}
// MARK: - 歲月
@IBAction func photoEffectTransfer() {
filter = CIFilter(name: CIPhotoEffectTransfer)
outputImage()
}
// MARK: - 單色
@IBAction func photoEffectMono() {
filter = CIFilter(name: CIPhotoEffectMono)
outputImage()
}
// MARK: - 褪色
@IBAction func photoEffectFade() {
filter = CIFilter(name: CIPhotoEffectFade)
outputImage()
}
// MARK: - 沖印
@IBAction func photoEffectProcess() {
filter = CIFilter(name: CIPhotoEffectProcess)
outputImage()
}
// MARK: - 鉻黃
@IBAction func photoEffectChrome() {
filter = CIFilter(name: CIPhotoEffectChrome)
outputImage()
}
func outputImage() {
println(filter)
let inputImage = CIImage(image: originalImage)
filter.setValue(inputImage, forKey: kCIInputImageKey)
let outputImage = filter.outputImage
let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
self.imageView.image = UIImage(CGImage: cgImage)
}
這些都寫好後,在UI上把各種按鈕及touch事件綁定好,UI看起來像這樣:
運行後各種濾鏡效果:
以上就是對Core Image內置濾鏡的簡單使用,如果你不需要對濾鏡做更加細粒度控制的話,上述方法就夠用了。