Смотрите реализацию некоторых фильтров по умолчанию?

#swift #metal #core-image #cifilter #metalkit

Вопрос:

Обновление: Отвечая на основной вопрос в соответствии с @FrankSchlegel — нет, нет способа проверить, как работает система CIFilter .

Можно ли увидеть, как реализованы некоторые CIFilter фильтры по умолчанию, такие как CIDissolveTransition или CISwipeTransition , например? Я хочу создать несколько пользовательских фильтров перехода и, если возможно, хочу посмотреть несколько Metal Shading Language примеров. На самом деле я не могу найти в Интернете примеров переходных фильтров, выполненных в MSL, только в обычном металлическом трубопроводе.

Обновление 1: Вот пример фильтра затухания, который я хочу перенести в 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 FadeTransitionUniformamp; 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);
    }
 

И вот моя неудачная попытка перенести его:

 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);
    
}
 

На данный момент это, похоже, ничего не дает, я думаю, что что-то упускаю, не добавляя constexpr sampler quadSampler эквивалент.

Обновление 2:

Вот как я инициализирую 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])
    }
    
}
 

Вот как я его вызываю:

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

Окончательное Обновление:

Чувствую себя очень глупо, но через пару дней я обнаружил ошибку. В том месте, где я вызываю CIKernel, я использовал filter.outputImage вместо filter.outputImage() этого, таким образом, в первую очередь не было никакого эффекта.

Комментарии:

1. К сожалению, вы не можете ознакомиться с реализацией системных фильтров. Но, может быть, вы можете разместить ссылку на фильтр «обычный металлический трубопровод», который вам нравится переносить, и мы сможем разобраться в этом вместе.

2. Эй, Фрэнк! Спасибо за ваше разъяснение относительно фильтра по умолчанию. Я добавил фильтр, который я хочу перенести в правку. @FrankSchlegel

3. Не могли бы вы также показать, как вы инициализируете CIKernel и как вы его вызываете?

4. @FrankSchlegel готово! Мои другие обычные (непереходные) пользовательские фильтры работают таким образом.

5. Хм, интересно. tween находится между 0.0 и 1.0 ? Каков выходной сигнал фильтра?

Ответ №1:

Хотя вы сами обнаружили ошибку, вот дополнение к вашему решению:

Вместо func outputImage() -> CIImage? { ... } этого вы должны переопределить существующее свойство CIFilter , так как это стандартный способ получения выходных данных фильтра:

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

И еще один совет: для такого типа ядра вы можете использовать a CIColorKernel , так как вам нужно пробовать только отдельные пиксели без необходимости смотреть на их соседей. Тогда ядро будет выглядеть следующим образом:

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

А затем инициализируйте ядро как CIColorKernel вместо CIKernel .

Кстати говоря, вам также не нужно инициализировать новое ядро для каждого экземпляра фильтра, а вместо этого использовать одно статическое ядро. (Это было рекомендовано инженерами Apple, когда я разговаривал с ними во время лаборатории WWDC). Вот как тогда выглядел бы весь фильтр:

 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])
    }

}
 

Комментарии:

1. Большое спасибо! Это выглядит намного более оптимизированным, я буду следовать вашим советам!

2. Единственное, что я получаю сбой, если в sample новом ядре есть ключевое слово. Следует ли его заменить на sample_t ?

3. Ах да, хороший улов! Я исправил свой ответ.

4. Хотя он очень старый, Основной Образ Для Swift ( books.apple.com/gb/book/core-image-for-swift/id1073029980 ) содержит множество примеров пользовательских фильтров и может быть вам полезен (и это бесплатно!).