Home > Software engineering >  See implementation of some default CIFilter's?
See implementation of some default CIFilter's?

Time:10-08

Update: Answering the main question accoring to @FrankSchlegel - no, there is no way to check how system CIFilter's are working.

Is it possible to see how some of CIFilter default filters are implemented such as CIDissolveTransition or CISwipeTransition for example? I want to build some custom transition filters and want to see some Metal Shading Language examples if possible. Can't really find any examples of transition filters on the Internet done in MSL, only in regular Metal pipeline.

Update 1: Here is an example of a Fade filter I wish to port to MSL:

    #include <metal_stdlib>
    using namespace metal;
    #include "../../Vender/Render/Base/OperationShaderTypes.h"

struct TwoInputVertexIO
{
    float4 position [[position]];
    float2 textureCoordinate [[user(texturecoord)]];
    float2 textureCoordinate2 [[user(texturecoord2)]];
};
    
    typedef struct
    {
        float tweenFactor;
    } FadeTransitionUniform;
    
    fragment half4 fadeTransition(TwoInputVertexIO fragmentInput [[stage_in]],
                                  texture2d<half> inputTexture [[texture(0)]],
                                  texture2d<half> inputTexture2 [[texture(1)]],
                                  constant FadeTransitionUniform& uniform [[ buffer(1) ]])
    {
        
        constexpr sampler quadSampler;
        half4 textureColor = inputTexture.sample(quadSampler, fragmentInput.textureCoordinate);
        half4 textureColor2 = inputTexture2.sample(quadSampler, fragmentInput.textureCoordinate2);
        
        return half4(mix(textureColor.rgb, textureColor2.rgb, textureColor2.a * half(uniform.tweenFactor)), textureColor.a);
    }

And here is my unsuccesful attempt to port it:

float4 fadeTransition(sampler fromTexture, sampler toTexture, float time) {
    
    float2 uv = fromTexture.coord();
    float4 textureColor = fromTexture.sample(uv);
    float4 textureColor2 = toTexture.sample(uv);
    
    return float4(mix(textureColor.rgb, textureColor2.rgb, textureColor2.a * float(time)), textureColor.a);
    
}

This doesn't seem to do anything at the moment, I think Iam missing something by not adding constexpr sampler quadSampler equivalent.

Update 2:

Here is how I initilize the CIKernel:

import CoreImage

class TestTransitionFilter: CIFilter {

    private let kernel: CIKernel
    @objc dynamic var inputImage: CIImage?
    @objc dynamic var inputImage2: CIImage?
    @objc dynamic var inputTime: CGFloat = 0
    
    override init() {
        let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
        let data = try! Data(contentsOf: url)
        kernel = try! CIKernel(functionName: "fadeTransition", fromMetalLibraryData: data)
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func outputImage() -> CIImage? {
        
        guard let inputImage = inputImage else {return nil}
        guard let inputImage2 = inputImage2 else {return nil}
        
        return kernel.apply(extent: inputImage.extent, roiCallback: {
            (index, rect) in
            return rect.insetBy(dx: -1, dy: -1)
        }, arguments: [inputImage, inputImage2, inputTime])
    }
    
}

Here is how I invoke it:

public class TestTransition: NoneTransition {
    
    override public func renderImage(foregroundImage: CIImage, backgroundImage: CIImage, forTweenFactor tween: Float64, renderSize: CGSize) -> CIImage {
        
    
        // This DOES work
        /*
        if let crossDissolveFilter = CIFilter(name: "CIFlashTransition") {
            crossDissolveFilter.setValue(backgroundImage, forKey: "inputImage")
            crossDissolveFilter.setValue(CIVector(x: backgroundImage.extent.width / 2.0, y: backgroundImage.extent.height / 2.0), forKey: "inputCenter")
            crossDissolveFilter.setValue(foregroundImage, forKey: "inputTargetImage")
            crossDissolveFilter.setValue(tween, forKey: "inputTime")
            if let outputImage = crossDissolveFilter.outputImage {
                return outputImage
            }
        }*/
        
        
        // This DOESN'T work
        let filter = TestTransitionFilter()
        filter.inputImage = backgroundImage
        filter.inputImage2 = foregroundImage
        filter.inputTime = CGFloat(tween)
        
        if let outputImage = filter.outputImage {
            return outputImage
        }
        
        return super.renderImage(foregroundImage: foregroundImage, backgroundImage: backgroundImage, forTweenFactor: tween, renderSize: renderSize)
    }
}

Final Update:

Feeling very silly, but after a couple days I found the mistake. In the place where I invoke the CIKernel, I used filter.outputImage instead of filter.outputImage() thus there was no effect in the first place.

CodePudding user response:

Though you found the bug yourself, here is an addition to your solution:

Instead of func outputImage() -> CIImage? { ... } you should override the existing property of CIFilter since it is the standard way of getting a filter's output:

override var outputImage: CIImage? {
    // your computation here
}

And one more tip: For this kind of kernel you can use a CIColorKernel since you only need to sample single pixels without needing to look at their neighbors. The kernel would then look like this:

float4 fadeTransition(sample foreground, sample background, float time) {
    return float4(mix(foreground.rgb, background.rgb, background.a * float(time)), foreground.a);
}

And then initialize the kernel as CIColorKernel instead of CIKernel.

Speaking of which, you also don't need to initialize a new kernel per filter instance but use one static kernel instead. (This was recommended by Apple engineers when I talked to them during a WWDC Lab). This is how the whole filter would look like then:

class TestTransitionFilter: CIFilter {

    @objc dynamic var inputImage: CIImage?
    @objc dynamic var inputImage2: CIImage?
    @objc dynamic var inputTime: CGFloat = 0

    private static var kernel: CIColorKernel {
        let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
        let data = try! Data(contentsOf: url)
        return try! CIColorKernel(functionName: "fadeTransition", fromMetalLibraryData: data)
    }

    override var outputImage: CIImage? {
        guard let inputImage = inputImage, let inputImage2 = inputImage2 else {return nil}
        return Self.kernel.apply(extent: inputImage.extent, arguments: [inputImage, inputImage2, inputTime])
    }

}
  • Related