问题
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 includeattributes
,setValue(forKey:)
, and most importantly,outputImage
. - Subclass
CIFilterConstructor
, and create aregisterFilter()
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