moxcms/conversions/katana/
xyz_rgb.rs1use crate::conversions::katana::pcs_stages::KatanaMatrixStage;
30use crate::conversions::katana::{
31 KatanaDefaultIntermediate, KatanaFinalStage, KatanaIntermediateStage,
32};
33use crate::mlaf::mlaf;
34use crate::{
35 CmsError, ColorProfile, GammaLutInterpolate, Layout, Matrix3f, PointeeSizeExpressible,
36 RenderingIntent, Rgb, TransformOptions, filmlike_clip,
37};
38use num_traits::AsPrimitive;
39
40pub(crate) struct KatanaXyzToRgbStage<T: Clone, const LAYOUT: u8> {
41 pub(crate) r_gamma: Box<[T; 65536]>,
42 pub(crate) g_gamma: Box<[T; 65536]>,
43 pub(crate) b_gamma: Box<[T; 65536]>,
44 pub(crate) intent: RenderingIntent,
45 pub(crate) bit_depth: usize,
46 pub(crate) gamma_lut: usize,
47}
48
49impl<T: Clone + AsPrimitive<f32> + PointeeSizeExpressible, const LAYOUT: u8>
50 KatanaFinalStage<f32, T> for KatanaXyzToRgbStage<T, LAYOUT>
51where
52 u32: AsPrimitive<T>,
53 f32: AsPrimitive<T>,
54{
55 fn to_output(&self, src: &mut [f32], dst: &mut [T]) -> Result<(), CmsError> {
56 let dst_cn = Layout::from(LAYOUT);
57 let dst_channels = dst_cn.channels();
58 if src.len() % 3 != 0 {
59 return Err(CmsError::LaneMultipleOfChannels);
60 }
61 if dst.len() % dst_channels != 0 {
62 return Err(CmsError::LaneMultipleOfChannels);
63 }
64 let src_chunks = src.len() / 3;
65 let dst_chunks = dst.len() / dst_channels;
66 if src_chunks != dst_chunks {
67 return Err(CmsError::LaneSizeMismatch);
68 }
69
70 let max_colors: T = (if T::FINITE {
71 ((1u32 << self.bit_depth) - 1) as f32
72 } else {
73 1.0
74 })
75 .as_();
76 let lut_cap = (self.gamma_lut - 1) as f32;
77
78 if self.intent != RenderingIntent::AbsoluteColorimetric {
79 for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
80 let mut rgb = Rgb::new(src[0], src[1], src[2]);
81 if rgb.is_out_of_gamut() {
82 rgb = filmlike_clip(rgb);
83 }
84 let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
85 let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
86 let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
87
88 dst[0] = self.r_gamma[r as usize];
89 dst[1] = self.g_gamma[g as usize];
90 dst[2] = self.b_gamma[b as usize];
91 if dst_cn == Layout::Rgba {
92 dst[3] = max_colors;
93 }
94 }
95 } else {
96 for (src, dst) in src.chunks_exact(3).zip(dst.chunks_exact_mut(dst_channels)) {
97 let rgb = Rgb::new(src[0], src[1], src[2]);
98 let r = mlaf(0.5, rgb.r, lut_cap).min(lut_cap).max(0.) as u16;
99 let g = mlaf(0.5, rgb.g, lut_cap).min(lut_cap).max(0.) as u16;
100 let b = mlaf(0.5, rgb.b, lut_cap).min(lut_cap).max(0.) as u16;
101
102 dst[0] = self.r_gamma[r as usize];
103 dst[1] = self.g_gamma[g as usize];
104 dst[2] = self.b_gamma[b as usize];
105 if dst_cn == Layout::Rgba {
106 dst[3] = max_colors;
107 }
108 }
109 }
110
111 Ok(())
112 }
113}
114
115pub(crate) struct KatanaXyzRgbState<T> {
116 pub(crate) stages: Vec<Box<dyn KatanaIntermediateStage<f32> + Send + Sync>>,
117 pub(crate) final_stage: Box<dyn KatanaFinalStage<f32, T> + Send + Sync>,
118}
119
120pub(crate) fn katana_prepare_inverse_lut_rgb_xyz<
121 T: Copy
122 + Default
123 + AsPrimitive<f32>
124 + Send
125 + Sync
126 + AsPrimitive<usize>
127 + PointeeSizeExpressible
128 + GammaLutInterpolate,
129 const BIT_DEPTH: usize,
130 const GAMMA_LUT: usize,
131>(
132 dest: &ColorProfile,
133 dest_layout: Layout,
134 options: TransformOptions,
135) -> Result<KatanaXyzRgbState<T>, CmsError>
136where
137 f32: AsPrimitive<T>,
138 u32: AsPrimitive<T>,
139{
140 let gamma_map_r = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
163 &dest.red_trc,
164 options.allow_use_cicp_transfer,
165 )?;
166 let gamma_map_g = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
167 &dest.green_trc,
168 options.allow_use_cicp_transfer,
169 )?;
170 let gamma_map_b = dest.build_gamma_table::<T, 65536, GAMMA_LUT, BIT_DEPTH>(
171 &dest.blue_trc,
172 options.allow_use_cicp_transfer,
173 )?;
174
175 let xyz_to_rgb = dest.rgb_to_xyz_matrix().inverse();
176
177 let mut matrices: Vec<Box<KatanaDefaultIntermediate>> =
178 vec![Box::new(KatanaMatrixStage::new(Matrix3f {
179 v: [
180 [65535.0 / 32768.0, 0.0, 0.0],
181 [0.0, 65535.0 / 32768.0, 0.0],
182 [0.0, 0.0, 65535.0 / 32768.0],
183 ],
184 }))];
185
186 matrices.push(Box::new(KatanaMatrixStage::new(xyz_to_rgb.to_f32())));
187 match dest_layout {
188 Layout::Rgb => {
189 let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgb as u8 }> {
190 r_gamma: gamma_map_r,
191 g_gamma: gamma_map_g,
192 b_gamma: gamma_map_b,
193 intent: options.rendering_intent,
194 bit_depth: BIT_DEPTH,
195 gamma_lut: GAMMA_LUT,
196 };
197 Ok(KatanaXyzRgbState {
198 stages: matrices,
199 final_stage: Box::new(xyz_to_rgb_stage),
200 })
201 }
202 Layout::Rgba => {
203 let xyz_to_rgb_stage = KatanaXyzToRgbStage::<T, { Layout::Rgba as u8 }> {
204 r_gamma: gamma_map_r,
205 g_gamma: gamma_map_g,
206 b_gamma: gamma_map_b,
207 intent: options.rendering_intent,
208 bit_depth: BIT_DEPTH,
209 gamma_lut: GAMMA_LUT,
210 };
211 Ok(KatanaXyzRgbState {
212 stages: matrices,
213 final_stage: Box::new(xyz_to_rgb_stage),
214 })
215 }
216 Layout::Gray => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
217 Layout::GrayAlpha => unreachable!("Gray layout must not be called on Rgb/Rgba path"),
218 _ => unreachable!(
219 "layout {:?} should not be called on xyz->rgb path",
220 dest_layout
221 ),
222 }
223}