Creating Chroma Key Compositing with Core Image Kernels in Palmier Pro
Palmier Pro implements chroma key compositing by compiling a Metal shader into a Core Image kernel that removes green or blue backgrounds with adjustable tolerance, softness, and spill suppression parameters.
Palmier Pro implements all image effects—including chroma key (green/blue screen) removal—as Core Image kernels compiled from Metal source files. The pipeline relies on Metal/ChromaKey.metal compiled into a .metallib resource, loaded at runtime by CIKernelLoader, and exposed to the UI through EffectRegistry with four tweakable parameters.
How the Chroma Key Kernel Loads at Runtime
The underlying GPU code lives in Metal/ChromaKey.metal. The CIKernelLoader utility in Sources/PalmierPro/Compositing/Kernels/CIKernelLoader.swift reads the compiled library and instantiates a CIColorKernel using the bundled resource:
// CIKernelLoader.swift
enum CIKernelLoader {
private static func data(_ lib: String) -> Data? {
BundledResource.url("\(lib).metallib").flatMap { try? Data(contentsOf: $0) }
}
static func colorKernel(_ lib: String, _ function: String) -> CIColorKernel? {
data(lib).flatMap { try? CIColorKernel(functionName: function, fromMetalLibraryData: $0) }
}
}
The chroma key kernel is instantiated privately within the wrapper by calling CIKernelLoader.colorKernel("ChromaKey", "chromaKey").
Bridging Swift to Metal with ChromaKeyKernel
ChromaKeyKernel.swift provides a type-safe Swift interface in Sources/PalmierPro/Compositing/Kernels/ChromaKeyKernel.swift. The apply(_:keyHue:tolerance:softness:spill:) method validates that tolerance is greater than zero and forwards arguments to the GPU:
// ChromaKeyKernel.swift
enum ChromaKeyKernel {
private static let kernel = CIKernelLoader.colorKernel("ChromaKey", "chromaKey")
static func apply(_ image: CIImage,
keyHue: Double,
tolerance: Double,
softness: Double,
spill: Double) -> CIImage {
guard let kernel, tolerance > 0 else { return image }
return kernel.apply(extent: image.extent,
arguments: [image,
Float(keyHue),
Float(tolerance),
Float(softness),
Float(spill)]) ?? image
}
}
Because the kernel operates on a CIImage, it integrates seamlessly with other Core Image filters in the chain and benefits from GPU acceleration.
Registering the Effect in EffectRegistry
The effect is declared in Sources/PalmierPro/Compositing/EffectRegistry.swift as an EffectDescriptor that defines four controllable parameters and points to the wrapper above:
// EffectRegistry.swift (excerpt)
private static let key: [EffectDescriptor] = [
EffectDescriptor(
id: "key.chroma",
displayName: "Chroma Key",
category: "Key",
params: [
EffectParamSpec(key: "keyHue", label: "Key Hue", range: 0...1, defaultValue: 0.333, unit: ""),
EffectParamSpec(key: "tolerance",label: "Tolerance", range: 0...1, defaultValue: 0, unit: ""),
EffectParamSpec(key: "softness", label: "Softness", range: 0...1, defaultValue: 0.1, unit: ""),
EffectParamSpec(key: "spill", label: "Spill", range: 0...1, defaultValue: 0.5, unit: "")
],
apply: { image, p, _ in
ChromaKeyKernel.apply(image,
keyHue: p.value("keyHue"),
tolerance: p.value("tolerance"),
softness: p.value("softness"),
spill: p.value("spill"))
})
]
The descriptor is reachable via EffectRegistry.descriptor(id:), allowing the inspector to display sliders for key hue, tolerance, softness, and spill.
Sampling Key Hues from Video Frames
When users click the preview to pick a background color, ChromaKeySamplerOverlayView in Sources/PalmierPro/Preview/ChromaKeySamplerOverlayView.swift converts screen coordinates to normalized space and delegates to VideoEngine.sampleKeyHue(at:frame:):
// VideoEngine.swift – sampling
func sampleKeyHue(at normalizedPoint: CGPoint, frame: Int? = nil) async -> Double? {
guard let item = player.currentItem else { return nil }
let time = frame.flatMap(playerTime(forPreviewFrame:)) ?? player.currentTime()
let generator = AVAssetImageGenerator(asset: item.asset)
generator.videoComposition = item.videoComposition
guard let cg = try? await generator.image(at: time).image else { return nil }
return Self.sampleKeyHue(from: cg, at: normalizedPoint)
}
The engine extracts a 9×9 pixel patch around the click point, computes the average color, converts it to HSV, and returns the hue if saturation is sufficient. The overlay view then commits the value to the editor model.
Practical Implementation Examples
Applying the Kernel to a Static Image
You can invoke the kernel directly on a CIImage without the editor timeline:
import CoreImage
import UIKit // or AppKit for macOS
let ciImage = CIImage(contentsOf: URL(fileURLWithPath: "myGreenScreen.png"))!
let result = ChromaKeyKernel.apply(
ciImage,
keyHue: 0.333, // green ≈ 1/3
tolerance: 0.12,
softness: 0.1,
spill: 0.4
)
let context = CIContext()
let cgImage = context.createCGImage(result, from: result.extent)!
let uiImage = UIImage(cgImage: cgImage) // macOS → NSImage
Adding the Effect to a Timeline Clip
Within the Palmier Pro editor, add the effect via the registry:
let descriptor = EffectRegistry.descriptor(id: "key.chroma")!
let effect = descriptor.makeEffect() // Creates Effect with default params
editor.activeClip?.addEffect(effect) // Triggers model update and rebuild
Triggering the Hue Sampler
Activate the color picker by setting the sampling clip ID:
editor.chromaKeySamplingClipId = clip.id
// The UI now shows a cross-hair cursor; clicking runs the sampling code
// from ChromaKeySamplerOverlayView.swift and commits via editor.commitChromaKeySample(hue:clipId:)
Summary
- Metal Source: The algorithm lives in
Metal/ChromaKey.metaland ships as a.metallibresource. - Runtime Loading:
CIKernelLoader.colorKernel(_:_:)inCIKernelLoader.swiftinstantiates theCIColorKernel. - Swift Wrapper:
ChromaKeyKernel.applyvalidates inputs and binds the four parameters (keyHue, tolerance, softness, spill). - UI Registration:
EffectRegistryexposes the effect as"key.chroma"with sliders for real-time adjustment. - Color Sampling:
VideoEngine.sampleKeyHueextracts average hues from 9×9 pixel patches to set the key color automatically.
Frequently Asked Questions
What parameters control the chroma key matte in Palmier Pro?
The effect exposes four parameters defined in EffectRegistry.swift: keyHue (the target color to remove, default 0.333 for green), tolerance (width of the color range to key out), softness (feathering of the matte edge), and spill (suppression of color fringing on foreground edges). All values are normalized from 0 to 1.
How does Palmier Pro sample the key color from a video frame?
When the user activates the sampler and clicks the preview, ChromaKeySamplerOverlayView calculates normalized coordinates and calls VideoEngine.sampleKeyHue(at:frame:). This method uses AVAssetImageGenerator to extract a frame, then analyzes a 9×9 pixel region to compute the average hue, returning it asynchronously to the UI.
Can the chroma key kernel be used outside the Palmier Pro editor?
Yes. The ChromaKeyKernel.apply(_:keyHue:tolerance:softness:spill:) method in Sources/PalmierPro/Compositing/Kernels/ChromaKeyKernel.swift is a pure function that accepts any CIImage and returns a processed CIImage. You can import the module and apply it directly to static images or custom video pipelines without involving the EffectRegistry or timeline system.
Where is the compiled chroma key shader stored?
The compiled GPU code resides in ChromaKey.metallib, which is bundled as a resource in the application package. At runtime, CIKernelLoader reads this file via BundledResource.url(_:) and passes the raw data to CIColorKernel(functionName:fromMetalLibraryData:) to create the executable kernel object.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →