On iOS, can you add multiple CIFilters to a SpriteKit node?

南楼画角 提交于 2019-12-11 07:33:29

问题


On iOS, can you add more than one CIFilter to a SKEffectsNode?

CIFilterGenerator seems like what I want but it isn't available on iOS.

I know you can use multiple filters on an image by passing the output of one as the input of the next, but that's not helpful if you want to affect non-image nodes.

Does this mean I have to create an artificial hierarchy of SKEffectNode and add a filter to each of them, with my actual content at the very bottom? Is there a better way?


回答1:


Where it's difficult or impossible to "chain" together multiple CIFilter calls to achieve the desired effect - maybe due to a class that has a single property, one way to overcome this is to do the following:

  • Subclass CIFilter, overriding everything you need to. This may include attributes, setValue(forKey:), and most importantly, outputImage.
  • Subclass CIFilterConstructor, and create a registerFilter() method.

For example, let's say you wish to combine a gaussian blur and then add a monochrome red tone to an image. At it's most basic you can do this:

class BlurThenColor:CIFilter {

    let blurFilter = CIFilter(name: "CIGaussianBlur")

    override public var attributes: [String : Any] {
        return [
            kCIAttributeFilterDisplayName: "Blur then Color",

            "inputImage": [kCIAttributeIdentity: 0,
                           kCIAttributeClass: "CIImage",
                           kCIAttributeDisplayName: "Image",
                           kCIAttributeType: kCIAttributeTypeImage]
        ]
    }
    override init() {
        super.init()
    }
    @available(*, unavailable) required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override public func setValue(_ value: Any?, forKey key: String) {
        switch key {
        case "inputImage":
            blurFilter?.setValue(inputImage, forKey: "inputImage")
        default:
            break
        }
    }
    override public var  outputImage: CIImage {
        return (blurFilter?.outputImage)! .applyingFilter("CIColorMonochrome", parameters: ["inputColor": CIColor(red: 1.0, green: 0.0, blue: 0.0)])
    }
}

If you wish to expose more attributes, you can simply add them to the attributes and setValue(forKey:) overrides along wist adding variables and setDefaults. Here I'm simply using the defaults.

Now that you've chained your effect together into one custom filter, you can register it and use it:

let CustomFilterCategory = "CustomFilter"

public class CustomFilterConstructor: NSObject, CIFilterConstructor {
    static public func registerFilter() {
        CIFilter.registerName(
            "BlurThenColor",
            constructor: CustomFilterConstructor(),
            classAttributes: [
                kCIAttributeFilterCategories: [CustomFilterCategory]
            ])
    }
    public func filter(withName name: String) -> CIFilter? {
        switch name {
        case "BlurThenColor":
            return BlurThenColor()
        default:
            return nil
        }
    }
}

To use this, just be sure to register the filter (I tend to put mine in AppDelegate if possible):

CustomFilterConstructor.registerFilter()

From there, you can use BlurThenColor just like any other CIFilter. Instantiate it, use setValue, and call outputImage.

Please note, this code will crash because of force-unwrapping of inputImage and/or typos. I'm sure you can make this more safe - but rest assured that I've tested this and it works. (I created this custom filter and replaced it in an app where the force-unwraps don't happen.)




回答2:


Following the useful suggestion by dfd I ended up going with this simple subclass. I'm marking his answer correct because a) he suggested this approach and I want to give him credit, and b) it has more general use info about using CIFilterConstructor to register your filter.

Useful references: - Apple Docs - Related Question - Free Core Image eBook

class MyChainFilter: CIFilter {
    let chainedFilters: [CIFilter]
    @objc dynamic var inputImage: CIImage?

    init(filters: [CIFilter]) {
        self.chainedFilters = filters
        super.init()
    }

    // run filters in order on the specified source image
    override var outputImage: CIImage? {
        get {
            let imageKey = "inputImage"
            var workingImage = self.inputImage
            for filter in chainedFilters {
                assert(filter.inputKeys.contains(imageKey))
                filter.setValue(workingImage, forKey: imageKey)
                guard let result = filter.outputImage else {
                    assertionFailure("filter failed: \(filter.name)")
                    return nil
                }
                workingImage = result
            }
            return workingImage
        }
    }

    required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
}


来源:https://stackoverflow.com/questions/55553869/on-ios-can-you-add-multiple-cifilters-to-a-spritekit-node

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!