From e92de36f7ab5b3911ebb2283343c46026b71819b Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sat, 1 Mar 2025 19:35:04 +0100 Subject: [PATCH] cmm: add more transfer functions --- src/cmm/cmm_transfer_function.rs | 9 ++ .../vulkan/shaders/transfer_functions.glsl | 144 +++++++++++++++++- src/gfx_apis/vulkan/transfer_functions.rs | 18 +++ src/theme.rs | 116 ++++++++++++++ 4 files changed, 281 insertions(+), 6 deletions(-) diff --git a/src/cmm/cmm_transfer_function.rs b/src/cmm/cmm_transfer_function.rs index 50613a64..b7c15e0e 100644 --- a/src/cmm/cmm_transfer_function.rs +++ b/src/cmm/cmm_transfer_function.rs @@ -4,4 +4,13 @@ use linearize::Linearize; pub enum TransferFunction { Srgb, Linear, + St2084Pq, + Bt1886, + Gamma22, + Gamma28, + St240, + ExtSrgb, + Log100, + Log316, + St428, } diff --git a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl index d051d13d..7526fb4a 100644 --- a/src/gfx_apis/vulkan/shaders/transfer_functions.glsl +++ b/src/gfx_apis/vulkan/shaders/transfer_functions.glsl @@ -3,8 +3,17 @@ #include "frag_spec_const.glsl" -#define SRGB 0 -#define LINEAR 1 +#define TF_SRGB 0 +#define TF_LINEAR 1 +#define TF_ST2084_PQ 2 +#define TF_BT1886 3 +#define TF_GAMMA22 4 +#define TF_GAMMA28 5 +#define TF_ST240 6 +#define TF_EXT_SRGB 7 +#define TF_LOG100 8 +#define TF_LOG316 9 +#define TF_ST428 10 vec3 eotf_srgb(vec3 c) { return mix( @@ -23,18 +32,141 @@ vec3 oetf_srgb(vec3 c) { ); } +vec3 eotf_ext_srgb(vec3 c) { + return mix( + -pow((c - vec3(0.055)) / vec3(-1.055), vec3(2.4)), + mix( + c * vec3(1.0 / 12.92), + pow((c + vec3(0.055)) / vec3(1.055), vec3(2.4)), + greaterThan(c, vec3(0.04045)) + ), + greaterThan(c, vec3(-0.04045)) + ); +} + +vec3 oetf_ext_srgb(vec3 c) { + c = clamp(c, -0.6038, 7.5913); + return mix( + vec3(-1.055) * pow(-c, vec3(1/2.4)) + vec3(0.055), + mix( + c * vec3(12.92), + vec3(1.055) * pow(c, vec3(1/2.4)) - vec3(0.055), + greaterThan(c, vec3(0.0031308)) + ), + greaterThan(c, vec3(-0.0031308)) + ); +} + +vec3 eotf_st2084_pq(vec3 c) { + vec3 cp = pow(c, vec3(1.0 / 78.84375)); + vec3 num = max(cp - vec3(0.8359375), 0.0); + vec3 den = vec3(18.8515625) - vec3(18.6875) * cp; + return pow(num / den, vec3(1.0 / 0.1593017578125)); +} + +vec3 oetf_st2084_pq(vec3 c) { + c = clamp(c, 0.0, 1.0); + vec3 num = vec3(0.8359375) + vec3(18.8515625) * pow(c, vec3(0.1593017578125)); + vec3 den = vec3(1.0) + vec3(18.6875) * pow(c, vec3(0.1593017578125)); + return pow(num / den, vec3(78.84375)); +} + +vec3 eotf_bt1886(vec3 c) { + return mix( + c * vec3(1.0 / 4.5), + pow((c + vec3(0.099)) * vec3(1.0 / 1.099), vec3(1.0 / 0.45)), + greaterThanEqual(c, vec3(0.081)) + ); +} + +vec3 oetf_bt1886(vec3 c) { + return mix( + vec3(4.5) * c, + vec3(1.099) * pow(c, vec3(0.45)) - vec3(0.099), + greaterThanEqual(c, vec3(0.018)) + ); +} + +vec3 eotf_st240(vec3 c) { + return mix( + c * vec3(1.0 / 4.0), + pow((c + vec3(0.1115)) * vec3(1.0 / 1.1115), vec3(1.0 / 0.45)), + greaterThanEqual(c, vec3(0.0913)) + ); +} + +vec3 oetf_st240(vec3 c) { + return mix( + vec3(4.0) * c, + vec3(1.1115) * pow(c, vec3(0.45)) - vec3(0.1115), + greaterThanEqual(c, vec3(0.0228)) + ); +} + +vec3 eotf_log100(vec3 c) { + return pow(vec3(10), vec3(2.0) * (c - vec3(1.0))); +} + +vec3 oetf_log100(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + vec3(0.0), + vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.0), + greaterThanEqual(c, vec3(0.01)) + ); +} + +vec3 eotf_log316(vec3 c) { + return pow(vec3(10), vec3(2.5) * (c - vec3(1.0))); +} + +vec3 oetf_log316(vec3 c) { + c = clamp(c, 0.0, 1.0); + return mix( + vec3(0.0), + vec3(1.0) + log2(c) / vec3(log2(10)) / vec3(2.5), + greaterThanEqual(c, vec3(sqrt(10) / 1000.0)) + ); +} + +vec3 eotf_st428(vec3 c) { + return pow(c, vec3(2.6)) * vec3(52.37 / 48.0); +} + +vec3 oetf_st428(vec3 c) { + return pow(vec3(48.0) * c / vec3(52.37), vec3(1.0 / 2.6)); +} + vec3 apply_eotf(vec3 c) { switch (eotf) { - case SRGB: return eotf_srgb(c); - case LINEAR: return c; + case TF_SRGB: return eotf_srgb(c); + case TF_LINEAR: return c; + case TF_ST2084_PQ: return eotf_st2084_pq(c); + case TF_BT1886: return eotf_bt1886(c); + case TF_GAMMA22: return pow(c, vec3(2.2)); + case TF_GAMMA28: return pow(c, vec3(2.8)); + case TF_ST240: return eotf_st240(c); + case TF_EXT_SRGB: return eotf_ext_srgb(c); + case TF_LOG100: return eotf_log100(c); + case TF_LOG316: return eotf_log316(c); + case TF_ST428: return eotf_st428(c); default: return c; } } vec3 apply_oetf(vec3 c) { switch (oetf) { - case SRGB: return oetf_srgb(c); - case LINEAR: return c; + case TF_SRGB: return oetf_srgb(c); + case TF_LINEAR: return c; + case TF_ST2084_PQ: return oetf_st2084_pq(c); + case TF_BT1886: return oetf_bt1886(c); + case TF_GAMMA22: return pow(c, vec3(1.0 / 2.2)); + case TF_GAMMA28: return pow(c, vec3(1.0 / 2.8)); + case TF_ST240: return oetf_st240(c); + case TF_EXT_SRGB: return oetf_ext_srgb(c); + case TF_LOG100: return oetf_log100(c); + case TF_LOG316: return oetf_log316(c); + case TF_ST428: return oetf_st428(c); default: return c; } } diff --git a/src/gfx_apis/vulkan/transfer_functions.rs b/src/gfx_apis/vulkan/transfer_functions.rs index b5bc2acc..c1354d67 100644 --- a/src/gfx_apis/vulkan/transfer_functions.rs +++ b/src/gfx_apis/vulkan/transfer_functions.rs @@ -2,6 +2,15 @@ use crate::cmm::cmm_transfer_function::TransferFunction; pub const TF_SRGB: u32 = 0; pub const TF_LINEAR: u32 = 1; +pub const TF_ST2084_PQ: u32 = 2; +pub const TF_BT1887: u32 = 3; +pub const TF_GAMMA22: u32 = 4; +pub const TF_GAMMA28: u32 = 5; +pub const TF_ST240: u32 = 6; +pub const TF_EXT_SRGB: u32 = 7; +pub const TF_LOG100: u32 = 8; +pub const TF_LOG316: u32 = 9; +pub const TF_ST428: u32 = 10; pub trait TransferFunctionExt: Sized { fn to_vulkan(self) -> u32; @@ -12,6 +21,15 @@ impl TransferFunctionExt for TransferFunction { match self { TransferFunction::Srgb => TF_SRGB, TransferFunction::Linear => TF_LINEAR, + TransferFunction::St2084Pq => TF_ST2084_PQ, + TransferFunction::Bt1886 => TF_BT1887, + TransferFunction::Gamma22 => TF_GAMMA22, + TransferFunction::Gamma28 => TF_GAMMA28, + TransferFunction::St240 => TF_ST240, + TransferFunction::ExtSrgb => TF_EXT_SRGB, + TransferFunction::Log100 => TF_LOG100, + TransferFunction::Log316 => TF_LOG316, + TransferFunction::St428 => TF_ST428, } } } diff --git a/src/theme.rs b/src/theme.rs index 3a19fabc..8d78d814 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,5 +1,8 @@ +#![expect(clippy::excessive_precision)] + use { crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell}, + num_traits::Float, std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc}, }; @@ -77,6 +80,51 @@ impl Color { fn linear(c: f32) -> f32 { c } + fn st2084_pq(c: f32) -> f32 { + let cp = c.powf(1.0 / 78.84375); + let num = (cp - 0.8359375).max(0.0); + let den = 18.8515625 - 18.6875 * cp; + (num / den).powf(1.0 / 0.1593017578125) + } + fn ext_srgb(c: f32) -> f32 { + let c = c.clamp(-0.6038, 7.5913); + if c <= -0.0031308 { + -1.055 * (-c).powf(1.0 / 2.4) + 0.055 + } else if c <= 0.0031308 { + c * 12.92 + } else { + 1.055 * c.powf(1.0 / 2.4) - 0.055 + } + } + fn bt1886(c: f32) -> f32 { + if c < 0.081 { + c / 4.5 + } else { + ((c + 0.099) / 1.099).powf(1.0 / 0.45) + } + } + fn st240(c: f32) -> f32 { + if c < 0.0913 { + c / 4.0 + } else { + ((c + 0.1115) / 1.1115).powf(1.0 / 0.45) + } + } + fn log100(c: f32) -> f32 { + 10.0.powf(2.0 * (c - 1.0)) + } + fn log316(c: f32) -> f32 { + 10.0.powf(2.5 * (c - 1.0)) + } + fn st428(c: f32) -> f32 { + c.powf(2.6) * 52.37 / 48.0 + } + fn gamma22(c: f32) -> f32 { + c.powf(2.2) + } + fn gamma28(c: f32) -> f32 { + c.powf(2.8) + } macro_rules! convert { ($tf:ident) => {{ r = $tf(r); @@ -87,6 +135,15 @@ impl Color { match transfer_function { TransferFunction::Srgb => convert!(srgb), TransferFunction::Linear => convert!(linear), + TransferFunction::St2084Pq => convert!(st2084_pq), + TransferFunction::Bt1886 => convert!(bt1886), + TransferFunction::Gamma22 => convert!(gamma22), + TransferFunction::Gamma28 => convert!(gamma28), + TransferFunction::St240 => convert!(st240), + TransferFunction::ExtSrgb => convert!(ext_srgb), + TransferFunction::Log100 => convert!(log100), + TransferFunction::Log316 => convert!(log316), + TransferFunction::St428 => convert!(st428), } Self { r, g, b, a: 1.0 } } @@ -185,6 +242,56 @@ impl Color { fn linear(c: f32) -> f32 { c } + fn st2084_pq(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + let num = 0.8359375 + 18.8515625 * c.powf(0.1593017578125); + let den = 1.0 + 18.6875 * c.powf(0.1593017578125); + (num / den).powf(78.84375) + } + fn ext_srgb(c: f32) -> f32 { + if c < -0.04045 { + -((c - 0.055) / -1.055).powf(2.4) + } else if c < 0.04045 { + c / 12.92 + } else { + ((c + 0.055) / 1.055).powf(2.4) + } + } + fn bt1886(c: f32) -> f32 { + if c < 0.018 { + 4.5 * c + } else { + 1.099 * c.powf(0.45) - 0.099 + } + } + fn st240(c: f32) -> f32 { + if c < 0.0228 { + 4.0 * c + } else { + 1.1115 * c.powf(0.45) - 0.1115 + } + } + fn log100(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + if c < 0.01 { 0.0 } else { 1.0 + c.log10() / 2.0 } + } + fn log316(c: f32) -> f32 { + let c = c.clamp(0.0, 1.0); + if c < 10.0.sqrt() / 1000.0 { + 0.0 + } else { + 1.0 + c.log10() / 2.5 + } + } + fn st428(c: f32) -> f32 { + (48.0 * c / 52.37).powf(1.0 / 2.6) + } + fn gamma22(c: f32) -> f32 { + c.powf(1.0 / 2.2) + } + fn gamma28(c: f32) -> f32 { + c.powf(1.0 / 2.8) + } macro_rules! convert { ($tf:ident) => {{ for c in &mut res[..3] { @@ -201,6 +308,15 @@ impl Color { match transfer_function { TransferFunction::Srgb => convert!(srgb), TransferFunction::Linear => convert!(linear), + TransferFunction::St2084Pq => convert!(st2084_pq), + TransferFunction::Bt1886 => convert!(bt1886), + TransferFunction::Gamma22 => convert!(gamma22), + TransferFunction::Gamma28 => convert!(gamma28), + TransferFunction::St240 => convert!(st240), + TransferFunction::ExtSrgb => convert!(ext_srgb), + TransferFunction::Log100 => convert!(log100), + TransferFunction::Log316 => convert!(log316), + TransferFunction::St428 => convert!(st428), } if self.a < 1.0 { for c in &mut res[..3] {