├── .gitignore ├── examples ├── all_examples.rs ├── oklab.rs └── Cargo.toml ├── Cargo.toml ├── deny.toml ├── matrix-gen ├── Cargo.toml └── src │ └── main.rs ├── kolor └── src │ ├── details │ ├── xyz.rs │ ├── cat.rs │ ├── conversion.rs │ ├── math.rs │ ├── color.rs │ ├── transform.rs │ └── generated_matrices.rs │ └── lib.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/all_examples.rs: -------------------------------------------------------------------------------- 1 | mod oklab; 2 | 3 | fn main() { 4 | oklab::example(); 5 | } 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["matrix-gen", "build/kolor", "build/kolor-64", "examples"] 3 | resolver = "2" -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = ["MIT", "Apache-2.0"] 3 | copyleft = "deny" 4 | confidence-threshold = 1.0 5 | exceptions = [ 6 | ] 7 | 8 | [bans] 9 | multiple-versions = "deny" 10 | wildcards = "deny" 11 | skip = [ 12 | ] 13 | 14 | [sources] 15 | unknown-git = "deny" 16 | -------------------------------------------------------------------------------- /examples/oklab.rs: -------------------------------------------------------------------------------- 1 | use kolor::{spaces, Color}; 2 | 3 | pub fn example() { 4 | let srgb = Color::srgb(0.35, 0.75, 0.8); 5 | let mut oklab = srgb.to(spaces::OK_LAB); 6 | // modify `a` 7 | oklab.value.y += 0.2; 8 | let modified_srgb = oklab.to(srgb.space); 9 | println!(" {:?} -> {:?}", srgb, modified_srgb); 10 | } 11 | -------------------------------------------------------------------------------- /matrix-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kolor-matrix-gen" 3 | version = "0.1.0" 4 | authors = ["Karl Bergström "] 5 | edition = "2018" 6 | publish = false 7 | license = "MIT OR Apache-2.0" 8 | 9 | [dependencies] 10 | kolor-64 = { version = "0.2", path = "../build/kolor-64", default-features = false, features = [ 11 | "std-glam", 12 | "f64", 13 | ] } 14 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kolor-examples" 3 | version = "0.1.0" 4 | authors = ["Karl Bergström "] 5 | edition = "2018" 6 | default-run = "all_examples" 7 | publish = false 8 | license = "MIT OR Apache-2.0" 9 | 10 | [[bin]] 11 | name = "all_examples" 12 | path = "./all_examples.rs" 13 | 14 | [dependencies] 15 | kolor = { path = "../build/kolor", version = "0.2" } 16 | -------------------------------------------------------------------------------- /kolor/src/details/xyz.rs: -------------------------------------------------------------------------------- 1 | use crate::{Float, Mat3, Vec3}; 2 | 3 | pub fn xyz_to_rgb(primaries: &[[Float; 2]; 3], white_point: &[Float; 3]) -> Mat3 { 4 | rgb_to_xyz(primaries, white_point).inverse() 5 | } 6 | 7 | #[rustfmt::skip] 8 | #[allow(non_snake_case)] 9 | pub fn rgb_to_xyz(primaries: &[[Float;2]; 3], white_point: &[Float;3]) -> Mat3 { 10 | let [[xr, yr], [xg, yg], [xb, yb]] = *primaries; 11 | let [Wx, Wy, Wz] = *white_point; 12 | 13 | let Xr = xr / yr; 14 | let Yr = 1.0; 15 | let Zr = (1.0 - xr - yr) / yr; 16 | let Xg = xg / yg; 17 | let Yg = 1.0; 18 | let Zg = (1.0 - xg - yg) / yg; 19 | let Xb = xb / yb; 20 | let Yb = 1.0; 21 | let Zb = (1.0 - xb - yb) / yb; 22 | let mut base_matrix = Mat3::from_cols_array(&[ 23 | Xr, Yr, Zr, 24 | Xg, Yg, Zg, 25 | Xb, Yb, Zb 26 | ]); 27 | 28 | let scale = base_matrix.inverse() * Vec3::new(Wx, Wy, Wz); 29 | base_matrix.x_axis *= scale.x; 30 | base_matrix.y_axis *= scale.y; 31 | base_matrix.z_axis *= scale.z; 32 | 33 | base_matrix 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `kolor` 2 | 3 | A crate for doing conversions between color spaces that also helps with some 4 | color math. 5 | 6 | `kolor` is intended for use in games or other interactive visual applications, 7 | where it can help implement correct color management, wide color gamut 8 | rendering and tonemapping. 9 | 10 | ## Example 11 | 12 | ```rust 13 | let conversion = kolor::ColorConversion::new( 14 | kolor::spaces::SRGB, 15 | kolor::spaces::ACES_CG, 16 | ); 17 | 18 | let srgb_color = kolor::Vec3::new(0.25, 0.5, 0.75); 19 | let aces_cg_color = conversion.convert(srgb_color); 20 | ``` 21 | 22 | ## Design 23 | 24 | `kolor` aims to supports all color spaces and color models which use 3-component 25 | vectors, such as RGB, LAB, XYZ, HSL, LMS and more. 26 | 27 | In the spirit of keeping things simple, kolor uses a single type, 28 | [`ColorConversion`](https://docs.rs/kolor/latest/kolor/struct.ColorConversion.html), 29 | to represent a color conversion between any supported color spaces. 30 | 31 | For more details on design and implementation, please have a look at the 32 | [module docs](https://docs.rs/kolor/latest/kolor/index.html). 33 | 34 | ### `no_std` Support 35 | 36 | `kolor` supports `no_std` by disabling the default-enabled `std` feature and enabling the `libm` feature. 37 | 38 | ### `f32` vs. `f64` 39 | 40 | [`kolor`](https://crates.io/crates/kolor) uses `f32` by default. 41 | 42 | The `f64` support is available in [`kolor-64`](https://crates.io/crates/kolor-64). 43 | 44 | ## Contributions 45 | 46 | Unless you explicitly state otherwise, any contribution intentionally 47 | submitted for inclusion in the work by you, as defined in the Apache-2.0 48 | license, shall be dual licensed as above, without any additional terms or 49 | conditions. 50 | 51 | See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). 52 | 53 | ## License 54 | 55 | Licensed under either of 56 | 57 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 58 | http://www.apache.org/licenses/LICENSE-2.0) 59 | 60 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 61 | 62 | at your option. 63 | 64 | **Please note** that some dependencies may be licensed under _other_ terms. 65 | These are listed in [`deny.toml`](deny.toml) under licenses.exceptions on a 66 | best-effort basis, and are validated in every CI run using 67 | [`cargo-deny`](https://github.com/EmbarkStudios/cargo-deny). 68 | -------------------------------------------------------------------------------- /kolor/src/details/cat.rs: -------------------------------------------------------------------------------- 1 | //! Implements [Chromatic Adaptation](https://en.wikipedia.org/wiki/Chromatic_adaptation) 2 | //! Transformation (CAT). 3 | //! 4 | //! Chromatic Adaptation Transformation means transforming a linear color 5 | //! space's coordinate system from one white point reference to another. 6 | //! 7 | //! [`Sharp`(LmsConeSpace::Sharp) is used as the default for conversions by 8 | //! [`ColorConversion`][crate::details::conversion::ColorConversion]. 9 | use crate::{Mat3, Vec3}; 10 | 11 | /// Supported conversion methods. 12 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] 13 | pub enum LmsConeSpace { 14 | VonKries, 15 | Bradford, 16 | #[default] 17 | Sharp, 18 | CmcCat2000, 19 | Cat02, 20 | } 21 | 22 | impl LmsConeSpace { 23 | /// Returns the matrix for the given cone space variant. 24 | // from S. Bianco. "Two New von Kries Based Chromatic Adapatation Transforms 25 | // Found by Numerical Optimization." 26 | #[rustfmt::skip] 27 | pub fn matrix(&self) -> Mat3 { 28 | match self { 29 | LmsConeSpace::VonKries => { 30 | Mat3::from_cols_array(&[0.40024, -0.2263, 0.0, 0.7076, 1.16532, 0.0, -0.08081, 0.0457, 0.91822]) 31 | } 32 | LmsConeSpace::Bradford => { 33 | Mat3::from_cols_array(&[0.8951, -0.7502, 0.0389, 0.2664, 1.7135, -0.0685, -0.1614, 0.0367, 1.0296]) 34 | } 35 | LmsConeSpace::Sharp => { 36 | Mat3::from_cols_array(&[1.2694, -0.8364, 0.0297, -0.0988, 1.8006, -0.0315, -0.1706, 0.0357, 1.0018]) 37 | } 38 | LmsConeSpace::CmcCat2000 => { 39 | Mat3::from_cols_array(&[0.7982, -0.5918, 0.0008, 0.3389, 1.5512, 0.239, -0.1371, 0.0406, 0.9753]) 40 | } 41 | LmsConeSpace::Cat02 => { 42 | Mat3::from_cols_array(&[0.7328, -0.7036, 0.0030, 0.4296, 1.6975, 0.0136, -0.1624, 0.0061, 0.9834]) 43 | } 44 | } 45 | } 46 | 47 | /// Calculate the CAT matrix for converting from one white point to another. 48 | #[rustfmt::skip] 49 | pub fn chromatic_adaptation_transform( 50 | &self, 51 | src_illuminant: Vec3, 52 | dst_illuminant: Vec3, 53 | ) -> Mat3 { 54 | let cone_space_transform = self.matrix(); 55 | let src_cone_response = cone_space_transform * src_illuminant; 56 | let dst_cone_response = cone_space_transform * dst_illuminant; 57 | let src_to_dst_cone = Mat3::from_cols_array(&[ 58 | dst_cone_response.x / src_cone_response.x, 0.0, 0.0, 59 | 0.0, dst_cone_response.y / src_cone_response.y, 0.0, 60 | 0.0, 0.0, dst_cone_response.z / src_cone_response.z, 61 | ]); 62 | 63 | cone_space_transform.inverse() * src_to_dst_cone * cone_space_transform 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /matrix-gen/src/main.rs: -------------------------------------------------------------------------------- 1 | use kolor::details::conversion::LinearColorConversion; 2 | use kolor_64 as kolor; 3 | 4 | fn main() { 5 | let mut conversions = Vec::new(); 6 | for src in &kolor::spaces::ALL_COLOR_SPACES { 7 | for dst in &kolor::spaces::ALL_COLOR_SPACES { 8 | let linear_src = src.as_linear(); 9 | let linear_dst = dst.as_linear(); 10 | if linear_src == linear_dst { 11 | continue; 12 | } 13 | if conversions.iter().any(|c: &LinearColorConversion| { 14 | c.input_space() == linear_src && c.output_space() == linear_dst 15 | }) { 16 | continue; 17 | } 18 | conversions.push(LinearColorConversion::new(linear_src, linear_dst)); 19 | } 20 | } 21 | let mut out_str = String::with_capacity(conversions.len() * 256); 22 | out_str += "use super::{ 23 | color::{RgbPrimaries, WhitePoint}, 24 | }; 25 | use crate::Mat3;\n\n"; 26 | let mut const_matches = String::with_capacity(conversions.len() * 128); 27 | for conversion in conversions { 28 | let src = conversion.input_space(); 29 | let dst = conversion.output_space(); 30 | let mat = conversion.matrix(); 31 | let from_name = format!("{:?}_{:?}", src.primaries(), src.white_point()); 32 | let to_name = format!("{:?}_{:?}", dst.primaries(), dst.white_point()); 33 | out_str += &format!( 34 | "#[rustfmt::skip] 35 | pub const {}_TO_{}: Mat3 = Mat3::from_cols_array(&[ 36 | {:?}, {:?}, {:?}, 37 | {:?}, {:?}, {:?}, 38 | {:?}, {:?}, {:?}, 39 | ]); 40 | \n", 41 | from_name, 42 | to_name, 43 | mat.x_axis.x, 44 | mat.x_axis.y, 45 | mat.x_axis.z, 46 | mat.y_axis.x, 47 | mat.y_axis.y, 48 | mat.y_axis.z, 49 | mat.z_axis.x, 50 | mat.z_axis.y, 51 | mat.z_axis.z, 52 | ); 53 | 54 | const_matches += &format!( 55 | " 56 | (RgbPrimaries::{:?}, WhitePoint::{:?}, RgbPrimaries::{:?}, WhitePoint::{:?}) => {{ 57 | Some({}_TO_{}) 58 | }}", 59 | src.primaries(), 60 | src.white_point(), 61 | dst.primaries(), 62 | dst.white_point(), 63 | from_name, 64 | to_name 65 | ); 66 | } 67 | 68 | out_str += &format!( 69 | r#" 70 | pub fn const_conversion_matrix( 71 | src_primaries: RgbPrimaries, 72 | src_wp: WhitePoint, 73 | dst_primaries: RgbPrimaries, 74 | dst_wp: WhitePoint, 75 | ) -> Option {{ 76 | if src_primaries == dst_primaries && src_wp == dst_wp {{ 77 | return Some(Mat3::IDENTITY); 78 | }} 79 | match (src_primaries, src_wp, dst_primaries, dst_wp) {{ 80 | {} 81 | _ => None, 82 | }} 83 | }}"#, 84 | const_matches 85 | ); 86 | 87 | std::fs::write("../kolor/src/details/generated_matrices.rs", out_str).unwrap(); 88 | } 89 | -------------------------------------------------------------------------------- /kolor/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `kolor` implements conversions between color spaces which use 3-component 2 | //! vectors. 3 | //! 4 | //! It is intended for use in games or other interactive visual applications, 5 | //! where it can help implement correct color management and wide color gamut 6 | //! rendering. 7 | //! 8 | //! ## Named Color Spaces 9 | //! 10 | //! Named color space definitions can be found in the [`spaces`] module. Some 11 | //! notable color spaces and models: 12 | //! 13 | //! * sRGB/linear sRGB/BT.709 14 | //! * BT.2020 15 | //! * ACEScg 16 | //! * ACES2065-1 17 | //! * Oklab 18 | //! * CIE LAB/Lch/Luv/xyY/uvV 19 | //! * HSL/HSV/HSI 20 | //! * ICtCp 21 | //! 22 | //! You can also construct custom [`ColorSpace`]s from a combination of 23 | //! primaries, whitepoint and transform function. 24 | //! 25 | //! ## Design 26 | //! 27 | //! `kolor` aims to supports all color spaces and color models which use 28 | //! 3-component vectors, such as RGB, LAB, XYZ, HSL and more. 29 | //! 30 | //! In the spirit of keeping things simple, `kolor` uses a single type, 31 | //! [`Color`], to represent a color in any supported color space. 32 | //! 33 | //! `kolor` can programmatically generate an efficient conversion between any 34 | //! two color spaces by using the CIE XYZ color space as a "connecting space", 35 | //! meaning all supported color spaces only need to support a conversion to a 36 | //! reference color space that is a linear transform of the CIE XYZ color space. 37 | //! 38 | //! Transformations between linear color spaces can be implemented with a 3×3 39 | //! matrix, and since 3×3 matrices are composable, these matrices can be 40 | //! pre-composed and applied to a color with a single multiply. 41 | //! 42 | //! `kolor` recognizes that users may want to perform conversions on colors 43 | //! stored in types defined by the user. [`ColorConversion`] represents a 44 | //! conversion between two color spaces and is intended to be compatible with 45 | //! 3-component vectors in many math libraries. 46 | //! 47 | //! `kolor` defines conversions from a source `ColorSpace` to a destination 48 | //! `ColorSpace` as three parts: 49 | //! 50 | //! - if the source color space is non-linear, apply the inverse of its 51 | //! transform function to convert to the non-linear color space's reference 52 | //! color space (which is always linear). 53 | //! 54 | //! - a linear 3×3 transformation matrix from the source to the destination 55 | //! linear color space. 56 | //! 57 | //! - if the destination color space is a non-linear color space, apply its 58 | //! transform function 59 | //! 60 | //! A "non-linear transform function" means any function that cannot be 61 | //! expressed as a linear transformation of the CIE XYZ color space. Examples 62 | //! include the sRGB logarithmic gamma compensation function, 63 | //! the Oklab transform function, and the HSL/HSV hexagonal/circular transform. 64 | //! 65 | //! For non-linear color spaces, many transform functions are supported 66 | //! to convert between popular spaces, but for GPU contexts, these 67 | //! implementations clearly can't be used directly. To implement data-driven 68 | //! conversions, you can read the required operations for transforming between 69 | //! spaces from a `ColorConversion` value and run these as appropriate. 70 | //! Feel free to port the implementations in the 71 | //! [`transform`](`details::transform`) module to your shaders or other code. 72 | //! 73 | //! ### Gamut-Agnostic Transforms 74 | //! 75 | //! Some color models like CIELAB or HSL are intended to provide an alternate 76 | //! view of some linear color spaces, and need a reference color space to 77 | //! provide information like which white point or RGB primaries to use. To 78 | //! construct these color spaces, refer to associated methods on `ColorSpace` 79 | //! and use an appropriate reference color space. 80 | //! 81 | //! ### Details 82 | //! 83 | //! `kolor` can calculate 3×3 conversion matrices between any linear color space 84 | //! defined by RGB primaries and a white point. `kolor` offers APIs for 85 | //! performing conversions directly, and for extracting the 3×3 matrix to use in 86 | //! a different context, for example on a GPU. 87 | //! 88 | //! ### Generating Conversion Matrices Between RGB Color Spaces 89 | //! 90 | //! [`LinearColorConversion`][details::conversion::LinearColorConversion] can be 91 | //! used to generate conversion matrices "offline", 92 | //! in which case you probably want to use the `f64` feature for better 93 | //! precision. The precision of the derived matrices won't be perfect, but 94 | //! probably good enough for games. 95 | //! 96 | //! Conversions between all combinations of built-in primaries and whitepoints 97 | //! color spaces are bundled with `kolor` as constants with the `color-matrices` 98 | //! feature, which is enabled by default. When a `ColorConversion` without a 99 | //! bundled pre-calculated conversion matrix is created, it is calculated 100 | //! on-demand, meaning the creation will be a bit slower to create than if there 101 | //! is a constant matrix available. 102 | //! 103 | //! ### Chromatic Adaptation Transformation (CAT) 104 | //! 105 | //! `kolor` implements CAT in the [`cat`](details::cat) module and supports the 106 | //! LMS cone spaces defined in [`LmsConeSpace`][details::cat::LmsConeSpace]. 107 | //! Chromatic Adaptation Transformation means converting a linear RGB color 108 | //! space from one reference [`WhitePoint`][details::color::WhitePoint] to 109 | //! another. 110 | //! 111 | //! Use [`ColorSpace::with_whitepoint()`] to change the `WhitePoint` for a color 112 | //! space. 113 | //! 114 | //! ### XYZ-RGB Conversions 115 | //! 116 | //! All supported RGB color spaces use the CIE XYZ color space as its reference 117 | //! color space. Functions in the [`xyz`](details::xyz) module can be used to 118 | //! create conversion matrices to/from an RGB color space given a set of 119 | //! primaries and a white point. 120 | //! 121 | //! ## Features 122 | //! 123 | //! ### `no_std` & `glam` Support 124 | //! 125 | //! By default `kolor` uses `std` and `glam`, but both can be disabled 126 | //! separately or together with the folowing features: 127 | //! 128 | //! | |`std`|`no_std`| 129 | //! |-|-|-| 130 | //! |`glam`|`std-glam`|`libm-glam`| 131 | //! |no `glam`|`std`|`libm`| 132 | //! 133 | //! ### List of Features 134 | #![doc = document_features::document_features!()] 135 | #![cfg_attr(not(feature = "std"), no_std)] 136 | #![allow(unexpected_cfgs)] 137 | 138 | #[cfg(all(feature = "f32", feature = "f64"))] 139 | compile_error!("Only `f32` or `f64` can be selected at a time."); 140 | 141 | #[cfg(feature = "f64")] 142 | pub type Float = f64; 143 | 144 | #[cfg(not(feature = "f64"))] 145 | pub type Float = f32; 146 | 147 | pub use details::math::{Mat3, Vec3}; 148 | 149 | /// Create a `Mat3` from a `[Float; 9]`. The order of components is 150 | /// column-major. 151 | #[cfg(not(feature = "glam"))] 152 | #[macro_export] 153 | macro_rules! const_mat3 { 154 | ($ftypex9:expr) => { 155 | Mat3::from_cols_array_const($ftypex9) 156 | }; 157 | } 158 | 159 | #[cfg(not(feature = "f64"))] 160 | pub(crate) use core::f32::consts::PI; 161 | #[cfg(not(feature = "f64"))] 162 | pub(crate) use core::f32::consts::TAU; 163 | #[cfg(feature = "f64")] 164 | pub(crate) use core::f64::consts::PI; 165 | #[cfg(feature = "f64")] 166 | pub(crate) use core::f64::consts::TAU; 167 | 168 | pub mod details { 169 | pub mod cat; 170 | pub mod color; 171 | pub mod conversion; 172 | #[allow(clippy::excessive_precision)] 173 | #[cfg(feature = "color-matrices")] 174 | pub mod generated_matrices; 175 | pub mod math; 176 | #[allow(clippy::excessive_precision)] 177 | #[allow(clippy::many_single_char_names)] 178 | #[allow(non_snake_case)] 179 | pub mod transform; 180 | pub mod xyz; 181 | } 182 | #[doc(inline)] 183 | pub use details::color::color_spaces as spaces; 184 | #[doc(inline)] 185 | pub use details::color::{Color, ColorSpace}; 186 | #[doc(inline)] 187 | pub use details::conversion::ColorConversion; 188 | -------------------------------------------------------------------------------- /kolor/src/details/conversion.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | color::{RgbPrimaries, TransformFn}, 3 | transform::ColorTransform, 4 | xyz::{rgb_to_xyz, xyz_to_rgb}, 5 | }; 6 | use crate::{ColorSpace, Float, Mat3, Vec3}; 7 | #[cfg(feature = "serde")] 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// A transformation from one linear color space to another. 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 13 | pub struct LinearColorConversion { 14 | mat: Mat3, 15 | input_space: ColorSpace, 16 | output_space: ColorSpace, 17 | } 18 | 19 | impl LinearColorConversion { 20 | pub fn input_space(&self) -> ColorSpace { 21 | self.input_space 22 | } 23 | 24 | pub fn output_space(&self) -> ColorSpace { 25 | self.output_space 26 | } 27 | 28 | pub fn convert(&self, color: Vec3) -> Vec3 { 29 | self.mat * color 30 | } 31 | 32 | pub fn matrix(&self) -> Mat3 { 33 | self.mat 34 | } 35 | 36 | pub fn new(src: ColorSpace, dst: ColorSpace) -> Self { 37 | if !src.is_linear() { 38 | panic!("{:?} is not a linear color space", src); 39 | } 40 | if !dst.is_linear() { 41 | panic!("{:?} is not a linear color space", dst); 42 | } 43 | #[cfg(feature = "color-matrices")] 44 | let const_conversion = super::generated_matrices::const_conversion_matrix( 45 | src.primaries(), 46 | src.white_point(), 47 | dst.primaries(), 48 | dst.white_point(), 49 | ); 50 | #[cfg(not(feature = "color-matrices"))] 51 | let const_conversion: Option = None; 52 | 53 | let mat = if let Some(const_mat) = const_conversion { 54 | const_mat 55 | } else { 56 | let src_to_xyz = if src.primaries() == RgbPrimaries::CieXyz { 57 | Mat3::IDENTITY 58 | } else { 59 | rgb_to_xyz(src.primaries().values(), src.white_point().values()) 60 | }; 61 | let xyz_to_dst = if dst.primaries() == RgbPrimaries::CieXyz { 62 | Mat3::IDENTITY 63 | } else { 64 | xyz_to_rgb(dst.primaries().values(), dst.white_point().values()) 65 | }; 66 | if src.white_point() != dst.white_point() { 67 | let white_point_transform = super::cat::LmsConeSpace::Sharp 68 | .chromatic_adaptation_transform( 69 | Vec3::from_slice(src.white_point().values()), 70 | Vec3::from_slice(dst.white_point().values()), 71 | ); 72 | xyz_to_dst * white_point_transform * src_to_xyz 73 | } else { 74 | xyz_to_dst * src_to_xyz 75 | } 76 | }; 77 | Self { 78 | mat, 79 | input_space: src, 80 | output_space: dst, 81 | } 82 | } 83 | } 84 | 85 | /// Defines an operation that maps a 3-component vector from a source 86 | /// [`ColorSpace`] to a destination `ColorSpace`. 87 | #[derive(Copy, Clone)] 88 | pub struct ColorConversion { 89 | src_space: ColorSpace, 90 | dst_space: ColorSpace, 91 | src_transform: Option, 92 | linear_transform: Option, 93 | dst_transform: Option, 94 | } 95 | impl PartialEq for ColorConversion { 96 | fn eq(&self, other: &Self) -> bool { 97 | self.src_space == other.src_space && self.dst_space == other.dst_space 98 | } 99 | } 100 | impl core::fmt::Debug for ColorConversion { 101 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 102 | let src_transform = if !self.src_space.is_linear() { 103 | self.src_space.transform_function() 104 | } else { 105 | TransformFn::None 106 | }; 107 | let dst_transform = if !self.dst_space.is_linear() { 108 | self.dst_space.transform_function() 109 | } else { 110 | TransformFn::None 111 | }; 112 | f.debug_struct("ColorConversion") 113 | .field("src_space", &self.src_space) 114 | .field("dst_space", &self.dst_space) 115 | .field("src_transform", &src_transform) 116 | .field("linear_transform", &self.linear_transform) 117 | .field("dst_transform", &dst_transform) 118 | .finish() 119 | } 120 | } 121 | 122 | impl ColorConversion { 123 | pub fn new(src: ColorSpace, dst: ColorSpace) -> Self { 124 | let src_transform = if !src.is_linear() { 125 | ColorTransform::new(src.transform_function(), TransformFn::None) 126 | } else { 127 | None 128 | }; 129 | let src_linear = ColorSpace::linear(src.primaries(), src.white_point()); 130 | let dst_linear = ColorSpace::linear(dst.primaries(), dst.white_point()); 131 | let linear_transform = LinearColorConversion::new(src_linear, dst_linear); 132 | let linear_transform = if linear_transform.mat == Mat3::IDENTITY { 133 | None 134 | } else { 135 | Some(linear_transform) 136 | }; 137 | let dst_transform = if !dst.is_linear() { 138 | ColorTransform::new(TransformFn::None, dst.transform_function()) 139 | } else { 140 | None 141 | }; 142 | Self { 143 | src_space: src, 144 | dst_space: dst, 145 | src_transform, 146 | dst_transform, 147 | linear_transform, 148 | } 149 | } 150 | 151 | pub fn invert(&self) -> Self { 152 | ColorConversion::new(self.dst_space, self.src_space) 153 | } 154 | 155 | pub fn is_linear(&self) -> bool { 156 | self.src_transform.is_none() && self.dst_transform.is_none() 157 | } 158 | 159 | pub fn linear_part(&self) -> LinearColorConversion { 160 | if let Some(transform) = self.linear_transform.as_ref() { 161 | *transform 162 | } else { 163 | LinearColorConversion { 164 | input_space: self.src_space, 165 | output_space: self.dst_space, 166 | mat: Mat3::IDENTITY, 167 | } 168 | } 169 | } 170 | 171 | pub fn src_transform(&self) -> Option { 172 | self.src_transform 173 | } 174 | 175 | pub fn dst_transform(&self) -> Option { 176 | self.dst_transform 177 | } 178 | 179 | pub fn src_transform_fn(&self) -> TransformFn { 180 | self.src_transform 181 | .map(|_| self.src_space.transform_function()) 182 | .unwrap_or(TransformFn::None) 183 | } 184 | 185 | pub fn dst_transform_fn(&self) -> TransformFn { 186 | self.dst_transform 187 | .map(|_| self.dst_space.transform_function()) 188 | .unwrap_or(TransformFn::None) 189 | } 190 | 191 | pub fn src_space(&self) -> ColorSpace { 192 | self.src_space 193 | } 194 | 195 | pub fn dst_space(&self) -> ColorSpace { 196 | self.dst_space 197 | } 198 | 199 | pub fn convert_float(&self, color: &mut [Float; 3]) { 200 | let vec3 = Vec3::from_slice(color); 201 | *color = self.convert(vec3).into(); 202 | } 203 | 204 | pub fn apply_src_transform(&self, color: Vec3) -> Vec3 { 205 | if let Some(src_transform) = self.src_transform.as_ref() { 206 | src_transform.apply(color, self.src_space.white_point()) 207 | } else { 208 | color 209 | } 210 | } 211 | 212 | pub fn apply_linear_part(&self, color: Vec3) -> Vec3 { 213 | if let Some(transform) = self.linear_transform.as_ref() { 214 | transform.convert(color) 215 | } else { 216 | color 217 | } 218 | } 219 | 220 | pub fn apply_dst_transform(&self, color: Vec3) -> Vec3 { 221 | if let Some(dst_transform) = self.dst_transform.as_ref() { 222 | dst_transform.apply(color, self.dst_space.white_point()) 223 | } else { 224 | color 225 | } 226 | } 227 | 228 | pub fn convert(&self, mut color: Vec3) -> Vec3 { 229 | color = self.apply_src_transform(color); 230 | color = self.apply_linear_part(color); 231 | color = self.apply_dst_transform(color); 232 | color 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /kolor/src/details/math.rs: -------------------------------------------------------------------------------- 1 | pub(crate) trait Cuberoot { 2 | fn cbrt(&self) -> Self; 3 | } 4 | 5 | pub(crate) mod prelude { 6 | #[cfg(feature = "glam")] 7 | pub(crate) use glam::Vec3Swizzles; 8 | 9 | pub(crate) use super::Cuberoot; 10 | } 11 | 12 | pub use math::*; 13 | 14 | #[cfg(feature = "glam")] 15 | #[allow(clippy::module_inception)] 16 | mod math { 17 | #[cfg(not(feature = "f64"))] 18 | pub use glam::f32::Mat3; 19 | #[cfg(not(feature = "f64"))] 20 | pub use glam::f32::Vec3; 21 | 22 | #[cfg(feature = "f64")] 23 | pub use glam::f64::DMat3 as Mat3; 24 | #[cfg(feature = "f64")] 25 | pub use glam::f64::DVec3 as Vec3; 26 | 27 | #[cfg(all(not(feature = "std"), feature = "libm"))] 28 | use num_traits::Float; 29 | 30 | impl super::Cuberoot for Vec3 { 31 | #[inline] 32 | fn cbrt(&self) -> Self { 33 | Self::new(self.x.cbrt(), self.y.cbrt(), self.z.cbrt()) 34 | } 35 | } 36 | } 37 | 38 | #[cfg(not(feature = "glam"))] 39 | #[allow(clippy::module_inception)] 40 | mod math { 41 | use crate::Float; 42 | #[cfg(all(not(feature = "std"), feature = "libm"))] 43 | use core::ops::{Add, Div, Mul, MulAssign, Sub}; 44 | #[cfg(all(not(feature = "std"), feature = "libm"))] 45 | use num_traits::Float; 46 | #[cfg(all(not(feature = "libm"), feature = "std"))] 47 | use std::ops::{Add, Div, Mul, MulAssign, Sub}; 48 | 49 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 50 | #[derive(Debug, Clone, Copy, PartialEq)] 51 | pub struct Vec3 { 52 | pub x: Float, 53 | pub y: Float, 54 | pub z: Float, 55 | } 56 | 57 | pub struct BVec3 { 58 | pub x: bool, 59 | pub y: bool, 60 | pub z: bool, 61 | } 62 | 63 | impl Vec3 { 64 | pub const fn new(x: Float, y: Float, z: Float) -> Self { 65 | Self { x, y, z } 66 | } 67 | 68 | pub const fn splat(value: Float) -> Self { 69 | Self { 70 | x: value, 71 | y: value, 72 | z: value, 73 | } 74 | } 75 | 76 | pub const fn from_slice(slice: &[Float]) -> Self { 77 | Self { 78 | x: slice[0], 79 | y: slice[1], 80 | z: slice[2], 81 | } 82 | } 83 | 84 | pub fn powf(self, n: Float) -> Self { 85 | Self { 86 | x: self.x.powf(n), 87 | y: self.y.powf(n), 88 | z: self.z.powf(n), 89 | } 90 | } 91 | 92 | pub fn cmplt(self, other: Self) -> BVec3 { 93 | BVec3 { 94 | x: self.x < other.x, 95 | y: self.y < other.y, 96 | z: self.z < other.z, 97 | } 98 | } 99 | 100 | pub const fn select(mask: BVec3, if_true: Vec3, if_false: Vec3) -> Self { 101 | Self { 102 | x: if mask.x { if_true.x } else { if_false.x }, 103 | y: if mask.y { if_true.y } else { if_false.y }, 104 | z: if mask.z { if_true.z } else { if_false.z }, 105 | } 106 | } 107 | 108 | pub const fn yz(self) -> Vec2 { 109 | Vec2 { 110 | x: self.y, 111 | y: self.z, 112 | } 113 | } 114 | 115 | pub fn dot(self, other: Self) -> Float { 116 | self.x * other.x + self.y * other.y + self.z * other.z 117 | } 118 | 119 | pub fn abs_diff_eq(self, other: Self, max_abs_diff: Float) -> bool { 120 | (self.x - other.x).abs() <= max_abs_diff 121 | && (self.y - other.y).abs() <= max_abs_diff 122 | && (self.z - other.z).abs() <= max_abs_diff 123 | } 124 | } 125 | 126 | impl super::Cuberoot for Vec3 { 127 | #[inline] 128 | fn cbrt(&self) -> Self { 129 | Self::new(self.x.cbrt(), self.y.cbrt(), self.z.cbrt()) 130 | } 131 | } 132 | 133 | impl Add for Vec3 { 134 | type Output = Self; 135 | 136 | fn add(self, other: Self) -> Self { 137 | Self { 138 | x: self.x + other.x, 139 | y: self.y + other.y, 140 | z: self.z + other.z, 141 | } 142 | } 143 | } 144 | 145 | impl Sub for Vec3 { 146 | type Output = Self; 147 | 148 | fn sub(self, other: Self) -> Self { 149 | Self { 150 | x: self.x - other.x, 151 | y: self.y - other.y, 152 | z: self.z - other.z, 153 | } 154 | } 155 | } 156 | 157 | impl Mul for Vec3 { 158 | type Output = Self; 159 | 160 | fn mul(self, other: Self) -> Self { 161 | Self { 162 | x: self.x * other.x, 163 | y: self.y * other.y, 164 | z: self.z * other.z, 165 | } 166 | } 167 | } 168 | 169 | impl MulAssign for Vec3 { 170 | fn mul_assign(&mut self, other: Float) { 171 | *self = *self * other 172 | } 173 | } 174 | 175 | impl Mul for Vec3 { 176 | type Output = Self; 177 | 178 | fn mul(self, other: Float) -> Self { 179 | Self { 180 | x: self.x * other, 181 | y: self.y * other, 182 | z: self.z * other, 183 | } 184 | } 185 | } 186 | 187 | impl Mul for Float { 188 | type Output = Vec3; 189 | 190 | fn mul(self, other: Vec3) -> Vec3 { 191 | other * self 192 | } 193 | } 194 | 195 | impl Div for Vec3 { 196 | type Output = Self; 197 | 198 | fn div(self, other: Float) -> Self { 199 | Self { 200 | x: self.x / other, 201 | y: self.y / other, 202 | z: self.z / other, 203 | } 204 | } 205 | } 206 | 207 | #[derive(Debug, Clone, Copy, PartialEq)] 208 | pub struct Vec2 { 209 | pub x: Float, 210 | pub y: Float, 211 | } 212 | 213 | impl Vec2 { 214 | pub fn length(self) -> Float { 215 | (self.x * self.x + self.y * self.y).sqrt() 216 | } 217 | } 218 | 219 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 220 | #[derive(Debug, Clone, Copy, PartialEq)] 221 | pub struct Mat3 { 222 | pub x_axis: Vec3, 223 | pub y_axis: Vec3, 224 | pub z_axis: Vec3, 225 | } 226 | 227 | impl Mat3 { 228 | pub const IDENTITY: Self = Self { 229 | x_axis: Vec3::new(1.0, 0.0, 0.0), 230 | y_axis: Vec3::new(0.0, 1.0, 0.0), 231 | z_axis: Vec3::new(0.0, 0.0, 1.0), 232 | }; 233 | 234 | pub const fn from_cols_array(m: &[Float; 9]) -> Self { 235 | Self { 236 | x_axis: Vec3::new(m[0], m[1], m[2]), 237 | y_axis: Vec3::new(m[3], m[4], m[5]), 238 | z_axis: Vec3::new(m[6], m[7], m[8]), 239 | } 240 | } 241 | 242 | pub fn transpose(&self) -> Self { 243 | Self { 244 | x_axis: Vec3::new(self.x_axis.x, self.y_axis.x, self.z_axis.x), 245 | y_axis: Vec3::new(self.x_axis.y, self.y_axis.y, self.z_axis.y), 246 | z_axis: Vec3::new(self.x_axis.z, self.y_axis.z, self.z_axis.z), 247 | } 248 | } 249 | 250 | pub fn inverse(&self) -> Self { 251 | let m00 = self.x_axis.x; 252 | let m01 = self.y_axis.x; 253 | let m02 = self.z_axis.x; 254 | 255 | let m10 = self.x_axis.y; 256 | let m11 = self.y_axis.y; 257 | let m12 = self.z_axis.y; 258 | 259 | let m20 = self.x_axis.z; 260 | let m21 = self.y_axis.z; 261 | let m22 = self.z_axis.z; 262 | 263 | let det = m00 * (m11 * m22 - m21 * m12) - m01 * (m10 * m22 - m12 * m20) 264 | + m02 * (m10 * m21 - m11 * m20); 265 | 266 | let inv_det = 1.0 / det; 267 | 268 | let result = Self { 269 | x_axis: Vec3::new( 270 | (m11 * m22 - m21 * m12) * inv_det, 271 | (m12 * m20 - m10 * m22) * inv_det, 272 | (m10 * m21 - m20 * m11) * inv_det, 273 | ), 274 | y_axis: Vec3::new( 275 | (m02 * m21 - m01 * m22) * inv_det, 276 | (m00 * m22 - m02 * m20) * inv_det, 277 | (m20 * m01 - m00 * m21) * inv_det, 278 | ), 279 | z_axis: Vec3::new( 280 | (m01 * m12 - m02 * m11) * inv_det, 281 | (m10 * m02 - m00 * m12) * inv_det, 282 | (m00 * m11 - m10 * m01) * inv_det, 283 | ), 284 | }; 285 | 286 | result 287 | } 288 | } 289 | 290 | impl Mul for Mat3 { 291 | type Output = Vec3; 292 | 293 | #[inline] 294 | fn mul(self, other: Vec3) -> Vec3 { 295 | let r0 = Vec3::new(self.x_axis.x, self.y_axis.x, self.z_axis.x); 296 | let r1 = Vec3::new(self.x_axis.y, self.y_axis.y, self.z_axis.y); 297 | let r2 = Vec3::new(self.x_axis.z, self.y_axis.z, self.z_axis.z); 298 | 299 | Vec3::new(r0.dot(other), r1.dot(other), r2.dot(other)) 300 | } 301 | } 302 | 303 | impl Mul for Mat3 { 304 | type Output = Mat3; 305 | 306 | #[inline] 307 | fn mul(self, other: Self) -> Self { 308 | let r0 = Vec3::new(self.x_axis.x, self.y_axis.x, self.z_axis.x); 309 | let r1 = Vec3::new(self.x_axis.y, self.y_axis.y, self.z_axis.y); 310 | let r2 = Vec3::new(self.x_axis.z, self.y_axis.z, self.z_axis.z); 311 | 312 | Self { 313 | x_axis: Vec3::new( 314 | r0.dot(other.x_axis), 315 | r1.dot(other.x_axis), 316 | r2.dot(other.x_axis), 317 | ), 318 | y_axis: Vec3::new( 319 | r0.dot(other.y_axis), 320 | r1.dot(other.y_axis), 321 | r2.dot(other.y_axis), 322 | ), 323 | z_axis: Vec3::new( 324 | r0.dot(other.z_axis), 325 | r1.dot(other.z_axis), 326 | r2.dot(other.z_axis), 327 | ), 328 | } 329 | } 330 | } 331 | 332 | impl From<[Float; 3]> for Vec3 { 333 | fn from(values: [Float; 3]) -> Self { 334 | Self { 335 | x: values[0], 336 | y: values[1], 337 | z: values[2], 338 | } 339 | } 340 | } 341 | 342 | impl From for [Float; 3] { 343 | fn from(v: Vec3) -> Self { 344 | [v.x, v.y, v.z] 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /kolor/src/details/color.rs: -------------------------------------------------------------------------------- 1 | use super::{conversion::ColorConversion, transform::ColorTransform}; 2 | use crate::{Float, Vec3}; 3 | #[cfg(feature = "serde")] 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Identifies an invertible mapping of colors in a linear [`ColorSpace`]. 7 | #[repr(u8)] 8 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 9 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 10 | pub enum TransformFn { 11 | None, 12 | /// The sRGB transfer functions (aka 'gamma correction'). 13 | Srgb, 14 | /// Oklab conversion from xyz. 15 | OkLab, 16 | /// Oklch (Oklab's LCh variant) conversion from xyz. 17 | OkLch, 18 | /// CIE xyY transform. 19 | CieXyY, 20 | /// CIELAB transform. 21 | CieLab, 22 | /// CIELCh transform. 23 | CieLch, 24 | /// CIE 1960 UCS transform. 25 | Cie1960Ucs, 26 | /// CIE 1960 UCS transform in uvV form. 27 | Cie1960UcsUvV, 28 | /// CIE 1964 UVW transform. 29 | Cie1964Uvw, 30 | /// CIE 1976 Luv transform. 31 | Cie1976Luv, 32 | /// (Hue, Saturation, Lightness), where L is defined as the average of the 33 | /// largest and smallest color components. 34 | Hsl, 35 | /// (Hue, Saturation, Value), 36 | /// where V is defined as the largest component of a color 37 | Hsv, 38 | /// (Hue, Saturation, Intensity), where I is defined as the average of the 39 | /// three components. 40 | Hsi, 41 | /// BT.2100 ICtCp with PQ transfer function. 42 | IctCpPq, 43 | /// BT.2100 ICtCp with HLG transfer function. 44 | IctCpHlg, 45 | /// The BT.601/BT.709/BT.2020 (they are equivalent) OETF and inverse. 46 | Bt601, 47 | /// SMPTE ST 2084:2014 aka "Perceptual Quantizer" transfer functions used in 48 | /// BT.2100 for digitally created/distributed HDR content. 49 | Pq, 50 | // ACEScc is a logarithmic transform 51 | // ACES_CC, 52 | // ACEScct is a logarithmic transform with toe 53 | // ACES_CCT, 54 | } 55 | 56 | impl TransformFn { 57 | pub const ENUM_COUNT: TransformFn = TransformFn::Pq; 58 | } 59 | 60 | /// A set of primary colors picked to define an RGB color space. 61 | #[repr(u8)] 62 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 63 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 64 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] 65 | pub enum RgbPrimaries { 66 | // Primaries 67 | None, 68 | /// BT.709 is the sRGB primaries. 69 | Bt709, 70 | // BT 2020 uses the same primaries as BT 2100. 71 | Bt2020, 72 | Ap0, 73 | Ap1, 74 | /// P3 is the primaries for DCI-P3 and the variations with different white 75 | /// points. 76 | P3, 77 | Adobe1998, 78 | AdobeWide, 79 | Apple, 80 | ProPhoto, 81 | CieRgb, 82 | /// The reference XYZ color space 83 | CieXyz, 84 | } 85 | impl RgbPrimaries { 86 | pub const ENUM_COUNT: RgbPrimaries = RgbPrimaries::CieXyz; 87 | 88 | pub const fn values(&self) -> &[[Float; 2]; 3] { 89 | match self { 90 | Self::None => &[[0.0; 2]; 3], 91 | Self::Bt709 => &[[0.64, 0.33], [0.30, 0.60], [0.15, 0.06]], 92 | Self::Bt2020 => &[[0.708, 0.292], [0.17, 0.797], [0.131, 0.046]], 93 | Self::Ap0 => &[[0.7347, 0.2653], [0.0000, 1.0000], [0.0001, -0.0770]], 94 | Self::Ap1 => &[[0.713, 0.293], [0.165, 0.830], [0.128, 0.044]], 95 | Self::Adobe1998 => &[[0.64, 0.33], [0.21, 0.71], [0.15, 0.06]], 96 | Self::AdobeWide => &[[0.735, 0.265], [0.115, 0.826], [0.157, 0.018]], 97 | Self::ProPhoto => &[ 98 | [0.734699, 0.265301], 99 | [0.159597, 0.840403], 100 | [0.036598, 0.000105], 101 | ], 102 | Self::Apple => &[[0.625, 0.34], [0.28, 0.595], [0.155, 0.07]], 103 | Self::P3 => &[[0.680, 0.320], [0.265, 0.690], [0.150, 0.060]], 104 | Self::CieRgb => &[[0.7350, 0.2650], [0.2740, 0.7170], [0.1670, 0.0090]], 105 | Self::CieXyz => &[[1.0, 0.0], [0.0, 1.0], [0.0, 0.0]], 106 | } 107 | } 108 | } 109 | 110 | /// Defines the color white ("achromatic point") in an RGB color system. 111 | /// 112 | /// White points are derived from an "illuminant" which are defined 113 | /// as some reference lighting condition based on a Spectral Power Distribution. 114 | #[repr(u8)] 115 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 116 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 117 | #[allow(non_camel_case_types, clippy::upper_case_acronyms)] 118 | pub enum WhitePoint { 119 | None, 120 | /// Incandescent/tungsten 121 | A, 122 | /// Old direct sunlight at noon 123 | B, 124 | /// Old daylight 125 | C, 126 | /// Equal energy 127 | E, 128 | /// ICC profile PCS 129 | D50, 130 | /// Mid-morning daylight 131 | D55, 132 | D60, 133 | /// Daylight, sRGB, Adobe-RGB 134 | D65, 135 | /// North sky daylight 136 | D75, 137 | /// P3-DCI white point, sort of greenish 138 | P3Dci, 139 | /// Cool fluorescent 140 | F2, 141 | /// Daylight fluorescent, D65 simulator 142 | F7, 143 | /// Ultralume 40, Philips TL84 144 | F11, 145 | } 146 | impl WhitePoint { 147 | pub const ENUM_COUNT: WhitePoint = WhitePoint::F11; 148 | 149 | // Pulled from http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html 150 | // Originally from ASTM E308-01 except B which comes from Wyszecki & Stiles, p. 151 | // 769 P3Dci is something I calculated myself from wikipedia constants 152 | pub const fn values(&self) -> &'static [Float; 3] { 153 | match self { 154 | Self::None => &[0.0, 0.0, 0.0], 155 | Self::A => &[1.09850, 1.00000, 0.35585], 156 | Self::B => &[0.99072, 1.00000, 0.85223], 157 | Self::C => &[0.98074, 1.00000, 1.18232], 158 | Self::D50 => &[0.96422, 1.00000, 0.82521], 159 | Self::D55 => &[0.95682, 1.00000, 0.92149], 160 | Self::D60 => &[0.9523, 1.00000, 1.00859], 161 | Self::D65 => &[0.95047, 1.00000, 1.08883], 162 | Self::D75 => &[0.94972, 1.00000, 1.22638], 163 | #[allow(clippy::excessive_precision)] 164 | Self::P3Dci => &[0.89458689458, 1.00000, 0.95441595441], 165 | Self::E => &[1.00000, 1.00000, 1.00000], 166 | Self::F2 => &[0.99186, 1.00000, 0.67393], 167 | Self::F7 => &[0.95041, 1.00000, 1.08747], 168 | Self::F11 => &[1.00962, 1.00000, 0.64350], 169 | } 170 | } 171 | } 172 | 173 | /// A color space defined in data by its [primaries][RgbPrimaries], [white 174 | /// point][WhitePoint], and an optional [invertible transform 175 | /// function][TransformFn]. 176 | /// 177 | /// See the [`spaces`][crate::spaces] module for defined color spaces. 178 | /// 179 | /// `ColorSpace` assumes that a color space is one of: 180 | /// 181 | /// * The CIE XYZ color space. 182 | /// 183 | /// * An RGB color space. 184 | /// 185 | /// * A color space which may be defined as an invertible mapping from one of 186 | /// the above ([`TransformFn`]). 187 | /// 188 | /// An example of a `TransformFn` is the sRGB "opto-eletronic transfer 189 | /// function", or 'gamma compensation'. 190 | /// 191 | /// `kolor` makes the distinction between 'linear' and 'non-linear' color 192 | /// spaces, where a linear color space can be defined as a linear transformation 193 | /// from the CIE XYZ color space. 194 | /// 195 | /// `ColorSpace` contains a reference [WhitePoint] to represent a color space's 196 | /// reference illuminant. 197 | /// 198 | /// A linear RGB `ColorSpace` can be thought of as defining a relative 199 | /// coordinate system in the CIE XYZ color coordinate space, where three RGB 200 | /// primaries each define an axis pointing from the black point (0,0,0) in CIE 201 | /// XYZ. 202 | /// 203 | /// Non-linear `ColorSpace`s -- such as sRGB with gamma compensation applied -- 204 | /// are defined as a non-linear mapping from a linear `ColorSpace`'s coordinate 205 | /// system. 206 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 207 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 208 | pub struct ColorSpace { 209 | primaries: RgbPrimaries, 210 | white_point: WhitePoint, 211 | transform_fn: TransformFn, 212 | } 213 | impl ColorSpace { 214 | pub const fn new( 215 | primaries: RgbPrimaries, 216 | white_point: WhitePoint, 217 | transform_fn: TransformFn, 218 | ) -> Self { 219 | Self { 220 | primaries, 221 | white_point, 222 | transform_fn, 223 | } 224 | } 225 | 226 | pub(crate) const fn linear(primaries: RgbPrimaries, white_point: WhitePoint) -> Self { 227 | Self { 228 | primaries, 229 | white_point, 230 | transform_fn: TransformFn::None, 231 | } 232 | } 233 | 234 | /// Whether the color space has a non-linear transform applied 235 | pub fn is_linear(&self) -> bool { 236 | self.transform_fn == TransformFn::None 237 | } 238 | 239 | pub fn as_linear(&self) -> Self { 240 | Self { 241 | primaries: self.primaries, 242 | white_point: self.white_point, 243 | transform_fn: TransformFn::None, 244 | } 245 | } 246 | 247 | pub fn primaries(&self) -> RgbPrimaries { 248 | self.primaries 249 | } 250 | 251 | pub fn white_point(&self) -> WhitePoint { 252 | self.white_point 253 | } 254 | 255 | pub fn transform_function(&self) -> TransformFn { 256 | self.transform_fn 257 | } 258 | 259 | /// Creates a new color space with the primaries and white point from 260 | /// `this`, but with the provided [`TransformFn`]. 261 | pub fn with_transform(&self, new_transform: TransformFn) -> Self { 262 | Self { 263 | primaries: self.primaries, 264 | white_point: self.white_point, 265 | transform_fn: new_transform, 266 | } 267 | } 268 | 269 | /// Creates a new color space with the transform function and white point 270 | /// from `this`, but with the provided [`WhitePoint`]. 271 | pub fn with_whitepoint(&self, new_wp: WhitePoint) -> Self { 272 | Self { 273 | primaries: self.primaries, 274 | white_point: new_wp, 275 | transform_fn: self.transform_fn, 276 | } 277 | } 278 | 279 | /// Creates a new color space with the primaries and transform function from 280 | /// `this`, but with the provided [`RgbPrimaries`]. 281 | pub fn with_primaries(&self, primaries: RgbPrimaries) -> Self { 282 | Self { 283 | primaries, 284 | white_point: self.white_point, 285 | transform_fn: self.transform_fn, 286 | } 287 | } 288 | 289 | /// Creates a CIE LAB color space using this space's white point. 290 | pub fn to_cie_lab(&self) -> Self { 291 | Self::new(RgbPrimaries::CieXyz, self.white_point, TransformFn::CieLab) 292 | } 293 | 294 | /// Creates a CIE uvV color space using this space's white point. 295 | pub fn to_cie_xyy(&self) -> Self { 296 | Self::new(RgbPrimaries::CieXyz, self.white_point, TransformFn::CieXyY) 297 | } 298 | 299 | /// Creates a CIE LCh color space using this space's white point. 300 | pub fn to_cie_lch(&self) -> Self { 301 | Self::new(RgbPrimaries::CieXyz, self.white_point, TransformFn::CieLch) 302 | } 303 | } 304 | 305 | pub mod color_spaces { 306 | use super::*; 307 | 308 | /// Linear sRGB is a linear encoding in [BT.709 309 | /// primaries][RgbPrimaries::Bt709] with a [D65 310 | /// whitepoint.][WhitePoint::D65] Linear sRGB is equivalent to [BT_709]. 311 | pub const LINEAR_SRGB: ColorSpace = ColorSpace::linear(RgbPrimaries::Bt709, WhitePoint::D65); 312 | 313 | /// Encoded sRGB is [linear sRGB][LINEAR_SRGB] with the [sRGB 314 | /// OETF](TransformFn::Srgb) applied (also called 'gamma-compressed'). 315 | pub const ENCODED_SRGB: ColorSpace = 316 | ColorSpace::new(RgbPrimaries::Bt709, WhitePoint::D65, TransformFn::Srgb); 317 | 318 | /// BT.709 is a linear encoding in [BT.709 primaries][RgbPrimaries::Bt709] 319 | /// with a [D65 whitepoint.][WhitePoint::D65]. It's equivalent to [Linear 320 | /// sRGB][LINEAR_SRGB] 321 | pub const BT_709: ColorSpace = ColorSpace::linear(RgbPrimaries::Bt709, WhitePoint::D65); 322 | 323 | /// Encoded BT.709 is [BT.709](BT_709) with the [BT.709 324 | /// OETF](TransformFn::Bt601) applied. 325 | pub const ENCODED_BT_709: ColorSpace = 326 | ColorSpace::new(RgbPrimaries::Bt709, WhitePoint::D65, TransformFn::Bt601); 327 | 328 | /// ACEScg is a linear encoding in [AP1 primaries][RgbPrimaries::Ap1] 329 | /// with a [D60 whitepoint][WhitePoint::D60]. 330 | pub const ACES_CG: ColorSpace = ColorSpace::linear(RgbPrimaries::Ap1, WhitePoint::D60); 331 | 332 | /// ACES2065-1 is a linear encoding in [AP0 primaries][RgbPrimaries::Ap0] 333 | /// with a [D60 whitepoint][WhitePoint::D60]. 334 | pub const ACES_2065_1: ColorSpace = ColorSpace::linear(RgbPrimaries::Ap0, WhitePoint::D60); 335 | 336 | /// CIE RGB is the original RGB space, defined in [CIE RGB 337 | /// primaries][RgbPrimaries::CieRgb] with white point 338 | /// [E][WhitePoint::E]. 339 | pub const CIE_RGB: ColorSpace = ColorSpace::linear(RgbPrimaries::CieRgb, WhitePoint::E); 340 | 341 | /// CIE XYZ reference color space. Uses [CIE XYZ 342 | /// primaries][RgbPrimaries::CieXyz] with white point 343 | /// [D65][WhitePoint::D65]. 344 | pub const CIE_XYZ: ColorSpace = ColorSpace::linear(RgbPrimaries::CieXyz, WhitePoint::D65); 345 | 346 | /// BT.2020 is a linear encoding in [BT.2020 347 | /// primaries][RgbPrimaries::Bt2020] with a [D65 white 348 | /// point][WhitePoint::D65] BT.2100 has the same linear color space as 349 | /// BT.2020. 350 | pub const BT_2020: ColorSpace = ColorSpace::linear(RgbPrimaries::Bt2020, WhitePoint::D65); 351 | 352 | /// Encoded BT.2020 is [BT.2020](BT_2020) with the [BT.2020 353 | /// OETF][TransformFn::Bt601] applied. 354 | pub const ENCODED_BT_2020: ColorSpace = 355 | ColorSpace::new(RgbPrimaries::Bt2020, WhitePoint::D65, TransformFn::Bt601); 356 | 357 | /// Encoded BT.2100 PQ is [BT.2020](BT_2020) (equivalent to the linear 358 | /// BT.2100 space) with the [Perceptual Quantizer inverse 359 | /// EOTF][TransformFn::Pq] applied. 360 | pub const ENCODED_BT_2100_PQ: ColorSpace = 361 | ColorSpace::new(RgbPrimaries::Bt2020, WhitePoint::D65, TransformFn::Pq); 362 | 363 | /// Oklab is a non-linear, perceptual encoding in 364 | /// [XYZ][RgbPrimaries::CieXyz], with a [D65 whitepoint][WhitePoint::D65]. 365 | /// 366 | /// Oklab's perceptual qualities make it a very attractive color space for 367 | /// performing blend operations between two colors which you want to be 368 | /// perceptually pleasing. See [this article](https://bottosson.github.io/posts/oklab/) 369 | /// for more on why you might want to use the Oklab colorspace. 370 | pub const OK_LAB: ColorSpace = 371 | ColorSpace::new(RgbPrimaries::CieXyz, WhitePoint::D65, TransformFn::OkLab); 372 | 373 | /// Oklch is a non-linear, perceptual encoding in 374 | /// [XYZ][RgbPrimaries::CieXyz], with a [D65 375 | /// whitepoint][WhitePoint::D65]. It is a variant of [Oklab](OK_LAB) with 376 | /// LCh coordinates intead of Lab. 377 | /// 378 | /// Oklch's qualities make it a very attractive color space for performing 379 | /// computational modifications to a color. You can think of it as an 380 | /// improved version of an HSL/HSV-style color space. See 381 | /// [this article](https://bottosson.github.io/posts/oklab/) for more on why 382 | /// you might want to use the Oklch colorspace. 383 | pub const OK_LCH: ColorSpace = 384 | ColorSpace::new(RgbPrimaries::CieXyz, WhitePoint::D65, TransformFn::OkLch); 385 | 386 | /// ICtCp_PQ is a non-linear encoding in [BT.2020 387 | /// primaries][RgbPrimaries::Bt2020], with a [D65 388 | /// whitepoint][WhitePoint::D65], using the PQ transfer function 389 | pub const ICT_CP_PQ: ColorSpace = 390 | ColorSpace::new(RgbPrimaries::Bt2020, WhitePoint::D65, TransformFn::IctCpPq); 391 | /// ICtCp_HLG is a non-linear encoding in [BT.2020 392 | /// primaries][RgbPrimaries::Bt2020], with a [D65 393 | /// whitepoint][WhitePoint::D65], using the HLG transfer function 394 | pub const ICT_CP_HLG: ColorSpace = 395 | ColorSpace::new(RgbPrimaries::Bt2020, WhitePoint::D65, TransformFn::IctCpHlg); 396 | 397 | /// Encoded Display P3 is [Display P3][DISPLAY_P3] with the [sRGB 398 | /// OETF](TransformFn::Srgb) applied. 399 | pub const ENCODED_DISPLAY_P3: ColorSpace = 400 | ColorSpace::new(RgbPrimaries::P3, WhitePoint::D65, TransformFn::Srgb); 401 | 402 | /// Display P3 by Apple is a linear encoding in [P3 403 | /// primaries][RgbPrimaries::P3] with a [D65 white 404 | /// point][WhitePoint::D65] 405 | pub const DISPLAY_P3: ColorSpace = ColorSpace::linear(RgbPrimaries::P3, WhitePoint::D65); 406 | 407 | /// P3-D60 (ACES Cinema) is a linear encoding in [P3 408 | /// primaries][RgbPrimaries::P3] with a [D60 white 409 | /// point][WhitePoint::D60] 410 | pub const P3_D60: ColorSpace = ColorSpace::linear(RgbPrimaries::P3, WhitePoint::D60); 411 | 412 | /// P3-DCI (Theater) is a linear encoding in [P3 413 | /// primaries][RgbPrimaries::P3] with a [P3-DCI white 414 | /// point][WhitePoint::P3Dci] 415 | pub const P3_THEATER: ColorSpace = ColorSpace::linear(RgbPrimaries::P3, WhitePoint::P3Dci); 416 | 417 | /// Adobe RGB (1998) is a linear encoding in [Adobe 1998 418 | /// primaries][RgbPrimaries::Adobe1998] with a [D65 white 419 | /// point][WhitePoint::D65] 420 | pub const ADOBE_1998: ColorSpace = ColorSpace::linear(RgbPrimaries::Adobe1998, WhitePoint::D65); 421 | 422 | /// Adobe Wide Gamut RGB is a linear encoding in [Adobe Wide 423 | /// primaries][RgbPrimaries::AdobeWide] with a [D50 white 424 | /// point][WhitePoint::D50] 425 | pub const ADOBE_WIDE: ColorSpace = ColorSpace::linear(RgbPrimaries::AdobeWide, WhitePoint::D50); 426 | 427 | /// Pro Photo RGB is a linear encoding in [Pro Photo 428 | /// primaries][RgbPrimaries::ProPhoto] with a [D50 white 429 | /// point][WhitePoint::D50] 430 | pub const PRO_PHOTO: ColorSpace = ColorSpace::linear(RgbPrimaries::ProPhoto, WhitePoint::D50); 431 | 432 | /// Apple RGB is a linear encoding in [Apple primaries][RgbPrimaries::Apple] 433 | /// with a [D65 white point][WhitePoint::D65] 434 | pub const APPLE: ColorSpace = ColorSpace::linear(RgbPrimaries::Apple, WhitePoint::D65); 435 | 436 | /// Array containing all built-in color spaces. 437 | pub const ALL_COLOR_SPACES: [ColorSpace; 22] = [ 438 | color_spaces::LINEAR_SRGB, 439 | color_spaces::ENCODED_SRGB, 440 | color_spaces::BT_709, 441 | color_spaces::ENCODED_BT_709, 442 | color_spaces::BT_2020, 443 | color_spaces::ENCODED_BT_2020, 444 | color_spaces::ENCODED_BT_2100_PQ, 445 | color_spaces::ACES_CG, 446 | color_spaces::ACES_2065_1, 447 | color_spaces::CIE_RGB, 448 | color_spaces::CIE_XYZ, 449 | color_spaces::OK_LAB, 450 | color_spaces::ICT_CP_PQ, 451 | color_spaces::ICT_CP_HLG, 452 | color_spaces::PRO_PHOTO, 453 | color_spaces::APPLE, 454 | color_spaces::P3_D60, 455 | color_spaces::P3_THEATER, 456 | color_spaces::DISPLAY_P3, 457 | color_spaces::ENCODED_DISPLAY_P3, 458 | color_spaces::ADOBE_1998, 459 | color_spaces::ADOBE_WIDE, 460 | ]; 461 | } 462 | 463 | /// A 3-component vector defined in a [`ColorSpace`]. 464 | #[derive(Copy, Clone, Debug)] 465 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 466 | pub struct Color { 467 | pub value: Vec3, 468 | pub space: ColorSpace, 469 | } 470 | impl Color { 471 | pub const fn new(x: Float, y: Float, z: Float, space: ColorSpace) -> Self { 472 | #[cfg(all(feature = "glam", feature = "f64"))] 473 | return Self { 474 | value: glam::f64::DVec3::new(x, y, z), 475 | space, 476 | }; 477 | #[cfg(all(feature = "glam", feature = "f32"))] 478 | return Self { 479 | value: glam::f32::Vec3::new(x, y, z), 480 | space, 481 | }; 482 | #[cfg(not(feature = "glam"))] 483 | return Self { 484 | value: Vec3::new(x, y, z), 485 | space, 486 | }; 487 | } 488 | 489 | pub const fn space(&self) -> ColorSpace { 490 | self.space 491 | } 492 | 493 | /// Equivalent to `Color::new(x, y, z, kolor::spaces::ENCODED_SRGB)` 494 | pub const fn srgb(x: Float, y: Float, z: Float) -> Self { 495 | #[cfg(all(feature = "glam", feature = "f64"))] 496 | return Self { 497 | value: glam::f64::DVec3::new(x, y, z), 498 | space: color_spaces::ENCODED_SRGB, 499 | }; 500 | #[cfg(all(feature = "glam", feature = "f32"))] 501 | return Self { 502 | value: glam::f32::Vec3::new(x, y, z), 503 | space: color_spaces::ENCODED_SRGB, 504 | }; 505 | #[cfg(not(feature = "glam"))] 506 | return Self { 507 | value: Vec3::new(x, y, z), 508 | space: color_spaces::ENCODED_SRGB, 509 | }; 510 | } 511 | 512 | /// Returns a `Color` converted into the provided [`ColorSpace`]. 513 | pub fn to(&self, space: ColorSpace) -> Color { 514 | let conversion = ColorConversion::new(self.space, space); 515 | let new_color = conversion.convert(self.value); 516 | Color { 517 | space, 518 | value: new_color, 519 | } 520 | } 521 | 522 | pub fn to_linear(&self) -> Color { 523 | if self.space.is_linear() { 524 | *self 525 | } else { 526 | let transform = ColorTransform::new(self.space.transform_function(), TransformFn::None) 527 | .unwrap_or_else(|| { 528 | panic!( 529 | "expected transform for {:?}", 530 | self.space.transform_function() 531 | ) 532 | }); 533 | let new_color_value = transform.apply(self.value, self.space().white_point); 534 | Self { 535 | value: new_color_value, 536 | space: self.space.as_linear(), 537 | } 538 | } 539 | } 540 | } 541 | 542 | #[cfg(test)] 543 | mod test { 544 | use super::*; 545 | use crate::details::conversion::LinearColorConversion; 546 | use color_spaces as spaces; 547 | #[test] 548 | fn linear_srgb_to_aces_cg() { 549 | let conversion = LinearColorConversion::new(spaces::LINEAR_SRGB, spaces::ACES_CG); 550 | let result = conversion.convert(Vec3::new(0.35, 0.2, 0.8)); 551 | assert!(result.abs_diff_eq(Vec3::new(0.32276854, 0.21838512, 0.72592676), 0.001)); 552 | } 553 | 554 | #[test] 555 | fn linear_srgb_to_aces_2065_1() { 556 | let conversion = ColorConversion::new(spaces::LINEAR_SRGB, spaces::ACES_2065_1); 557 | let result = conversion.convert(Vec3::new(0.35, 0.2, 0.8)); 558 | assert!(result.abs_diff_eq(Vec3::new(0.3741492, 0.27154857, 0.7261116), 0.001)); 559 | } 560 | 561 | #[test] 562 | fn linear_srgb_to_srgb() { 563 | let transform = ColorTransform::new(TransformFn::None, TransformFn::Srgb).unwrap(); 564 | let test = Vec3::new(0.35, 0.1, 0.8); 565 | let result = transform.apply(test, WhitePoint::D65); 566 | let expected = Vec3::new(0.6262097, 0.34919018, 0.9063317); 567 | assert!( 568 | result.abs_diff_eq(expected, 0.001), 569 | "{} != {}", 570 | result, 571 | expected 572 | ); 573 | } 574 | 575 | // #[test] 576 | // fn working_space_conversions() { 577 | // // just make sure we aren't missing a conversion 578 | // for src in &WORKING_SPACE_BY_WHITE_POINT { 579 | // for dst in &WORKING_SPACE_BY_WHITE_POINT { 580 | // let conversion = LinearColorConversion::new(*src, *dst); 581 | // let mut result = Vec3::new(0.35, 0.2, 0.8); 582 | // conversion.apply(&mut result); 583 | // } 584 | // } 585 | // } 586 | 587 | #[test] 588 | fn aces_cg_to_srgb() { 589 | let conversion = ColorConversion::new(spaces::ACES_CG, spaces::ENCODED_SRGB); 590 | let result = conversion.convert(Vec3::new(0.35, 0.1, 0.8)); 591 | let expected = Vec3::new(0.713855624199, 0.271821975708, 0.955197274685); 592 | assert!( 593 | result.abs_diff_eq(expected, 0.01), 594 | "{} != {}", 595 | result, 596 | expected 597 | ); 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /kolor/src/details/transform.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | color::{TransformFn, WhitePoint}, 3 | math::prelude::*, 4 | }; 5 | use crate::{Float, Mat3, Vec3, PI, TAU}; 6 | #[cfg(all(not(feature = "std"), feature = "libm"))] 7 | use num_traits::Float; 8 | 9 | /// Represents a reference to a function that can apply a [`TransformFn`] or 10 | /// its inverse. 11 | #[derive(Copy, Clone)] 12 | pub struct ColorTransform { 13 | first: for<'r> fn(Vec3, WhitePoint) -> Vec3, 14 | second: Option fn(Vec3, WhitePoint) -> Vec3>, 15 | } 16 | impl ColorTransform { 17 | #[inline] 18 | pub fn new(src_transform: TransformFn, dst_transform: TransformFn) -> Option { 19 | use super::transform::*; 20 | let from_transform = if src_transform == TransformFn::None { 21 | None 22 | } else { 23 | Some(TRANSFORMS_INVERSE[src_transform as usize - 1]) 24 | }; 25 | let to_transform = if dst_transform == TransformFn::None { 26 | None 27 | } else { 28 | Some(TRANSFORMS[dst_transform as usize - 1]) 29 | }; 30 | if let Some(from_transform) = from_transform { 31 | Some(Self { 32 | first: from_transform, 33 | second: to_transform, 34 | }) 35 | } else { 36 | to_transform.map(|to_transform| Self { 37 | first: to_transform, 38 | second: None, 39 | }) 40 | } 41 | } 42 | 43 | #[inline(always)] 44 | pub fn apply(&self, color: Vec3, white_point: WhitePoint) -> Vec3 { 45 | let mut color = (self.first)(color, white_point); 46 | if let Some(second) = self.second { 47 | color = second(color, white_point); 48 | } 49 | color 50 | } 51 | } 52 | 53 | // Keep in sync with TransformFn 54 | const TRANSFORMS: [fn(Vec3, WhitePoint) -> Vec3; 17] = [ 55 | // sRGB, 56 | srgb_oetf, 57 | // Oklab, 58 | xyz_to_ok_lab, 59 | // Oklch, 60 | xyz_to_ok_lch, 61 | // CIE_xyY, 62 | xyz_to_xyy, 63 | // CIE LAB, 64 | xyz_to_cie_lab, 65 | // CIE LCh, 66 | xyz_to_cie_lch, 67 | // CIE 1960 UCS, 68 | xyz_to_cie_1960_ucs, 69 | // CIE 1960 UCS_uvV, 70 | xyz_to_cie_1960_ucs_uvv, 71 | // CIE 1964 UVW, 72 | xyz_to_cie_1964_uvw, 73 | // CIE 1976 Luv, 74 | xyz_to_cie_1976_luv, 75 | // HSL, 76 | hsx::rgb_to_hsl, 77 | // HSV, 78 | hsx::rgb_to_hsv, 79 | // HSI, 80 | hsx::rgb_to_hsi, 81 | // ICtCp PQ, 82 | ict_cp::rgb_to_ict_cp_pq, 83 | // ICtCp HLG, 84 | ict_cp::rgb_to_ict_cp_hlg, 85 | // BT 601, 86 | bt601_oetf, 87 | // PQ, 88 | st_2084_pq_eotf_inverse, 89 | ]; 90 | 91 | // Keep in sync with TransformFn 92 | const TRANSFORMS_INVERSE: [fn(Vec3, WhitePoint) -> Vec3; 17] = [ 93 | // sRGB, 94 | srgb_eotf, 95 | // Oklab, 96 | ok_lab_to_xyz, 97 | // Oklch, 98 | ok_lch_to_xyz, 99 | //CIE_xyY, 100 | xyy_to_xyz, 101 | //CIELAB, 102 | cie_lab_to_xyz, 103 | //CIELCh, 104 | cie_lch_to_xyz, 105 | //CIE_1960_UCS, 106 | cie_1960_ucs_to_xyz, 107 | //CIE_1960_UCS_uvV, 108 | cie_1960_ucs_uvv_to_xyz, 109 | //CIE_1964_UVW, 110 | cie_1964_uvw_to_xyz, 111 | //CIE_1976_Luv, 112 | cie_1976_luv_to_xyz, 113 | //HSL, 114 | hsx::hsl_to_rgb, 115 | //HSV, 116 | hsx::hsv_to_rgb, 117 | //HSI, 118 | hsx::hsi_to_rgb, 119 | //ICtCp_PQ, 120 | ict_cp::ict_cp_pq_to_rgb, 121 | //ICtCp_HLG, 122 | ict_cp::ict_cp_hlg_to_rgb, 123 | //BT_601, 124 | bt601_oetf_inverse, 125 | //PQ, 126 | st_2084_pq_eotf, 127 | ]; 128 | 129 | /// Applies the sRGB OETF (opto-eletronic transfer function), sometimes called 130 | /// 'gamma compensation'. 131 | #[inline] 132 | pub fn srgb_oetf(color: Vec3, _wp: WhitePoint) -> Vec3 { 133 | let cutoff = color.cmplt(Vec3::splat(0.0031308)); 134 | let higher = Vec3::splat(1.055) * color.powf(1.0 / 2.4) - Vec3::splat(0.055); 135 | let lower = color * Vec3::splat(12.92); 136 | 137 | Vec3::select(cutoff, lower, higher) 138 | } 139 | 140 | /// Applies the sRGB EOTF (electro-optical transfer function), which is also the 141 | /// direct inverse of the sRGB OETF. 142 | #[inline] 143 | pub fn srgb_eotf(color: Vec3, _wp: WhitePoint) -> Vec3 { 144 | let cutoff = color.cmplt(Vec3::splat(0.04045)); 145 | let higher = ((color + Vec3::splat(0.055)) / 1.055).powf(2.4); 146 | let lower = color / 12.92; 147 | 148 | Vec3::select(cutoff, lower, higher) 149 | } 150 | 151 | /// Applies the BT.601/BT.709/BT.2020 OETF. 152 | #[inline] 153 | pub fn bt601_oetf(color: Vec3, _wp: WhitePoint) -> Vec3 { 154 | let cutoff = color.cmplt(Vec3::splat(0.0181)); 155 | let higher = 1.0993 * color.powf(0.45) - Vec3::splat(0.0993); 156 | let lower = 4.5 * color; 157 | 158 | Vec3::select(cutoff, lower, higher) 159 | } 160 | 161 | /// Applies the inverse of the BT.601/BT.709/BT.2020 OETF. 162 | #[inline] 163 | pub fn bt601_oetf_inverse(color: Vec3, _wp: WhitePoint) -> Vec3 { 164 | let cutoff = color.cmplt(Vec3::splat(0.08145)); 165 | let higher = ((color + Vec3::splat(0.0993)) / 1.0993).powf(1.0 / 0.45); 166 | let lower = color / 4.5; 167 | 168 | Vec3::select(cutoff, lower, higher) 169 | } 170 | 171 | #[rustfmt::skip] 172 | const OKLAB_M_1: Mat3 = 173 | Mat3::from_cols_array(&[0.8189330101,0.0329845436,0.0482003018, 174 | 0.3618667424,0.9293118715,0.2643662691, 175 | -0.1288597137,0.0361456387,0.6338517070]); 176 | 177 | #[rustfmt::skip] 178 | const OKLAB_M_2: Mat3 = 179 | Mat3::from_cols_array(&[0.2104542553,1.9779984951,0.02599040371, 180 | 0.7936177850,-2.4285922050,0.7827717662, 181 | -0.0040720468,0.4505937099,-0.8086757660]); 182 | 183 | #[inline] 184 | pub fn xyz_to_ok_lab(color: Vec3, _wp: WhitePoint) -> Vec3 { 185 | let mut lms = OKLAB_M_1 * color; 186 | // [cbrt] raises `lms` to (1. / 3.) but also avoids NaN when a component of 187 | // `lms` is negative. `lms` can contain negative numbers in some cases like 188 | // when `color` is (0.0, 0.0, 1.0) 189 | lms = lms.cbrt(); // non-linearity 190 | OKLAB_M_2 * lms 191 | } 192 | 193 | #[inline] 194 | pub fn ok_lab_to_xyz(color: Vec3, _wp: WhitePoint) -> Vec3 { 195 | let mut lms = OKLAB_M_2.inverse() * color; 196 | lms = lms.powf(3.0); // reverse non-linearity 197 | OKLAB_M_1.inverse() * lms 198 | } 199 | 200 | #[inline] 201 | pub fn xyz_to_ok_lch(color: Vec3, _wp: WhitePoint) -> Vec3 { 202 | let oklab = xyz_to_ok_lab(color, _wp); 203 | let lightness = oklab.x; 204 | let ab = oklab.yz(); 205 | let chroma = ab.length(); 206 | let hue = ab.y.atan2(ab.x); 207 | Vec3::new(lightness, chroma, hue) 208 | } 209 | 210 | #[inline] 211 | pub fn ok_lch_to_xyz(color: Vec3, _wp: WhitePoint) -> Vec3 { 212 | let lightness = color.x; 213 | let chroma = color.y; 214 | let hue = color.z; 215 | let (hue_s, hue_c) = hue.sin_cos(); 216 | let a = chroma * hue_c; 217 | let b = chroma * hue_s; 218 | let oklab = Vec3::new(lightness, a, b); 219 | ok_lab_to_xyz(oklab, _wp) 220 | } 221 | 222 | #[inline] 223 | pub fn xyz_to_xyy(color: Vec3, _wp: WhitePoint) -> Vec3 { 224 | let x = color.x / (color.x + color.y + color.z); 225 | let y = color.y / (color.x + color.y + color.z); 226 | let Y = color.y; 227 | Vec3::new(x, y, Y) 228 | } 229 | 230 | #[inline] 231 | pub fn xyy_to_xyz(color: Vec3, _wp: WhitePoint) -> Vec3 { 232 | let x = (color.z / color.y) * color.x; 233 | let y = color.z; 234 | let z = (color.z / color.y) * (1.0 - color.x - color.y); 235 | Vec3::new(x, y, z) 236 | } 237 | 238 | // CIELAB 239 | #[inline] 240 | pub fn xyz_to_cie_lab(color: Vec3, wp: WhitePoint) -> Vec3 { 241 | fn magic_f(v: Float) -> Float { 242 | if v > 0.008856 { 243 | v.powf(1.0 / 3.0) 244 | } else { 245 | v * 7.78703703704 + 0.13793103448 246 | } 247 | } 248 | let wp_value = wp.values(); 249 | let x = magic_f(color.x / wp_value[0]); 250 | let y = magic_f(color.y / wp_value[1]); 251 | let z = magic_f(color.z / wp_value[2]); 252 | let l = 116.0 * y - 16.0; 253 | let a = 500.0 * (x - y); 254 | let b = 200.0 * (y - z); 255 | Vec3::new(l, a, b) 256 | } 257 | 258 | #[inline] 259 | #[allow(non_snake_case)] 260 | pub fn cie_lab_to_xyz(color: Vec3, wp: WhitePoint) -> Vec3 { 261 | fn magic_f_inverse(v: Float) -> Float { 262 | if v > 0.008856 { 263 | v.powf(3.0) 264 | } else { 265 | 0.12841854934 * (v - 0.13793103448) 266 | } 267 | } 268 | let wp_value = wp.values(); 269 | let L = (color.x + 16.0) / 116.0; 270 | let a = color.y / 500.0; 271 | let b = color.z / 200.0; 272 | let X = wp_value[0] * magic_f_inverse(L + a); 273 | let Y = wp_value[1] * magic_f_inverse(L); 274 | let Z = wp_value[2] * magic_f_inverse(L - b); 275 | Vec3::new(X, Y, Z) 276 | } 277 | 278 | // CIELCh 279 | #[inline] 280 | pub fn xyz_to_cie_lch(color: Vec3, wp: WhitePoint) -> Vec3 { 281 | xyz_to_cie_lab(color, wp); 282 | cie_lab_to_cie_lch(color) 283 | } 284 | #[inline] 285 | pub fn cie_lch_to_xyz(color: Vec3, wp: WhitePoint) -> Vec3 { 286 | cie_lch_to_cie_lab(color); 287 | cie_lab_to_xyz(color, wp) 288 | } 289 | #[inline] 290 | pub fn cie_lab_to_cie_lch(color: Vec3) -> Vec3 { 291 | let mut h = color.z.atan2(color.y); 292 | if h > 0.0 { 293 | h = (h / PI) * 180.0; 294 | } else { 295 | h = 360.0 - (h.abs() / PI) * 180.0 296 | } 297 | let C = (color.y * color.y + color.z * color.z).sqrt(); 298 | Vec3::new(color.x, C, h) 299 | } 300 | #[inline] 301 | pub fn cie_lch_to_cie_lab(color: Vec3) -> Vec3 { 302 | let angle = (color.z / 360.0) * TAU; 303 | let a = color.y * angle.cos(); 304 | let b = color.y * angle.sin(); 305 | Vec3::new(color.x, a, b) 306 | } 307 | 308 | // CIE 1960 UCS 309 | #[inline] 310 | pub fn xyz_to_cie_1960_ucs(color: Vec3, _wp: WhitePoint) -> Vec3 { 311 | let U = (2.0 / 3.0) * color.x; 312 | let V = color.y; 313 | let W = 0.5 * (-color.x + 3.0 * color.y + color.z); 314 | Vec3::new(U, V, W) 315 | } 316 | 317 | #[inline] 318 | pub fn cie_1960_ucs_to_xyz(color: Vec3, _wp: WhitePoint) -> Vec3 { 319 | let X = (3.0 / 2.0) * color.x; 320 | let Y = color.y; 321 | let Z = (3.0 / 2.0) * color.x - 3.0 * color.y + 2.0 * color.z; 322 | Vec3::new(X, Y, Z) 323 | } 324 | 325 | #[inline] 326 | pub fn cie_1960_ucs_uvv_to_xyz(color: Vec3, wp: WhitePoint) -> Vec3 { 327 | cie_1960_ucs_to_xyz(cie_1960_uvv_to_ucs(color, wp), wp) 328 | } 329 | #[inline] 330 | pub fn xyz_to_cie_1960_ucs_uvv(color: Vec3, wp: WhitePoint) -> Vec3 { 331 | cie_1960_ucs_to_uvv(xyz_to_cie_1960_ucs(color, wp), wp) 332 | } 333 | 334 | #[inline] 335 | pub fn cie_1960_ucs_to_uvv(color: Vec3, _wp: WhitePoint) -> Vec3 { 336 | let u_v_w = color.x + color.y + color.z; 337 | 338 | let u = color.x / u_v_w; 339 | let v = color.y / u_v_w; 340 | Vec3::new(u, v, color.y) 341 | } 342 | 343 | #[inline] 344 | pub fn cie_1960_uvv_to_ucs(color: Vec3, _wp: WhitePoint) -> Vec3 { 345 | let U = color.z * (color.x / color.y); 346 | let W = -color.z * (color.x + color.y - 1.0) / color.y; 347 | Vec3::new(U, color.y, W) 348 | } 349 | 350 | #[inline] 351 | pub fn cie_1960_uvv_to_xyv(color: Vec3, _wp: WhitePoint) -> Vec3 { 352 | let d = 2.0 * color.x - 8.0 * color.y - 4.0; 353 | let x = 3.0 * (color.x / d); 354 | let y = 2.0 * (color.y / d); 355 | Vec3::new(x, y, color.z) 356 | } 357 | 358 | #[inline] 359 | pub fn cie_1960_xyv_to_uvv(color: Vec3, _wp: WhitePoint) -> Vec3 { 360 | let d = 12.0 * color.y - 2.0 * color.x + 3.0; 361 | let u = 4.0 * (color.x / d); 362 | let v = 6.0 * (color.y / d); 363 | Vec3::new(u, v, color.z) 364 | } 365 | 366 | // TODO finish implementing this. The wikipedia articles are so convoluted, 367 | // jeez. CIE 1964 UVW 368 | #[inline] 369 | pub fn xyz_to_cie_1964_uvw(_color: Vec3, _wp: WhitePoint) -> Vec3 { 370 | // // Convert the white point to uvV form 371 | // let mut wp_value = Vec3::from_slice(wp.values()); 372 | // XYZ_to_CIE_1960_UCS(&mut wp_value, wp); 373 | // CIE_1960_UCS_to_uvV(&mut wp_value); 374 | 375 | // // Convert the color to uvV form 376 | // let mut XYZ = *color; 377 | // XYZ_to_CIE_1960_UCS(&mut XYZ, wp); 378 | // CIE_1960_UCS_to_uvV(&mut XYZ); 379 | 380 | // // apply the UVW transform 381 | // let uvV = XYZ; 382 | // let W = 25.0 * color.z.powf(1.0 / 3.0) - 17.0; 383 | // let U = 13.0 * W * (uvV.x - wp_value.x); 384 | // let V = 13.0 * W * (uvV.y - wp_value.y); 385 | // *color = Vec3::new(U, V, W); 386 | todo!(); 387 | } 388 | 389 | #[inline] 390 | pub fn cie_1964_uvw_to_xyz(_color: Vec3, _wp: WhitePoint) -> Vec3 { 391 | // TODO 392 | todo!(); 393 | } 394 | 395 | // CIE 1976 Luv 396 | #[inline] 397 | pub fn xyz_to_cie_1976_luv(color: Vec3, wp: WhitePoint) -> Vec3 { 398 | let U = (4.0 * color.x) / (color.x + (15.0 * color.y) + (3.0 * color.z)); 399 | let V = (9.0 * color.y) / (color.x + (15.0 * color.y) + (3.0 * color.z)); 400 | 401 | let Y = color.y / 100.0; 402 | let Y = if Y > 0.008856 { 403 | Y.powf(1.0 / 3.0) 404 | } else { 405 | (7.787 * Y) + (16.0 / 116.0) 406 | }; 407 | 408 | let wp_values = wp.values(); 409 | let ref_U = 410 | (4.0 * wp_values[0]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2])); 411 | let ref_V = 412 | (9.0 * wp_values[1]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2])); 413 | 414 | Vec3::new( 415 | (116.0 * Y) - 16.0, 416 | 13.0 * color.x * (U - ref_U), 417 | 13.0 * color.x * (V - ref_V), 418 | ) 419 | } 420 | 421 | // Inverse of CIE 1976 Luv 422 | #[inline] 423 | pub fn cie_1976_luv_to_xyz(color: Vec3, wp: WhitePoint) -> Vec3 { 424 | let Y = (color.x + 16.0) / 116.0; 425 | let Y = if Y.powf(3.0) > 0.008856 { 426 | Y.powf(3.0) 427 | } else { 428 | (Y - 16.0 / 116.0) / 7.787 429 | }; 430 | 431 | let wp_values = wp.values(); 432 | let ref_U = 433 | (4.0 * wp_values[0]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2])); 434 | let ref_V = 435 | (9.0 * wp_values[1]) / (wp_values[0] + (15.0 * wp_values[1]) + (3.0 * wp_values[2])); 436 | 437 | let U = color.y / (13.0 * color.x) + ref_U; 438 | let V = color.z / (13.0 * color.x) + ref_V; 439 | 440 | Vec3::new( 441 | Y * 100.0, 442 | -(9.0 * color.y * U) / ((U - 4.0) * V - U * V), 443 | (9.0 * color.y - (15.0 * V * color.y) - (V * color.x)) / (3.0 * V), 444 | ) 445 | } 446 | 447 | /// ARIB STD-B67 or "Hybrid Log-Gamma" used in BT.2100 448 | #[allow(non_upper_case_globals)] 449 | pub mod hlg { 450 | use super::*; 451 | const HLG_a: Float = 0.17883277; 452 | const HLG_b: Float = 0.28466892; 453 | const HLG_c: Float = 0.55991073; 454 | const HLG_r: Float = 0.5; 455 | fn HLG_channel(E: Float) -> Float { 456 | if E <= 1.0 { 457 | HLG_r * E.sqrt() 458 | } else { 459 | HLG_a * (E - HLG_b).ln() + HLG_c 460 | } 461 | } 462 | /// ARIB STD-B67 or "Hybrid Log-Gamma" 463 | #[allow(non_upper_case_globals)] 464 | #[inline] 465 | pub fn arib_hlg_oetf(color: Vec3, _wp: WhitePoint) -> Vec3 { 466 | Vec3::new( 467 | HLG_channel(color.x), 468 | HLG_channel(color.y), 469 | HLG_channel(color.z), 470 | ) 471 | } 472 | 473 | /// Inverse of ARIB STD-B67 or "Hybrid Log-Gamma" 474 | #[inline] 475 | pub fn arib_hlg_oetf_inverse(color: Vec3, _wp: WhitePoint) -> Vec3 { 476 | fn HLG_channel_inverse(E_p: Float) -> Float { 477 | if E_p <= HLG_channel(1.0) { 478 | (E_p / HLG_r).powf(2.0) 479 | } else { 480 | ((E_p - HLG_c) / HLG_a).exp() + HLG_b 481 | } 482 | } 483 | Vec3::new( 484 | HLG_channel_inverse(color.x), 485 | HLG_channel_inverse(color.y), 486 | HLG_channel_inverse(color.z), 487 | ) 488 | } 489 | } 490 | 491 | pub use hlg::*; 492 | 493 | /// SMPTE ST 2084:2014 EOTF aka 'Perceptual Quantizer' used in BT.2100 494 | #[allow(non_upper_case_globals)] 495 | pub mod pq { 496 | use super::*; 497 | const L_p: Float = 10000.0; 498 | const M_1: Float = 0.1593017578125; 499 | const M_2: Float = 78.84375; 500 | const M_1_d: Float = 1.0 / M_1; 501 | const M_2_d: Float = 1.0 / M_2; 502 | const C_1: Float = 0.8359375; 503 | const C_2: Float = 18.8515625; 504 | const C_3: Float = 18.6875; 505 | 506 | /// SMPTE ST 2084:2014 perceptual electo-optical transfer function inverse. 507 | #[inline] 508 | pub fn st_2084_pq_eotf_inverse_float(f: Float) -> Float { 509 | let Y_p = (f / L_p).powf(M_1); 510 | ((C_1 + C_2 * Y_p) / (C_3 * Y_p + 1.0)).powf(M_2) 511 | } 512 | 513 | /// SMPTE ST 2084:2014 perceptual electo-optical transfer function inverse. 514 | #[inline] 515 | pub fn st_2084_pq_eotf_inverse(color: Vec3, _wp: WhitePoint) -> Vec3 { 516 | Vec3::new( 517 | st_2084_pq_eotf_inverse_float(color.x), 518 | st_2084_pq_eotf_inverse_float(color.y), 519 | st_2084_pq_eotf_inverse_float(color.z), 520 | ) 521 | } 522 | 523 | /// SMPTE ST 2084:2014 perceptual electo-optical transfer function inverse. 524 | #[inline] 525 | pub fn st_2084_pq_eotf_float(f: Float) -> Float { 526 | let V_p = f.powf(M_2_d); 527 | 528 | let n = (V_p - C_1).max(0.0); 529 | let L = (n / (C_2 - C_3 * V_p)).powf(M_1_d); 530 | L_p * L 531 | } 532 | 533 | /// SMPTE ST 2084:2014 perceptual electro-optical transfer function. 534 | #[inline] 535 | pub fn st_2084_pq_eotf(color: Vec3, _wp: WhitePoint) -> Vec3 { 536 | Vec3::new( 537 | st_2084_pq_eotf_float(color.x), 538 | st_2084_pq_eotf_float(color.y), 539 | st_2084_pq_eotf_float(color.z), 540 | ) 541 | } 542 | } 543 | 544 | pub use pq::*; 545 | 546 | /// BT.2100 ICtCp 547 | pub mod ict_cp { 548 | use super::*; 549 | 550 | #[rustfmt::skip] 551 | pub(crate) const ICT_CP_LMS: Mat3 = Mat3::from_cols_array(&[ 552 | 0.412109, 0.166748, 0.0241699, 553 | 0.523926, 0.720459, 0.0754395, 554 | 0.0639648, 0.112793, 0.900391, 555 | ]); 556 | 557 | #[rustfmt::skip] 558 | pub(crate) const ICT_CP_LMS_INVERSE: Mat3 = Mat3::from_cols_array(&[ 559 | 3.43661, -0.79133, -0.0259498, 560 | -2.50646, 1.9836, -0.0989137, 561 | 0.0698454, -0.192271, 1.12486, 562 | ]); 563 | 564 | #[rustfmt::skip] 565 | pub(crate) const ICT_CP_FROM_PQ_INVERSE: Mat3 = Mat3::from_cols_array(&[ 566 | 1.0, 1.0, 1.0, 567 | 0.00860904, -0.00860904, 0.560031, 568 | 0.11103, -0.11103, -0.320627, 569 | ]); 570 | 571 | #[rustfmt::skip] 572 | pub(crate) const ICT_CP_FROM_PQ: Mat3 = Mat3::from_cols_array(&[ 573 | 0.5, 1.61377, 4.37817, 574 | 0.5, -3.32349, -4.24561, 575 | 0.0, 1.70972, -0.132568, 576 | ]); 577 | 578 | /// ICtCp with the HLG transfer function. 579 | #[inline] 580 | pub fn rgb_to_ict_cp_hlg(color: Vec3, wp: WhitePoint) -> Vec3 { 581 | #[rustfmt::skip] 582 | const ICT_CP_FROM_HLG: Mat3 = Mat3::from_cols_array(&[ 583 | 0.5, 0.88501, 2.31934, 584 | 0.5, -1.82251, -2.24902, 585 | 0.0, 0.9375, -0.0703125 586 | ]); 587 | let lms = ICT_CP_LMS * color; 588 | let hlg = hlg::arib_hlg_oetf(lms, wp); 589 | ICT_CP_FROM_HLG * hlg 590 | } 591 | 592 | /// Inverse ICtCp with the HLG transfer function. 593 | #[inline] 594 | pub fn ict_cp_hlg_to_rgb(color: Vec3, wp: WhitePoint) -> Vec3 { 595 | #[rustfmt::skip] 596 | const ICT_CP_FROM_HLG_INVERSE: Mat3 = Mat3::from_cols_array(&[ 597 | 0.999998, 1.0, 1.0, 598 | 0.0157186, -0.0157186, 1.02127, 599 | 0.209581, -0.209581, -0.605275, 600 | ]); 601 | let lms_hlg = ICT_CP_FROM_HLG_INVERSE * color; 602 | let lms = hlg::arib_hlg_oetf_inverse(lms_hlg, wp); 603 | ICT_CP_LMS_INVERSE * lms 604 | } 605 | 606 | /// ICtCp with the PQ transfer function. 607 | #[inline] 608 | pub fn rgb_to_ict_cp_pq(color: Vec3, _wp: WhitePoint) -> Vec3 { 609 | let lms = ICT_CP_LMS * color; 610 | let pq = pq::st_2084_pq_eotf_inverse(lms, WhitePoint::D65); 611 | ICT_CP_FROM_PQ * pq 612 | } 613 | /// Inverse ICtCp with the PQ transfer function. 614 | #[inline] 615 | pub fn ict_cp_pq_to_rgb(color: Vec3, _wp: WhitePoint) -> Vec3 { 616 | let lms_pq = ICT_CP_FROM_PQ_INVERSE * color; 617 | let lms = pq::st_2084_pq_eotf(lms_pq, WhitePoint::D65); 618 | ICT_CP_LMS_INVERSE * lms 619 | } 620 | } 621 | 622 | pub use ict_cp::*; 623 | 624 | /// Transforms for Hue/Saturation/X color models, like HSL, HSI, HSV. 625 | pub mod hsx { 626 | use super::*; 627 | fn hsx_hue_and_chroma_from_rgb(color: Vec3, x_max: Float, x_min: Float) -> (Float, Float) { 628 | let chroma = x_max - x_min; 629 | let hue = if chroma == 0.0 { 630 | 0.0 631 | } else if color.x > color.y && color.x > color.z { 632 | 60.0 * (color.y - color.z) / chroma 633 | } else if color.y > color.x && color.y > color.z { 634 | 60.0 * (2.0 + (color.z - color.x) / chroma) 635 | } else { 636 | 60.0 * (4.0 + (color.x - color.y) / chroma) 637 | }; 638 | let hue = if hue < 0.0 { 360.0 + hue } else { hue }; 639 | (hue, chroma) 640 | } 641 | 642 | #[inline] 643 | pub fn rgb_to_hsl(color: Vec3, _wp: WhitePoint) -> Vec3 { 644 | rgb_to_hsx(color, |_, x_max, x_min, _| { 645 | let lightness = (x_max + x_min) / 2.0; 646 | let saturation = if lightness <= 0.0 || lightness >= 1.0 { 647 | 0.0 648 | } else { 649 | (x_max - lightness) / lightness.min(1.0 - lightness) 650 | }; 651 | (saturation, lightness) 652 | }) 653 | } 654 | 655 | #[inline] 656 | pub fn rgb_to_hsv(color: Vec3, _wp: WhitePoint) -> Vec3 { 657 | rgb_to_hsx(color, |_, max, _, chroma| { 658 | let value = max; 659 | let saturation = if value == 0.0 { 0.0 } else { chroma / value }; 660 | (saturation, value) 661 | }) 662 | } 663 | 664 | #[inline] 665 | pub fn rgb_to_hsi(color: Vec3, _wp: WhitePoint) -> Vec3 { 666 | rgb_to_hsx(color, |color, _, min, _| { 667 | let intensity = (color.x + color.y + color.z) * (1.0 / 3.0); 668 | let saturation = if intensity == 0.0 { 669 | 0.0 670 | } else { 671 | 1.0 - min / intensity 672 | }; 673 | (saturation, intensity) 674 | }) 675 | } 676 | 677 | #[inline(always)] 678 | fn rgb_to_hsx (Float, Float)>( 679 | color: Vec3, 680 | f: F, 681 | ) -> Vec3 { 682 | let x_max = color.x.max(color.y.max(color.z)); 683 | let x_min = color.x.min(color.y.min(color.z)); 684 | let (hue, chroma) = hsx_hue_and_chroma_from_rgb(color, x_max, x_min); 685 | let (saturation, vli) = f(color, x_max, x_min, chroma); 686 | 687 | Vec3::new(hue, saturation, vli) 688 | } 689 | 690 | #[inline(always)] 691 | fn hsx_to_rgb< 692 | F: FnOnce( 693 | Vec3, 694 | ) -> ( 695 | /* hue_prime: */ Float, 696 | /* chroma: */ Float, 697 | /* largest_component: */ Float, 698 | /* lightness_match: */ Float, 699 | ), 700 | >( 701 | color: Vec3, 702 | f: F, 703 | ) -> Vec3 { 704 | let (hue_prime, chroma, largest_component, lightness_match) = f(color); 705 | let (r, g, b) = rgb_from_hcx(hue_prime, chroma, largest_component); 706 | Vec3::new( 707 | r + lightness_match, 708 | g + lightness_match, 709 | b + lightness_match, 710 | ) 711 | } 712 | #[inline] 713 | pub fn hsl_to_rgb(color: Vec3, _wp: WhitePoint) -> Vec3 { 714 | hsx_to_rgb(color, |color| { 715 | let chroma = (1.0 - (2.0 * color.z - 1.0).abs()) * color.y; 716 | let hue_prime = color.x / 60.0; 717 | let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs()); 718 | let lightness_match = color.z - chroma / 2.0; 719 | (chroma, hue_prime, largest_component, lightness_match) 720 | }) 721 | } 722 | 723 | #[inline] 724 | pub fn hsv_to_rgb(color: Vec3, _wp: WhitePoint) -> Vec3 { 725 | hsx_to_rgb(color, |color| { 726 | let chroma = color.z * color.y; 727 | let hue_prime = color.x / 60.0; 728 | let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs()); 729 | let lightness_match = color.z - chroma; 730 | (chroma, hue_prime, largest_component, lightness_match) 731 | }) 732 | } 733 | 734 | #[inline] 735 | pub fn hsi_to_rgb(color: Vec3, _wp: WhitePoint) -> Vec3 { 736 | hsx_to_rgb(color, |color| { 737 | let hue_prime = color.x / 60.0; 738 | let z = 1.0 - (hue_prime % 2.0 - 1.0).abs(); 739 | let chroma = (3.0 * color.z * color.y) / (1.0 + z); 740 | let largest_component = chroma * z; 741 | let lightness_match = color.z * (1.0 - color.y); 742 | (hue_prime, chroma, largest_component, lightness_match) 743 | }) 744 | } 745 | 746 | #[inline(always)] 747 | fn rgb_from_hcx( 748 | hue_prime: Float, 749 | chroma: Float, 750 | largest_component: Float, 751 | ) -> (Float, Float, Float) { 752 | let (r, g, b) = if hue_prime < 1.0 { 753 | (chroma, largest_component, 0.0) 754 | } else if hue_prime < 2.0 { 755 | (largest_component, chroma, 0.0) 756 | } else if hue_prime < 3.0 { 757 | (0.0, chroma, largest_component) 758 | } else if hue_prime < 4.0 { 759 | (0.0, largest_component, chroma) 760 | } else if hue_prime < 5.0 { 761 | (largest_component, 0.0, chroma) 762 | } else { 763 | (chroma, 0.0, largest_component) 764 | }; 765 | (r, g, b) 766 | } 767 | } 768 | 769 | pub use hsx::*; 770 | 771 | #[cfg(test)] 772 | mod test { 773 | use crate::spaces; 774 | use crate::ColorConversion; 775 | 776 | use super::*; 777 | #[test] 778 | fn ictcp_pq_inverse() { 779 | let to_conv = ColorConversion::new(spaces::LINEAR_SRGB, spaces::ICT_CP_PQ); 780 | let from_conv = ColorConversion::new(spaces::ICT_CP_PQ, spaces::LINEAR_SRGB); 781 | let value = 0.5; 782 | 783 | let pq_val = crate::details::transform::pq::st_2084_pq_eotf_inverse_float(value); 784 | let inverse_val = crate::details::transform::pq::st_2084_pq_eotf_float(pq_val); 785 | assert!( 786 | (value - inverse_val).abs() < 0.001, 787 | "pq_val {:?} inverse {:?}", 788 | value, 789 | inverse_val 790 | ); 791 | // validate inverse matrices 792 | let value = Vec3::new(0.5, 0.5, 0.5); 793 | let result = ict_cp::ICT_CP_FROM_PQ_INVERSE * (ict_cp::ICT_CP_FROM_PQ * value); 794 | assert!( 795 | value.abs_diff_eq(result, 0.0001), 796 | "{:?} != {:?}", 797 | value, 798 | result 799 | ); 800 | 801 | let result = ict_cp::ICT_CP_LMS_INVERSE * (ict_cp::ICT_CP_LMS * value); 802 | assert!( 803 | value.abs_diff_eq(result, 0.0001), 804 | "{:?} != {:?}", 805 | value, 806 | result 807 | ); 808 | 809 | let to = |value: Vec3| from_conv.convert(to_conv.convert(value)); 810 | let from = |value: Vec3| from_conv.convert(to_conv.convert(value)); 811 | 812 | let value = Vec3::new(0.5, 0.5, 0.5); 813 | let result = from(to(value)); 814 | let allowed_error = 0.002; 815 | assert!( 816 | value.abs_diff_eq(result, allowed_error), 817 | "{:?} != {:?}", 818 | value, 819 | result 820 | ); 821 | 822 | let value = Vec3::new(1.0, 1.0, 1.0); 823 | let result = from(to(value)); 824 | assert!( 825 | value.abs_diff_eq(result, allowed_error), 826 | "{:?} != {:?}", 827 | value, 828 | result 829 | ); 830 | 831 | let value = Vec3::new(0.5, 0.5, 0.5); 832 | let result = from(to(value)); 833 | assert!( 834 | value.abs_diff_eq(result, allowed_error), 835 | "{:?} != {:?}", 836 | value, 837 | result 838 | ); 839 | } 840 | } 841 | -------------------------------------------------------------------------------- /kolor/src/details/generated_matrices.rs: -------------------------------------------------------------------------------- 1 | use super::color::{RgbPrimaries, WhitePoint}; 2 | use crate::Mat3; 3 | 4 | #[rustfmt::skip] 5 | pub const BT_709_D65_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 6 | 0.6274523942229207, 0.06910918409232207, 0.016397562151213345, 7 | 0.3292484773484975, 0.919531079275882, 0.088030140698202, 8 | 0.04329912842858205, 0.01135973663179622, 0.8955722971505848, 9 | ]); 10 | 11 | #[rustfmt::skip] 12 | pub const BT_709_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 13 | 0.6141439932086278, 0.07058736865623247, 0.020322353402197846, 14 | 0.3348473538601437, 0.9163972000879493, 0.1081907727011861, 15 | 0.05100865293122844, 0.01301543125581777, 0.8714868738966164, 16 | ]); 17 | 18 | #[rustfmt::skip] 19 | pub const BT_709_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 20 | 0.44031076211968634, 0.09011947729436987, 0.017244301211473752, 21 | 0.3795605229730174, 0.8131824149012692, 0.11019333877094085, 22 | 0.18012871490729662, 0.09669810780436126, 0.8725623600175858, 23 | ]); 24 | 25 | #[rustfmt::skip] 26 | pub const BT_709_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 27 | 0.8458657280419494, 0.09647578689010416, 0.0159702649068187, 28 | 0.21621113822313157, 0.8152111904937543, 0.10221736503498981, 29 | -0.0620768662650813, 0.08831302261614174, 0.8818123700581916, 30 | ]); 31 | 32 | #[rustfmt::skip] 33 | pub const BT_709_D65_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 34 | 0.4124564390896922, 0.21267285140562253, 0.0193338955823293, 35 | 0.357576077643909, 0.715152155287818, 0.11919202588130297, 36 | 0.18043748326639894, 0.07217499330655958, 0.9503040785363679, 37 | ]); 38 | 39 | #[rustfmt::skip] 40 | pub const BT_709_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 41 | 0.5329726559395586, 0.10009717799863485, 0.015622008755598295, 42 | 0.3154915630402366, 0.8723090507654531, 0.11222577163625706, 43 | 0.15153578102020504, 0.027593771235912504, 0.872152219608145, 44 | ]); 45 | 46 | #[rustfmt::skip] 47 | pub const BT_709_D65_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 48 | 0.9339964668491743, -0.02343991165235305, -0.0009532408239193435, 49 | 0.07679518382044535, 1.0401905017945687, -0.03208655629779869, 50 | -0.010791650669619457, -0.016750590142215978, 1.0330397971217185, 51 | ]); 52 | 53 | #[rustfmt::skip] 54 | pub const BT_709_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 55 | 0.8075781604673232, 0.033102859164962355, 0.01650441376091509, 56 | 0.18392605492200859, 0.9660576203335867, 0.07026264373757989, 57 | 0.008495784610668078, 0.0008395205014512276, 0.913232942501505, 58 | ]); 59 | 60 | #[rustfmt::skip] 61 | pub const BT_709_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 62 | 0.8508162319504055, 0.03353364183459157, 0.01652157729855184, 63 | 0.142111793278051, 0.9644901042729992, 0.0675453243302976, 64 | 0.007071974771543588, 0.0019762538924096677, 0.9159330983711503, 65 | ]); 66 | 67 | #[rustfmt::skip] 68 | pub const BT_709_D65_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 69 | 0.8224885805723389, 0.033200048527963384, 0.017089065365509325, 70 | 0.17751141942766147, 0.9667999514720368, 0.07241151216859087, 71 | 5.551115123125783e-17, -1.734723475976807e-17, 0.9104994224658999, 72 | ]); 73 | 74 | #[rustfmt::skip] 75 | pub const BT_709_D65_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 76 | 0.7151627357665418, 4.206704429243757e-17, 3.469446951953614e-18, 77 | 0.2848372642334579, 1.0, 0.04117050854683148, 78 | 0.0, 2.7755575615628914e-17, 0.9588294914531689, 79 | ]); 80 | 81 | #[rustfmt::skip] 82 | pub const BT_709_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 83 | 0.597501984923119, 0.09702573461016586, 0.010172006119462146, 84 | 0.3893468845053031, 0.8418084763809839, 0.06338000171115732, 85 | 0.013151130571577963, 0.06116578900885043, 0.9264479921693811, 86 | ]); 87 | 88 | #[rustfmt::skip] 89 | pub const BT_2020_D65_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 90 | 1.6603626561622697, -0.12456354851556833, -0.018156605779362994, 91 | -0.5875399968755312, 1.1329113745720412, -0.1006017318464808, 92 | -0.07282265928673903, -0.008347826056473065, 1.1187583376258436, 93 | ]); 94 | 95 | #[rustfmt::skip] 96 | pub const BT_2020_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 97 | 0.9770658332195041, 0.0028146277665209727, 0.004442606498810242, 98 | 0.013386657258545143, 0.9954145343208096, 0.022957272767006, 99 | 0.009547509521950659, 0.0017708379126687088, 0.9726001207341838, 100 | ]); 101 | 102 | #[rustfmt::skip] 103 | pub const BT_2020_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 104 | 0.6805256148458169, 0.046582418078859995, -0.0009370503256916523, 105 | 0.15318698933962568, 0.8585868129242537, 0.02692628565393756, 106 | 0.16628739581455765, 0.09483076899688637, 0.9740107646717544, 107 | ]); 108 | 109 | #[rustfmt::skip] 110 | pub const BT_2020_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 111 | 1.3786389455514936, 0.05703573036187016, -0.0022268458228812643, 112 | -0.24578684915486262, 0.8579942038410319, 0.017708194541964817, 113 | -0.13285209639663143, 0.08497006579709794, 0.9845186512809164, 114 | ]); 115 | 116 | #[rustfmt::skip] 117 | pub const BT_2020_D65_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 118 | 0.6370101914111008, 0.26272171736164046, 4.994515405547192e-17, 119 | 0.14461502739696927, 0.6779892755022618, 0.028072328847646908, 120 | 0.16884478119192986, 0.0592890071360975, 1.060757671152353, 121 | ]); 122 | 123 | #[rustfmt::skip] 124 | pub const BT_2020_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 125 | 0.8428777706211985, 0.057038696344272555, -0.003876364428896088, 126 | 0.02903646573698275, 0.9266617689312463, 0.03022327450505458, 127 | 0.12808576364181884, 0.01629953472448147, 0.9736530899238417, 128 | ]); 129 | 130 | #[rustfmt::skip] 131 | pub const BT_2020_D65_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 132 | 1.5414029136852074, -0.16818444014524703, -0.016342406504998852, 133 | -0.4606724852049418, 1.1939006752016097, -0.13971675014669718, 134 | -0.08073042848026585, -0.025716235056363355, 1.1560591566516965, 135 | ]); 136 | 137 | #[rustfmt::skip] 138 | pub const BT_2020_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 139 | 1.3178078828946478, -0.06538805693234334, 0.0020699375167084716, 140 | -0.2669672408216405, 1.074923955581199, -0.021968470506622317, 141 | -0.05084064207300748, -0.009535898648855661, 1.0198985329899135, 142 | ]); 143 | 144 | #[rustfmt::skip] 145 | pub const BT_2020_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 146 | 1.394833146472516, -0.064498185331963, 0.0023878884958014805, 147 | -0.33959995210584965, 1.0727806394101889, -0.025328677193069016, 148 | -0.055233194366666294, -0.008282454078225733, 1.022940788697267, 149 | ]); 150 | 151 | #[rustfmt::skip] 152 | pub const BT_2020_D65_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 153 | 1.343517871996279, -0.06530391190143064, 0.0028226319764587523, 154 | -0.282140231873606, 1.0757923055499805, -0.01960250237098883, 155 | -0.06137764012267283, -0.010488393648550264, 1.0167798703945299, 156 | ]); 157 | 158 | #[rustfmt::skip] 159 | pub const BT_2020_D65_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 160 | 1.151949159163225, -0.12456354851556817, -0.022537433724726153, 161 | -0.09749133498570377, 1.1329113745720412, -0.04981736995604846, 162 | -0.05445782417752176, -0.008347826056473065, 1.0723548036807746, 163 | ]); 164 | 165 | #[rustfmt::skip] 166 | pub const BT_2020_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 167 | 0.943332773318376, 0.055128692326713746, -0.00782676978795735, 168 | 0.08871617324415176, 0.8905345139894264, -0.02737480806261575, 169 | -0.0320489465625276, 0.05433679368385984, 1.0352015778505734, 170 | ]); 171 | 172 | #[rustfmt::skip] 173 | pub const AP1_D60_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 174 | 1.70150705829719, -0.13072902802234107, -0.023448377492443606, 175 | -0.6110425859262977, 1.140104545849696, -0.1272893163664402, 176 | -0.09046447237089245, -0.009375517827355101, 1.1507376938588838, 177 | ]); 178 | 179 | #[rustfmt::skip] 180 | pub const AP1_D60_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 181 | 1.0235570497856787, -0.002886007096581449, -0.004607244286351307, 182 | -0.013533964229444466, 1.004686935863517, -0.023652830658976578, 183 | -0.010023085556234018, -0.0018009287669354355, 1.028260074945328, 184 | ]); 185 | 186 | #[rustfmt::skip] 187 | pub const AP1_D60_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 188 | 0.6953485652425724, 0.044764966266919184, -0.005524339448726105, 189 | 0.1407615899913178, 0.8597374933493469, 0.004027057756628111, 190 | 0.16388984476611007, 0.09549754038373406, 1.0014972816920982, 191 | ]); 192 | 193 | #[rustfmt::skip] 194 | pub const AP1_D60_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 195 | 1.4124370364812864, 0.05551166869030181, -0.00686632764683196, 196 | -0.2624549583886008, 0.8592338655338057, -0.0054653231735984735, 197 | -0.1499820780926857, 0.08525446577589288, 1.0123316508204308, 198 | ]); 199 | 200 | #[rustfmt::skip] 201 | pub const AP1_D60_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 202 | 0.65082100305539, 0.2666808251374308, -0.004968186659891823, 203 | 0.13267789859374557, 0.6762089485818938, 0.003113980486518425, 204 | 0.16697109835086446, 0.0571102262806755, 1.0906842061733732, 205 | ]); 206 | 207 | #[rustfmt::skip] 208 | pub const AP1_D60_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 209 | 0.8620595623780856, 0.05563291137426895, -0.008540762358878459, 210 | 0.014735489314968304, 0.9298474833987243, 0.007387739975208292, 211 | 0.12320494830694627, 0.014519605227006998, 1.0011530223836702, 212 | ]); 213 | 214 | #[rustfmt::skip] 215 | pub const AP1_D60_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 216 | 1.5794152677295317, -0.17547349421914055, -0.02165040880038787, 217 | -0.48178341634029087, 1.2023808750435392, -0.16749448751267396, 218 | -0.09763185138924052, -0.026907380824398953, 1.189144896313062, 219 | ]); 220 | 221 | #[rustfmt::skip] 222 | pub const AP1_D60_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 223 | 1.3498562534092278, -0.06998671059441484, -0.0025168213891498996, 224 | -0.28485113880786134, 1.081072565838644, -0.04622293706936381, 225 | -0.06500511460136688, -0.011085855244228823, 1.0487397584585134, 226 | ]); 227 | 228 | #[rustfmt::skip] 229 | pub const AP1_D60_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 230 | 1.4289258610376225, -0.06907546554409785, -0.002195699257895263, 231 | -0.3587628358179171, 1.0788775130796278, -0.049675153925283176, 232 | -0.07016302521970591, -0.009802047535528706, 1.051870853183178, 233 | ]); 234 | 235 | #[rustfmt::skip] 236 | pub const AP1_D60_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 237 | 1.3762642298880274, -0.06989870104184694, -0.0017388554289439586, 238 | -0.30019397293806327, 1.0819663760951292, -0.04378230153446635, 239 | -0.07607025694996405, -0.012067675053282375, 1.0455211569634102, 240 | ]); 241 | 242 | #[rustfmt::skip] 243 | pub const AP1_D60_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 244 | 1.1796179440401169, -0.13072902802234096, -0.027865176431994407, 245 | -0.11225062764095695, 1.1401045458496961, -0.07511006652986907, 246 | -0.0673673163991601, -0.00937551782735508, 1.1029752429618633, 247 | ]); 248 | 249 | #[rustfmt::skip] 250 | pub const AP1_D60_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 251 | 0.9654465322442687, 0.05360692986711079, -0.012701568057950956, 252 | 0.07712299655349224, 0.8926770634112212, -0.05188263242874399, 253 | -0.04256952879776088, 0.05371600672166812, 1.0645842004866954, 254 | ]); 255 | 256 | #[rustfmt::skip] 257 | pub const AP0_D60_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 258 | 2.5160003103784887, -0.2770794071389051, -0.014731740460086115, 259 | -1.120815791571519, 1.3719171373747592, -0.15110489612376043, 260 | -0.3951845188069694, -0.09483773023585473, 1.1658366365838464, 261 | ]); 262 | 263 | #[rustfmt::skip] 264 | pub const AP0_D60_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 265 | 1.4868045741853277, -0.08107174635702696, 0.0036715936220255846, 266 | -0.2580996336789089, 1.1823452693102523, -0.03293394690523746, 267 | -0.22870494050641854, -0.10127352295322543, 1.0292623532832117, 268 | ]); 269 | 270 | #[rustfmt::skip] 271 | pub const AP0_D60_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 272 | 1.4516557250041906, -0.0765086914114762, 0.00831509386898968, 273 | -0.2366671199242865, 1.1761388905796017, -0.006034772993874998, 274 | -0.2149886050799043, -0.09963019916812614, 0.9977196791248852, 275 | ]); 276 | 277 | #[rustfmt::skip] 278 | pub const AP0_D60_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 279 | 2.069205280578771, 0.015553871876073877, -0.001131766411004165, 280 | -0.642055781290266, 0.9969260872585566, -0.010912136852638905, 281 | -0.427149499288505, -0.012479959134630403, 1.0120439032636432, 282 | ]); 283 | 284 | #[rustfmt::skip] 285 | pub const AP0_D60_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 286 | 0.9360054029938909, 0.3358677616752651, 0.0016187983759302863, 287 | 0.0010120714020409225, 0.7318564125095669, -0.0017437516095178966, 288 | 0.013452525604068349, -0.06772417418483223, 1.0889549532335876, 289 | ]); 290 | 291 | #[rustfmt::skip] 292 | pub const AP0_D60_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 293 | 1.2513107667262555, 0.009739152008420904, -0.0046387915338147005, 294 | -0.18743368567091243, 1.0803756841033534, 0.004668594705548271, 295 | -0.06387708105534297, -0.09011483611177436, 0.9999701968282665, 296 | ]); 297 | 298 | #[rustfmt::skip] 299 | pub const AP0_D60_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 300 | 2.328816016277643, -0.34694342719479554, -0.008726304388860531, 301 | -0.9398506893120087, 1.455858094862869, -0.19904906033145844, 302 | -0.38896532696563385, -0.10891466766807434, 1.207775364720319, 303 | ]); 304 | 305 | #[rustfmt::skip] 306 | pub const AP0_D60_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 307 | 1.980779622441597, -0.18440023636872904, 0.008603267785723689, 308 | -0.654098803073904, 1.2881219420707029, -0.06009785142650678, 309 | -0.3266808193676931, -0.10372170570197396, 1.0514945836407827, 310 | ]); 311 | 312 | #[rustfmt::skip] 313 | pub const AP0_D60_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 314 | 2.1011734697364726, -0.18289880667877567, 0.009359586506648344, 315 | -0.7597112738874179, 1.2853168459242563, -0.06425303242530164, 316 | -0.3414621958490551, -0.10241803924547992, 1.0548934459186528, 317 | ]); 318 | 319 | #[rustfmt::skip] 320 | pub const AP0_D60_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 321 | 2.0201967651473653, -0.18434902497485728, 0.009519113721169023, 322 | -0.6783272311001937, 1.2891582831664907, -0.057392020490003706, 323 | -0.34186953404717135, -0.10480925819163375, 1.0478729067688344, 324 | ]); 325 | 326 | #[rustfmt::skip] 327 | pub const AP0_D60_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 328 | 1.7204271248548744, -0.2770794071389049, -0.025532727313327733, 329 | -0.41079256362580624, 1.3719171373747592, -0.08840130447659705, 330 | -0.3096345612290683, -0.09483773023585478, 1.1139340317899247, 331 | ]); 332 | 333 | #[rustfmt::skip] 334 | pub const AP0_D60_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 335 | 1.395241436545884, 0.009967906305015595, -0.005616714115258846, 336 | -0.1375251971797913, 1.0369010493999957, -0.06443966219709778, 337 | -0.2577162393660929, -0.046868955705011456, 1.0700563763123572, 338 | ]); 339 | 340 | #[rustfmt::skip] 341 | pub const CIE_RGB_E_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 342 | 1.2185334191192088, -0.14361955351160366, -0.005420528602215063, 343 | -0.33818327540366827, 1.282135043995923, -0.14249696827539357, 344 | 0.1196498562844589, -0.13851549048431913, 1.1479174968776085, 345 | ]); 346 | 347 | #[rustfmt::skip] 348 | pub const CIE_RGB_E_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 349 | 0.717050487791725, -0.047912368438415454, 0.0024836527186421226, 350 | 0.20377711062258566, 1.1539727225157472, -0.020295190175264297, 351 | 0.07917240158568983, -0.10606035407733123, 1.0178115374566221, 352 | ]); 353 | 354 | #[rustfmt::skip] 355 | pub const CIE_RGB_E_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 356 | 0.6999878585579238, -0.04567003955800025, 0.004501236779897692, 357 | 0.21435772118210014, 1.149218837423462, 0.007658263657653921, 358 | 0.08565442025997627, -0.10354879786546178, 0.9878404995624483, 359 | ]); 360 | 361 | #[rustfmt::skip] 362 | pub const CIE_RGB_E_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 363 | 0.48104467274943546, -0.007499455413997103, 0.00045708997161162675, 364 | 0.3120743163183718, 0.9983535840969844, 0.011113516053665774, 365 | 0.20688101093219316, 0.009145871317012957, 0.9884293939747227, 366 | ]); 367 | 368 | #[rustfmt::skip] 369 | pub const CIE_RGB_E_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 370 | 0.45025897180509056, 0.15604791692624442, 0.0012895419111052487, 371 | 0.2932631561804732, 0.834714520874453, 0.010866423081768984, 372 | 0.20694787201443635, 0.009237562199302746, 1.076674035007126, 373 | ]); 374 | 375 | #[rustfmt::skip] 376 | pub const CIE_RGB_E_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 377 | 0.6033128312832805, -0.003458452670432044, -0.001809401524296317, 378 | 0.20266696119208083, 1.080634782922561, 0.014326445396159904, 379 | 0.19402020752463883, -0.07717633025212906, 0.9874829561281367, 380 | ]); 381 | 382 | #[rustfmt::skip] 383 | pub const CIE_RGB_E_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 384 | 1.1271351146339714, -0.17786321407137096, -0.002152920678724392, 385 | -0.21586241048509802, 1.3439785891923755, -0.1880219673641468, 386 | 0.0887272958511266, -0.16611537512100455, 1.1901748880428713, 387 | ]); 388 | 389 | #[rustfmt::skip] 390 | pub const CIE_RGB_E_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 391 | 0.957599547549719, -0.09841237458277918, 0.0050698849231954665, 392 | -0.038502010482023286, 1.2273018670851046, -0.045628244520213065, 393 | 0.08090246293230446, -0.12888949250232523, 1.0405583595970171, 394 | ]); 395 | 396 | #[rustfmt::skip] 397 | pub const CIE_RGB_E_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 398 | 1.0162996460197995, -0.09766848724257338, 0.005466423195296596, 399 | -0.10653304472536686, 1.2249844352556476, -0.049502783404690914, 400 | 0.09023339870556724, -0.12731594801307364, 1.044036360209394, 401 | ]); 402 | 403 | #[rustfmt::skip] 404 | pub const CIE_RGB_E_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 405 | 0.9767357114699056, -0.09839600871775112, 0.005488500040855956, 406 | -0.05055827060240418, 1.2283403971611084, -0.04268130607669524, 407 | 0.0738225591324988, -0.12994438844335726, 1.0371928060358393, 408 | ]); 409 | 410 | #[rustfmt::skip] 411 | pub const CIE_RGB_E_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 412 | 0.830541492927576, -0.1436195535116035, -0.01111025273841082, 413 | 0.12334376188146558, 1.2821350439959234, -0.08384414383808768, 414 | 0.04611474519095815, -0.1385154904843191, 1.0949543965764987, 415 | ]); 416 | 417 | #[rustfmt::skip] 418 | pub const CIE_RGB_E_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 419 | 0.6720870248256822, -0.0030026082918414576, -0.0017295159912901915, 420 | 0.295256110337545, 1.037783727676027, -0.05419431121347184, 421 | 0.03265686483677277, -0.034781119384185474, 1.0559238272047626, 422 | ]); 423 | 424 | #[rustfmt::skip] 425 | pub const CIE_XYZ_D65_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 426 | 3.240454162114104, -0.9692660305051866, 0.05564343095911472, 427 | -1.5371385127977162, 1.8760108454466937, -0.20402591351675378, 428 | -0.4985314095560159, 0.04155601753034983, 1.057225188223179, 429 | ]); 430 | 431 | #[rustfmt::skip] 432 | pub const CIE_XYZ_D65_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 433 | 1.7165106697619739, -0.6666930011826243, 0.01764363876745901, 434 | -0.355641669986716, 1.616502208346911, -0.04277978166904463, 435 | -0.25334554182190727, 0.01576875038999502, 0.9423050727200187, 436 | ]); 437 | 438 | #[rustfmt::skip] 439 | pub const CIE_XYZ_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 440 | 1.6683875898867835, -0.6587733206900565, 0.009480533572179267, 441 | -0.32625420396515725, 1.6080130379716489, -0.006077114685137944, 442 | -0.23832751540142538, 0.01665203947781438, 0.9157225204492794, 443 | ]); 444 | 445 | #[rustfmt::skip] 446 | pub const CIE_XYZ_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 447 | 1.06893470000754, -0.4907814415963974, -0.0023749289879929675, 448 | -0.0015098980622441612, 1.3672839905988126, 0.0021916865089035313, 449 | -0.013299106613449435, 0.09109690786928681, 0.9184772758339819, 450 | ]); 451 | 452 | #[rustfmt::skip] 453 | pub const CIE_XYZ_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 454 | 2.52796883750449, -0.47261710988528555, 0.0017421574634275722, 455 | -0.8819330576680001, 1.3630302419998659, -0.012700198200724612, 456 | -0.47833498646702716, 0.07914747245465469, 0.92856031685062, 457 | ]); 458 | 459 | #[rustfmt::skip] 460 | pub const CIE_XYZ_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 461 | 1.4297101770354976, -0.5196038018429585, -0.009624683083990499, 462 | -0.25830442795549097, 1.4769681681094518, 0.0085819200915327, 463 | -0.09238559188435147, 0.015520932953928073, 0.9189368886229329, 464 | ]); 465 | 466 | #[rustfmt::skip] 467 | pub const CIE_XYZ_D65_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 468 | 2.951537290948746, -1.085109338223177, 0.08549335448914223, 469 | -1.2894115658994107, 1.9908566080903678, -0.26949635273220945, 470 | -0.47384447804399604, 0.037202561107440885, 1.0912975249496384, 471 | ]); 472 | 473 | #[rustfmt::skip] 474 | pub const CIE_XYZ_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 475 | 2.439119468762483, -0.8290518233399817, 0.03619401666215811, 476 | -0.8980455789709774, 1.761279609428826, -0.07987927368905673, 477 | -0.38597788681768497, 0.024530254586231606, 0.9601847365265239, 478 | ]); 479 | 480 | #[rustfmt::skip] 481 | pub const CIE_XYZ_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 482 | 2.6196803751992856, -0.8260733000294576, 0.03903368565135238, 483 | -1.0426619981020324, 1.757444836598104, -0.08555427887032041, 484 | -0.4107763453556871, 0.025452139343051752, 0.9629179317885062, 485 | ]); 486 | 487 | #[rustfmt::skip] 488 | pub const CIE_XYZ_D65_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 489 | 2.493180755328967, -0.8295031158210789, 0.0358536257800717, 490 | -0.9312655254971399, 1.7626941211197926, -0.07618895478265218, 491 | -0.40265972375888176, 0.023625088741739585, 0.9570926215180214, 492 | ]); 493 | 494 | #[rustfmt::skip] 495 | pub const CIE_XYZ_D65_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 496 | 2.0413689792600795, -0.9692660305051864, 0.013447387216170281, 497 | -0.5649463871751956, 1.876010845446694, -0.11838974235412561, 498 | -0.34469438437784833, 0.041556017530349806, 1.0154095719504168, 499 | ]); 500 | 501 | #[rustfmt::skip] 502 | pub const CIE_XYZ_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 503 | 1.5595288587075375, -0.4981254404399512, 0.023080581784363265, 504 | -0.1907075059542211, 1.417620432099403, -0.0857536096921985, 505 | -0.2677900943044664, 0.05127784436097246, 0.9770269088136947, 506 | ]); 507 | 508 | #[rustfmt::skip] 509 | pub const PRO_PHOTO_D50_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 510 | 2.014817406399223, -0.23099828322662186, -0.006365327585173719, 511 | -0.6864632345027819, 1.2297708723281646, -0.1459470578200357, 512 | -0.3283541718964413, 0.001227410898457293, 1.152312385405209, 513 | ]); 514 | 515 | #[rustfmt::skip] 516 | pub const PRO_PHOTO_D50_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 517 | 1.1878705594081254, -0.0732400220799353, 0.007002671223725079, 518 | -0.032142193233522465, 1.0817137033090751, -0.03370556246894202, 519 | -0.15572836617460267, -0.008473681229139692, 1.026702891245217, 520 | ]); 521 | 522 | #[rustfmt::skip] 523 | pub const PRO_PHOTO_D50_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 524 | 1.1597141568820133, -0.06954836841710854, 0.01040664918027808, 525 | -0.017246312393758734, 1.0766033868504017, -0.008091632700577207, 526 | -0.1424678444882549, -0.007055018433293462, 0.9976849835202989, 527 | ]); 528 | 529 | #[rustfmt::skip] 530 | pub const PRO_PHOTO_D50_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 531 | 0.7983213802784045, -0.006884965417016886, 0.0037355009029674093, 532 | 0.13822606951717306, 0.9240515355290525, -0.003672929650349064, 533 | 0.06345255020442248, 0.08283342988796424, 0.9999374287473817, 534 | ]); 535 | 536 | #[rustfmt::skip] 537 | pub const PRO_PHOTO_D50_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 538 | 1.6547157301806925, 0.0055065679320672745, 0.0029521072759545975, 539 | -0.30570562757692443, 0.9234068703273834, -0.013956982496940848, 540 | -0.3490101026037681, 0.07108656174054917, 1.0110048752209866, 541 | ]); 542 | 543 | #[rustfmt::skip] 544 | pub const PRO_PHOTO_D50_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 545 | 0.7472764091110655, 0.26283862528744534, 0.005372119234487403, 546 | 0.13026614385564078, 0.7229474684675605, -0.0053872111511367735, 547 | 0.07292744703329353, 0.014213906244994037, 1.0888450919166492, 548 | ]); 549 | 550 | #[rustfmt::skip] 551 | pub const PRO_PHOTO_D50_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 552 | 1.8641614756921925, -0.2874027391513043, -0.0010843035022783526, 553 | -0.5451387457857313, 1.2977313176967176, -0.18957386655002437, 554 | -0.3190227299064614, -0.010328578545413376, 1.190658170052303, 555 | ]); 556 | 557 | #[rustfmt::skip] 558 | pub const PRO_PHOTO_D50_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 559 | 1.584581853357658, -0.15646677877091197, 0.01120980320896767, 560 | -0.32942574591068857, 1.165183101156069, -0.05820638163507183, 561 | -0.2551561074469701, -0.008716322385156738, 1.0469965784261037, 562 | ]); 563 | 564 | #[rustfmt::skip] 565 | pub const PRO_PHOTO_D50_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 566 | 1.6813667580710945, -0.15524397251125266, 0.011854893344772854, 567 | -0.4103212524753601, 1.1627937962041544, -0.061953923835351504, 568 | -0.2710455055957345, -0.0075498236929013515, 1.0500990304905784, 569 | ]); 570 | 571 | #[rustfmt::skip] 572 | pub const PRO_PHOTO_D50_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 573 | 1.616159475560828, -0.1564370933461824, 0.011908784270627207, 574 | -0.34630979824352387, 1.1661518069904386, -0.05556615845500732, 575 | -0.2698496773173037, -0.00971471364425635, 1.04365737418438, 576 | ]); 577 | 578 | #[rustfmt::skip] 579 | pub const PRO_PHOTO_D50_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 580 | 1.3751254093936205, -0.23099828322662164, -0.015613580605309985, 581 | -0.1406483538822113, 1.2297708723281646, -0.08930805101884001, 582 | -0.23447705551140952, 0.0012274108984572825, 1.1049216316241501, 583 | ]); 584 | 585 | #[rustfmt::skip] 586 | pub const PRO_PHOTO_D50_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 587 | 1.113835226426624, 0.0006434858275612965, -4.308155947052583e-5, 588 | 0.06672494379025404, 0.9596999777766997, -0.06425218691005824, 589 | -0.18056017021687829, 0.039656536395739, 1.064295268469529, 590 | ]); 591 | 592 | #[rustfmt::skip] 593 | pub const APPLE_D65_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 594 | 1.0687054988550844, 0.02411041553491973, 0.0017350289124138177, 595 | -0.07859531961022259, 0.9600703144118777, 0.029747576048495428, 596 | 0.009889820755138279, 0.01581927005320257, 0.9685173950390905, 597 | ]); 598 | 599 | #[rustfmt::skip] 600 | pub const APPLE_D65_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 601 | 0.6785752668186412, 0.09604735095096123, 0.02120045193936393, 602 | 0.26807491181921705, 0.8777207586094969, 0.10986745823615428, 603 | 0.05334982136214203, 0.0262318904395423, 0.8689320898244818, 604 | ]); 605 | 606 | #[rustfmt::skip] 607 | pub const APPLE_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 608 | 0.6645008729608355, 0.09755440847132052, 0.025839190240474576, 609 | 0.27472554464976623, 0.8746450887457072, 0.12819812935931924, 610 | 0.06077358238939848, 0.027800502782971956, 0.8459626804002062, 611 | ]); 612 | 613 | #[rustfmt::skip] 614 | pub const APPLE_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 615 | 0.4800264231402328, 0.11608512088021444, 0.022599807638077504, 616 | 0.3351568181976468, 0.7765058619430704, 0.1303946471964188, 617 | 0.18481675866212088, 0.10740901717671578, 0.8470055451655039, 618 | ]); 619 | 620 | #[rustfmt::skip] 621 | pub const APPLE_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 622 | 0.9090865900795666, 0.12291251015492827, 0.021061983027342822, 623 | 0.1392501719103686, 0.7777046170204908, 0.12311245025169504, 624 | -0.04833676198993514, 0.09938287282458137, 0.8558255667209621, 625 | ]); 626 | 627 | #[rustfmt::skip] 628 | pub const APPLE_D65_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 629 | 0.449728836561033, 0.24465248708920198, 0.025184814847417834, 630 | 0.3162486093896715, 0.6720282949530518, 0.14118241490610334, 631 | 0.18449255404929576, 0.08331921795774648, 0.9224627702464787, 632 | ]); 633 | 634 | #[rustfmt::skip] 635 | pub const APPLE_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 636 | 0.577460359786002, 0.12805401422674054, 0.020914345965193353, 637 | 0.2655127500552227, 0.8304317027441366, 0.13246122956749434, 638 | 0.15702689015877572, 0.041514283029123716, 0.8466244244673125, 639 | ]); 640 | 641 | #[rustfmt::skip] 642 | pub const APPLE_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 643 | 0.8675124948905033, 0.05867071486668177, 0.020916904837773406, 644 | 0.11336281074414185, 0.924906487197536, 0.09332637519708435, 645 | 0.019124694365354966, 0.01642279793578276, 0.8857567199651418, 646 | ]); 647 | 648 | #[rustfmt::skip] 649 | pub const APPLE_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 650 | 0.9127106300696042, 0.0590952734762466, 0.020874416753276785, 651 | 0.0697775144817165, 0.9234015191217945, 0.09079653161772622, 652 | 0.01751185544867956, 0.017503207401959667, 0.8883290516289966, 653 | ]); 654 | 655 | #[rustfmt::skip] 656 | pub const APPLE_D65_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 657 | 0.8832779428877661, 0.05879102299322121, 0.021588792597024824, 658 | 0.1057796913957724, 0.9255865649580165, 0.09526217351241187, 659 | 0.01094236571646201, 0.015622412048762697, 0.8831490338905631, 660 | ]); 661 | 662 | #[rustfmt::skip] 663 | pub const APPLE_D65_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 664 | 0.7711658930904475, 0.024110415534919837, 0.00265623495859435, 665 | 0.21725535803794266, 0.960070314411878, 0.06804943629959671, 666 | 0.011578748871609867, 0.015819270053202605, 0.929294328741809, 667 | ]); 668 | 669 | #[rustfmt::skip] 670 | pub const APPLE_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 671 | 0.647963789628587, 0.12409441268708254, 0.01400641110437829, 672 | 0.3272307406063183, 0.8023890939316797, 0.08760936820025444, 673 | 0.024805469765095067, 0.07351649338123839, 0.8983842206953676, 674 | ]); 675 | 676 | #[rustfmt::skip] 677 | pub const P3_D60_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 678 | 1.2482102533002837, -0.042754335519770426, -0.019268846991021594, 679 | -0.2368158837072179, 1.0433156964464794, -0.07599113930920087, 680 | -0.011394369593065612, -0.0005613609267097336, 1.0952599863002228, 681 | ]); 682 | 683 | #[rustfmt::skip] 684 | pub const P3_D60_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 685 | 0.7682813877763954, 0.04672996287001924, -0.0005527105281827093, 686 | 0.19162906112629663, 0.942131816546889, 0.0199044651851114, 687 | 0.04008955109730836, 0.011138220583091395, 0.9806482453430717, 688 | ]); 689 | 690 | #[rustfmt::skip] 691 | pub const P3_D60_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 692 | 0.7512817752823954, 0.048677131594867806, 0.003948398063807901, 693 | 0.20003624206980417, 0.9383863154947397, 0.04183919484945107, 694 | 0.04868198264780052, 0.012936552910391818, 0.9542124070867413, 695 | ]); 696 | 697 | #[rustfmt::skip] 698 | pub const P3_D60_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 699 | 0.5299016773207688, 0.07585772073182523, -4.0839745589962587e-17, 700 | 0.2780406828639037, 0.8197160545049514, 0.044575707697196904, 701 | 0.1920576398153277, 0.10442622476322316, 0.9554242923028036, 702 | ]); 703 | 704 | #[rustfmt::skip] 705 | pub const P3_D60_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 706 | 1.0477704607483118, 0.0838665635134947, -0.001427494749481395, 707 | 0.02997932616307752, 0.820964625027786, 0.035853042335789106, 708 | -0.07774978691138923, 0.09516881145871928, 0.9655744524136928, 709 | ]); 710 | 711 | #[rustfmt::skip] 712 | pub const P3_D60_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 713 | 0.49606760645754155, 0.23349384962564138, 0.0007255269520290713, 714 | 0.26167684843839906, 0.6902804997421549, 0.04756164830722708, 715 | 0.19272554510405962, 0.07622565063220348, 1.0405428247407442, 716 | ]); 717 | 718 | #[rustfmt::skip] 719 | pub const P3_D60_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 720 | 0.6488533819544192, 0.0871156299151109, -0.0021039544611262738, 721 | 0.19142553266236823, 0.8842922410375262, 0.04711152846611486, 722 | 0.15972108538321278, 0.028592129047362653, 0.9549924259950119, 723 | ]); 724 | 725 | #[rustfmt::skip] 726 | pub const P3_D60_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 727 | 1.16274858207755, -0.07340782722066358, -0.019723491363024835, 728 | -0.14024350815553172, 1.092070917637793, -0.1117525363973299, 729 | -0.02250507392201745, -0.018663090417130518, 1.1314760277603555, 730 | ]); 731 | 732 | #[rustfmt::skip] 733 | pub const P3_D60_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 734 | 1.0557853803038855, 0.0005828219109824108, 8.557199901031035e-5, 735 | -0.0537567406846886, 0.9981761880454939, -0.003044274509056047, 736 | -0.0020286396191970812, 0.001240990043524539, 1.0029587025100455, 737 | ]); 738 | 739 | #[rustfmt::skip] 740 | pub const P3_D60_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 741 | 1.0190492967079894, 0.00010575147693769788, 0.0006905664648886753, 742 | -0.009577909859960168, 1.0008152658631984, 0.0023112166790994343, 743 | -0.009471386848028673, -0.0009210173401365991, 0.996998216856012, 744 | ]); 745 | 746 | #[rustfmt::skip] 747 | pub const P3_D60_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 748 | 0.8804954315985086, -0.04275433551977037, -0.02023575649722097, 749 | 0.12781329344261477, 1.0433156964464796, -0.029908707661194814, 750 | -0.008308725041123566, -0.0005613609267097544, 1.0501444641584161, 751 | ]); 752 | 753 | #[rustfmt::skip] 754 | pub const P3_D60_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 755 | 0.7289084294918491, 0.08393896050232541, -0.007864552129707538, 756 | 0.2637143859951938, 0.8506467037323988, -0.006685380427873408, 757 | 0.007377184512957158, 0.06541433576527553, 1.0145499325575817, 758 | ]); 759 | 760 | #[rustfmt::skip] 761 | pub const P3_P3_DCI_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 762 | 1.182354106621989, -0.04107090001594271, -0.018298506227700292, 763 | -0.17359964469264416, 1.0430042167369105, -0.07378477562253867, 764 | -0.008754461929344093, -0.0019333167209677135, 1.0920832818502393, 765 | ]); 766 | 767 | #[rustfmt::skip] 768 | pub const P3_P3_DCI_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 769 | 0.7275560743544834, 0.043737692386858484, -0.0006153874156946622, 770 | 0.2312872210542876, 0.9462392876834534, 0.022889595985874547, 771 | 0.041156704591229526, 0.010023019929688555, 0.9777257914298204, 772 | ]); 773 | 774 | #[rustfmt::skip] 775 | pub const P3_P3_DCI_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 776 | 0.7114498080831262, 0.04558384447674402, 0.003637817603697678, 777 | 0.23886836101684814, 0.9425918611042775, 0.0450130153609723, 778 | 0.049681830900025675, 0.011824294418978044, 0.9513491670353302, 779 | ]); 780 | 781 | #[rustfmt::skip] 782 | pub const P3_P3_DCI_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 783 | 0.501718259081526, 0.07138556948062749, -0.00010345702471034101, 784 | 0.30615467729678786, 0.825373130292669, 0.047556694480555, 785 | 0.19212706362168616, 0.10324130022670353, 0.9525467625441557, 786 | ]); 787 | 788 | #[rustfmt::skip] 789 | pub const P3_P3_DCI_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 790 | 0.9923687450848496, 0.07897108912812728, -0.0014515000280000195, 791 | 0.08324746667516547, 0.8270043903322568, 0.03877638257583654, 792 | -0.0756162117600152, 0.09402452053961594, 0.9626751174521637, 793 | ]); 794 | 795 | #[rustfmt::skip] 796 | pub const P3_P3_DCI_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 797 | 0.4696818568161132, 0.22076198199592964, 0.0005750419617924146, 798 | 0.2880375262929713, 0.7036613664472136, 0.05084345498400761, 799 | 0.19275061689091594, 0.07557665155685697, 1.0374115030542002, 800 | ]); 801 | 802 | #[rustfmt::skip] 803 | pub const P3_P3_DCI_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 804 | 0.6144320075931488, 0.08201886686616203, -0.0020975500622206896, 805 | 0.22535413329252127, 0.8904091834916181, 0.049988422041326364, 806 | 0.16021385911433034, 0.027571949642219824, 0.9521091280208949, 807 | ]); 808 | 809 | #[rustfmt::skip] 810 | pub const P3_P3_DCI_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 811 | 1.1013579819201393, -0.07012932511971914, -0.01871232961829197, 812 | -0.08124749471613638, 1.090228178451082, -0.10952354089061031, 813 | -0.02011048720400227, -0.02009885333136297, 1.1282358705089028, 814 | ]); 815 | 816 | #[rustfmt::skip] 817 | pub const P3_P3_DCI_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 818 | 0.9471338856671345, -0.0005529164308617503, -8.248731390037532e-5, 819 | 0.05101350957153741, 1.0017935831975777, 0.003036385579039527, 820 | 0.0018526047613276475, -0.00124066676671589, 0.9970461017348607, 821 | ]); 822 | 823 | #[rustfmt::skip] 824 | pub const P3_P3_DCI_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 825 | 0.965182197130394, -0.0004531304252394793, 0.0005705412846523529, 826 | 0.04236143363088979, 1.0026129094981415, 0.005377861265457126, 827 | -0.00754363076128306, -0.0021597790729021293, 0.9940515974498907, 828 | ]); 829 | 830 | #[rustfmt::skip] 831 | pub const P3_P3_DCI_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 832 | 0.8338770747364402, -0.041070900015942546, -0.01923605726079095, 833 | 0.17293447085281105, 1.043004216736911, -0.027806004867596726, 834 | -0.006811545589251122, -0.001933316720967665, 1.047042062128388, 835 | ]); 836 | 837 | #[rustfmt::skip] 838 | pub const P3_P3_DCI_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 839 | 0.6902274525789381, 0.0790257014272277, -0.007528774859710993, 840 | 0.30139395680820025, 0.8566530534780787, -0.00401800483478959, 841 | 0.008378590612861947, 0.06432124509469359, 1.0115467796945015, 842 | ]); 843 | 844 | #[rustfmt::skip] 845 | pub const P3_D65_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 846 | 1.224900542983793, -0.04206325973338326, -0.01964475842589928, 847 | -0.22490054298379336, 1.042063259733383, -0.0786535768895766, 848 | 0.0, 2.0816681711721685e-17, 1.098298335315476, 849 | ]); 850 | 851 | #[rustfmt::skip] 852 | pub const P3_D65_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 853 | 0.7538669132425743, 0.045750243217439035, -0.0012107533203739543, 854 | 0.19857772608370303, 0.941773376950237, 0.017605190205230055, 855 | 0.047555360673722624, 0.012476379832324028, 0.9836055631151441, 856 | ]); 857 | 858 | #[rustfmt::skip] 859 | pub const P3_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 860 | 0.7371784868705633, 0.04766016774574233, 0.003221856035134071, 861 | 0.20679879452838343, 0.9380450057725793, 0.039625561114924386, 862 | 0.05602271860105304, 0.014294826481677674, 0.9571525828499419, 863 | ]); 864 | 865 | #[rustfmt::skip] 866 | pub const P3_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 867 | 0.5198327536497372, 0.07428268257461915, -0.0006538138863785318, 868 | 0.2823321786250631, 0.8195139465956947, 0.042320026420121455, 869 | 0.19783506772519982, 0.10620337082968641, 0.9583337874662574, 870 | ]); 871 | 872 | #[rustfmt::skip] 873 | pub const P3_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 874 | 1.0282263293472498, 0.08214791570802277, -0.002060600405534458, 875 | 0.0399525895332899, 0.8208580385659908, 0.033567542310028294, 876 | -0.06817891888054028, 0.09699404572598645, 0.9684930580955062, 877 | ]); 878 | 879 | #[rustfmt::skip] 880 | pub const P3_D65_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 881 | 0.48663265, 0.2290036, -3.972579210032023e-17, 882 | 0.2656631625, 0.6917267249999999, 0.04511261250000004, 883 | 0.1981741875, 0.079269675, 1.0437173875, 884 | ]); 885 | 886 | #[rustfmt::skip] 887 | pub const P3_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 888 | 0.6365910082850053, 0.08537485254155586, -0.0027183944386991875, 889 | 0.19697749567977313, 0.8843189544449657, 0.04483506350137651, 890 | 0.16643149603522167, 0.030306193013478776, 0.957883330937323, 891 | ]); 892 | 893 | #[rustfmt::skip] 894 | pub const P3_D65_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 895 | 1.1410345229956071, -0.0721363024628075, -0.02011177731010516, 896 | -0.12918207102985801, 1.0905334477315547, -0.11447411218331474, 897 | -0.011852451965749244, -0.018397145268747618, 1.1345858894934204, 898 | ]); 899 | 900 | #[rustfmt::skip] 901 | pub const P3_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 902 | 0.9812995002020412, -0.00010431461347204616, -0.0006794509977291543, 903 | 0.009369593702863117, 0.9991822706442649, -0.002322769506927544, 904 | 0.00933090609509557, 0.0009220439692071078, 1.0030022205046567, 905 | ]); 906 | 907 | #[rustfmt::skip] 908 | pub const P3_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 909 | 1.0360486519876642, 0.0004669552947975098, -0.0005971719689227621, 910 | -0.04381579010664352, 0.9973625283450084, -0.005370625232457939, 911 | 0.007767138118979333, 0.002170516360194257, 1.0059677972013803, 912 | ]); 913 | 914 | #[rustfmt::skip] 915 | pub const P3_D65_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 916 | 0.8640220395350139, -0.04206325973338315, -0.020567739525586215, 917 | 0.13597796046498564, 1.0420632597333832, -0.03251309478881315, 918 | 0.0, 2.7755575615628914e-17, 1.0530808343143996, 919 | ]); 920 | 921 | #[rustfmt::skip] 922 | pub const P3_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 923 | 0.7152449558537866, 0.08223607927060822, -0.008406120655241963, 924 | 0.27031117933193294, 0.8505856364827138, -0.009110166900753794, 925 | 0.01444386481428056, 0.06717828424667806, 1.0175162875559962, 926 | ]); 927 | 928 | #[rustfmt::skip] 929 | pub const ADOBE_1998_D65_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 930 | 1.3982831459026692, 1.0842021724855044e-17, 6.938893903907228e-18, 931 | -0.39828314590266956, 0.9999999999999998, -0.0429383002022965, 932 | 5.551115123125783e-17, 0.0, 1.042938300202296, 933 | ]); 934 | 935 | #[rustfmt::skip] 936 | pub const ADOBE_1998_D65_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 937 | 0.8773561076981875, 0.09663420734337877, 0.022928434789933144, 938 | 0.07748557289826628, 0.8915182882431102, 0.043044915911570086, 939 | 0.04515831940354631, 0.01184750441351132, 0.9340266492984967, 940 | ]); 941 | 942 | #[rustfmt::skip] 943 | pub const ADOBE_1998_D65_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 944 | 0.8587471948609877, 0.09870112790562824, 0.02841640424737102, 945 | 0.08805392735530782, 0.8877245803440288, 0.06267655684227907, 946 | 0.0531988777837043, 0.01357429175034244, 0.9089070389103497, 947 | ]); 948 | 949 | #[rustfmt::skip] 950 | pub const ADOBE_1998_D65_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 951 | 0.615679117631517, 0.12601254621827573, 0.024112415746872735, 952 | 0.19645774662544324, 0.7731372935954656, 0.06585887967588244, 953 | 0.18786313574303995, 0.10085016018625892, 0.9100287045772449, 954 | ]); 955 | 956 | #[rustfmt::skip] 957 | pub const ADOBE_1998_D65_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 958 | 1.182759791217749, 0.13490046679613024, 0.02233095225480546, 959 | -0.1180174498333602, 0.7729944995108641, 0.057993153419345814, 960 | -0.06474234138438917, 0.0921050336930058, 0.9196758943258484, 961 | ]); 962 | 963 | #[rustfmt::skip] 964 | pub const ADOBE_1998_D65_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 965 | 0.5767308871981476, 0.2973768637115448, 0.027034260337413137, 966 | 0.185553950711214, 0.6273490714521998, 0.07068721931855779, 967 | 0.18818516209063835, 0.07527406483625534, 0.9911085203440287, 968 | ]); 969 | 970 | #[rustfmt::skip] 971 | pub const ADOBE_1998_D65_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 972 | 0.7452466820272671, 0.13996419694791049, 0.021843991548097033, 973 | 0.09671084809569294, 0.8312572021831361, 0.06855505501612455, 974 | 0.15804246987704001, 0.028778600868953598, 0.9096009534357784, 975 | ]); 976 | 977 | #[rustfmt::skip] 978 | pub const ADOBE_1998_D65_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 979 | 1.3059915179278416, -0.03277563340493284, -0.0013329005780727855, 980 | -0.2947364921220919, 1.0502454654152404, -0.07606386947337708, 981 | -0.01125502580574994, -0.017469832010308065, 1.0773967700514497, 982 | ]); 983 | 984 | #[rustfmt::skip] 985 | pub const ADOBE_1998_D65_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 986 | 1.1292229307805395, 0.04628717005155653, 0.023077843594891664, 987 | -0.13808350994127472, 0.9528372618636752, 0.024476543663847106, 988 | 0.00886057916073496, 0.0008755680847685569, 0.9524456127412606, 989 | ]); 990 | 991 | #[rustfmt::skip] 992 | pub const ADOBE_1998_D65_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 993 | 1.1896819973966681, 0.046889526198046115, 0.02310184308029319, 994 | -0.1970576307439755, 0.9510493629266364, 0.0216364482054762, 995 | 0.007375633347307176, 0.002061110875317917, 0.95526170871423, 996 | ]); 997 | 998 | #[rustfmt::skip] 999 | pub const ADOBE_1998_D65_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 1000 | 1.150071919911711, 0.04642306829980194, 0.023895352079820728, 1001 | -0.1500719199117111, 0.9535769317001981, 0.02650992791842105, 1002 | 1.1102230246251565e-16, 1.0408340855860843e-17, 0.9495947200017577, 1003 | ]); 1004 | 1005 | #[rustfmt::skip] 1006 | pub const ADOBE_1998_D65_TO_ADOBE_WIDE_D50: Mat3 = Mat3::from_cols_array(&[ 1007 | 0.8354769551613883, 0.13566944942422027, 0.014223344716862733, 1008 | 0.15080722707455171, 0.8005384065563572, 0.01954856110417299, 1009 | 0.013715817764060001, 0.06379214401942276, 0.9662280941789644, 1010 | ]); 1011 | 1012 | #[rustfmt::skip] 1013 | pub const ADOBE_WIDE_D50_TO_BT_709_D65: Mat3 = Mat3::from_cols_array(&[ 1014 | 1.8093866923489754, -0.20813865470257426, -0.005627152586981786, 1015 | -0.8391010386181105, 1.2903771373179227, -0.07906408658737296, 1016 | 0.029714346269134995, -0.08223848261534873, 1.0846912391743542, 1017 | ]); 1018 | 1019 | #[rustfmt::skip] 1020 | pub const ADOBE_WIDE_D50_TO_BT_2020_D65: Mat3 = Mat3::from_cols_array(&[ 1021 | 1.0665310262487178, -0.0664086467533202, 0.0063075337164284345, 1022 | -0.10506465404786897, 1.127654146400289, 0.029025263872785353, 1023 | 0.03853362779915137, -0.06124549964696849, 0.9646672024107859, 1024 | ]); 1025 | 1026 | #[rustfmt::skip] 1027 | pub const ADOBE_WIDE_D50_TO_AP1_D60: Mat3 = Mat3::from_cols_array(&[ 1028 | 1.0412422572612832, -0.06309107472265485, 0.00934832430492482, 1029 | -0.08728244520195952, 1.1222390081591018, 0.053651078057901, 1030 | 0.046040187940676186, -0.059147933436447066, 0.9370005976371737, 1031 | ]); 1032 | 1033 | #[rustfmt::skip] 1034 | pub const ADOBE_WIDE_D50_TO_AP0_D60: Mat3 = Mat3::from_cols_array(&[ 1035 | 0.7166776050835062, -0.006737845934949693, 0.003356074300453388, 1036 | 0.10606929095115247, 0.9660473020918684, 0.05873290799264664, 1037 | 0.17725310396534144, 0.04069054384308091, 0.9379110177068998, 1038 | ]); 1039 | 1040 | #[rustfmt::skip] 1041 | pub const ADOBE_WIDE_D50_TO_CIE_RGB_E: Mat3 = Mat3::from_cols_array(&[ 1042 | 1.4858456123903017, 0.004388093591317532, 0.002658907190655399, 1043 | -0.4258648506053944, 0.9639945608509651, 0.04877859542761592, 1044 | -0.059980761784907244, 0.031617345557717044, 0.9485624973817286, 1045 | ]); 1046 | 1047 | #[rustfmt::skip] 1048 | pub const ADOBE_WIDE_D50_TO_CIE_XYZ_D65: Mat3 = Mat3::from_cols_array(&[ 1049 | 0.6708524390571574, 0.23555047994769124, 0.004826519405767202, 1050 | 0.10104924421804815, 0.7386555304664713, 0.0624446493344398, 1051 | 0.17856831672479434, 0.02579398958583737, 1.0215588312597925, 1052 | ]); 1053 | 1054 | #[rustfmt::skip] 1055 | pub const ADOBE_WIDE_D50_TO_PRO_PHOTO_D50: Mat3 = Mat3::from_cols_array(&[ 1056 | 0.897834926579531, -0.0006020048599792438, 0.0, 1057 | -0.052095847301785764, 1.039434333443575, 0.06274920755669666, 1058 | 0.15426092072225456, -0.0388323285835958, 0.9372507924433031, 1059 | ]); 1060 | 1061 | #[rustfmt::skip] 1062 | pub const ADOBE_WIDE_D50_TO_APPLE_D65: Mat3 = Mat3::from_cols_array(&[ 1063 | 1.6740374578348158, -0.2588214577648681, -0.0008594011663698442, 1064 | -0.6837694239377945, 1.3632308662929693, -0.12228024126437372, 1065 | 0.009731966102978384, -0.10440940852810146, 1.1231396424307434, 1066 | ]); 1067 | 1068 | #[rustfmt::skip] 1069 | pub const ADOBE_WIDE_D50_TO_P3_D60: Mat3 = Mat3::from_cols_array(&[ 1070 | 1.4228912478686708, -0.141182784719579, 0.0100995933657206, 1071 | -0.44097740843507904, 1.2187356471820507, 0.004612509917540142, 1072 | 0.01808616056640766, -0.07755286246247128, 0.9852878967167386, 1073 | ]); 1074 | 1075 | #[rustfmt::skip] 1076 | pub const ADOBE_WIDE_D50_TO_P3_P3_DCI: Mat3 = Mat3::from_cols_array(&[ 1077 | 1.5098368151741686, -0.1400834681780238, 0.010681033859055975, 1078 | -0.5311021141391654, 1.216261615322336, 0.0008782557798583945, 1079 | 0.02126529896499718, -0.0761781471443117, 0.9884407103610852, 1080 | ]); 1081 | 1082 | #[rustfmt::skip] 1083 | pub const ADOBE_WIDE_D50_TO_P3_D65: Mat3 = Mat3::from_cols_array(&[ 1084 | 1.4512529042625706, -0.1411567152740667, 0.010725573548710347, 1085 | -0.4610943449674783, 1.2196783585376076, 0.007110902108159581, 1086 | 0.009841440704908222, -0.07852164326354114, 0.9821635243431295, 1087 | ]); 1088 | 1089 | #[rustfmt::skip] 1090 | pub const ADOBE_WIDE_D50_TO_ADOBE_1998_D65: Mat3 = Mat3::from_cols_array(&[ 1091 | 1.234720291973154, -0.2081386547025741, -0.013964654115663472, 1092 | -0.23254630073963653, 1.2903771373179227, -0.022683494974196666, 1093 | -0.002173991233517858, -0.08223848261534872, 1.03664814908986, 1094 | ]); 1095 | 1096 | pub fn const_conversion_matrix( 1097 | src_primaries: RgbPrimaries, 1098 | src_wp: WhitePoint, 1099 | dst_primaries: RgbPrimaries, 1100 | dst_wp: WhitePoint, 1101 | ) -> Option { 1102 | if src_primaries == dst_primaries && src_wp == dst_wp { 1103 | return Some(Mat3::IDENTITY); 1104 | } 1105 | match (src_primaries, src_wp, dst_primaries, dst_wp) { 1106 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1107 | Some(BT_709_D65_TO_BT_2020_D65) 1108 | } 1109 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1110 | Some(BT_709_D65_TO_AP1_D60) 1111 | } 1112 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1113 | Some(BT_709_D65_TO_AP0_D60) 1114 | } 1115 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1116 | Some(BT_709_D65_TO_CIE_RGB_E) 1117 | } 1118 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1119 | Some(BT_709_D65_TO_CIE_XYZ_D65) 1120 | } 1121 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1122 | Some(BT_709_D65_TO_PRO_PHOTO_D50) 1123 | } 1124 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::Apple, WhitePoint::D65) => { 1125 | Some(BT_709_D65_TO_APPLE_D65) 1126 | } 1127 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1128 | Some(BT_709_D65_TO_P3_D60) 1129 | } 1130 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1131 | Some(BT_709_D65_TO_P3_P3_DCI) 1132 | } 1133 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D65) => { 1134 | Some(BT_709_D65_TO_P3_D65) 1135 | } 1136 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1137 | Some(BT_709_D65_TO_ADOBE_1998_D65) 1138 | } 1139 | (RgbPrimaries::Bt709, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1140 | Some(BT_709_D65_TO_ADOBE_WIDE_D50) 1141 | } 1142 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::Bt709, WhitePoint::D65) => { 1143 | Some(BT_2020_D65_TO_BT_709_D65) 1144 | } 1145 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1146 | Some(BT_2020_D65_TO_AP1_D60) 1147 | } 1148 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1149 | Some(BT_2020_D65_TO_AP0_D60) 1150 | } 1151 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1152 | Some(BT_2020_D65_TO_CIE_RGB_E) 1153 | } 1154 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1155 | Some(BT_2020_D65_TO_CIE_XYZ_D65) 1156 | } 1157 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1158 | Some(BT_2020_D65_TO_PRO_PHOTO_D50) 1159 | } 1160 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::Apple, WhitePoint::D65) => { 1161 | Some(BT_2020_D65_TO_APPLE_D65) 1162 | } 1163 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1164 | Some(BT_2020_D65_TO_P3_D60) 1165 | } 1166 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1167 | Some(BT_2020_D65_TO_P3_P3_DCI) 1168 | } 1169 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D65) => { 1170 | Some(BT_2020_D65_TO_P3_D65) 1171 | } 1172 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1173 | Some(BT_2020_D65_TO_ADOBE_1998_D65) 1174 | } 1175 | (RgbPrimaries::Bt2020, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1176 | Some(BT_2020_D65_TO_ADOBE_WIDE_D50) 1177 | } 1178 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::Bt709, WhitePoint::D65) => { 1179 | Some(AP1_D60_TO_BT_709_D65) 1180 | } 1181 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1182 | Some(AP1_D60_TO_BT_2020_D65) 1183 | } 1184 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::Ap0, WhitePoint::D60) => { 1185 | Some(AP1_D60_TO_AP0_D60) 1186 | } 1187 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::CieRgb, WhitePoint::E) => { 1188 | Some(AP1_D60_TO_CIE_RGB_E) 1189 | } 1190 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1191 | Some(AP1_D60_TO_CIE_XYZ_D65) 1192 | } 1193 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1194 | Some(AP1_D60_TO_PRO_PHOTO_D50) 1195 | } 1196 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::Apple, WhitePoint::D65) => { 1197 | Some(AP1_D60_TO_APPLE_D65) 1198 | } 1199 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::D60) => { 1200 | Some(AP1_D60_TO_P3_D60) 1201 | } 1202 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1203 | Some(AP1_D60_TO_P3_P3_DCI) 1204 | } 1205 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::D65) => { 1206 | Some(AP1_D60_TO_P3_D65) 1207 | } 1208 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1209 | Some(AP1_D60_TO_ADOBE_1998_D65) 1210 | } 1211 | (RgbPrimaries::Ap1, WhitePoint::D60, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1212 | Some(AP1_D60_TO_ADOBE_WIDE_D50) 1213 | } 1214 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::Bt709, WhitePoint::D65) => { 1215 | Some(AP0_D60_TO_BT_709_D65) 1216 | } 1217 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1218 | Some(AP0_D60_TO_BT_2020_D65) 1219 | } 1220 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::Ap1, WhitePoint::D60) => { 1221 | Some(AP0_D60_TO_AP1_D60) 1222 | } 1223 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::CieRgb, WhitePoint::E) => { 1224 | Some(AP0_D60_TO_CIE_RGB_E) 1225 | } 1226 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1227 | Some(AP0_D60_TO_CIE_XYZ_D65) 1228 | } 1229 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1230 | Some(AP0_D60_TO_PRO_PHOTO_D50) 1231 | } 1232 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::Apple, WhitePoint::D65) => { 1233 | Some(AP0_D60_TO_APPLE_D65) 1234 | } 1235 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::D60) => { 1236 | Some(AP0_D60_TO_P3_D60) 1237 | } 1238 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1239 | Some(AP0_D60_TO_P3_P3_DCI) 1240 | } 1241 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::D65) => { 1242 | Some(AP0_D60_TO_P3_D65) 1243 | } 1244 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1245 | Some(AP0_D60_TO_ADOBE_1998_D65) 1246 | } 1247 | (RgbPrimaries::Ap0, WhitePoint::D60, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1248 | Some(AP0_D60_TO_ADOBE_WIDE_D50) 1249 | } 1250 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Bt709, WhitePoint::D65) => { 1251 | Some(CIE_RGB_E_TO_BT_709_D65) 1252 | } 1253 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1254 | Some(CIE_RGB_E_TO_BT_2020_D65) 1255 | } 1256 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Ap1, WhitePoint::D60) => { 1257 | Some(CIE_RGB_E_TO_AP1_D60) 1258 | } 1259 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Ap0, WhitePoint::D60) => { 1260 | Some(CIE_RGB_E_TO_AP0_D60) 1261 | } 1262 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1263 | Some(CIE_RGB_E_TO_CIE_XYZ_D65) 1264 | } 1265 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1266 | Some(CIE_RGB_E_TO_PRO_PHOTO_D50) 1267 | } 1268 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Apple, WhitePoint::D65) => { 1269 | Some(CIE_RGB_E_TO_APPLE_D65) 1270 | } 1271 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::P3, WhitePoint::D60) => { 1272 | Some(CIE_RGB_E_TO_P3_D60) 1273 | } 1274 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1275 | Some(CIE_RGB_E_TO_P3_P3_DCI) 1276 | } 1277 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::P3, WhitePoint::D65) => { 1278 | Some(CIE_RGB_E_TO_P3_D65) 1279 | } 1280 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1281 | Some(CIE_RGB_E_TO_ADOBE_1998_D65) 1282 | } 1283 | (RgbPrimaries::CieRgb, WhitePoint::E, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1284 | Some(CIE_RGB_E_TO_ADOBE_WIDE_D50) 1285 | } 1286 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Bt709, WhitePoint::D65) => { 1287 | Some(CIE_XYZ_D65_TO_BT_709_D65) 1288 | } 1289 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1290 | Some(CIE_XYZ_D65_TO_BT_2020_D65) 1291 | } 1292 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1293 | Some(CIE_XYZ_D65_TO_AP1_D60) 1294 | } 1295 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1296 | Some(CIE_XYZ_D65_TO_AP0_D60) 1297 | } 1298 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1299 | Some(CIE_XYZ_D65_TO_CIE_RGB_E) 1300 | } 1301 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1302 | Some(CIE_XYZ_D65_TO_PRO_PHOTO_D50) 1303 | } 1304 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Apple, WhitePoint::D65) => { 1305 | Some(CIE_XYZ_D65_TO_APPLE_D65) 1306 | } 1307 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1308 | Some(CIE_XYZ_D65_TO_P3_D60) 1309 | } 1310 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1311 | Some(CIE_XYZ_D65_TO_P3_P3_DCI) 1312 | } 1313 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D65) => { 1314 | Some(CIE_XYZ_D65_TO_P3_D65) 1315 | } 1316 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1317 | Some(CIE_XYZ_D65_TO_ADOBE_1998_D65) 1318 | } 1319 | (RgbPrimaries::CieXyz, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1320 | Some(CIE_XYZ_D65_TO_ADOBE_WIDE_D50) 1321 | } 1322 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Bt709, WhitePoint::D65) => { 1323 | Some(PRO_PHOTO_D50_TO_BT_709_D65) 1324 | } 1325 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1326 | Some(PRO_PHOTO_D50_TO_BT_2020_D65) 1327 | } 1328 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Ap1, WhitePoint::D60) => { 1329 | Some(PRO_PHOTO_D50_TO_AP1_D60) 1330 | } 1331 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Ap0, WhitePoint::D60) => { 1332 | Some(PRO_PHOTO_D50_TO_AP0_D60) 1333 | } 1334 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::CieRgb, WhitePoint::E) => { 1335 | Some(PRO_PHOTO_D50_TO_CIE_RGB_E) 1336 | } 1337 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1338 | Some(PRO_PHOTO_D50_TO_CIE_XYZ_D65) 1339 | } 1340 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Apple, WhitePoint::D65) => { 1341 | Some(PRO_PHOTO_D50_TO_APPLE_D65) 1342 | } 1343 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::D60) => { 1344 | Some(PRO_PHOTO_D50_TO_P3_D60) 1345 | } 1346 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1347 | Some(PRO_PHOTO_D50_TO_P3_P3_DCI) 1348 | } 1349 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::D65) => { 1350 | Some(PRO_PHOTO_D50_TO_P3_D65) 1351 | } 1352 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1353 | Some(PRO_PHOTO_D50_TO_ADOBE_1998_D65) 1354 | } 1355 | (RgbPrimaries::ProPhoto, WhitePoint::D50, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1356 | Some(PRO_PHOTO_D50_TO_ADOBE_WIDE_D50) 1357 | } 1358 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::Bt709, WhitePoint::D65) => { 1359 | Some(APPLE_D65_TO_BT_709_D65) 1360 | } 1361 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1362 | Some(APPLE_D65_TO_BT_2020_D65) 1363 | } 1364 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1365 | Some(APPLE_D65_TO_AP1_D60) 1366 | } 1367 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1368 | Some(APPLE_D65_TO_AP0_D60) 1369 | } 1370 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1371 | Some(APPLE_D65_TO_CIE_RGB_E) 1372 | } 1373 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1374 | Some(APPLE_D65_TO_CIE_XYZ_D65) 1375 | } 1376 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1377 | Some(APPLE_D65_TO_PRO_PHOTO_D50) 1378 | } 1379 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1380 | Some(APPLE_D65_TO_P3_D60) 1381 | } 1382 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1383 | Some(APPLE_D65_TO_P3_P3_DCI) 1384 | } 1385 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D65) => { 1386 | Some(APPLE_D65_TO_P3_D65) 1387 | } 1388 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1389 | Some(APPLE_D65_TO_ADOBE_1998_D65) 1390 | } 1391 | (RgbPrimaries::Apple, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1392 | Some(APPLE_D65_TO_ADOBE_WIDE_D50) 1393 | } 1394 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Bt709, WhitePoint::D65) => { 1395 | Some(P3_D60_TO_BT_709_D65) 1396 | } 1397 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1398 | Some(P3_D60_TO_BT_2020_D65) 1399 | } 1400 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Ap1, WhitePoint::D60) => { 1401 | Some(P3_D60_TO_AP1_D60) 1402 | } 1403 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Ap0, WhitePoint::D60) => { 1404 | Some(P3_D60_TO_AP0_D60) 1405 | } 1406 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::CieRgb, WhitePoint::E) => { 1407 | Some(P3_D60_TO_CIE_RGB_E) 1408 | } 1409 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1410 | Some(P3_D60_TO_CIE_XYZ_D65) 1411 | } 1412 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1413 | Some(P3_D60_TO_PRO_PHOTO_D50) 1414 | } 1415 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Apple, WhitePoint::D65) => { 1416 | Some(P3_D60_TO_APPLE_D65) 1417 | } 1418 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1419 | Some(P3_D60_TO_P3_P3_DCI) 1420 | } 1421 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::P3, WhitePoint::D65) => { 1422 | Some(P3_D60_TO_P3_D65) 1423 | } 1424 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1425 | Some(P3_D60_TO_ADOBE_1998_D65) 1426 | } 1427 | (RgbPrimaries::P3, WhitePoint::D60, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1428 | Some(P3_D60_TO_ADOBE_WIDE_D50) 1429 | } 1430 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Bt709, WhitePoint::D65) => { 1431 | Some(P3_P3_DCI_TO_BT_709_D65) 1432 | } 1433 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1434 | Some(P3_P3_DCI_TO_BT_2020_D65) 1435 | } 1436 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Ap1, WhitePoint::D60) => { 1437 | Some(P3_P3_DCI_TO_AP1_D60) 1438 | } 1439 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Ap0, WhitePoint::D60) => { 1440 | Some(P3_P3_DCI_TO_AP0_D60) 1441 | } 1442 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::CieRgb, WhitePoint::E) => { 1443 | Some(P3_P3_DCI_TO_CIE_RGB_E) 1444 | } 1445 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1446 | Some(P3_P3_DCI_TO_CIE_XYZ_D65) 1447 | } 1448 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1449 | Some(P3_P3_DCI_TO_PRO_PHOTO_D50) 1450 | } 1451 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Apple, WhitePoint::D65) => { 1452 | Some(P3_P3_DCI_TO_APPLE_D65) 1453 | } 1454 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::P3, WhitePoint::D60) => { 1455 | Some(P3_P3_DCI_TO_P3_D60) 1456 | } 1457 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::P3, WhitePoint::D65) => { 1458 | Some(P3_P3_DCI_TO_P3_D65) 1459 | } 1460 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1461 | Some(P3_P3_DCI_TO_ADOBE_1998_D65) 1462 | } 1463 | (RgbPrimaries::P3, WhitePoint::P3Dci, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1464 | Some(P3_P3_DCI_TO_ADOBE_WIDE_D50) 1465 | } 1466 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Bt709, WhitePoint::D65) => { 1467 | Some(P3_D65_TO_BT_709_D65) 1468 | } 1469 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1470 | Some(P3_D65_TO_BT_2020_D65) 1471 | } 1472 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1473 | Some(P3_D65_TO_AP1_D60) 1474 | } 1475 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1476 | Some(P3_D65_TO_AP0_D60) 1477 | } 1478 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1479 | Some(P3_D65_TO_CIE_RGB_E) 1480 | } 1481 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1482 | Some(P3_D65_TO_CIE_XYZ_D65) 1483 | } 1484 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1485 | Some(P3_D65_TO_PRO_PHOTO_D50) 1486 | } 1487 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Apple, WhitePoint::D65) => { 1488 | Some(P3_D65_TO_APPLE_D65) 1489 | } 1490 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1491 | Some(P3_D65_TO_P3_D60) 1492 | } 1493 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1494 | Some(P3_D65_TO_P3_P3_DCI) 1495 | } 1496 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1497 | Some(P3_D65_TO_ADOBE_1998_D65) 1498 | } 1499 | (RgbPrimaries::P3, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1500 | Some(P3_D65_TO_ADOBE_WIDE_D50) 1501 | } 1502 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::Bt709, WhitePoint::D65) => { 1503 | Some(ADOBE_1998_D65_TO_BT_709_D65) 1504 | } 1505 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1506 | Some(ADOBE_1998_D65_TO_BT_2020_D65) 1507 | } 1508 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::Ap1, WhitePoint::D60) => { 1509 | Some(ADOBE_1998_D65_TO_AP1_D60) 1510 | } 1511 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::Ap0, WhitePoint::D60) => { 1512 | Some(ADOBE_1998_D65_TO_AP0_D60) 1513 | } 1514 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::CieRgb, WhitePoint::E) => { 1515 | Some(ADOBE_1998_D65_TO_CIE_RGB_E) 1516 | } 1517 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1518 | Some(ADOBE_1998_D65_TO_CIE_XYZ_D65) 1519 | } 1520 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1521 | Some(ADOBE_1998_D65_TO_PRO_PHOTO_D50) 1522 | } 1523 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::Apple, WhitePoint::D65) => { 1524 | Some(ADOBE_1998_D65_TO_APPLE_D65) 1525 | } 1526 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D60) => { 1527 | Some(ADOBE_1998_D65_TO_P3_D60) 1528 | } 1529 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1530 | Some(ADOBE_1998_D65_TO_P3_P3_DCI) 1531 | } 1532 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::P3, WhitePoint::D65) => { 1533 | Some(ADOBE_1998_D65_TO_P3_D65) 1534 | } 1535 | (RgbPrimaries::Adobe1998, WhitePoint::D65, RgbPrimaries::AdobeWide, WhitePoint::D50) => { 1536 | Some(ADOBE_1998_D65_TO_ADOBE_WIDE_D50) 1537 | } 1538 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Bt709, WhitePoint::D65) => { 1539 | Some(ADOBE_WIDE_D50_TO_BT_709_D65) 1540 | } 1541 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Bt2020, WhitePoint::D65) => { 1542 | Some(ADOBE_WIDE_D50_TO_BT_2020_D65) 1543 | } 1544 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Ap1, WhitePoint::D60) => { 1545 | Some(ADOBE_WIDE_D50_TO_AP1_D60) 1546 | } 1547 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Ap0, WhitePoint::D60) => { 1548 | Some(ADOBE_WIDE_D50_TO_AP0_D60) 1549 | } 1550 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::CieRgb, WhitePoint::E) => { 1551 | Some(ADOBE_WIDE_D50_TO_CIE_RGB_E) 1552 | } 1553 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::CieXyz, WhitePoint::D65) => { 1554 | Some(ADOBE_WIDE_D50_TO_CIE_XYZ_D65) 1555 | } 1556 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::ProPhoto, WhitePoint::D50) => { 1557 | Some(ADOBE_WIDE_D50_TO_PRO_PHOTO_D50) 1558 | } 1559 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Apple, WhitePoint::D65) => { 1560 | Some(ADOBE_WIDE_D50_TO_APPLE_D65) 1561 | } 1562 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::D60) => { 1563 | Some(ADOBE_WIDE_D50_TO_P3_D60) 1564 | } 1565 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::P3Dci) => { 1566 | Some(ADOBE_WIDE_D50_TO_P3_P3_DCI) 1567 | } 1568 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::P3, WhitePoint::D65) => { 1569 | Some(ADOBE_WIDE_D50_TO_P3_D65) 1570 | } 1571 | (RgbPrimaries::AdobeWide, WhitePoint::D50, RgbPrimaries::Adobe1998, WhitePoint::D65) => { 1572 | Some(ADOBE_WIDE_D50_TO_ADOBE_1998_D65) 1573 | } 1574 | _ => None, 1575 | } 1576 | } 1577 | --------------------------------------------------------------------------------