mirror of
https://github.com/status-im/react-native-transparent-video.git
synced 2025-01-10 06:46:04 +00:00
138 lines
4.8 KiB
Swift
138 lines
4.8 KiB
Swift
|
//
|
||
|
// 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)
|
||
|
}
|
||
|
}
|
||
|
}
|