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.)