#Task-11 照片編輯App- PhotoEditor (03- 照片調色&濾鏡)

這次的作業是要做一個可以編輯照片的 App,會分成 4 篇來介紹,第三篇是照片調色&濾鏡

💡 第一篇跟第二篇在這裡

從主頁點選下方的調色或濾鏡按鈕,就會連結到新的 view controller,並將主頁的照片傳遞過去。照片調色跟濾鏡都要用到 CIFilter,寫法也很像,我們先從調色開始!

⭐️ 照片調色

可以調整照片的亮度、對比跟飽和度,使用 slider 調整數值

1️⃣ import CoreImage、CoreImage.CIFilterBuiltins

import CoreImage
import CoreImage.CIFilterBuiltins

2️⃣ 需要使用 Core Image filters,參考官方文件可以知道,filter 的效果會作用在 CIImage,產生的也是CIImage,所以我們要先把 UIImage 轉成 CIImage

@IBAction func adjustImage(_ sender: UISlider){
//轉換照片為CIImage
let ciImage = CIImage(image: editedImage) //editedImage是從主頁傳遞過來的照片
}

3️⃣ 參考另一份官方文件發現,亮度、對比、飽和度被包在 CIColorControls filter 中,所以我們建立 filter 時要指定是 CIColorControls

let filter = CIFilter(name: "CIColorControls")

4️⃣ 將要套用效果的 ciImage 輸入 filter 中,他才知道要對誰下手(?

filter?.setValue(ciImage, forKey: kCIInputImageKey)

5️⃣ 針對每個 slider 設定個別對應的功能,讀取 slider 的值來帶入 key 中並將效果套用到照片上

我的 3 個 slider 都連到同一個 IBAction,以陣列的方式宣告 property

filter?.setValue(adjustSliders[0].value, forKey: kCIInputBrightnessKey)filter?.setValue(adjustSliders[1].value, forKey: kCIInputContrastKey)filter?.setValue(adjustSliders[2].value, forKey: kCIInputSaturationKey)

這時候問題就來了,那我 slider 的 Minimun 跟 Maximun 的值應該要設多少? 必須先知道定義中亮度、對比、飽和度的最小值跟最大值啊!

要查詢屬性的數值區間,可以用底下的方法

print(CIFilter(name: “CIColorControls”)?.attributes ?? “”)
  • 原圖的亮度是 0,可調整區間是 -1 ~ 1
  • 原圖的對比是 1,可調整區間是 0.25 ~ 4
  • 原圖的飽和度是 1,可調整區間是 0 ~ 2

設定在這些區間裡面,就都能對照片產生作用哦!
超出範圍也不會不能執行,只是到達最小或最大值之後就不會對圖片再產生更多影響了

6️⃣ 輸出套用效果後的照片,用 if let 防止出錯,輸出的照片仍是 CIImage

if let outputImage = filter?.outputImage

輸出的照片直接轉回 UIImage 之後也會顯示調色後的結果,可是照片大小可能會跟我們預期的…不太一樣😅 所以我們還要再多一道工!!

7️⃣ 用 CIContext().createCGImage 產生最終正確大小的照片,createCGImage 我們要告訴他對象是哪張 image,還有 CGRect 的值,此時照片會變成 CGImage,需轉回 UIImage 才能正常顯示

if let outputImage = filter?.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent){    //把圖片轉回UIImage,放入imageView中
let updateImage = UIImage(cgImage: cgImage)
imageView.image = updateImage

粗體字說明:

CIImage 的處理會發生在 CIContext 物件,但是要產生 CIContext 的成本很高,所以官方建議宣告一個 context 讓大家共用同一個 CIContext

extent 代表照片指定的 CGRect 範圍(本身的大小)

✨ 整段完整的 IBAction function

//調整照片亮度、對比、飽和度
@IBAction func adjustImage(_ sender: UISlider){
//轉換照片為CIImage
let ciImage = CIImage(image: editedImage)
//使用CIColorControls filter,裡面才有調整亮度、對比、飽和度的功能
let filter = CIFilter(name: "CIColorControls")
//指定ciImage為輸入filter的對象
filter?.setValue(ciImage, forKey: kCIInputImageKey)
//設定3個slider個別對應的功能
filter?.setValue(adjustSliders[0].value, forKey: kCIInputBrightnessKey)
filter?.setValue(adjustSliders[1].value, forKey: kCIInputContrastKey)
filter?.setValue(adjustSliders[2].value, forKey: kCIInputSaturationKey)
//輸出調整後的圖片,格式為CIImage,再產生正確大小的CGImage
if let outputImage = filter?.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent){
//把圖片轉回UIImage,放入imageView中
let updateImage = UIImage(cgImage: cgImage)
imageView.image = updateImage

}

}

⭐️ 套用濾鏡

整段程式跟照片調色差不多,但是沒有調數值,單純套用濾鏡而已

我直接使用內建的 8 種濾鏡,加上可以選擇不套用濾鏡,所以放 9 個按鈕,設定好 tag 值以判斷選擇的按鈕是哪一個,並同樣用陣列宣告,連到同一個 IBAction function

內建的濾鏡可以參考官方文件

1️⃣ import CoreImage、CoreImage.CIFilterBuiltins

import CoreImage
import CoreImage.CIFilterBuiltins

2️⃣ 宣告陣列儲存每個濾鏡的名字

let filterArray = ["", "CIPhotoEffectChrome", "CIPhotoEffectFade", "CIPhotoEffectInstant", "CIPhotoEffectMono", "CIPhotoEffectNoir", "CIPhotoEffectProcess", "CIPhotoEffectTonal", "CIPhotoEffectTransfer"]

3️⃣ 設定濾鏡選單按鈕顯示的圖片為編輯的照片,並套用濾鏡顯示

偷懶一點可以固定圖片,不過我想隨著選擇的照片顯示才是模仿 iOS 照片編輯 App 的樣子!

func filterAdoptUI(){
let image = editedImage //editedImage儲存從主頁傳過來的照片
for i in 0...8{
//設定按鈕背景圖片
filterButton[i].configuration?.background.image = image
//設定圖片顯示模式為AspectFill,填滿整個Button
filterButton[i].configuration?.background.imageContentMode = .scaleAspectFill
//轉換照片為CIImage
let ciImage = CIImage(image: (filterButton[i].configuration?.background.image)!)
//設定濾鏡
if let filter = CIFilter(name: filterArray[i]){
//指定ciImage為輸入filter的對象
filter.setValue(ciImage, forKey: kCIInputImageKey)
//輸出套用濾鏡後的圖片,格式為CIImage,再產生正確大小的CGImage
if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent){
//把圖片轉回UIImage,放入對應的按鈕中
let filterImage = UIImage(cgImage: cgImage)
filterButton[i].configuration?.background.image = filterImage
}
}
}

}

4️⃣ 使用濾鏡

@IBAction func filterAdopt(_ sender: UIButton){
//轉換照片為CIImage
let ciImage = CIImage(image: editedImage)
//如果選擇第一個按鈕,顯示原圖(不套用濾鏡)
if sender.tag == 0{
imageView.image = editedImage
//選擇第2~8個按鈕,根據tag編號套用對應濾鏡
}else if sender.tag >= 1{
//判斷使用的濾鏡是哪一個
if let filter = CIFilter(name: filterArray[sender.tag]){
//指定ciImage為輸入filter的對象
filter.setValue(ciImage, forKey: kCIInputImageKey)
//輸出套用濾鏡後的圖片,格式為CIImage,再產生正確大小的CGImage
if let outputImage = filter.outputImage, let cgImage = context.createCGImage(outputImage, from: outputImage.extent){
//把圖片轉回UIImage,放入imageView中
let filterImage = UIImage(cgImage: cgImage)
imageView.image = filterImage
}
}
}
}

最後補充 2 個重要的事情!

  1. 要使用「原圖」,也就是儲存傳輸過來的照片的 property,作為轉換成 CIImage 的對象,不然如果是用 imageView.image 的照片,效果會一直疊加,變得很可怕!!!
  2. CIImage 轉回 UIImage 可能會發生圖片轉向的問題,可以參考彼得潘的文章解決

💡 續集-最終篇

參考資料

--

--