Skip to content

Instantly share code, notes, and snippets.

@stephancasas
Created November 28, 2023 16:37
Show Gist options
  • Save stephancasas/ed842fdd793e27d72aebd47c754fe875 to your computer and use it in GitHub Desktop.
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.
//
// 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