kolor_64/details/
conversion.rs

1use super::{
2    color::{RGBPrimaries, TransformFn},
3    transform::ColorTransform,
4    xyz::{rgb_to_xyz, xyz_to_rgb},
5};
6use crate::{ColorSpace, FType, Mat3, Vec3};
7#[cfg(feature = "serde1")]
8use serde::{Deserialize, Serialize};
9
10/// A transformation from one linear color space to another.
11#[derive(Copy, Clone, Debug, PartialEq)]
12#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
13pub struct LinearColorConversion {
14    mat: Mat3,
15    input_space: ColorSpace,
16    output_space: ColorSpace,
17}
18
19impl LinearColorConversion {
20    pub fn input_space(&self) -> ColorSpace {
21        self.input_space
22    }
23    pub fn output_space(&self) -> ColorSpace {
24        self.output_space
25    }
26    pub fn convert(&self, color: Vec3) -> Vec3 {
27        self.mat * color
28    }
29    pub fn matrix(&self) -> Mat3 {
30        self.mat
31    }
32
33    pub fn new(src: ColorSpace, dst: ColorSpace) -> Self {
34        if !src.is_linear() {
35            panic!("{:?} is not a linear color space", src);
36        }
37        if !dst.is_linear() {
38            panic!("{:?} is not a linear color space", dst);
39        }
40        #[cfg(feature = "color-matrices")]
41        let const_conversion = super::generated_matrices::const_conversion_matrix(
42            src.primaries(),
43            src.white_point(),
44            dst.primaries(),
45            dst.white_point(),
46        );
47        #[cfg(not(feature = "color-matrices"))]
48        let const_conversion: Option<Mat3> = None;
49
50        let mat = if let Some(const_mat) = const_conversion {
51            const_mat
52        } else {
53            let src_to_xyz = if src.primaries() == RGBPrimaries::CIE_XYZ {
54                Mat3::IDENTITY
55            } else {
56                rgb_to_xyz(src.primaries().values(), src.white_point().values())
57            };
58            let xyz_to_dst = if dst.primaries() == RGBPrimaries::CIE_XYZ {
59                Mat3::IDENTITY
60            } else {
61                xyz_to_rgb(dst.primaries().values(), dst.white_point().values())
62            };
63            if src.white_point() != dst.white_point() {
64                let white_point_transform = super::cat::chromatic_adaptation_transform(
65                    Vec3::from_slice(src.white_point().values()),
66                    Vec3::from_slice(dst.white_point().values()),
67                    super::cat::LMSConeSpace::Sharp,
68                );
69                xyz_to_dst * white_point_transform * src_to_xyz
70            } else {
71                xyz_to_dst * src_to_xyz
72            }
73        };
74        Self {
75            mat,
76            input_space: src,
77            output_space: dst,
78        }
79    }
80}
81
82/// [ColorConversion] defines an operation that maps a 3-component vector
83/// from a source [ColorSpace] to a destination [ColorSpace].
84#[derive(Copy, Clone)]
85pub struct ColorConversion {
86    src_space: ColorSpace,
87    dst_space: ColorSpace,
88    src_transform: Option<ColorTransform>,
89    linear_transform: Option<LinearColorConversion>,
90    dst_transform: Option<ColorTransform>,
91}
92impl PartialEq for ColorConversion {
93    fn eq(&self, other: &Self) -> bool {
94        self.src_space == other.src_space && self.dst_space == other.dst_space
95    }
96}
97impl core::fmt::Debug for ColorConversion {
98    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
99        let src_transform = if !self.src_space.is_linear() {
100            self.src_space.transform_function()
101        } else {
102            TransformFn::NONE
103        };
104        let dst_transform = if !self.dst_space.is_linear() {
105            self.dst_space.transform_function()
106        } else {
107            TransformFn::NONE
108        };
109        f.debug_struct("ColorConversion")
110            .field("src_space", &self.src_space)
111            .field("dst_space", &self.dst_space)
112            .field("src_transform", &src_transform)
113            .field("linear_transform", &self.linear_transform)
114            .field("dst_transform", &dst_transform)
115            .finish()
116    }
117}
118
119impl ColorConversion {
120    pub fn new(src: ColorSpace, dst: ColorSpace) -> Self {
121        let src_transform = if !src.is_linear() {
122            ColorTransform::new(src.transform_function(), TransformFn::NONE)
123        } else {
124            None
125        };
126        let src_linear = ColorSpace::linear(src.primaries(), src.white_point());
127        let dst_linear = ColorSpace::linear(dst.primaries(), dst.white_point());
128        let linear_transform = LinearColorConversion::new(src_linear, dst_linear);
129        let linear_transform = if linear_transform.mat == Mat3::IDENTITY {
130            None
131        } else {
132            Some(linear_transform)
133        };
134        let dst_transform = if !dst.is_linear() {
135            ColorTransform::new(TransformFn::NONE, dst.transform_function())
136        } else {
137            None
138        };
139        Self {
140            src_space: src,
141            dst_space: dst,
142            src_transform,
143            dst_transform,
144            linear_transform,
145        }
146    }
147    pub fn invert(&self) -> Self {
148        ColorConversion::new(self.dst_space, self.src_space)
149    }
150    pub fn is_linear(&self) -> bool {
151        self.src_transform.is_none() && self.dst_transform.is_none()
152    }
153    pub fn linear_part(&self) -> LinearColorConversion {
154        if let Some(transform) = self.linear_transform.as_ref() {
155            *transform
156        } else {
157            LinearColorConversion {
158                input_space: self.src_space,
159                output_space: self.dst_space,
160                mat: Mat3::IDENTITY,
161            }
162        }
163    }
164    pub fn src_transform(&self) -> Option<ColorTransform> {
165        self.src_transform
166    }
167    pub fn dst_transform(&self) -> Option<ColorTransform> {
168        self.dst_transform
169    }
170    pub fn src_transform_fn(&self) -> TransformFn {
171        self.src_transform
172            .map(|_| self.src_space.transform_function())
173            .unwrap_or(TransformFn::NONE)
174    }
175    pub fn dst_transform_fn(&self) -> TransformFn {
176        self.dst_transform
177            .map(|_| self.dst_space.transform_function())
178            .unwrap_or(TransformFn::NONE)
179    }
180    pub fn src_space(&self) -> ColorSpace {
181        self.src_space
182    }
183    pub fn dst_space(&self) -> ColorSpace {
184        self.dst_space
185    }
186    pub fn convert_float(&self, color: &mut [FType; 3]) {
187        let vec3 = Vec3::from_slice(color);
188        *color = self.convert(vec3).into();
189    }
190    pub fn apply_src_transform(&self, color: Vec3) -> Vec3 {
191        if let Some(src_transform) = self.src_transform.as_ref() {
192            src_transform.apply(color, self.src_space.white_point())
193        } else {
194            color
195        }
196    }
197    pub fn apply_linear_part(&self, color: Vec3) -> Vec3 {
198        if let Some(transform) = self.linear_transform.as_ref() {
199            transform.convert(color)
200        } else {
201            color
202        }
203    }
204    pub fn apply_dst_transform(&self, color: Vec3) -> Vec3 {
205        if let Some(dst_transform) = self.dst_transform.as_ref() {
206            dst_transform.apply(color, self.dst_space.white_point())
207        } else {
208            color
209        }
210    }
211    pub fn convert(&self, mut color: Vec3) -> Vec3 {
212        color = self.apply_src_transform(color);
213        color = self.apply_linear_part(color);
214        color = self.apply_dst_transform(color);
215        color
216    }
217}