Created
November 28, 2023 16:37
-
-
Save stephancasas/ed842fdd793e27d72aebd47c754fe875 to your computer and use it in GitHub Desktop.
Color cube-style CoreImage filter enabling real-time alpha knock-out of solid colors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CIFilter+Knockout.swift | |
// | |
// | |
// Created by Stephan Casas on 10/13/23. | |
// | |
import Foundation | |
import CoreImage; | |
fileprivate let kCIFilterKnockoutThreshold: Float = 0.8; | |
fileprivate var __knockoutBlack: CIFilter? = nil; | |
fileprivate var __knockoutGreen: CIFilter? = nil; | |
public extension CIFilter { | |
// MARK: - Black Knockout | |
/// A color-cube filter which diminishes the alpha value of blacks to 0. | |
static var knockoutBlack: CIFilter { | |
if let cached = __knockoutBlack { | |
return cached; | |
} | |
let _size = 64; | |
let capacity = _size * _size * _size * 4; | |
var cube: [Float] = .init(repeating: 0.0, count: capacity) | |
guard let colorSpace = CGColorSpace( | |
name: CGColorSpace.displayP3 | |
) else { | |
fatalError("Could not acquire P3 color space."); | |
} | |
let size = Float(_size); | |
for _b in 0..<_size { | |
for _g in 0..<_size { | |
for _r in 0..<_size { | |
let r = Float(_r); | |
let g = Float(_g); | |
let b = Float(_b); | |
let i = 4 * ((_b * _size * _size) + (_g * _size) + _r); | |
cube[i + 0] = r / size; | |
cube[i + 1] = g / size; | |
cube[i + 2] = b / size; | |
// knockout darker colors below threshold | |
cube[i + 3] = ( | |
r / size < kCIFilterKnockoutThreshold | |
&& | |
g / size < kCIFilterKnockoutThreshold | |
&& | |
b / size < kCIFilterKnockoutThreshold | |
) ? 0 : 1; | |
} | |
} | |
} | |
guard let ciFilter = CIFilter(name: "CIColorCubeWithColorSpace", parameters: [ | |
"inputCubeData": Data(bytes: &cube, count: capacity * MemoryLayout<Float>.size), | |
"inputCubeDimension": _size, | |
"inputColorSpace": colorSpace | |
]) else { | |
fatalError("Could not create CIFilter for black knockout."); | |
} | |
__knockoutBlack = ciFilter; | |
return ciFilter; | |
} | |
// MARK: - Green Knockout | |
/// A color-cube filter which diminishes the alpha value of greens to 0. | |
static var knockoutGreen: CIFilter { | |
if let cached = __knockoutGreen { | |
return cached; | |
} | |
let _size = 64; | |
let capacity = _size * _size * _size * 4; | |
var cube: [Float] = .init(repeating: 0.0, count: capacity); | |
guard let colorSpace = CGColorSpace( | |
name: CGColorSpace.displayP3 | |
) else { | |
fatalError("Could not acquire P3 color space."); | |
} | |
let size = Float(_size); | |
for _b in 0..<_size { | |
for _g in 0..<_size { | |
for _r in 0..<_size { | |
let r = Float(_r); | |
let g = Float(_g); | |
let b = Float(_b); | |
let i = 4 * ((_b * _size * _size) + (_g * _size) + _r); | |
cube[i + 0] = r / size; | |
cube[i + 1] = g / size; | |
cube[i + 2] = b / size; | |
cube[i + 3] = ( | |
g / size > kCIFilterKnockoutThreshold | |
&& | |
r / size < kCIFilterKnockoutThreshold | |
&& | |
b / size < kCIFilterKnockoutThreshold | |
) ? 0 : 1; | |
} | |
} | |
} | |
guard let ciFilter = CIFilter(name: "CIColorCubeWithColorSpace", parameters: [ | |
"inputCubeData": Data(bytes: &cube, count: capacity * MemoryLayout<Float>.size), | |
"inputCubeDimension": _size, | |
"inputColorSpace": colorSpace | |
]) else { | |
fatalError("Could not create CIFilter for green knockout."); | |
} | |
__knockoutGreen = ciFilter; | |
return ciFilter; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment