import Foundation import ForgeColor /// Named input transform (camera log -> linear Rec709 working space). public enum InputTransform: String, Sendable, Codable, CaseIterable { case arriLogC4AWG4 case redLog3G10RWG case sonySLog3SGamut3Cine case none public var colorSpace: ColorSpace { switch self { case .arriLogC4AWG4: return .arriLogC4AWG4 case .redLog3G10RWG: return .redLog3G10RWG case .sonySLog3SGamut3Cine: return .sonySLog3SGamut3Cine case .none: return .rec709 } } } /// Named output transform (linear working space -> display). public enum OutputTransform: String, Sendable, Codable, CaseIterable { case rec709Display // 2.4 gamma case none public func apply(_ rgb: SIMD3) -> SIMD3 { switch self { case .none: return rgb case .rec709Display: let g: Float = 1 / 2.4 return SIMD3( pow(max(rgb.x, 0), g), pow(max(rgb.y, 0), g), pow(max(rgb.z, 0), g)) } } } /// One node in a grade stack. public struct GradeNode: Identifiable, Sendable { public enum Kind: Sendable { case inputTransform(InputTransform) case cdl(CDL) case saturation(Float) case curves(CurveSet) case lut3d(Lut3D) case outputTransform(OutputTransform) } public let id: UUID public var kind: Kind public var isEnabled: Bool public init(id: UUID = UUID(), kind: Kind, isEnabled: Bool = true) { self.id = id self.kind = kind self.isEnabled = isEnabled } public func apply(_ rgb: SIMD3) -> SIMD3 { guard isEnabled else { return rgb } switch kind { case .inputTransform(let t): return t.colorSpace.toLinearRec709(rgb) case .cdl(let cdl): return cdl.apply(rgb) case .saturation(let sat): let luma = (rgb * CDL.lumaWeights).sum() return SIMD3(repeating: luma) + (rgb - SIMD3(repeating: luma)) * sat case .curves(let set): return set.apply(rgb) case .lut3d(let lut): return lut.sample(rgb) case .outputTransform(let t): return t.apply(rgb) } } } /// Ordered node stack. Evaluation composes nodes top to bottom. public struct GradeStack: Sendable { public var nodes: [GradeNode] public init(nodes: [GradeNode] = []) { self.nodes = nodes } public func evaluate(_ rgb: SIMD3) -> SIMD3 { var v = rgb for node in nodes { v = node.apply(v) } return v } }