Core Image框架詳細(xì)解析(十八) —— 使用Metal Shading Language創(chuàng)建你自己的Core Image濾波器進(jìn)行像素級(jí)圖像處理(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2021.10.08 星期五

前言

Core Image是IOS5中新加入的一個(gè)框架,里面提供了強(qiáng)大高效的圖像處理功能,用來對(duì)基于像素的圖像進(jìn)行操作與分析。還提供了很多強(qiáng)大的濾鏡,可以實(shí)現(xiàn)你想要的效果,下面我們就一起解析一下這個(gè)框架。感興趣的可以參考上面幾篇。
1. Core Image框架詳細(xì)解析(一) —— 基本概覽
2. Core Image框架詳細(xì)解析(二) —— Core Image濾波器參考
3. Core Image框架詳細(xì)解析(三) —— 關(guān)于Core Image
4. Core Image框架詳細(xì)解析(四) —— Processing Images處理圖像(一)
5. Core Image框架詳細(xì)解析(五) —— Processing Images處理圖像(二)
6. Core Image框架詳細(xì)解析(六) —— 圖像中的面部識(shí)別Detecting Faces in an Image(一)
7. Core Image框架詳細(xì)解析(七) —— 自動(dòng)增強(qiáng)圖像 Auto Enhancing Images
8. Core Image框架詳細(xì)解析(八) —— 查詢系統(tǒng)中的過濾器 Querying the System for Filters
9. Core Image框架詳細(xì)解析(九) —— 子類化CIFilter:自定義效果的配方 Subclassing CIFilter: Recipes for Custom Effects(一)
10. Core Image框架詳細(xì)解析(十) —— 子類化CIFilter:自定義效果的配方 Subclassing CIFilter: Recipes for Custom Effects(二)
11. Core Image框架詳細(xì)解析(十一) —— 獲得最佳性能 Getting the Best Performance
12. Core Image框架詳細(xì)解析(十二) —— 使用反饋處理圖像 Using Feedback to Process Images
13. Core Image框架詳細(xì)解析(十三) —— 在寫一個(gè)自定義濾波器之前你需要知道什么?
14. Core Image框架詳細(xì)解析(十四) —— 創(chuàng)建自定義濾波器 Creating Custom Filters(一)
15. Core Image框架詳細(xì)解析(十五) —— 創(chuàng)建自定義濾波器 Creating Custom Filters(二)
16. Core Image框架詳細(xì)解析(十六) —— 包裝和加載圖像單元 Packaging and Loading Image Units
17. Core Image框架詳細(xì)解析(十七) —— 一個(gè)簡(jiǎn)單說明和示例(一)

開始

首先看下主要內(nèi)容:

學(xué)習(xí)使用 Metal Shading Language 創(chuàng)建您自己的 Core Image 過濾器,以構(gòu)建提供像素級(jí)圖像處理的內(nèi)核。內(nèi)容來自翻譯。

接著看下寫作環(huán)境:

Swift 5, iOS 14, Xcode 12

下面就是原文了。

Core Image 是一個(gè)強(qiáng)大而高效的圖像處理框架。 您可以使用框架提供的內(nèi)置過濾器創(chuàng)建漂亮的效果,也可以創(chuàng)建自定義過濾器和圖像處理器。 您可以調(diào)整顏色、幾何形狀并執(zhí)行復(fù)雜的卷積。

制作漂亮的濾鏡是一門藝術(shù),最偉大的藝術(shù)家之一是列奧納多·達(dá)·芬奇 (Leonardo da Vinci)。 在本教程中,您將為達(dá)芬奇的名畫添加一些有趣的元素。

在此過程中,您將:

  • 了解 Core Image 的類和內(nèi)置過濾器。
  • 使用內(nèi)置過濾器創(chuàng)建過濾器。
  • 使用自定義顏色內(nèi)核(color kernel)轉(zhuǎn)換圖像的顏色。
  • 使用自定義扭曲內(nèi)核轉(zhuǎn)換圖像的幾何形狀。
  • 學(xué)習(xí)調(diào)試 Core Image 問題。

注意:由于 Apple問題,本教程不適用于 Xcode 13iOS 15。您現(xiàn)在必須使用 Xcode 12。

準(zhǔn)備好你的畫筆,哎呀,我的意思是你的 Xcode 準(zhǔn)備好了。 是時(shí)候潛入 Core Image 的奇妙世界了!

打開下載項(xiàng)目。 在 starter 中打開 RayVinci 項(xiàng)目。 構(gòu)建并運(yùn)行。

您將看到達(dá)芬奇最著名的四部作品。點(diǎn)擊一幅畫會(huì)打開一個(gè)sheet,但圖像的輸出是空的。

在本教程中,您將為這些圖像創(chuàng)建過濾器,然后查看在輸出中應(yīng)用過濾器的結(jié)果。

向下滑動(dòng)以關(guān)閉sheet。接下來,點(diǎn)擊右上角的Filter List。

該按鈕應(yīng)顯示可用內(nèi)置過濾器的列表。但是等等,它目前是空的。你接下來會(huì)解決這個(gè)問題。


Introducing Core Image Classes

在填充過濾器列表之前,您需要了解 Core Image 框架的基本類。

  • CIImage:表示準(zhǔn)備好進(jìn)行處理或由 Core Image 過濾器生成的圖像。 CIImage 對(duì)象包含圖像的所有數(shù)據(jù),但實(shí)際上不是圖像。這就像一個(gè)食譜,它包含了做一道菜的所有成分,但不是這道菜本身。

您將在本教程后面看到如何渲染要顯示的圖像。

  • CIFilter:獲取一張或多張圖像,通過應(yīng)用轉(zhuǎn)換來處理每張圖像,并生成一個(gè) CIImage 作為其輸出。您可以鏈接多個(gè)過濾器并創(chuàng)建有趣的效果。 CIFilters 的對(duì)象是可變的,不是線程安全的。

  • CIContext:渲染過濾器的處理結(jié)果。例如,CIContext 幫助從 CIImage 對(duì)象創(chuàng)建 Quartz 2D 圖像。

要了解有關(guān)這些類的更多信息,請(qǐng)參閱Core Image Tutorial: Getting Started。

現(xiàn)在您已經(jīng)熟悉了 Core Image 類,是時(shí)候填充過濾器列表了。


Fetching the List of Built-In Filters

打開 RayVinci 并選擇 FilterListView.swift。 將 FilterListView 中的 filterList 替換為:

let filterList = CIFilter.filterNames(inCategory: nil)

在這里,您通過使用 filterNames(inCategory:) 并傳遞 nil 作為類別來獲取 Core Image 提供的所有可用內(nèi)置過濾器的列表。 您可以在 CIFilter 的開發(fā)人員文檔developer documentation中查看可用類別列表。

打開 FilterDetailView.swift。 將正文中的Text("Filter Details")替換為:

// 1
if let ciFilter = CIFilter(name: filter) {
  // 2
  ScrollView {
    Text(ciFilter.attributes.description)
  }
} else {
  // 3
  Text("Unknown filter!")
}

在這里,你:

  • 1) 使用過濾器名稱初始化過濾器 ciFilter。 由于名稱是一個(gè)字符串并且可能拼寫錯(cuò)誤,因此初始化程序返回一個(gè)可選的。 因此,您需要檢查過濾器是否存在。
  • 2) 您可以使用屬性檢查過濾器的各種屬性。 在這里,如果過濾器存在,您將創(chuàng)建一個(gè) ScrollView 并在文本視圖中填充屬性的描述。
  • 3) 如果過濾器不存在或未知,您將顯示一個(gè)Text視圖來解釋情況。

構(gòu)建并運(yùn)行。 點(diǎn)擊Filter List。 哇,過濾器太多了!

點(diǎn)按任何過濾器以查看其屬性。

很了不起,不是嗎?你才剛剛開始!在下一節(jié)中,您將使用這些內(nèi)置過濾器來使對(duì)“蒙娜麗莎”的陽光下熠熠生輝。


Using Built-In Filters

現(xiàn)在,你已經(jīng)看到可用過濾器列表中,您將使用這些來創(chuàng)建一個(gè)有趣的效果。

打開ImageProcessor.swift。在頂部,在類聲明之前,添加:

enum ProcessEffect {
  case builtIn
  case colorKernel
  case warpKernel
  case blendKernel
}

在這里,您將 ProcessEffect 聲明為enum。 它包含您將在本教程中使用的所有過濾器案例。

將以下內(nèi)容添加到 ImageProcessor

// 1
private func applyBuiltInEffect(input: CIImage) {
  // 2
  let noir = CIFilter(
    name: "CIPhotoEffectNoir",
    parameters: ["inputImage": input]
  )?.outputImage
  // 3
  let sunGenerate = CIFilter(
    name: "CISunbeamsGenerator",
    parameters: [
      "inputStriationStrength": 1,
      "inputSunRadius": 300,
      "inputCenter": CIVector(
        x: input.extent.width - input.extent.width / 5,
        y: input.extent.height - input.extent.height / 10)
    ])?
    .outputImage
  // 4
  let compositeImage = input.applyingFilter(
    "CIBlendWithMask",
    parameters: [
      kCIInputBackgroundImageKey: noir as Any,
      kCIInputMaskImageKey: sunGenerate as Any
    ])
}

在這里,你:

  • 1) 聲明一個(gè)將 CIImage 作為輸入并應(yīng)用內(nèi)置過濾器的私有方法。
  • 2) 您首先使用 CIPhotoEffectNoir 創(chuàng)建一個(gè)變暗、喜怒無常的黑色效果。 CIFilter 以一個(gè)字符串作為名稱和字典形式的參數(shù)。您從 outputImage 獲取生成的過濾圖像。
  • 3) 接下來,您使用 CISunbeamsGenerator 創(chuàng)建一個(gè)生成器過濾器。這將創(chuàng)建一個(gè)陽光遮罩。在參數(shù)中,您設(shè)置:
    • inputStriationStrength:表示陽光的強(qiáng)度。
    • inputSunRadius:代表太陽的半徑。
    • inputCenter:陽光中心的 x 和 y 位置。在這種情況下,您將位置設(shè)置在圖像的右上角。
  • 4) 在這里,您使用 CIBlendWithMask創(chuàng)建風(fēng)格化效果。您可以通過將 CIPhotoEffectNoir 的結(jié)果設(shè)置為背景圖像并將 sunGenerate 設(shè)置為蒙版圖像來對(duì)輸入input應(yīng)用過濾器。這種組合的結(jié)果是一個(gè) CIImage。

ImageProcessor 有輸出output,一個(gè)發(fā)布的屬性,它是一個(gè) UIImage。您需要將合成結(jié)果轉(zhuǎn)換為 UIImage 以顯示它。

ImageProcessor 中,在@Published var output = UIImage()下面添加以下內(nèi)容:

let context = CIContext()

在這里,您創(chuàng)建了一個(gè)所有過濾器都將使用的 CIContext 實(shí)例。

將以下內(nèi)容添加到 ImageProcessor

private func renderAsUIImage(_ image: CIImage) -> UIImage? {
  if let cgImage = context.createCGImage(image, from: image.extent) {
    return UIImage(cgImage: cgImage)
  }
  return nil
}

在這里,您使用上下文從 CIImage 創(chuàng)建一個(gè) CGImage 實(shí)例。

然后使用 cgImage 創(chuàng)建一個(gè) UIImage。 用戶將看到此圖像。

1. Displaying a Built-In Filter’s Output

將以下內(nèi)容添加到 applyBuiltInEffect(input:) 的末尾:

if let outputImage = renderAsUIImage(compositeImage) {
  output = outputImage
}

這將使用 renderAsUIImage(_:)compositeImage(CIImage)轉(zhuǎn)換為 UIImage。 然后將結(jié)果保存到輸出output。

將以下新方法添加到 ImageProcessor

// 1
func process(painting: Painting, effect: ProcessEffect) {
  // 2
  guard
    let paintImage = UIImage(named: painting.image),
    let input = CIImage(image: paintImage)
  else {
    print("Invalid input image")
    return
  }
  switch effect {
  // 3
  case .builtIn:
    applyBuiltInEffect(input: input)
  default:
    print("Unsupported effect")
  }
}

在這里,你:

  • 1) 創(chuàng)建一個(gè)方法作為 ImageProcessor 的入口點(diǎn)。 它需要一個(gè) Painting 和一個(gè)effect實(shí)例來應(yīng)用。
  • 2) 檢查有效圖像。
  • 3) 如果效果是 .builtIn 類型,則調(diào)用 applyBuiltInEffect(input:) 來應(yīng)用過濾器。

打開 PaintWall.swift。 在 Buttonaction 閉包中 selectedPainting = Painting[index] 下方,添加:

var effect = ProcessEffect.builtIn
if let painting = selectedPainting {
  switch index {
  case 0:
    effect = .builtIn
  default:
    effect = .builtIn
  }
  ImageProcessor.shared.process(painting: painting, effect: effect)
}

在這里,您將第一幅畫的effect設(shè)置為 .builtIn。 您還將其設(shè)置為默認(rèn)效果。 然后通過在 ImageProcessor 上調(diào)用 process(painting:, effect:) 來應(yīng)用過濾器。

構(gòu)建并運(yùn)行。 點(diǎn)擊“Mona Lisa”。 您將看到在輸出中應(yīng)用了一個(gè)內(nèi)置過濾器!

讓蒙娜麗莎陽光普照的偉大工作。難怪她在笑!現(xiàn)在是使用 CIKernel 創(chuàng)建過濾器的時(shí)候了。


Meet CIKernel

使用 CIKernel,您可以放置自定義代碼,稱為內(nèi)核(kernel),以逐個(gè)像素地操作圖像。 GPU 處理這些像素。您使用 Metal Shading Language 編寫內(nèi)核,與較舊的 Core Image Kernel Language(自 iOS 12 起已棄用)相比,它具有以下優(yōu)勢(shì):

  • 支持 Core Image 內(nèi)核的所有強(qiáng)大功能,如連接和平鋪。
  • 在構(gòu)建時(shí)預(yù)編譯,帶有錯(cuò)誤診斷。這樣,您無需等待運(yùn)行時(shí)出現(xiàn)錯(cuò)誤。
  • 提供語法高亮和語法檢查。

有不同類型的內(nèi)核:

  • CIColorKernel:改變像素的顏色但不知道像素的位置。
  • CIWarpKernel:改變像素的位置但不知道像素的顏色。
  • CIBlendKernel:以優(yōu)化的方式混合兩個(gè)圖像。

要?jiǎng)?chuàng)建和應(yīng)用內(nèi)核,您需要:

  • 1) 首先,向項(xiàng)目添加自定義構(gòu)建規(guī)則。
  • 2) 然后,添加 Metal 源文件。
  • 3) 加載內(nèi)核。
  • 4) 最后,初始化并應(yīng)用內(nèi)核。

接下來,您將實(shí)施這些步驟中的每一個(gè)。 準(zhǔn)備好享受有趣的旅程吧!


Creating Build Rules

您需要編譯 Core Image Metal 代碼并將其與特殊標(biāo)志鏈接。

在項(xiàng)目導(dǎo)航器中選擇 RayVinci target。 然后,選擇Build Rules選項(xiàng)卡。 單擊 + 添加新的構(gòu)建規(guī)則。

然后,設(shè)置第一個(gè)新的構(gòu)建規(guī)則:

  • 1) 將Process設(shè)置為Source files with name matching:。 然后將 *.ci.metal 設(shè)置為值。
  • 2) 取消選中Run once per architecture。
  • 3) 添加以下腳本:
xcrun metal -c -I $MTL_HEADER_SEARCH_PATHS -fcikernel "${INPUT_FILE_PATH}" \
  -o "${SCRIPT_OUTPUT_FILE_0}"

這會(huì)使用所需的 -fcikernel 標(biāo)志調(diào)用 Metal 編譯器。

  • 4) 在輸出文件Output Files中添加以下內(nèi)容
$(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.air

這會(huì)產(chǎn)生一個(gè)以 .ci.air 結(jié)尾的輸出二進(jìn)制文件。

接下來,再次單擊 + 添加另一個(gè)新的構(gòu)建規(guī)則。

對(duì)于第二個(gè)新構(gòu)建規(guī)則,請(qǐng)按照以下步驟操作:

  • 1) 將Process設(shè)置為Source files with name matching:。 然后將 *.ci.air 設(shè)置為值。
  • 2) 取消選中Run once per architecture。
  • 3) 添加以下腳本:
xcrun metallib -cikernel "${INPUT_FILE_PATH}" -o "${SCRIPT_OUTPUT_FILE_0}"

這將使用所需的 -cikernel 標(biāo)志調(diào)用 Metal 鏈接器。

  • 4) 在輸出文件Output Files中添加以下內(nèi)容:
$(METAL_LIBRARY_OUTPUT_DIR)/$(INPUT_FILE_BASE).metallib

這會(huì)在應(yīng)用程序包中生成一個(gè)以 .ci.metallib 結(jié)尾的文件。

接下來,是時(shí)候添加 Metal 源了。


Adding the Metal Source

首先,您將為顏色內(nèi)核創(chuàng)建一個(gè)源文件。 在項(xiàng)目導(dǎo)航器中,突出顯示 RayVinci 項(xiàng)目正下方的 RayVinci。

右鍵單擊并選擇New Group。 將此新組命名為 Filters。 然后,突出顯示該組并添加一個(gè)名為 ColorFilterKernel.ci.metal 的新 Metal 文件。

打開文件并添加:

// 1
#include <CoreImage/CoreImage.h>
 
// 2
extern "C" {
  namespace coreimage {
    // 3
    float4 colorFilterKernel(sample_t s) {
      // 4
      float4 swappedColor;
      swappedColor.r = s.g;
      swappedColor.g = s.b;
      swappedColor.b = s.r;
      swappedColor.a = s.a;
      return swappedColor;
    }
  }
}

下面是代碼分解:

  • 1) 包含 Core Imageheader可讓您訪問框架提供的類。 這會(huì)自動(dòng)包含 Core Image Metal 內(nèi)核庫 CIKernelMetalLib.h。
  • 2) 內(nèi)核需要位于 extern "C"閉包內(nèi),以便在運(yùn)行時(shí)可以通過名稱訪問它。 接下來,指定 coreimage 的命名空間。 您在 coreimage 命名空間中聲明所有擴(kuò)展以避免與 Metal 發(fā)生沖突。
  • 3) 在這里,您聲明 colorFilterKernel,它接受類型為 sample_t 的輸入。 sample_t 表示來自輸入圖像的單個(gè)顏色樣本。 colorFilterKernel返回一個(gè)表示像素的 RGBA 值的 float4。
  • 4) 然后,您聲明一個(gè)新的 float4、swappedColor,并交換來自輸入樣本的 RGBA 值。 然后返回具有交換值的樣本。

接下來,您將編寫代碼來加載和應(yīng)用內(nèi)核。


Loading the Kernel Code

要加載和應(yīng)用內(nèi)核,首先要?jiǎng)?chuàng)建 CIFilter 的子類。

在過濾器Filters組中創(chuàng)建一個(gè)新的 Swift 文件。 將其命名為 ColorFilter.swift 并添加:

// 1
import CoreImage

class ColorFilter: CIFilter {
  // 2
  var inputImage: CIImage?

  // 3
  static var kernel: CIKernel = { () -> CIColorKernel in
    guard let url = Bundle.main.url(
      forResource: "ColorFilterKernel.ci",
      withExtension: "metallib"),
      let data = try? Data(contentsOf: url) else {
      fatalError("Unable to load metallib")
    }

    guard let kernel = try? CIColorKernel(
      functionName: "colorFilterKernel",
      fromMetalLibraryData: data) else {
      fatalError("Unable to create color kernel")
    }

    return kernel
  }()

  // 4
  override var outputImage: CIImage? {
    guard let inputImage = inputImage else { return nil }
    return ColorFilter.kernel.apply(
      extent: inputImage.extent,
      roiCallback: { _, rect in
        return rect
      },
      arguments: [inputImage])
  }
}

在這里,你:

  • 1) 首先導(dǎo)入 Core Image 框架。
  • 2) 子類化 CIFilter 包括兩個(gè)主要步驟:
    • 指定輸入?yún)?shù)。在這里,您使用 inputImage。
    • 重寫 outputImage
  • 3) 然后,您聲明一個(gè)靜態(tài)屬性 kernel,它加載 ColorFilterKernel.ci.metallib 的內(nèi)容。這樣,庫只加載一次。然后使用 ColorFilterKernel.ci.metallib 的內(nèi)容創(chuàng)建 CIColorKernel 的實(shí)例。
  • 4) 接下來,您override outputImage。在這里,您通過使用 apply(extent:roiCallback:arguments:) 來應(yīng)用內(nèi)核。extent決定了有多少輸入圖像被傳遞到內(nèi)核。

您傳遞了整個(gè)圖像,因此過濾器將應(yīng)用于整個(gè)圖像。 roiCallback 確定在 outputImage 中渲染矩形所需的輸入圖像的矩形。在這里, inputImageoutputImagerect 沒有改變,所以你返回相同的值并將參數(shù)數(shù)組中的 inputImage 傳遞給內(nèi)核。

現(xiàn)在您已經(jīng)創(chuàng)建了顏色內(nèi)核過濾器,您將把它應(yīng)用到圖像上。


Applying the Color Kernel Filter

打開 ImageProcessor.swift。將以下方法添加到 ImageProcessor

private func applyColorKernel(input: CIImage) {
  let filter = ColorFilter()
  filter.inputImage = input
  if let outputImage = filter.outputImage,
    let renderImage = renderAsUIImage(outputImage) {
    output = renderImage
  }
}

在這里,您聲明 applyColorKernel(input:)。 這需要一個(gè) CIImage 作為輸入。 您可以通過創(chuàng)建 ColorFilter 的實(shí)例來創(chuàng)建自定義過濾器。

過濾器的 outputImage 應(yīng)用了顏色內(nèi)核。 然后使用 renderAsUIImage(_:) 創(chuàng)建 UIImage 實(shí)例并將其設(shè)置為輸出。

接下來,在 process(painting:effect:) 中處理 .colorKernel,如下所示。 在default之上添加這個(gè)新case

case .colorKernel:
  applyColorKernel(input: input)

在這里,您調(diào)用 applyColorKernel(input:) 來應(yīng)用您的自定義顏色內(nèi)核過濾器。

最后,打開 PaintingWall.swift。 在 Buttonaction 閉包中 case 0 正下方的 switch 語句中添加以下內(nèi)容:

case 1:
  effect = .colorKernel

這將第二幅畫的效果設(shè)置為 .colorKernel。

構(gòu)建并運(yùn)行。 現(xiàn)在點(diǎn)擊第二幅畫“The Last Supper”。 您將看到應(yīng)用的顏色內(nèi)核過濾器和圖像中交換的 RGBA 值。

很好! 接下來,您將對(duì)達(dá)芬奇的神秘Salvator Mundi創(chuàng)建酷炫的扭曲效果。


Creating a Warp Kernel

與顏色內(nèi)核類似,您將從添加 Metal 源文件開始。 在過濾器Filters組中創(chuàng)建一個(gè)名為 WarpFilterKernel.ci.metal 的新 Metal 文件。 打開文件并添加:

#include <CoreImage/CoreImage.h>
//1
extern "C" {
  namespace coreimage {
    //2
    float2 warpFilter(destination dest) {
      float y = dest.coord().y + tan(dest.coord().y / 10) * 20;
      float x = dest.coord().x + tan(dest.coord().x/ 10) * 20;
      return float2(x,y);
    }
  }
}

這是您添加的內(nèi)容:

  • 1) 就像在顏色內(nèi)核 Metal 源中一樣,您包含 Core Image header并將該方法包含在extern "C"括號(hào)中。 然后指定 coreimage 命名空間。
  • 2) 接下來,您使用destination類型的輸入?yún)?shù)聲明 warpFilter(_:),允許訪問您當(dāng)前正在計(jì)算的像素的位置。 它返回輸入圖像坐標(biāo)中的位置,然后您可以將其用作源。

您可以使用 coord() 訪問目標(biāo)像素的 xy 坐標(biāo)。 然后,您應(yīng)用簡(jiǎn)單的數(shù)學(xué)運(yùn)算來轉(zhuǎn)換坐標(biāo)并將它們作為源像素坐標(biāo)返回,以創(chuàng)建有趣的平鋪效果。

注意:嘗試在 warpFilter(_:) 中用 sin 替換 tan,你會(huì)得到一個(gè)有趣的失真效果!


Loading the Warp Kernel

與您為顏色內(nèi)核創(chuàng)建的過濾器類似,您將創(chuàng)建一個(gè)自定義過濾器來加載和初始化扭曲內(nèi)核。

在過濾器Filters組中創(chuàng)建一個(gè)新的 Swift 文件。 將其命名為 WarpFilter.swift 并添加:

import CoreImage

// 1
class WarpFilter: CIFilter {
  var inputImage: CIImage?
  // 2
  static var kernel: CIWarpKernel = { () -> CIWarpKernel in
    guard let url = Bundle.main.url(
      forResource: "WarpFilterKernel.ci",
      withExtension: "metallib"),
    let data = try? Data(contentsOf: url) else {
      fatalError("Unable to load metallib")
    }

    guard let kernel = try? CIWarpKernel(
      functionName: "warpFilter",
      fromMetalLibraryData: data) else {
      fatalError("Unable to create warp kernel")
    }

    return kernel
  }()

  // 3
  override var outputImage: CIImage? {
    guard let inputImage = inputImage else { return .none }

    return WarpFilter.kernel.apply(
      extent: inputImage.extent,
      roiCallback: { _, rect in
        return rect
      },
      image: inputImage,
      arguments: [])
  }
}

在這里,你:

  • 1) 創(chuàng)建 WarpFilter 作為 CIFilter 的子類,以 inputImage 作為輸入?yún)?shù)。
  • 2) 接下來,您聲明靜態(tài)屬性kernel以加載 WarpFilterKernel.ci.metallib的內(nèi)容。 然后使用 .metallib 的內(nèi)容創(chuàng)建 CIWarpKernel 的實(shí)例。
  • 3) 最后,您通過重寫 outputImage 來提供輸出。 在override中,您使用 apply(extent:roiCallback:arguments:) 將內(nèi)核應(yīng)用于 inputImage 并返回結(jié)果。

Applying the Warp Kernel Filter

打開 ImageProcessor.swift。 將以下內(nèi)容添加到 ImageProcessor

private func applyWarpKernel(input: CIImage) {
  let filter = WarpFilter()
  filter.inputImage = input
  if let outputImage = filter.outputImage,
    let renderImage = renderAsUIImage(outputImage) {
    output = renderImage
  }
}

在這里,您聲明 applyColorKernel(input:),它將 CIImage 作為輸入。 然后創(chuàng)建一個(gè) WarpFilter 實(shí)例并設(shè)置 inputImage。

過濾器的 outputImage 應(yīng)用了扭曲內(nèi)核。 然后使用 renderAsUIImage(_:) 創(chuàng)建 UIImage 實(shí)例并將其保存到輸出。

接下來,在 process(painting:effect:) 中添加以下 case,在 case .colorKernel 下:

case .warpKernel:
  applyWarpKernel(input: input)

在這里,您處理 .warpKernelcase并調(diào)用 applyWarpKernel(input:)來應(yīng)用扭曲內(nèi)核過濾器。

最后,打開 PaintingWall.swift。 在actioncase 1正下方的 switch 語句中添加以下 case

case 2:
  effect = .warpKernel

這將第三幅畫的效果設(shè)置為 .warpKernel。

構(gòu)建并運(yùn)行。 點(diǎn)擊 Salvator Mundi 的畫作。 你會(huì)看到一個(gè)有趣的基于扭曲的瓷磚效果應(yīng)用。

恭喜! 您將自己的風(fēng)格應(yīng)用于杰作!

1. Challenge: Implementing a Blend Kernel

CIBlendKernel 針對(duì)混合兩個(gè)圖像進(jìn)行了優(yōu)化。 作為一個(gè)有趣的挑戰(zhàn),為 CIBlendKernel 實(shí)現(xiàn)一個(gè)自定義過濾器。 一些提示:

  • 1) 創(chuàng)建一個(gè) CIFilter 的子類,它接收兩個(gè)圖像:輸入圖像和背景圖像。
  • 2) 使用內(nèi)置的可用 CIBlendKernel 內(nèi)核。 對(duì)于此挑戰(zhàn),請(qǐng)使用built-in multiply混合內(nèi)核。
  • 3) 在 ImageProcessor 中創(chuàng)建一個(gè)方法,將混合內(nèi)核過濾器應(yīng)用于圖像并將結(jié)果設(shè)置為輸出。 您可以使用項(xiàng)目資產(chǎn)中提供的 multi_color 圖片作為濾鏡的背景圖片。 另外,處理 .blendKernelcase。
  • 4) 將此過濾器應(yīng)用于 PaintWall.swift 中的第四張圖像。

您將在下載的材料中找到在最終項(xiàng)目中實(shí)施的解決方案。 祝你好運(yùn)!


Debugging Core Image Issues

了解 Core Image 如何渲染圖像可以幫助您在圖像未按預(yù)期顯示時(shí)進(jìn)行調(diào)試。 最簡(jiǎn)單的方法是在調(diào)試時(shí)使用 Core Image Quick Look。

1. Using Core Image Quick Look

打開 ImageProcessor.swift。 在 applyColorKernel(input:)中設(shè)置output的行上放置一個(gè)斷點(diǎn)。 構(gòu)建并運(yùn)行。 點(diǎn)擊“The Last Supper”。

當(dāng)您遇到斷點(diǎn)時(shí),將鼠標(biāo)懸停在 outputImage 上。 你會(huì)看到一個(gè)顯示地址的小彈出框。

單擊眼睛符號(hào)。 將出現(xiàn)一個(gè)窗口,顯示制作圖像的圖形。 很酷吧?

2. Using CI_PRINT_TREE

CI_PRINT_TREE 是基于與 Core Image Quick Look 相同的基礎(chǔ)架構(gòu)的調(diào)試功能。 它有多種模式和操作。

選擇并編輯 RayVincischeme。 選擇 Run 選項(xiàng)卡并添加 CI_PRINT_TREE 作為值為 7 pdf 的新環(huán)境變量。

CI_PRINT_TREE 的值采用 graph_type output_type 選項(xiàng)的形式。

graph_type 表示Core Image渲染的階段。 以下是您可以指定的值:

  • 1:顯示顏色空間的初始圖形。
  • 2:一個(gè)優(yōu)化的圖表,顯示了 Core Image 是如何優(yōu)化的。
  • 4:顯示您需要多少內(nèi)存的連接圖。
  • 7:詳細(xì)日志記錄。 這將打印所有上述圖表。

對(duì)于 output_type,您可以指定 PDFPNG。 它將文檔保存到一個(gè)臨時(shí)目錄。

構(gòu)建并運(yùn)行。 在模擬器中選擇“The Last Supper”。 現(xiàn)在,通過使用終端導(dǎo)航到 /tmp 來打開 Mac 上的臨時(shí)目錄。

您將看到所有圖形為 PDF 文件。 打開以 _initial_graph.pdf 作為后綴的文件之一。

輸入在底部,輸出在頂部。 紅色節(jié)點(diǎn)代表顏色內(nèi)核(color kernels),而綠色節(jié)點(diǎn)代表扭曲內(nèi)核(warp kernels)。 您還將看到每個(gè)步驟的ROI和范圍。

要了解有關(guān)可以為 CI_PRINT_TREE 設(shè)置的各種選項(xiàng)的更多信息,請(qǐng)查看此 WWDC 會(huì)議:Discover Core Image debugging techniques

在本教程中,您學(xué)習(xí)了使用基于 MetalCore Image 內(nèi)核創(chuàng)建和應(yīng)用自定義過濾器。 要了解更多信息,請(qǐng)查看這些 WWDC 視頻:

您還可以參考 Apple 的指南,Metal Shading Language for Core Image Kernels。

后記

本篇主要講述了使用Metal Shading Language創(chuàng)建你自己的Core Image濾波器進(jìn)行像素級(jí)圖像處理,感興趣的給個(gè)贊或者關(guān)注~~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容