Core Image框架详细解析(十八) —— 使用Metal Shading Language创建你自己的Core Image滤波器进行像素级图像处理(一)

版本记录

版本号 时间
V1.0 2021.10.08 星期五

前言

Core Image是IOS5中新加入的一个框架,里面提供了强大高效的图像处理功能,用来对基于像素的图像进行操作与分析。还提供了很多强大的滤镜,可以实现你想要的效果,下面我们就一起解析一下这个框架。感兴趣的可以参考上面几篇。
1. Core Image框架详细解析(一) —— 基本概览
2. Core Image框架详细解析(二) —— Core Image滤波器参考
3. Core Image框架详细解析(三) —— 关于Core Image
4. Core Image框架详细解析(四) —— Processing Images处理图像(一)
5. Core Image框架详细解析(五) —— Processing Images处理图像(二)
6. Core Image框架详细解析(六) —— 图像中的面部识别Detecting Faces in an Image(一)
7. Core Image框架详细解析(七) —— 自动增强图像 Auto Enhancing Images
8. Core Image框架详细解析(八) —— 查询系统中的过滤器 Querying the System for Filters
9. Core Image框架详细解析(九) —— 子类化CIFilter:自定义效果的配方 Subclassing CIFilter: Recipes for Custom Effects(一)
10. Core Image框架详细解析(十) —— 子类化CIFilter:自定义效果的配方 Subclassing CIFilter: Recipes for Custom Effects(二)
11. Core Image框架详细解析(十一) —— 获得最佳性能 Getting the Best Performance
12. Core Image框架详细解析(十二) —— 使用反馈处理图像 Using Feedback to Process Images
13. Core Image框架详细解析(十三) —— 在写一个自定义滤波器之前你需要知道什么?
14. Core Image框架详细解析(十四) —— 创建自定义滤波器 Creating Custom Filters(一)
15. Core Image框架详细解析(十五) —— 创建自定义滤波器 Creating Custom Filters(二)
16. Core Image框架详细解析(十六) —— 包装和加载图像单元 Packaging and Loading Image Units
17. Core Image框架详细解析(十七) —— 一个简单说明和示例(一)

开始

首先看下主要内容:

学习使用 Metal Shading Language 创建您自己的 Core Image 过滤器,以构建提供像素级图像处理的内核。内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是原文了。

Core Image 是一个强大而高效的图像处理框架。 您可以使用框架提供的内置过滤器创建漂亮的效果,也可以创建自定义过滤器和图像处理器。 您可以调整颜色、几何形状并执行复杂的卷积。

制作漂亮的滤镜是一门艺术,最伟大的艺术家之一是列奥纳多·达·芬奇 (Leonardo da Vinci)。 在本教程中,您将为达芬奇的名画添加一些有趣的元素。

在此过程中,您将:

  • 了解 Core Image 的类和内置过滤器。
  • 使用内置过滤器创建过滤器。
  • 使用自定义颜色内核(color kernel)转换图像的颜色。
  • 使用自定义扭曲内核转换图像的几何形状。
  • 学习调试 Core Image 问题。

注意:由于 Apple问题,本教程不适用于 Xcode 13iOS 15。您现在必须使用 Xcode 12

准备好你的画笔,哎呀,我的意思是你的 Xcode 准备好了。 是时候潜入 Core Image 的奇妙世界了!

打开下载项目。 在 starter 中打开 RayVinci 项目。 构建并运行。

您将看到达芬奇最著名的四部作品。点击一幅画会打开一个sheet,但图像的输出是空的。

在本教程中,您将为这些图像创建过滤器,然后查看在输出中应用过滤器的结果。

向下滑动以关闭sheet。接下来,点击右上角的Filter List

该按钮应显示可用内置过滤器的列表。但是等等,它目前是空的。你接下来会解决这个问题。


Introducing Core Image Classes

在填充过滤器列表之前,您需要了解 Core Image 框架的基本类。

  • CIImage:表示准备好进行处理或由 Core Image 过滤器生成的图像。 CIImage 对象包含图像的所有数据,但实际上不是图像。这就像一个食谱,它包含了做一道菜的所有成分,但不是这道菜本身。

您将在本教程后面看到如何渲染要显示的图像。

  • CIFilter:获取一张或多张图像,通过应用转换来处理每张图像,并生成一个 CIImage 作为其输出。您可以链接多个过滤器并创建有趣的效果。 CIFilters 的对象是可变的,不是线程安全的。

  • CIContext:渲染过滤器的处理结果。例如,CIContext 帮助从 CIImage 对象创建 Quartz 2D 图像。

要了解有关这些类的更多信息,请参阅Core Image Tutorial: Getting Started

现在您已经熟悉了 Core Image 类,是时候填充过滤器列表了。


Fetching the List of Built-In Filters

打开 RayVinci 并选择 FilterListView.swift。 将 FilterListView 中的 filterList 替换为:

let filterList = CIFilter.filterNames(inCategory: nil)

在这里,您通过使用 filterNames(inCategory:) 并传递 nil 作为类别来获取 Core Image 提供的所有可用内置过滤器的列表。 您可以在 CIFilter 的开发人员文档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。 由于名称是一个字符串并且可能拼写错误,因此初始化程序返回一个可选的。 因此,您需要检查过滤器是否存在。
  • 2) 您可以使用属性检查过滤器的各种属性。 在这里,如果过滤器存在,您将创建一个 ScrollView 并在文本视图中填充属性的描述。
  • 3) 如果过滤器不存在或未知,您将显示一个Text视图来解释情况。

构建并运行。 点击Filter List。 哇,过滤器太多了!

点按任何过滤器以查看其属性。

很了不起,不是吗?你才刚刚开始!在下一节中,您将使用这些内置过滤器来使对“蒙娜丽莎”的阳光下熠熠生辉。


Using Built-In Filters

现在,你已经看到可用过滤器列表中,您将使用这些来创建一个有趣的效果。

打开ImageProcessor.swift。在顶部,在类声明之前,添加:

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

在这里,您将 ProcessEffect 声明为enum。 它包含您将在本教程中使用的所有过滤器案例。

将以下内容添加到 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) 声明一个将 CIImage 作为输入并应用内置过滤器的私有方法。
  • 2) 您首先使用 CIPhotoEffectNoir 创建一个变暗、喜怒无常的黑色效果。 CIFilter 以一个字符串作为名称和字典形式的参数。您从 outputImage 获取生成的过滤图像。
  • 3) 接下来,您使用 CISunbeamsGenerator 创建一个生成器过滤器。这将创建一个阳光遮罩。在参数中,您设置:
    • inputStriationStrength:表示阳光的强度。
    • inputSunRadius:代表太阳的半径。
    • inputCenter:阳光中心的 x 和 y 位置。在这种情况下,您将位置设置在图像的右上角。
  • 4) 在这里,您使用 CIBlendWithMask创建风格化效果。您可以通过将 CIPhotoEffectNoir 的结果设置为背景图像并将 sunGenerate 设置为蒙版图像来对输入input应用过滤器。这种组合的结果是一个 CIImage

ImageProcessor 有输出output,一个发布的属性,它是一个 UIImage。您需要将合成结果转换为 UIImage 以显示它。

ImageProcessor 中,在@Published var output = UIImage()下面添加以下内容:

let context = CIContext()

在这里,您创建了一个所有过滤器都将使用的 CIContext 实例。

将以下内容添加到 ImageProcessor

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

在这里,您使用上下文从 CIImage 创建一个 CGImage 实例。

然后使用 cgImage 创建一个 UIImage。 用户将看到此图像。

1. Displaying a Built-In Filter’s Output

将以下内容添加到 applyBuiltInEffect(input:) 的末尾:

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

这将使用 renderAsUIImage(_:)compositeImage(CIImage)转换为 UIImage。 然后将结果保存到输出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) 创建一个方法作为 ImageProcessor 的入口点。 它需要一个 Painting 和一个effect实例来应用。
  • 2) 检查有效图像。
  • 3) 如果效果是 .builtIn 类型,则调用 applyBuiltInEffect(input:) 来应用过滤器。

打开 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设置为 .builtIn。 您还将其设置为默认效果。 然后通过在 ImageProcessor 上调用 process(painting:, effect:) 来应用过滤器。

构建并运行。 点击“Mona Lisa”。 您将看到在输出中应用了一个内置过滤器!

让蒙娜丽莎阳光普照的伟大工作。难怪她在笑!现在是使用 CIKernel 创建过滤器的时候了。


Meet CIKernel

使用 CIKernel,您可以放置自定义代码,称为内核(kernel),以逐个像素地操作图像。 GPU 处理这些像素。您使用 Metal Shading Language 编写内核,与较旧的 Core Image Kernel Language(自 iOS 12 起已弃用)相比,它具有以下优势:

  • 支持 Core Image 内核的所有强大功能,如连接和平铺。
  • 在构建时预编译,带有错误诊断。这样,您无需等待运行时出现错误。
  • 提供语法高亮和语法检查。

有不同类型的内核:

  • CIColorKernel:改变像素的颜色但不知道像素的位置。
  • CIWarpKernel:改变像素的位置但不知道像素的颜色。
  • CIBlendKernel:以优化的方式混合两个图像。

要创建和应用内核,您需要:

  • 1) 首先,向项目添加自定义构建规则。
  • 2) 然后,添加 Metal 源文件。
  • 3) 加载内核。
  • 4) 最后,初始化并应用内核。

接下来,您将实施这些步骤中的每一个。 准备好享受有趣的旅程吧!


Creating Build Rules

您需要编译 Core Image Metal 代码并将其与特殊标志链接。

在项目导航器中选择 RayVinci target。 然后,选择Build Rules选项卡。 单击 + 添加新的构建规则。

然后,设置第一个新的构建规则:

  • 1) 将Process设置为Source files with name matching:。 然后将 *.ci.metal 设置为值。
  • 2) 取消选中Run once per architecture
  • 3) 添加以下脚本:
xcrun metal -c -I $MTL_HEADER_SEARCH_PATHS -fcikernel "${INPUT_FILE_PATH}" \
  -o "${SCRIPT_OUTPUT_FILE_0}"

这会使用所需的 -fcikernel 标志调用 Metal 编译器。

  • 4) 在输出文件Output Files中添加以下内容
$(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.air

这会产生一个以 .ci.air 结尾的输出二进制文件。

接下来,再次单击 + 添加另一个新的构建规则。

对于第二个新构建规则,请按照以下步骤操作:

  • 1) 将Process设置为Source files with name matching:。 然后将 *.ci.air 设置为值。
  • 2) 取消选中Run once per architecture
  • 3) 添加以下脚本:
xcrun metallib -cikernel "${INPUT_FILE_PATH}" -o "${SCRIPT_OUTPUT_FILE_0}"

这将使用所需的 -cikernel 标志调用 Metal 链接器。

  • 4) 在输出文件Output Files中添加以下内容:
$(METAL_LIBRARY_OUTPUT_DIR)/$(INPUT_FILE_BASE).metallib

这会在应用程序包中生成一个以 .ci.metallib 结尾的文件。

接下来,是时候添加 Metal 源了。


Adding the Metal Source

首先,您将为颜色内核创建一个源文件。 在项目导航器中,突出显示 RayVinci 项目正下方的 RayVinci

右键单击并选择New Group。 将此新组命名为 Filters。 然后,突出显示该组并添加一个名为 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可让您访问框架提供的类。 这会自动包含 Core Image Metal 内核库 CIKernelMetalLib.h
  • 2) 内核需要位于 extern "C"闭包内,以便在运行时可以通过名称访问它。 接下来,指定 coreimage 的命名空间。 您在 coreimage 命名空间中声明所有扩展以避免与 Metal 发生冲突。
  • 3) 在这里,您声明 colorFilterKernel,它接受类型为 sample_t 的输入。 sample_t 表示来自输入图像的单个颜色样本。 colorFilterKernel返回一个表示像素的 RGBA 值的 float4
  • 4) 然后,您声明一个新的 float4swappedColor,并交换来自输入样本的 RGBA 值。 然后返回具有交换值的样本。

接下来,您将编写代码来加载和应用内核。


Loading the Kernel Code

要加载和应用内核,首先要创建 CIFilter 的子类。

在过滤器Filters组中创建一个新的 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) 首先导入 Core Image 框架。
  • 2) 子类化 CIFilter 包括两个主要步骤:
    • 指定输入参数。在这里,您使用 inputImage
    • 重写 outputImage
  • 3) 然后,您声明一个静态属性 kernel,它加载 ColorFilterKernel.ci.metallib 的内容。这样,库只加载一次。然后使用 ColorFilterKernel.ci.metallib 的内容创建 CIColorKernel 的实例。
  • 4) 接下来,您override outputImage。在这里,您通过使用 apply(extent:roiCallback:arguments:) 来应用内核。extent决定了有多少输入图像被传递到内核。

您传递了整个图像,因此过滤器将应用于整个图像。 roiCallback 确定在 outputImage 中渲染矩形所需的输入图像的矩形。在这里, inputImageoutputImagerect 没有改变,所以你返回相同的值并将参数数组中的 inputImage 传递给内核。

现在您已经创建了颜色内核过滤器,您将把它应用到图像上。


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:)。 这需要一个 CIImage 作为输入。 您可以通过创建 ColorFilter 的实例来创建自定义过滤器。

过滤器的 outputImage 应用了颜色内核。 然后使用 renderAsUIImage(_:) 创建 UIImage 实例并将其设置为输出。

接下来,在 process(painting:effect:) 中处理 .colorKernel,如下所示。 在default之上添加这个新case

case .colorKernel:
  applyColorKernel(input: input)

在这里,您调用 applyColorKernel(input:) 来应用您的自定义颜色内核过滤器。

最后,打开 PaintingWall.swift。 在 Buttonaction 闭包中 case 0 正下方的 switch 语句中添加以下内容:

case 1:
  effect = .colorKernel

这将第二幅画的效果设置为 .colorKernel

构建并运行。 现在点击第二幅画“The Last Supper”。 您将看到应用的颜色内核过滤器和图像中交换的 RGBA 值。

很好! 接下来,您将对达芬奇的神秘Salvator Mundi创建酷炫的扭曲效果。


Creating a Warp Kernel

与颜色内核类似,您将从添加 Metal 源文件开始。 在过滤器Filters组中创建一个名为 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);
    }
  }
}

这是您添加的内容:

  • 1) 就像在颜色内核 Metal 源中一样,您包含 Core Image header并将该方法包含在extern "C"括号中。 然后指定 coreimage 命名空间。
  • 2) 接下来,您使用destination类型的输入参数声明 warpFilter(_:),允许访问您当前正在计算的像素的位置。 它返回输入图像坐标中的位置,然后您可以将其用作源。

您可以使用 coord() 访问目标像素的 xy 坐标。 然后,您应用简单的数学运算来转换坐标并将它们作为源像素坐标返回,以创建有趣的平铺效果。

注意:尝试在 warpFilter(_:) 中用 sin 替换 tan,你会得到一个有趣的失真效果!


Loading the Warp Kernel

与您为颜色内核创建的过滤器类似,您将创建一个自定义过滤器来加载和初始化扭曲内核。

在过滤器Filters组中创建一个新的 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) 创建 WarpFilter 作为 CIFilter 的子类,以 inputImage 作为输入参数。
  • 2) 接下来,您声明静态属性kernel以加载 WarpFilterKernel.ci.metallib的内容。 然后使用 .metallib 的内容创建 CIWarpKernel 的实例。
  • 3) 最后,您通过重写 outputImage 来提供输出。 在override中,您使用 apply(extent:roiCallback:arguments:) 将内核应用于 inputImage 并返回结果。

Applying the Warp Kernel Filter

打开 ImageProcessor.swift。 将以下内容添加到 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 作为输入。 然后创建一个 WarpFilter 实例并设置 inputImage

过滤器的 outputImage 应用了扭曲内核。 然后使用 renderAsUIImage(_:) 创建 UIImage 实例并将其保存到输出。

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

case .warpKernel:
  applyWarpKernel(input: input)

在这里,您处理 .warpKernelcase并调用 applyWarpKernel(input:)来应用扭曲内核过滤器。

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

case 2:
  effect = .warpKernel

这将第三幅画的效果设置为 .warpKernel

构建并运行。 点击 Salvator Mundi 的画作。 你会看到一个有趣的基于扭曲的瓷砖效果应用。

恭喜! 您将自己的风格应用于杰作!

1. Challenge: Implementing a Blend Kernel

CIBlendKernel 针对混合两个图像进行了优化。 作为一个有趣的挑战,为 CIBlendKernel 实现一个自定义过滤器。 一些提示:

  • 1) 创建一个 CIFilter 的子类,它接收两个图像:输入图像和背景图像。
  • 2) 使用内置的可用 CIBlendKernel 内核。 对于此挑战,请使用built-in multiply混合内核。
  • 3) 在 ImageProcessor 中创建一个方法,将混合内核过滤器应用于图像并将结果设置为输出。 您可以使用项目资产中提供的 multi_color 图片作为滤镜的背景图片。 另外,处理 .blendKernelcase
  • 4) 将此过滤器应用于 PaintWall.swift 中的第四张图像。

您将在下载的材料中找到在最终项目中实施的解决方案。 祝你好运!


Debugging Core Image Issues

了解 Core Image 如何渲染图像可以帮助您在图像未按预期显示时进行调试。 最简单的方法是在调试时使用 Core Image Quick Look

1. Using Core Image Quick Look

打开 ImageProcessor.swift。 在 applyColorKernel(input:)中设置output的行上放置一个断点。 构建并运行。 点击“The Last Supper”

当您遇到断点时,将鼠标悬停在 outputImage 上。 你会看到一个显示地址的小弹出框。

单击眼睛符号。 将出现一个窗口,显示制作图像的图形。 很酷吧?

2. Using CI_PRINT_TREE

CI_PRINT_TREE 是基于与 Core Image Quick Look 相同的基础架构的调试功能。 它有多种模式和操作。

选择并编辑 RayVincischeme。 选择 Run 选项卡并添加 CI_PRINT_TREE 作为值为 7 pdf 的新环境变量。

CI_PRINT_TREE 的值采用 graph_type output_type 选项的形式。

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

  • 1:显示颜色空间的初始图形。
  • 2:一个优化的图表,显示了 Core Image 是如何优化的。
  • 4:显示您需要多少内存的连接图。
  • 7:详细日志记录。 这将打印所有上述图表。

对于 output_type,您可以指定 PDFPNG。 它将文档保存到一个临时目录。

构建并运行。 在模拟器中选择“The Last Supper”。 现在,通过使用终端导航到 /tmp 来打开 Mac 上的临时目录。

您将看到所有图形为 PDF 文件。 打开以 _initial_graph.pdf 作为后缀的文件之一。

输入在底部,输出在顶部。 红色节点代表颜色内核(color kernels),而绿色节点代表扭曲内核(warp kernels)。 您还将看到每个步骤的ROI和范围。

要了解有关可以为 CI_PRINT_TREE 设置的各种选项的更多信息,请查看此 WWDC 会议:Discover Core Image debugging techniques

在本教程中,您学习了使用基于 MetalCore Image 内核创建和应用自定义过滤器。 要了解更多信息,请查看这些 WWDC 视频:

您还可以参考 Apple 的指南,Metal Shading Language for Core Image Kernels

后记

本篇主要讲述了使用Metal Shading Language创建你自己的Core Image滤波器进行像素级图像处理,感兴趣的给个赞或者关注~~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容