本文是一個(gè)系列吩翻,是函數(shù)式Swift的讀書(shū)筆記(其實(shí)是為了備忘)
本章圍繞如何用高階函數(shù)以小巧且函數(shù)式的方式將面向?qū)ο蟮腶pi封裝。
1. Core Image介紹
Core Image 是一個(gè)強(qiáng)大的圖像處理框架担平,但是它的 API 有時(shí)可能略顯笨拙尖飞。Core Image 的 API 是弱類(lèi)型的 —— 我們通過(guò)鍵值編碼 (KVC) 來(lái)配置圖像濾鏡 (filter)
我們的目標(biāo)并不是圍繞 Core Image 構(gòu)建一個(gè)完整的封裝琉挖,而是要說(shuō)明如何把像高階函數(shù)這樣的函數(shù)式編程概念運(yùn)用到實(shí)際的生產(chǎn)代碼中
2.類(lèi)型
CIFilter 是 Core Image 中的核心類(lèi)之一,用于創(chuàng)建圖像濾鏡决摧。當(dāng)實(shí)例化一個(gè) CIFilter 對(duì)象時(shí)亿蒸,你 (幾乎) 總是通過(guò) kCIInputImageKey 鍵提供輸入圖像,再通過(guò) outputImage 屬性取回處理后的圖像掌桩。取回的結(jié)果可以作為下一個(gè)濾鏡的輸入值
我們用Filter來(lái)定義一個(gè)函數(shù)边锁,該函數(shù)接受一個(gè)圖像作為參數(shù)并返回一個(gè)新的圖
typealias Filter = (CIImage)->CIImage
我們將以這個(gè)類(lèi)型為基礎(chǔ)進(jìn)行后續(xù)的構(gòu)建(類(lèi)型的選擇)
3.構(gòu)建濾鏡
構(gòu)建濾鏡的函數(shù)大概如下
func myFilter() -> Filter
3.1 模糊
//定義一個(gè)高斯模糊濾鏡,只需要模糊半徑一個(gè)參數(shù)
func blur(radius:Double) -> Filter {
return{
image in
let parameters:[String:Any] = [
kCIInputRadiusKey:radius,
kCIInputImageKey:image
]
guard let filter = CIFilter(name:"CIGaussianBlur", withInputParameters:parameters) else {
fatalError()
}
guard let outputImage = filter.outputImage else{
fatalError()
}
return outputImage
}
}
3.2 顏色疊層
//“讓我們來(lái)定義一個(gè)能夠在圖像上覆蓋純色疊層的濾鏡波岛。Core Image 默認(rèn)不包含這樣一個(gè)濾鏡茅坛,但是我們完全可以用已經(jīng)存在的濾鏡來(lái)組成它”
//我們將使用的兩個(gè)基礎(chǔ)組件:顏色生成濾鏡 (CIConstantColorGenerator) 和圖像覆蓋合成濾鏡 (CISourceOverCompositing)。首先讓我們來(lái)定義一個(gè)生成固定顏色的濾鏡”
func generate(color: UIColor) -> Filter {
return { _ in
let parameters = [kCIInputColorKey: CIColor(cgColor: color.cgColor)]
guard let filter = CIFilter(name: "CIConstantColorGenerator",
withInputParameters: parameters)
else { fatalError() }
guard let outputImage = filter.outputImage
else { fatalError() }
return outputImage
}
}
//生成顏色濾鏡不必檢查輸入圖像则拷,因此用一個(gè)用一個(gè)匿名參數(shù) _
func compositeSourceOver(overlay: CIImage) -> Filter {
return { image in
let parameters = [
kCIInputBackgroundImageKey: image,
kCIInputImageKey: overlay
]
guard let filter = CIFilter(name: "CISourceOverCompositing",
withInputParameters: parameters)
else { fatalError() }
guard let outputImage = filter.outputImage
else { fatalError() }
return outputImage.cropped(to: image.extent)
}
}
最后贡蓖,通過(guò)兩個(gè)濾鏡來(lái)創(chuàng)建顏色折疊濾鏡
func overlay(color:UIColor)->Filter{
return {
image in
let overlay = generate(color:color)(image).cropped(to:image.extend)
return compositeSourceOver(overlay:overlay)(image)
}
}
3.3 組合濾鏡
//創(chuàng)建一個(gè)先模糊再疊層的濾鏡
let url = URL(string: "http://via.placeholder.com/500x500")!
let image = CIImage(contentsOf: url)!
let radius = 5.0
let color = UIColor.red.withAlphaComponent(0.2)
let blurredImage = blur(radius: radius)(image)
let overlaidImage = overlay(color: color)(blurredImage)
3.4 復(fù)合函數(shù)
我們可以將上面的代碼里調(diào)用兩個(gè)濾鏡的表達(dá)式合二為一
let result = overlay(color:color)(blur(radius:radius)(image))
//但是,括號(hào)過(guò)多煌茬,失去了可讀性斥铺。更好的解決途徑是,
func compose (filter filter1:@escaping Filter,with filter2:@escaping Filter)->Filter{
return {
image in filter2(filter1(image))
}
}
//compose(filter:with:)函數(shù)接受兩個(gè)Filter類(lèi)型的參數(shù)坛善,并返回一個(gè)新濾鏡晾蜘。 “這個(gè)復(fù)合濾鏡接受一個(gè) CIImage 類(lèi)型的圖像參數(shù),然后將該參數(shù)傳遞給 filter1浑吟,取得返回值之后再傳遞給 filter2笙纤。
let blurAndOverlay = compose(filter: blur(radius: radius),
with: overlay(color: color))
let result1 = blurAndOverlay(image)
為了可讀性(真的是為了可讀性嗎??), 我們可以自定義運(yùn)算符來(lái)進(jìn)一步
infix operator >>>
func >>>(filter1: @escaping Filter, filter2: @escaping Filter) -> Filter {
return { image in filter2(filter1(image)) }
}
//現(xiàn)在我們使用>>> 來(lái)代替compose(filter:with:)
let blurAndOverlay2 =
blur(radius: radius) >>> overlay(color: color)
let result2 = blurAndOverlay2(image)
// 運(yùn)算符 >>> 默認(rèn)是左結(jié)合的 (left-associative)组力,就像 Unix 的管道一樣省容,因此濾鏡將以從左到右的順序被應(yīng)用到圖像上
//我們定義的組合濾鏡運(yùn)算符是一個(gè)復(fù)合函數(shù)的例子
4 ,柯里化
本章中,我們反復(fù)見(jiàn)這種代碼
blur(radius:radius)(image)
先調(diào)用一個(gè)函數(shù)燎字,返回新函數(shù)(本例是Filter)腥椒,然后傳入一個(gè)參數(shù),并調(diào)用返回的新函數(shù)候衍。
其實(shí)和下邊的效果相同
let blurredImage = blur(image:image,radius:radius)
其實(shí)和下例相同
func add1(_ x: Int ,_ y:Int)->Int{
return x+y
}
在swift中笼蛛,我們可以寫(xiě)另外一個(gè)版本
func add2(_ x:Int)-> ((int)->Int){
return {y in x+y}
}
//這里,add2 函數(shù)接受一個(gè)參數(shù)x后蛉鹿,返回一個(gè)閉包滨砍。然后再接受第二個(gè)參數(shù)y。
因?yàn)樵摵瘮?shù)的箭頭是向右結(jié)合 ,所以可以寫(xiě)為如下版本惋戏,和add2一樣
func add3(_ x:Int)-> (int)->Int{
return {y in x+y}
}
// add1 和add2 的區(qū)別在于調(diào)用方式:
add1(1,2)
add2(1)(2)
//在第一種方法中领追,我們將兩個(gè)參數(shù)同時(shí)傳遞給 add1;而第二種方法則首先向函數(shù)傳遞第一個(gè)參數(shù) 1响逢,然后將返回的函數(shù)應(yīng)用到第二個(gè)參數(shù) 2绒窑。兩個(gè)版本是完全等價(jià)的
//add1 和 add2 的例子向我們展示了如何將一個(gè)接受多參數(shù)的函數(shù)變換為一系列只接受單個(gè)參數(shù)的函數(shù),這個(gè)過(guò)程被稱(chēng)為柯里化 (Currying)舔亭,它得名于邏輯學(xué)家 Haskell Curry些膨;我們將 add2 稱(chēng)為 add1 的柯里化版本
//在本章中為了創(chuàng)建濾鏡而定義的函數(shù)全部都已經(jīng)被柯里化了 —— 它們都接受一個(gè)附加的圖像參數(shù)。按照柯里化風(fēng)格來(lái)定義濾鏡钦铺,我們可以很容易地使用 >>> 運(yùn)算符將它們進(jìn)行組合