46 lines
1.6 KiB
Swift
46 lines
1.6 KiB
Swift
import Foundation
|
|
|
|
/// ASC CDL primary correction: out = clamp0(in * slope + offset)^power, then saturation.
|
|
/// Saturation uses Rec709 luma weights per ASC CDL 1.2.
|
|
public struct CDL: Equatable, Sendable {
|
|
public var slope: SIMD3<Float>
|
|
public var offset: SIMD3<Float>
|
|
public var power: SIMD3<Float>
|
|
public var saturation: Float
|
|
|
|
public init(slope: SIMD3<Float>, offset: SIMD3<Float>, power: SIMD3<Float>, saturation: Float) {
|
|
self.slope = slope
|
|
self.offset = offset
|
|
self.power = power
|
|
self.saturation = saturation
|
|
}
|
|
|
|
public static let identity = CDL(slope: .one, offset: .zero, power: .one, saturation: 1)
|
|
|
|
public var isIdentity: Bool { self == .identity }
|
|
|
|
/// Rec709 luma weights (ASC CDL saturation spec).
|
|
static let lumaWeights = SIMD3<Float>(0.2126, 0.7152, 0.0722)
|
|
|
|
public func apply(_ rgb: SIMD3<Float>) -> SIMD3<Float> {
|
|
// SOP, clamped to >=0 before power for determinism (ASC pre-power clamp).
|
|
let sop = rgb * slope + offset
|
|
let zero = SIMD3<Float>.zero
|
|
let clamped = sop.replacing(with: zero, where: sop .< zero)
|
|
var out = SIMD3<Float>(
|
|
pow(clamped.x, power.x),
|
|
pow(clamped.y, power.y),
|
|
pow(clamped.z, power.z))
|
|
|
|
// Saturation about Rec709 luma.
|
|
if saturation != 1 {
|
|
let luma = (out * Self.lumaWeights).sum()
|
|
out = SIMD3(repeating: luma) + (out - SIMD3(repeating: luma)) * saturation
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
extension SIMD3 where Scalar == Float {
|
|
public static var one: SIMD3<Float> { SIMD3(1, 1, 1) }
|
|
}
|