在 CALayer 上使用自定义 CIFilter 显示 CALayer 没有变化

Using custom CIFilter on CALayer shows no change to CALayer

我们正在尝试创建一个自定义 CIFilter 以添加到我们的 CALayer's. 之上 但是似乎只有默认的 CIFilters 在 CALayer.

上工作

我们在 ViewController.swift 上创建了一个新的小项目,我们添加了:

import Cocoa
import CoreImage

class ViewController: NSViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Create some layers to work with! (square with gradient color)
        let mainLayer = CALayer()
        let shapeLayer = CAShapeLayer()
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [NSColor.red.cgColor, NSColor.white.cgColor, NSColor.yellow.cgColor, NSColor.black.cgColor]
        
        shapeLayer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 500, height: 500), transform: nil)
        shapeLayer.fillColor = CGColor.black
        
        gradientLayer.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
        gradientLayer.mask = shapeLayer
        
        gradientLayer.setAffineTransform(CGAffineTransform(translationX: 50, y: 50))
        mainLayer.addSublayer(gradientLayer)
        
        mainLayer.filters = []
        self.view.layer?.addSublayer(mainLayer)

        // Register the custom filter
        CustomFilterRegister.register()
        
        // Test with a normal image file, WORKS!
//      if let image = NSImage(named: "test"), let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
//          if let filter = CIFilter(name: "CustomFilter") {
//              filter.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
//              let output = filter.outputImage
//              // WORKS! Image filtered as expected!
//          }
//      }
        
        // Does NOT work. No change in color of the layer!
        if let filter = CIFilter(name: "CustomFilter") {
            filter.name = "custom"
            mainLayer.filters?.append(filter)
        }

        // This works: mainLayer and sublayers are blurred!
//      if let filter = CIFilter(name: "CIGaussianBlur") {
//          filter.name = "blur"
//          mainLayer.filters?.append(filter)
//      }


    }
}
}

我们创建了一个简单的自定义项 CIFilter,以便在我们开始构建自定义项之前先尝试一下 CIFilter.

class CustomFilter: CIFilter {
    
    // Error in xcode if you don't add this in!
    override class var supportsSecureCoding: Bool {
        return true
    }
        
    @objc dynamic var inputImage: CIImage?
    @objc dynamic var inputSaturation: CGFloat = 1
    @objc dynamic var inputBrightness: CGFloat = 0
    @objc dynamic var inputContrast: CGFloat = 1
    override func setDefaults() {
        inputSaturation = 1
        inputBrightness = 0
        inputContrast = 2
    }
    
    override public var outputImage: CIImage? {
        guard let image = inputImage else {
            return nil
        }
        return image.applyingFilter("CIPhotoEffectProcess")
            .applyingFilter("CIColorControls", parameters: [
                kCIInputSaturationKey: inputSaturation,
                kCIInputBrightnessKey: inputBrightness,
                kCIInputContrastKey: inputContrast
            ])
    }
}

class CustomFilterRegister: CIFilterConstructor {
    static func register() {
        CIFilter.registerName(
            "CustomFilter", constructor: CustomFilterRegister(),
            classAttributes: [
                kCIAttributeFilterCategories: [kCICategoryBlur, kCICategoryVideo, kCICategoryStillImage]
            ])
    }
    func filter(withName name: String) -> CIFilter? {
        switch name {
        case "CustomFilter":
            return CustomFilter()
        default:
            return nil
        }
    }
}

在 ViewController 中,我们添加了代码以使用普通图像进行测试。这确实有效,所以过滤器似乎没问题。我们还尝试了默认的 CIGaussianBlur,它确实适用于 CALayer.

我们不知道需要什么才能让自定义 CIFilterCALayer, 一起工作,似乎找不到任何相关信息。

请注意,我们不是在寻找这种类型的 CIFilter 或其他获取过滤器结果的方法。我们需要自定义 CIFilter 来处理 CALayer.

我假设您在代码中未显示的地方完成了此操作:

self.view.wantsLayer = true

但你也想这样做:

self.view.layerUsesCoreImageFilters = true
    

结果没有那一行:

结果那一行:

(不要问我为什么“CIGaussianBlur”仍然有效...)


编辑 - 我用来生成上述输出的确切代码:

import CoreImage

class ViewController: NSViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.view.wantsLayer = true
        self.view.layerUsesCoreImageFilters = true
        
        // Create some layers to work with! (square with gradient color)
        let mainLayer = CALayer()
        let shapeLayer = CAShapeLayer()
        let gradientLayer = CAGradientLayer()
        gradientLayer.colors = [NSColor.red.cgColor, NSColor.white.cgColor, NSColor.yellow.cgColor, NSColor.black.cgColor]
        
        shapeLayer.path = CGPath(rect: CGRect(x: 0, y: 0, width: 500, height: 500), transform: nil)
        shapeLayer.fillColor = CGColor.black
        
        gradientLayer.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
        gradientLayer.mask = shapeLayer
        
        gradientLayer.setAffineTransform(CGAffineTransform(translationX: 50, y: 50))
        mainLayer.addSublayer(gradientLayer)
        
        mainLayer.filters = []
        self.view.layer?.addSublayer(mainLayer)

        // Register the custom filter
        CustomFilterRegister.register()
        
        let t = 2
        
        if t == 1 {
            // Test with a normal image file, WORKS!
            if let image = NSImage(named: "test"), let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) {
                if let filter = CIFilter(name: "CustomFilter") {
                    filter.setValue(CIImage(cgImage: cgImage), forKey: kCIInputImageKey)
                    let output = filter.outputImage
                    // WORKS! Image filtered as expected!
                    print()
                }
            }
        }
        
        else if t == 2 {
            // 
            // Does NOT work. No change in color of the layer!
            // 
            // This NOW works for me
            // 
            if let filter = CIFilter(name: "CustomFilter") {
                filter.name = "custom"
                mainLayer.filters?.append(filter)
            }
            
        } else {
            
            // This works: mainLayer and sublayers are blurred!
            if let filter = CIFilter(name: "CIGaussianBlur") {
                filter.name = "blur"
                mainLayer.filters?.append(filter)
            }
            
        }
        
    }
}

class CustomFilter: CIFilter {
    
    // Error in xcode if you don't add this in!
    override class var supportsSecureCoding: Bool {
        return true
    }
    
    @objc dynamic var inputImage: CIImage?
    @objc dynamic var inputSaturation: CGFloat = 1
    @objc dynamic var inputBrightness: CGFloat = 0
    @objc dynamic var inputContrast: CGFloat = 1
    override func setDefaults() {
        inputSaturation = 1
        inputBrightness = 0
        inputContrast = 2
    }
    
    override public var outputImage: CIImage? {
        guard let image = inputImage else {
            return nil
        }
        return image.applyingFilter("CIPhotoEffectProcess")
            .applyingFilter("CIColorControls", parameters: [
                kCIInputSaturationKey: inputSaturation,
                kCIInputBrightnessKey: inputBrightness,
                kCIInputContrastKey: inputContrast
            ])
    }
}

class CustomFilterRegister: CIFilterConstructor {
    static func register() {
        CIFilter.registerName(
            "CustomFilter", constructor: CustomFilterRegister(),
            classAttributes: [
                kCIAttributeFilterCategories: [kCICategoryBlur, kCICategoryVideo, kCICategoryStillImage]
            ])
    }
    func filter(withName name: String) -> CIFilter? {
        switch name {
        case "CustomFilter":
            return CustomFilter()
        default:
            return nil
        }
    }
}

编辑

奇怪的是,如果我像这样添加一个计数器变量和 print() 语句:

var applyCount: Int = 0

override public var outputImage: CIImage? {
    print("getting outputImage...")
    guard let image = inputImage else {
        return nil
    }
    applyCount += 1
    print("apply", applyCount)
    return image.applyingFilter("CIPhotoEffectProcess")
        .applyingFilter("CIColorControls", parameters: [
            kCIInputSaturationKey: inputSaturation,
            kCIInputBrightnessKey: inputBrightness,
            kCIInputContrastKey: inputContrast
        ])
}

在 macOS 10.15.4 / Xcode 12.4 上,我在调试控制台中得到了这个:

getting outputImage...
apply 1
getting outputImage...
apply 2
getting outputImage...
apply 3
getting outputImage...
apply 4
getting outputImage...
apply 5
getting outputImage...
apply 6
getting outputImage...
apply 7
getting outputImage...
apply 8
getting outputImage...
apply 9
getting outputImage...
apply 10
getting outputImage...
apply 11
getting outputImage...
apply 12
getting outputImage...
apply 13
getting outputImage...
apply 14
getting outputImage...
apply 15
getting outputImage...
apply 16

(例如,当 window 改变大小时,它会继续被重复调用)。

但是,运行 在 macOS 11.4 / Xcode 12.5.1 上,我得到 nothing调试控制台... outputImage 从未被请求?

正如@DonMag 指出的那样,它应该适用于他描述的更改。不幸的是,我们今天收到了 Apple 的回复;

此时,有一个错误阻止 CALayer 上的自定义 CIFilters 工作。目前没有针对此错误的解决方法。

当我们提交错误时,我会在此处为感兴趣的人添加 link。但是目前在 macOS 11 上还不能给 CALayer 添加自定义的 CIFilter。

希望他们能为阅读本文的所有人解决此问题以寻求解决方案。


编辑:

好坏消息...目前在 macOS 12.2.1 上,它仍然有同样的问题,根据我们的票没有任何反应。 Apple 似乎不想解决这个问题。对于那些正在寻找的人:这仍然不适用于 CALayer 即使所有选项都像其他答案中描述的那样。内置 CIFilter 按预期工作。

请注意,使用 AVVideoCompositionCoreAnimationToolCALayer 上使用相同的自定义 CIFilter 确实有效!