美文网首页
iOS开发—核心图像教程:自定义过滤器

iOS开发—核心图像教程:自定义过滤器

作者: iOS丶lant | 来源:发表于2022-04-28 14:37 被阅读0次

您将看到多·达·纸莱昂纳奇纳的四部作品。
在本教程中,您将有这些图像创建过滤器,然后在输出中查看应用过滤器的结果。
其实就是以关闭工作表来实现,点击关闭的过滤器列表。

介绍核心图像类

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

  • CIImage:表示很好或由过滤器生成的对象中包含所有的图像CIImage,但实际上是这个像包含图像但包含制作图像但不是所有内容的准备。

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

  • CIFilter:获取一个或多个图像,通过应用转换处理每个图像并生成一个CIImage作为输出。并创建有趣的效果CIFilters

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

要了解有关这些类的更多信息,请参阅核心图像教程:入门

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

获取压缩器列表

打开RayVinci并选择FilterListView.swift替换为:filterList``FilterListView

让 filterList =  CIFilter .filterNames(inCategory: nil )

filterNames(inCategory:)在这里,您可以通过使用并nil作为类别传递来获取核心图像,提供所有可用的组合过滤器的列表。可以在开发人员文档中查看可用类别的CIFilter列表。

打开FilterDetailView.swift。替换Text("Filter Details")body

// 1 
if  let ciFilter =  CIFilter (name: filter) {
   // 2
  滚动视图 {
    文本(ciFilter.attributes.description)
  } 
} 另外 { // 3 Text ( "未知过滤器!" ) 
}

在这里,你:

  1. ciFilter使用过滤器名称初始化器由于名称是一个过滤器字符串可能拼写错误,因此,初始化程序返回一个可选项。因此,需要检查过滤器是否存在。
  2. 您可以使用。检查过滤器的各种属性attributes。在这里,如果过滤器存在,您将在视图中创建并填充ScrollView属性的描述。Text
  3. 如果过滤器不存在或未知,则显示Text情况的视图。

哇,点击过滤器啊!

点击过滤器以查看其属性。

很了不起,不是吗?你刚开始!在下一节将使用其中的一位美丽中让娜丽莎: “照在”蒙娜莎上。]

使用组合过滤器

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

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

枚举ProcessEffect {
   case builtIn
   case colorKernel
   case warpKernel
   case blendKernel 
}

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

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

ImageProcessorhas output,一个已发布的属性,它是一个UIImage. 您需要将合成的结果转换为 aUIImage以显示它。

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

让上下文=  CIContext ()

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

将以下内容添加到ImageProcessor

私有 函数 renderAsUIImage ( _image  : CIImage ) -> UIImage?{
   if  let cgImage = context.createCGImage(image, from: image.extent) {
     return  UIImage (cgImage: cgImage) 
  } return nil 
}

在这里,您用于context创建CGImagefrom的实例CIImage

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

显示内置滤波器的输出

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

如果 让outputImage = renderAsUIImage(compositeImage) { 
  output = outputImage 
}

这会将compositeImagea转换CIImageUIImageusing renderAsUIImage(_:)。然后将结果保存到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 (
  
  
  “不支持的效果” ) 
  } 
}

在这里,你:

  1. 创建一个方法作为ImageProcessor. 它需要一个实例Painting和一个effect来应用。
  2. 检查有效的图像。
  3. 如果效果是 type .builtIn,则调用applyBuiltInEffect(input:)以应用过滤器。

打开PaintingWall.swift。在下面selectedPainting = paintings[index]action闭包中Button,添加:

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

在这里,您为第一幅画设置了effectto 。.builtIn您还将其设置为默认效果。然后通过调用来应用过滤process(painting:, effect:)ImageProcessor

构建并运行。点击“蒙娜丽莎”。您将在输出中看到一个内置过滤器!

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

认识 CIKernel

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

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

有不同类型的内核:

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

要创建和应用内核,您:

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

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

创建构建规则

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

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

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

  1. 进程设置为名称匹配的源文件: . 然后将*.ci.metal设置为值。
  2. 取消选中Run once per architecture
  3. 添加以下脚本:
xcrun metal -c -fcikernel "${INPUT_FILE_PATH}" \
  -o "${SCRIPT_OUTPUT_FILE_0}"

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

  1. 在输出文件中添加以下内容:
$(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.air
这会生成一个以*.ci.air*结尾的输出二进制文件。

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

按照以下步骤获取第二个新构建规则:

  1. 进程设置为名称匹配的源文件: . 然后将*.ci.air设置为值。
  2. 取消选中Run once per architecture
  3. 添加以下脚本:
xcrun metallib -cikernel "${INPUT_FILE_PATH}" -o "${SCRIPT_OUTPUT_FILE_0}"

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

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

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

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

添加金属源

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

右键单击并选择新建组。将此新组命名为 Filters。然后,突出显示该组并添加一个名为ColorFilterKernel.ci.metal的新金属文件。

打开文件并添加:

// 1 
#包含 <CoreImage/CoreImage.h>
 
 // 2 
extern  "C" { 
  namespace coreimage { // 3 
    float4 colorFilterKernel(sample_ts) { // 4 
      float4 swappedColor; 
      交换颜色.r = sg; 
      swappedColor.g = 某人;
      交换颜色.b = sr; 
      交换颜色.a = sa; 返回交换颜色;
    } 
  } 
}

下面是代码分解:

  1. 包含 Core Image 标头可以让您访问框架提供的类。这会自动包含核心映像金属内核库CIKernelMetalLib.h
  2. 内核需要在一个extern "C"外壳内,以便在运行时通过名称访问它。接下来,您指定coreimage. 您在命名空间中声明所有扩展coreimage以避免与 Metal 冲突。
  3. 在这里,您声明colorFilterKernel,它接受类型的输入sample_tsample_t表示来自输入图像的单个颜色样本。colorFilterKernel返回float4表示像素的 RGBA 值的 a。
  4. 然后,您声明一个新的float4, swappedColor,并交换输入样本中的 RGBA 值。然后,您返回带有交换值的样本。

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

加载内核代码

要加载和应用内核,首先要创建一个CIFilter.

在 Filters 组中创建一个新的 Swift 文件。将其命名为ColorFilter.swift并添加:

// 1
导入CoreImage类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? 数据(contentsOf:url)否则{
      

 
  

  
     
        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 ? {
    守卫让inputImage = inputImage else {返回

       

    

  
     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.metallibCIColorKernel的内容创建一个实例。

  4. 接下来,你override outputImage。在这里,您使用apply(extent:roiCallback:arguments:). 确定有extent多少输入图像传递给内核。

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

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

应用颜色内核过滤器

打开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:). 这需要 aCIImage作为输入。您可以通过创建ColorFilter.

过滤器outputImage应用了颜色内核。然后,您创建一个UIImageusing实例renderAsUIImage(_:)并将其设置为输出。

接下来,.colorKernel如下process(painting:effect:)图处理。在上面添加这个新案例default

案例.colorKernel:
  applyColorKernel(输入:输入)

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

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

案例 1:
  效果= .colorKernel

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

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

做得好!接下来,您将为达芬奇神秘的“ Salvator Mundi ”创建酷炫的经线效果。

创建 Warp 内核

与颜色内核类似,您将从添加 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 ;
      浮动x = dest.coord().x + tan(dest.coord().x/ 10 ) * 20 ;
      返回浮动2(x,y);
    } 
  } 
}

这是您添加的内容:

  1. 就像在颜色内核 Metal 源中一样,您包含 Core Image 标头并将方法封装在一个extern "C"附件中。然后指定coreimage命名空间。

  2. 接下来,您warpFilter(_:)使用 type 的输入参数声明destination,允许访问您当前正在计算的像素的位置。它返回输入图像坐标中的位置,然后您可以将其用作源。

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

    注意:尝试用 in 替换tansinwarpFilter(_:)会得到一个有趣的失真效果!

加载 Warp 内核

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

在 Filters 组中创建一个新的 Swift 文件。将其命名为WarpFilter.swift并添加:

import CoreImage // 1类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? 数据(contentsOf:url)else {
       fatalError


 
     
        ( "无法加载 metallib" ) }
    让内核=试试看?CIWarpKernel ( 
      functionName: "warpFilter" , 
      fromMetalLibraryData: data) else {
       fatalError ( "Unable to create warp kernel" ) 
    } return kernel 
  }() // 3 override var outputImage: CIImage ? {
    守卫让inputImage = inputImage else { return .none }返回

       

    

  
    

     WarpFilter .kernel.apply(
      范围:inputImage.extent,
      roiCallback:{ _,矩形返回矩形
      },图像
        
      :inputImage,
      参数:[])
  } 
}

在这里,你:

  1. 创建WarpFilterCIFilterwithinputImage作为输入参数的子类。
  2. 接下来,您声明静态属性kernel以加载WarpFilterKernel.ci.metallib的内容。然后,您创建一个CIWarpKernel使用.metallib.
  3. 最后,您通过覆盖提供输出outputImage。在override中,您将内核应用于inputImage使用apply(extent:roiCallback:arguments:)并返回结果。

应用变形内核过滤器

打开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应用了扭曲内核。然后,您创建一个UIImageusing实例renderAsUIImage(_:)并将其保存到输出。

接下来,将以下案例添加到process(painting:effect:)以下case .colorKernel

案例.warpKernel:
  applyWarpKernel(输入:输入)

在这里,您处理案例.warpKernel并调用applyWarpKernel(input:)以应用 warp 内核过滤器。

最后,打开PaintingWall.swiftswitch在下面的语句case 1中添加以下案例action

案例 2:
  效果= .warpKernel

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

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

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

挑战:实现混合内核

CIBlendKernel针对混合两个图像进行了优化。作为一个有趣的挑战,为CIBlendKernel. 一些提示:

  1. 创建一个包含CIFilter两个图像的子类:一个输入图像和一个背景图像。
  2. 使用内置的可用 CIBlendKernel内核。对于这个挑战,使用内置的乘法混合内核。
  3. 创建一个方法ImageProcessor,将混合内核过滤器应用于图像并将结果设置为输出。您可以使用项目资产中提供的多色图像作为滤镜的背景图像。此外,处理.blendKernel.
  4. 将此过滤器应用于PaintingWall.swift中的第四张图像。

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

调试核心映像问题

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

使用核心图像快速查看

打开ImageProcessor.swiftoutput在您设置的行上放置一个断点applyColorKernel(input:)。构建并运行。点击“最后的晚餐”

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

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

使用 CI_PRINT_TREE

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

选择并编辑RayVinci方案。选择Run选项卡并将CI_PRINT_TREE添加为值为7 pdf的新环境变量。

CI_PRINT_TREE的值采用graph_type output_type options.

graph_type表示核心图像渲染的阶段。以下是您可以指定的值:

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

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

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

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

输入在底部,输出在顶部。红色节点代表颜色内核,而绿色节点代表扭曲内核。您还将看到每个步骤的投资回报率和范围。

结论

在本教程中,您学习了使用基于 Metal 的 Core Image 内核创建和应用自定义过滤器。

本教程的最终项目资料下载地址
链接:https://pan.baidu.com/s/1bf3_325mYd-B7Cd7qPw3DQ
提取码:51ok

这里也推荐一些面试相关的内容,祝各位网友都能拿到满意offer!
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点

相关文章

网友评论

      本文标题:iOS开发—核心图像教程:自定义过滤器

      本文链接:https://www.haomeiwen.com/subject/jicdyrtx.html