CIFilterFactory

为内置 CIFilter 类型生成的 Swift 和 Objective-C 类。类型安全、自动补全友好,并且(大部分)带有文档。

支持 macOS、tvOS、iOS 和 Mac Catalyst

Swift Package Manager

原因

我非常喜欢 CIFilter。但我一直觉得 CIFilter 接口存在以下问题:

  1. 由于其不透明的接口(没有代码补全!)而难以使用,并且
  2. 容易犯诸如类型不匹配等基本错误,这些错误在审查中很难发现(例如)。鉴于过滤器的参数是 Any? 值,因此 API 周围没有类型检查。
  3. Xcode 中关于每个过滤器的文档大部分都缺失。
  4. 关于 CIFilter 出现的操作系统版本的信息隐藏在不透明的接口中。

什么是 CIFilterFactory

此包包含为所有内置 CIFilter 类型自动生成的类型安全且带有文档的类包装器。

它还包括用于生成类的脚本,以便在新添加内置 CIFilter 时,可以轻松地(重新)生成这些类以包含新的过滤器。

类型安全 所有过滤器属性都通过类属性公开,尽可能使用类型安全的 Swift/Objective-C 类和类型。
范围钳制 对于定义了有效属性范围的过滤器,当分配的值超出范围时,会产生未定义的行为。这些类会自动将分配的值钳制到有效范围内,以避免无效值可能导致的崩溃。
自动补全 由于过滤器属性作为属性公开,因此 Xcode 可以在您编码时提供自动完成信息。
版本信息 生成的类被 @available() 包裹,以确保只有您的部署目标定义的过滤器可用。
文档 所有类和属性都已记录在案,因此 Xcode 可以提供
• 有意义的内联文档。
• 在 Xcode 快速帮助检查器中有意义的文档。
• 使用 Xcode 13,您可以编译一个 docarchive,并在 Xcode 文档查看器中获得所有过滤器的完整文档

长话短说:给我看点东西!

之前

let filter = CIFilter(name: "CICheckerboardGenerator")!
filter.setValue(CIVector(x: 150.0, y: 150.0), forKey: "inputCenter")
filter.setValue(CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), forKey: "inputColor0")
filter.setValue(CIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0), forKey: "inputColor1")
filter.setValue(80.0, forKey: "inputWidth")
filter.setValue(1.0, forKey: "inputSharpness")
let output = filter.outputImagelet output = filter.outputImage

之后

let filter = CIFF.CheckerboardGenerator()!
filter.center = CGPoint(x: 150.0, y: 150.0)
filter.color0 = CIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
filter.color1 = CIColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
filter.width = 80.0
filter.sharpness = 1.0
let output = filter.outputImage

特性

类型安全和发现

CIFilter 使用字符串常量来定义参数。 功能非常强大且可扩展,但如果不求助于 CIFilter.app 等在线文档,则不太容易被发现。并且忘记 Xcode 代码补全吧。 由于参数值定义为 String: Any?,因此您可能会花费很长时间来调试问题,因为您输入了错误的键字符串或不正确的值类型。

此库提取“软”的基于字符串的参数,例如:-

// compiles, but filter is nil as there's a typo in CIBloom
let filter = CIFilter("CIBLoom")!

// no code completion, no type safety
filter.setValue(11.8, forKey: "inputRadius")

// These compile fine, but result in undefined behaviour during runtime.
filter.setValue(vector, forKey: "inputRadius")
filter.setValue(12.0, forKey: "inputNoodles")

并创建类型安全的参数,例如:-

// No chance of a poorly named filter
guard let filter = CIFF.Bloom() else { fatalError() }

// code completion support, type safe, range safe. Read documentation directly in Xcode
filter.inputRadius = 11.8

// compile time error, invalid type
filter.inputRadius = vector
filter.inputTitle = 12.0

文档

CIFilterFactory 在代码本身中定义了所有可用文档,从而使 Xcode 可以为所有可用过滤器和参数提供有用的快速帮助。

例如:-

过滤器定义

/// Accordion Fold Transition
///
/// Transitions from one image to another of a differing dimensions by unfolding.
///
/// **CIFilter Name**
/// - CIAccordionFoldTransition
///
/// **Availability**
/// - macOS 10.10, iOS 8, tvOS 8
///
/// **Categories**
/// - BuiltIn (*CICategoryBuiltIn*)
/// - HighDynamicRange (*CICategoryHighDynamicRange*)
/// - StillImage (*CICategoryStillImage*)
/// - Transition (*CICategoryTransition*)
/// - Video (*CICategoryVideo*)
///
/// **Documentation Links**
/// - [CIAccordionFoldTransition Online Documentation](http://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIAccordionFoldTransition)
/// - [CoreImage.CIFilterBuiltins Xcode documentation](https://developer.apple.com/documentation/coreimage/ciaccordionfoldtransition)   /// - [CIFilter.app documentation](https://cifilter.app/CIAccordionFoldTransition/)
@available(macOS 10.10, iOS 8, tvOS 8, *)
@objc(CIFFAccordionFoldTransition) class AccordionFoldTransition: Core {

Api 参数定义

/// The width of each bar.
///
/// CIFilter attribute information
/// - Attribute key: `inputWidth`
/// - Internal class: `NSNumber`
/// - Type: `CIAttributeTypeDistance`
/// - Minimum Value: `2.0`
/// - Default Value: `30.0`
@objc public var width: Double {
   ...
}

/// `width` range definition
public static let widthRange = PartialRangeFrom<Double>(2.0)

值范围

许多 API 为输入值定义了最小值和/或最大值。 不幸的是,这些值只能从 Apple 的文档或通过编程接口公开可见。 生成的代码将 min/max 定义嵌入到代码注释中,并为输入值定义钳制范围,以确保正确的行为。 还自动生成每个范围适当参数的范围定义,并使其可用于您的代码以进行验证(如果需要)。

操作系统相关的类型

某些支持的类型(例如仿射变换)根据平台使用不同的类类型(macOS 上的 NSAffineTransform,其他所有平台上的 CGAffineTransform 包装在 NSValue 中)。 此库定义了一个通用的 AffineTransform 类,该类包装了操作系统相关的类型,这意味着您无需为您的代码这样做。

函数式 CIImage 接口

可链式过滤器

每个支持 inputImage 的过滤器都会在 CIImage 上生成一个扩展,以便将过滤器简单地链接到 CIImage 上。 每次调用都会返回原始图像,或者返回一个应用了过滤器的新 CIImage

每个函数调用都有一个 isActive 参数,使您可以轻松地启用/禁用链中的过滤器。

例如,CIBokehBlur 过滤器生成:-

public extension CIImage {
   /// Apply the 'Bokeh Blur' filter to this image and return a new filtered image
   ///
   /// - Parameters:
   ///   - radius: The radius determines how many pixels are used to create the blur. The larger the radius, the blurrier the result. (0.0...500.0)
   ///   - ringAmount: The amount of extra emphasis at the ring of the bokeh. (0.0...1.0)
   ///   - ringSize: The size of extra emphasis at the ring of the bokeh. (0.0...0.2)
   ///   - softness: No Description (0.0...10.0)
   ///   - isActive: If true applies the filter and returns a new image, else returns this image
   /// - Returns: The filtered image, or this image if the filter is not active
   ///
   /// Smooths an image using a disc-shaped convolution kernel.
   ///
   /// **Categories**: Blur, BuiltIn, HighDynamicRange, StillImage, Video
   ///
   /// **Documentation Links**
   /// - [CIBokehBlur Online Documentation](http://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBokehBlur)
   /// - [CoreImage.CIFilterBuiltins Xcode documentation](https://developer.apple.com/documentation/coreimage/ciqrcodegenerator?language=objc)
   /// - [CIFilter.app documentation](https://cifilter.app/CIBokehBlur/)
   @inlinable func applyingBokehBlur(
      radius: Double = CIFF.BokehBlur.radiusDefault,
      ringAmount: Double = CIFF.BokehBlur.ringAmountDefault,
      ringSize: Double = CIFF.BokehBlur.ringSizeDefault,
      softness: Double = CIFF.BokehBlur.softnessDefault,
      isActive: Bool = true
   ) -> CIImage {
      ...
   }
}

然后,您可以简单地链接这些过滤器

let myImage = CIImage(...)
let filtered = myImage
   .applyingBokehBlur(radius: 100)
   .applyingSepiaTone()

生成过滤器

对于像 CIQRCodeGenerator 这样的生成过滤器(这些过滤器没有显式的 inputImage),会生成一个静态创建函数

extension CIImage {
   /// Create a new CIImage using the 'QR Code Generator' filter
   ///
   /// - Parameters:
   ///   - message: The message to encode in the QR Code
   ///   - correctionLevel: QR Code correction level L, M, Q, or H.
   /// - Returns: A new image by running the filter, or nil if the image could not be created
   ///
   /// Generate a QR Code image for message data.
   ///
   /// **Categories**: BuiltIn, Generator, StillImage
   ///
   /// **Documentation Links**
   /// - [CIQRCodeGenerator Online Documentation](http://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIQRCodeGenerator)
   /// - [CoreImage.CIFilterBuiltins Xcode documentation](https://developer.apple.com/documentation/coreimage/ciqrcodegenerator?language=objc)
   /// - [CIFilter.app documentation](https://cifilter.app/CIQRCodeGenerator/)
   @inlinable static func createUsingQRCodeGenerator(
      message: Data,
      correctionLevel: String
   ) -> CIImage? {
      return CIFF.QRCodeGenerator(
         message: message,
         correctionLevel: correctionLevel
      )?.outputImage
   }
}

原理

CIFilter 提供了一个非常棒的编程发现接口,您可以在其中询问它注册了哪些过滤器、特定过滤器接受的参数以及所有类型的有用信息,例如本地化的参数描述等。非常酷,但当您只想创建一个棕褐色滤镜时,也有点无用。

CIFilterFactory 是使用 swift 脚本文件自动生成的,该脚本文件枚举系统上可用的过滤器,并为每个过滤器构建类型安全的类结构。它还使用发现 api 来记录生成的代码,这意味着 Xcode 能够完全记录每个创建的类。不仅如此,CIFilter 还定义了它支持的操作系统(macOS、tvOS、iOS)以及过滤器何时可用,并且生成的代码通过将生成的类包装在 #available 调用中来利用这一点。

使用 CIFilterFactory,Xcode 现在可以:-

酷!

示例

Swift 示例

使用 CIFilter 方法

guard let bloomFilter = CIFilter(name: "CIBloom") else { fatalError() }
bloomFilter.setValue(inputImage, forKey: kCIInputImageKey)
bloomFilter.setValue(0.3, forKey: kCIInputIntensityKey)
bloomFilter.setValue(5, forKey: kCIInputRadiusKey)
let outputImage = bloomFilter.outputImage

使用 CIFilterFactory

guard let bloomFilter = CIFF.Bloom() else { fatalError() }
bloomFilter.inputImage = image
bloomFilter.intensity = 0.3
bloomFilter.radius = 5
let outputImage = bloomFilter.outputImage

使用生成的接口提供:-

  1. 避免因不正确的参数字符串标识符而造成的错误
  2. 代码补全
  3. 类型检查
  4. 自动范围验证
  5. 在以下内容中自动生成富有表现力的 Xcode 文档

Objective-C 示例

id appimage = [NSImage imageNamed:NSImageNameApplicationIcon];
id bir = [[NSBitmapImageRep alloc] initWithData:[appimage TIFFRepresentation]];
id image = [[CIImage alloc] initWithBitmapImageRep:bir];

CIFFBloom* filter = [[CIFFBloom alloc] init];
[filter setInputImage: image];
[filter setRadius: 10];
[filter setIntensity: 4];
CIImage* output = [filter outputImage];
assert(output != nil);

那么 import CoreImage.CIFilterBuiltins 呢?

在较新版本的 Xcode 中,SDK 现在附带了一组预生成的 CIFilter 类(例如 CIFilter.pixellate())。 该库是在 CIFilterBuiltins 可用之前编写的,并且还需要支持 Objective-C(诚然,随着时间的推移,这个问题越来越少)。

用法

使用 Swift Package Manager。

您可以在 Demo 子文件夹中找到一些针对 Swift 和 Objective-C 的非常简单的示例。

重新生成 CIFilterFactory

注意:这仅适用于 Xcode 13 及更高版本

tools/generator 文件夹中有一个项目,名为 ciff_generator。 在 XCode 13 中打开此项目并构建/运行该项目。

此外,tools 目录中还有一个脚本,它将重建过滤器并更新 Xcode 文档。

  1. 打开终端并导航到 CIFilterFactory/tools 文件夹
  2. 运行 sh regenerate.sh

生成 docarchive 文件

注意:这仅适用于 Xcode 13 及更高版本

  1. 打开终端并导航到 CIFilterFactory/tools 文件夹
  2. 运行 sh make-docs.sh

许可

MIT License

Copyright (c) 2024 Darren Ford

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.