react-native-transparent-video/ios/AlphaFrameFilter.swift

138 lines
4.8 KiB
Swift
Raw Normal View History

//
// ChromaKeyFilter.swift
// MyTransparentVideoExample
//
// Created by Quentin on 27/10/2017.
// Copyright © 2017 Quentin Fasquel. All rights reserved.
//
import CoreImage
typealias AlphaFrameFilterError = AlphaFrameFilter.Error
final class AlphaFrameFilter: CIFilter {
enum Error: Swift.Error {
/// This error is thrown when using renderingMode `builtInFilter` and the filter named *CIBlendWithMask* was not found
case buildInFilterNotFound
/// This error is thrown when `inputImage` and `maskImage` have different **extents**
case incompatibleExtents
/// This error is thrown when a kernel couldn't be initialized,
/// which may happen when using renderingMode `colorKernel` or `metalKernel`
case invalidKernel
/// This error is thrown when `inputImage` or `maskImage` is missing
case invalidParameters
/// This error would be thrown when output image is `nil` in any other case, it typically should not happen
case unknown
}
private(set) var inputImage: CIImage?
private(set) var maskImage: CIImage?
private(set) var outputError: Swift.Error?
private let renderingMode: RenderingMode
required init(renderingMode: RenderingMode) {
self.renderingMode = renderingMode
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var outputImage: CIImage? {
// Output is nil if an input image and a mask image aren't provided
guard let inputImage = inputImage, let maskImage = maskImage else {
outputError = Error.invalidParameters
return nil
}
// Input image & mask image should have the same extent
guard inputImage.extent == maskImage.extent else {
outputError = Error.incompatibleExtents
return nil
}
outputError = nil
return render(using: renderingMode, inputImage: inputImage, maskImage: maskImage)
}
func process(_ inputImage: CIImage, mask maskImage: CIImage) throws -> CIImage {
self.inputImage = inputImage
self.maskImage = maskImage
guard let outputImage = self.outputImage else {
throw outputError ?? Error.unknown
}
return outputImage
}
// MARK: - Rendering
enum RenderingMode {
case builtInFilter
case colorKernel
case metalKernel
}
private static var colorKernel: CIColorKernel? = {
// `init(source:)` was deprecated in iOS 12.0: Core Image Kernel Language API deprecated.
// This warning is silent because of preprocessor macro `CI_SILENCE_GL_DEPRECATION`
return CIColorKernel(source: """
kernel vec4 alphaFrame(__sample s, __sample m) {
return vec4( s.rgb, m.r );
}
""")
}()
private static var metalKernelError: Swift.Error?
private static var metalKernel: CIKernel? = {
do { return try CIKernel(functionName: "alphaFrame") }
catch { metalKernelError = error; return nil }
}()
private func render(using renderingMode: RenderingMode, inputImage: CIImage, maskImage: CIImage) -> CIImage? {
switch renderingMode {
case .builtInFilter:
guard let filter = CIFilter(name: "CIBlendWithMask") else {
outputError = Error.buildInFilterNotFound
return nil
}
let outputExtent = inputImage.extent
let backgroundImage = CIImage(color: .clear).cropped(to: outputExtent)
filter.setValue(backgroundImage, forKey: kCIInputBackgroundImageKey)
filter.setValue(inputImage, forKey: kCIInputImageKey)
filter.setValue(maskImage, forKey: kCIInputMaskImageKey)
return filter.outputImage
case .colorKernel:
// Force a fatal error if our kernel source isn't correct
guard let colorKernel = Self.colorKernel else {
outputError = Error.invalidKernel
return nil
}
// Apply our color kernel with the proper parameters
let outputExtent = inputImage.extent
let arguments = [inputImage, maskImage]
return colorKernel.apply(extent: outputExtent, arguments: arguments)
case .metalKernel:
guard let metalKernel = Self.metalKernel else {
outputError = Self.metalKernelError ?? Error.invalidKernel
return nil
}
let outputExtent = inputImage.extent
let roiCallback: CIKernelROICallback = { _, rect in rect }
let arguments = [inputImage, maskImage]
return metalKernel.apply(extent: outputExtent, roiCallback: roiCallback, arguments: arguments)
}
}
}