iOS
CoreImage
Swift

iOSのビルドインCIFilterのコードを自動生成する

More than 1 year has passed since last update.

CIFilter

CIFilterを使うときは基本的に文字列でフィルタや入力などを指定する必要があります。

これを少し自動化してみました。

成果物

こういう感じで、全てのフィルタについて生成されます。

    /// [CILanczosScaleTransform](http://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CILanczosScaleTransform)
    ///
    /// - parameter inputImage: The image to use as an input image. For filters that also use a background image, this is the foreground image.
    /// - parameter inputScale: The scaling factor to use on the image. Values less than 1.0 scale down the images. Values greater than 1.0 scale up the image. defaultValue = 1.
    /// - parameter inputAspectRatio: The additional horizontal scaling factor to use on the image. defaultValue = 1.
    ///
    /// - returns: Generated CIFilter (you can get result with ["outputImage"])
    @available(iOS 6, *)
    static func lanczosScaleTransform(inputImage: CIImage, inputScale: NSNumber = 1, inputAspectRatio: NSNumber = 1) -> CIFilter? {
        guard let filter = CIFilter(name: "CILanczosScaleTransform") else {
            return nil
        }
        filter.setDefaults()
        filter.setValue(inputImage, forKey: "inputImage")
        filter.setValue(inputScale, forKey: "inputScale")
        filter.setValue(inputAspectRatio, forKey: "inputAspectRatio")
        return filter
    }

全体は以下のGistを参照ください

https://gist.github.com/ha1f/de0e7a23a79444105c4b13e6c0dc7fa1

生成方法

CIFilterの一覧

ビルドインのCIFilterは

CIFilter.filterNames(inCategory: kCICategoryBuiltIn)

で取得できます。

(参考:Core Image入門(objc.io Issue 21: Camera and Photos "An Introduction to Core Image"の翻訳)

Attributes

CIFilterのattributesからいろいろなデータを取得できるので、それから雑にコードを生成しました。

実装

extension CIFilter {
    var displayName: String? {
        return self.attributes[kCIAttributeDisplayName] as? String
    }

    var filterName: String? {
        return self.attributes[kCIAttributeFilterName] as? String
    }

    var referenceDocumentationUrl: URL? {
        return self.attributes[kCIAttributeReferenceDocumentation] as? URL
    }

    var availableIos: String? {
        return self.attributes[kCIAttributeFilterAvailable_iOS] as? String
    }

    private func parameterInformation(forInputKey inputKey: String) -> [String: Any] {
        return (self.attributes[inputKey] as? [String: Any]) ?? [:]
    }

    static func generateCode() {
        let printer = CodePrinter()
        printer.print("import Foundation")
        printer.print("import CoreImage")
        printer.print("import AVFoundation")
        printer.print("")
        printer.print("extension CIFilter {")
        printer.shiftRight()
        CIFilter.filterNames(inCategory: kCICategoryBuiltIn)
            .forEach { filterName in
                guard let filter = CIFilter(name: filterName) else {
                    return
                }
                let inputs: [String] = filter.inputKeys.map { inputKey in
                    let information = filter.parameterInformation(forInputKey: inputKey)
                    let type = (information[kCIAttributeClass].map { "\($0)" } ?? "")
                    let defaultValue = information[kCIAttributeDefault]
                    if let value = defaultValue as? NSNumber {
                        return "\(inputKey): \(type) = \(value)"
                    } else if let value = defaultValue as? NSString {
                        return "\(inputKey): \(type) = \"\(value)\""
                    } else {
                        return "\(inputKey): \(type)"
                    }
                }
                let filterDisplayName = filter.displayName ?? filterName
                printer.print("")
                if let url = filter.referenceDocumentationUrl {
                    // I don't know why, but SeeAlso does not work on my Xcode.
                    // printer.print("/// \(filterDisplayName)")
                    // printer.print("/// - SeeAlso: [Reference/\(filterDisplayName)](\(url))")
                    printer.print("/// [\(filterDisplayName)](\(url))")
                } else {
                    printer.print("/// \(filterDisplayName)")
                }
                printer.print("/// ")
                filter.inputKeys.forEach { inputKey in
                    let information = filter.parameterInformation(forInputKey: inputKey)
                    let description = (information[kCIAttributeDescription] as? String) ?? ""
                    if let defaultValue = information[kCIAttributeDefault] {
                        printer.print("/// - parameter \(inputKey): \(description) defaultValue = \(defaultValue).")
                    } else {
                        printer.print("/// - parameter \(inputKey): \(description)")
                    }
                }
                printer.print("/// ")
                printer.print("/// - returns: Generated CIFilter (you can get result with \(filter.outputKeys))")
                if let availableIos = filter.availableIos {
                    printer.print("@available(iOS \(availableIos), *)")
                }

                // 関数名はfilterNameからCIを除いて、lowerCamelCaseにしたもの
                var methodName = filterName.hasPrefix("CI") ? String(filterName.dropFirst(2)) : filterName
                let initialString = methodName.removeFirst()
                let functionName =  String(initialString).lowercased() + methodName

                printer.print("static func \(functionName)(\(inputs.joined(separator: ", "))) -> CIFilter? {")
                printer.withShiftedRight {
                    let filterVariableName = "filter"
                    printer.print("guard let \(filterVariableName) = CIFilter(name: \"\(filterName)\") else {")
                    printer.withShiftedRight {
                        printer.print("return nil")
                    }
                    printer.print("}")
                    printer.print("\(filterVariableName).setDefaults()")
                    filter.inputKeys.forEach { inputKey in
                        printer.print("\(filterVariableName).setValue(\(inputKey), forKey: \"\(inputKey)\")")
                    }
                    printer.print("return \(filterVariableName)")
                }
                printer.print("}")
        }
        printer.shiftLeft()
        printer.print("}")
        printer.commitPrint()
    }
}

AVFoundationは、depthBlurEffectに必要なのでインポートさせていますが、それさえ使わなければ不要です。

雑なCodePrinterは以下です

https://github.com/ha1f/CoreImageSample/blob/master/CoreImageSample/Common/CodePrinter/CodePrinter.swift

所感

全部使うこともないだろうし、必要な部分だけコピペして、初期値などを必要に応じて書き換えると、少し楽になるんじゃないかと思う。