原文首發(fā)于我的blog:https://chengwey.com
Chapter 3 Wrapping Core Image
本文是《Functional Programming in Swift》中第三章的筆記夫否,如果你感興趣照筑,請購買英文原版针余。
上一節(jié)討論了高階函數(shù)的概念,并展示了函數(shù)作為參數(shù)傳遞夺刑。本節(jié)我們展示如何使用高階函數(shù)來封裝一些面向?qū)ο蟮?API 。Core Image 是一個很強(qiáng)大圖像處理框架,我們就用他來開刀重寫一個 Filter贮泞。
<h2 id='TheFilterType'>1. The Filter Type</h2>
我們使用 Core Image中 的 CIFilter 類來創(chuàng)建 image filters资昧。具體過程為:
- 1.初始化一個 CIFilter 對象實例
- 2.提供一個輸入 image酬土,使用字典進(jìn)行封裝,key 為 kCIInputImageKey
- 3.獲取結(jié)果為一個字典格带,使用 kCIOutputImageKey 得到最終 image
- 4.然后將該 image 繼續(xù)作為下一個 filter 的輸入?yún)?shù)
我們改寫的 API 將封裝這些 key-value撤缴,提供更安全、便利的 API 給用戶叽唱。我們首先定義自己的 Filter 類型:
typealias Filter = CIImage -> CIImage
<h2 id='BuildingFilters'>2. Building Filters</h2>
我們有了基本的 Filter 類型腹泌,就可以開始定義一些特定的 filter 了,我們可以定義一些函數(shù)尔觉,根據(jù)不同的參數(shù)凉袱,輸出不同的 filter。具體函數(shù)形式如下:
func myFilter(/* parameters */) -> Filter
Blur
首先來定義高斯模糊濾鏡侦铜,該濾鏡只需要一個模糊半徑(blur radius)做參數(shù)
func blur(radius: Double) -> Filter {
return { image in
let parameters = [
kCIInputRadiusKey: radius,
kCIInputImageKey: image
]
let filter = CIFilter(name: "CIGaussianBlur",
withInputParameters: parameters)
return filter.outputImage
}
}
這個 blur 函數(shù)的返回值是 Filter 類型专甩,該類型正是我們之前定義的,使用一個CIImage 類型的 image 做輸入钉稍,并返回一個新的 image涤躲。這個例子我們簡單封裝了 Core Image 中的 API,我們可以使用這種方式來創(chuàng)建自己的 filter functions贡未。
Color Overlay
接著我們來實現(xiàn)一個圖層顏色种樱,Core Image 沒有默認(rèn)的相關(guān)濾鏡,但我們可以通過組合現(xiàn)有 filters 的方式來實現(xiàn)俊卤。我們具體構(gòu)建這個 color overlay 需要使用兩個現(xiàn)成的 Filter:
- color generator filter (CIConstantColorGenerator)
- source-over compositing filter (CISourceOverCompositing)
func colorGenerator(color: UIColor) -> Filter {
return { _ in
let parameters = [kCIInputColorKey: color]
let filter = CIFilter(name: "CIConstantColorGenerator",
withInputParameters: parameters)
return filter.outputImage
}
}
該函數(shù)與之前的高斯模糊函數(shù)只有一點(diǎn)不同嫩挤,color generator filter 不檢查input imgage,因此我們使用了一個無名參數(shù)"_"來強(qiáng)調(diào) image 參數(shù)被無視忽略掉了消恍。
接著我們繼續(xù)定義 composite filter:
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters = [kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay]
let filter = CIFilter(name: "CISourceOverCompositing",
withInputParameters: parameters)
let cropRect = image.extent()
return filter.outputImage.imageByCroppingToRect(cropRect)
}
}
這里岂昭,我們修剪了輸出 image 的尺寸,使其和輸入 image 尺寸匹配狠怨,這不是必要步驟约啊,但卻是一個更好的選項邑遏。最后我們合并這兩個filter:
func colorOverlay(color: NSColor) -> Filter {
return { image in
let overlay = colorGenerator(color)(image)
return compositeSourceOver(overlay)(image)
}
}
該函數(shù)中的 overlay 可以看做是由 UIColor ->(CIImage -> CIImage) 生成的一個image,然后傳入一個 CIImage ->(CIImage -> CIImage) 函數(shù)得到最終結(jié)果恰矩。
<h2 id='ComposingFilters'>3. Composing Filters</h2>
現(xiàn)在有了 blur和color overlay filter记盒,我們可以把他們合在一起用:首先 blur 一張圖片,然后把紅色圖層覆蓋在圖片上:
先載入一張圖片
let url = NSURL(string: "http://tinyurl.com/m74sldb");
let image = CIImage(contentsOfURL: url)
我們可以用鏈?zhǔn)浇Y(jié)構(gòu)將所有的 filter 連起來
let blurRadius = 5.0
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let blurredImage = blur(blurRadius)(image)
let overlaidImage = colorOverlay(overlayColor)(blurredImage)
Function Composition
當(dāng)然外傅,我們也可以用一行代碼來調(diào)用兩個 filter:
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
不過這樣可讀性就變得比較差孽鸡,更好的方式是自定義一個 filter 合成函數(shù):
func composeFilters(filter1: Filter, filter2: Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
有了上面的composeFilters函數(shù),我們就可以任意合成我們想要的 filter 了栏豺,比如我們可以合成一個 myFilter1 濾鏡:
let myFilter1 = composeFilters(blur(blurRadius),
colorOverlay(overlayColor))
let result1 = myFilter1(image)
讓我們來更進(jìn)一步增加可讀性彬碱,swift 允許我們自定義操作符,我們來定義一個 “>>>” 運(yùn)算符:
infix operator >>> { associativity left } //表運(yùn)算符順序
func >>> (filter1: Filter, filter2: Filter) -> Filter {
return { img in filter2(filter1(img)) }
}
//使用
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
<h2 id='TheoreticalBackgroundCurrying'>4. TheoreticalBackground:Currying</h2>
這一章奥洼,我們看到了有兩種方式來定義一個帶兩個參數(shù)的 function:
//第一種形式為常見的:
func add1(x: Int, y: Int) -> Int {
return x + y
}
//第二種方式:
func add2(x: Int) -> (Int -> Int) {
return { y in return x + y }
}
add2帶一個參數(shù) x巷疼,返回一個閉包,然后繼續(xù)帶一個參數(shù) y灵奖。二者調(diào)用方式也不同:
- add1(1, 2)
- add2(1)(2)
swift中的函數(shù)箭頭 “->” 是右相關(guān)的嚼沿,也就是說類型 “ A -> B -> C ” 可以看做是 “ A -> (B -> C) ”。add1和add2 展示了一個接受多個參數(shù)的函數(shù)轉(zhuǎn)換成一系列只接受單個參數(shù)的函數(shù)瓷患,這個過程就稱為柯理化(add2 是 add1 的柯理化版本)
還有第三種版本 curry functions:
func add3(x: Int)(y: Int) -> Int {
return x + y
}
// 調(diào)用
add3(1)(y: 2)
>3
這里需要主要的就是骡尽,調(diào)用時必須明確提供第二參數(shù)的參數(shù)名(這里是y)
為什么要使用“柯理化”呢,目前我們都是將函數(shù)作為參數(shù)傳給另一個函數(shù)擅编。如果我們使用“非柯理化”的函數(shù)攀细,比如 add1,我們就要一次提供所有的參數(shù)爱态。而對于“柯理化”的函數(shù)谭贪,比如 add2,我們可以選擇:提供1個或2個參數(shù)锦担。我們本章創(chuàng)建的 filter 都是“柯理化”的俭识,他們都有一個附加的 image 做參數(shù)。這種方式寫出的 filter洞渔,我們也很容易使用操作符 ">>>" 來組合他們套媚。當(dāng)然,我們也可以寫出“非柯理化”的filter磁椒,但最后產(chǎn)生的code將變得笨重堤瘤。
<h2 id='Discussion'>5. Discussion</h2>
本章再一次展示了如何將復(fù)雜的code簡化成許 code snippets,而這些 code snippets 又能通過 function 很方便地進(jìn)行重組衷快。還展示了高階函數(shù)在實際案例中的應(yīng)用宙橱。
本章通過這種方式設(shè)計的 API 有這么幾個優(yōu)勢:
- Safety 不會因為未定義的 keys 或類型轉(zhuǎn)換失敗而產(chǎn)生 runtime error
- Modularity 很方便地使用操作符 >>> 進(jìn)行組合姨俩,這樣做允許你將復(fù)雜的 filter 拆分成功能單一蘸拔、小巧师郑、可重用的子filter。加之调窍,組合后的filters和他的子模塊擁有相同的類型宝冕,所以你可以交替使用他們。
- Clarity 盡管之前你可能沒有使用過 Core Image邓萨,但你不出5分鐘就能學(xué)會我們定義的這套 API:使用 function 來裝配這些簡單的 filters地梨。為了得到最終結(jié)果,你不需要知道各種“Key”( eg: kCIInputImageKey or kCIInputRadiusKey...etc )缔恳,你甚至不需要看文檔就能學(xué)會如何使用這套API宝剖。
我們的 API 展示了一連串 functions 可以定義、組合成一個復(fù)雜的 filter歉甚,而其中每一個 filter 都是是安全万细、孤立且可以重用的。