#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 ) содержит множество примеров пользовательских фильтров и может быть вам полезен (и это бесплатно!).