83 lines
3.5 KiB
Swift
83 lines
3.5 KiB
Swift
import Foundation
|
|
|
|
/// 3x3 RGB->RGB gamut conversion matrix (row-major rows).
|
|
public struct GamutMatrix: Equatable, Sendable {
|
|
public var rows: (SIMD3<Float>, SIMD3<Float>, SIMD3<Float>)
|
|
|
|
public init(_ r0: SIMD3<Float>, _ r1: SIMD3<Float>, _ r2: SIMD3<Float>) {
|
|
rows = (r0, r1, r2)
|
|
}
|
|
|
|
public static func == (l: GamutMatrix, r: GamutMatrix) -> Bool {
|
|
l.rows.0 == r.rows.0 && l.rows.1 == r.rows.1 && l.rows.2 == r.rows.2
|
|
}
|
|
|
|
public func apply(_ v: SIMD3<Float>) -> SIMD3<Float> {
|
|
SIMD3(
|
|
(rows.0 * v).sum(),
|
|
(rows.1 * v).sum(),
|
|
(rows.2 * v).sum())
|
|
}
|
|
|
|
public func inverted() -> GamutMatrix {
|
|
let (a, b, c) = (rows.0.x, rows.0.y, rows.0.z)
|
|
let (d, e, f) = (rows.1.x, rows.1.y, rows.1.z)
|
|
let (g, h, i) = (rows.2.x, rows.2.y, rows.2.z)
|
|
let det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
|
|
let inv = 1 / det
|
|
return GamutMatrix(
|
|
SIMD3((e * i - f * h) * inv, (c * h - b * i) * inv, (b * f - c * e) * inv),
|
|
SIMD3((f * g - d * i) * inv, (a * i - c * g) * inv, (c * d - a * f) * inv),
|
|
SIMD3((d * h - e * g) * inv, (b * g - a * h) * inv, (a * e - b * d) * inv))
|
|
}
|
|
|
|
public static let identity = GamutMatrix(SIMD3(1, 0, 0), SIMD3(0, 1, 0), SIMD3(0, 0, 1))
|
|
|
|
/// ARRI Wide Gamut 4 -> Rec709 (D65).
|
|
public static let awg4ToRec709 = GamutMatrix(
|
|
SIMD3(1.7245, -0.6421, -0.0824),
|
|
SIMD3(-0.1421, 1.1797, -0.0376),
|
|
SIMD3(-0.0292, -0.3563, 1.3855))
|
|
|
|
/// REDWideGamutRGB -> Rec709 (D65).
|
|
public static let rwgToRec709 = GamutMatrix(
|
|
SIMD3(1.66225, -0.588228, -0.0740245),
|
|
SIMD3(-0.124545, 1.13307, -0.00852952),
|
|
SIMD3(-0.0433403, -0.253201, 1.29654))
|
|
|
|
/// Sony S-Gamut3.Cine -> Rec709 (D65).
|
|
public static let sGamut3CineToRec709 = GamutMatrix(
|
|
SIMD3(1.6410, -0.3245, -0.3165),
|
|
SIMD3(-0.6698, 1.6175, 0.0522),
|
|
SIMD3(0.0421, -0.1567, 1.1146))
|
|
}
|
|
|
|
/// Camera color space: transfer curve + gamut, with conversion to linear Rec709 working space.
|
|
public struct ColorSpace: Equatable, Sendable {
|
|
public var name: String
|
|
public var transfer: TransferFunction
|
|
public var gamutToRec709: GamutMatrix
|
|
|
|
public init(name: String, transfer: TransferFunction, gamutToRec709: GamutMatrix) {
|
|
self.name = name
|
|
self.transfer = transfer
|
|
self.gamutToRec709 = gamutToRec709
|
|
}
|
|
|
|
/// Camera-log-encoded RGB -> linear Rec709.
|
|
public func toLinearRec709(_ encoded: SIMD3<Float>) -> SIMD3<Float> {
|
|
let lin = SIMD3(transfer.decode(encoded.x), transfer.decode(encoded.y), transfer.decode(encoded.z))
|
|
return gamutToRec709.apply(lin)
|
|
}
|
|
|
|
/// Linear Rec709 -> camera-log-encoded RGB.
|
|
public func fromLinearRec709(_ linear709: SIMD3<Float>) -> SIMD3<Float> {
|
|
let lin = gamutToRec709.inverted().apply(linear709)
|
|
return SIMD3(transfer.encode(lin.x), transfer.encode(lin.y), transfer.encode(lin.z))
|
|
}
|
|
|
|
public static let arriLogC4AWG4 = ColorSpace(name: "ARRI LogC4/AWG4", transfer: .logC4, gamutToRec709: .awg4ToRec709)
|
|
public static let redLog3G10RWG = ColorSpace(name: "RED Log3G10/RWG", transfer: .log3G10, gamutToRec709: .rwgToRec709)
|
|
public static let sonySLog3SGamut3Cine = ColorSpace(name: "Sony S-Log3/SGamut3.Cine", transfer: .sLog3, gamutToRec709: .sGamut3CineToRec709)
|
|
public static let rec709 = ColorSpace(name: "Rec709", transfer: .linear, gamutToRec709: .identity)
|
|
}
|