/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/transform.rs
Line | Count | Source |
1 | | /* |
2 | | * // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved. |
3 | | * // |
4 | | * // Redistribution and use in source and binary forms, with or without modification, |
5 | | * // are permitted provided that the following conditions are met: |
6 | | * // |
7 | | * // 1. Redistributions of source code must retain the above copyright notice, this |
8 | | * // list of conditions and the following disclaimer. |
9 | | * // |
10 | | * // 2. Redistributions in binary form must reproduce the above copyright notice, |
11 | | * // this list of conditions and the following disclaimer in the documentation |
12 | | * // and/or other materials provided with the distribution. |
13 | | * // |
14 | | * // 3. Neither the name of the copyright holder nor the names of its |
15 | | * // contributors may be used to endorse or promote products derived from |
16 | | * // this software without specific prior written permission. |
17 | | * // |
18 | | * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
19 | | * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 | | * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 | | * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
22 | | * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 | | * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
24 | | * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
25 | | * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
26 | | * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
27 | | * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | */ |
29 | | use crate::conversions::{ |
30 | | LutBarycentricReduction, RgbXyzFactory, RgbXyzFactoryOpt, ToneReproductionRgbToGray, |
31 | | TransformMatrixShaper, make_gray_to_unfused, make_gray_to_x, make_lut_transform, |
32 | | make_rgb_to_gray, |
33 | | }; |
34 | | use crate::err::CmsError; |
35 | | use crate::trc::GammaLutInterpolate; |
36 | | use crate::{ColorProfile, DataColorSpace, LutWarehouse, RenderingIntent, Vector3f, Xyzd}; |
37 | | use num_traits::AsPrimitive; |
38 | | use std::marker::PhantomData; |
39 | | |
40 | | /// Transformation executor itself |
41 | | pub trait TransformExecutor<V: Copy + Default> { |
42 | | /// Count of samples always must match. |
43 | | /// If there is N samples of *Cmyk* source then N samples of *Rgb* is expected as an output. |
44 | | fn transform(&self, src: &[V], dst: &mut [V]) -> Result<(), CmsError>; |
45 | | } |
46 | | |
47 | | /// Helper for intermediate transformation stages |
48 | | pub trait Stage { |
49 | | fn transform(&self, src: &[f32], dst: &mut [f32]) -> Result<(), CmsError>; |
50 | | } |
51 | | |
52 | | /// Helper for intermediate transformation stages |
53 | | pub trait InPlaceStage { |
54 | | fn transform(&self, dst: &mut [f32]) -> Result<(), CmsError>; |
55 | | } |
56 | | |
57 | | /// Barycentric interpolation weights size. |
58 | | /// |
59 | | /// Bigger weights increases precision. |
60 | | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)] |
61 | | pub enum BarycentricWeightScale { |
62 | | #[default] |
63 | | /// Low scale weights is enough for common case. |
64 | | /// |
65 | | /// However, it might crush dark zones and gradients. |
66 | | /// Weights increasing costs 5% performance. |
67 | | Low, |
68 | | #[cfg(feature = "options")] |
69 | | High, |
70 | | } |
71 | | |
72 | | /// Declares additional transformation options |
73 | | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
74 | | pub struct TransformOptions { |
75 | | pub rendering_intent: RenderingIntent, |
76 | | /// If set it will try to use Transfer Characteristics from CICP |
77 | | /// on transform. This might be more precise and faster. |
78 | | pub allow_use_cicp_transfer: bool, |
79 | | /// Prefers fixed point where implemented as default. |
80 | | /// Most of the applications actually do not need floating point. |
81 | | /// |
82 | | /// Do not change it if you're not sure that extreme precision is required, |
83 | | /// in most cases it is a simple way to spend energy to warming up environment |
84 | | /// a little. |
85 | | /// |
86 | | /// Q2.13 for RGB->XYZ->RGB is used. |
87 | | /// LUT interpolation use Q0.15. |
88 | | pub prefer_fixed_point: bool, |
89 | | /// Interpolation method for 3D LUT |
90 | | /// |
91 | | /// This parameter has no effect on LAB/XYZ interpolation and scene linear RGB. |
92 | | /// |
93 | | /// Technically, it should be assumed to perform cube dividing interpolation: |
94 | | /// - Source colorspace is gamma-encoded (discards scene linear RGB and XYZ). |
95 | | /// - Colorspace is uniform. |
96 | | /// - Colorspace has linear scaling (discards LAB). |
97 | | /// - Interpolation doesn't shift hues (discards LAB). |
98 | | /// |
99 | | /// For LAB, XYZ and scene linear RGB `trilinear/quadlinear` always in force. |
100 | | pub interpolation_method: InterpolationMethod, |
101 | | /// Barycentric weights scale. |
102 | | /// |
103 | | /// This value controls LUT weights precision. |
104 | | pub barycentric_weight_scale: BarycentricWeightScale, |
105 | | /// For floating points transform, it will try to detect gamma function on *Matrix Shaper* profiles. |
106 | | /// If gamma function is found, then it will be used instead of LUT table. |
107 | | /// This allows to work with excellent precision with extended range, |
108 | | /// at a cost of execution time. |
109 | | pub allow_extended_range_rgb_xyz: bool, |
110 | | // pub black_point_compensation: bool, |
111 | | } |
112 | | |
113 | | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default)] |
114 | | /// Defines the interpolation method. |
115 | | /// |
116 | | /// All methods produce very close results that almost not possible to separate without |
117 | | /// some automation tools. |
118 | | /// |
119 | | /// This implementation chooses the fastest method as default. |
120 | | pub enum InterpolationMethod { |
121 | | /// General Tetrahedron interpolation. |
122 | | /// This is used in lcms2 and others CMS. |
123 | | #[cfg(feature = "options")] |
124 | | Tetrahedral, |
125 | | /// Divides cube into a pyramids and interpolate then in the pyramid. |
126 | | #[cfg(feature = "options")] |
127 | | Pyramid, |
128 | | /// Interpolation by dividing cube into prisms. |
129 | | #[cfg(feature = "options")] |
130 | | Prism, |
131 | | /// Trilinear/Quadlinear interpolation |
132 | | #[default] |
133 | | Linear, |
134 | | } |
135 | | |
136 | | impl Default for TransformOptions { |
137 | 0 | fn default() -> Self { |
138 | 0 | Self { |
139 | 0 | rendering_intent: RenderingIntent::default(), |
140 | 0 | allow_use_cicp_transfer: true, |
141 | 0 | prefer_fixed_point: true, |
142 | 0 | interpolation_method: InterpolationMethod::default(), |
143 | 0 | barycentric_weight_scale: BarycentricWeightScale::default(), |
144 | 0 | allow_extended_range_rgb_xyz: false, |
145 | 0 | // black_point_compensation: false, |
146 | 0 | } |
147 | 0 | } |
148 | | } |
149 | | |
150 | | pub type Transform8BitExecutor = dyn TransformExecutor<u8> + Send + Sync; |
151 | | pub type Transform16BitExecutor = dyn TransformExecutor<u16> + Send + Sync; |
152 | | pub type TransformF32BitExecutor = dyn TransformExecutor<f32> + Send + Sync; |
153 | | pub type TransformF64BitExecutor = dyn TransformExecutor<f64> + Send + Sync; |
154 | | |
155 | | /// Layout declares a data layout. |
156 | | /// For RGB it shows also the channel order. |
157 | | /// To handle different data bit-depth appropriate executor must be used. |
158 | | /// Cmyk8 uses the same layout as Rgba8. |
159 | | #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] |
160 | | pub enum Layout { |
161 | | Rgb = 0, |
162 | | Rgba = 1, |
163 | | Gray = 2, |
164 | | GrayAlpha = 3, |
165 | | Inks5 = 4, |
166 | | Inks6 = 5, |
167 | | Inks7 = 6, |
168 | | Inks8 = 7, |
169 | | Inks9 = 8, |
170 | | Inks10 = 9, |
171 | | Inks11 = 10, |
172 | | Inks12 = 11, |
173 | | Inks13 = 12, |
174 | | Inks14 = 13, |
175 | | Inks15 = 14, |
176 | | } |
177 | | |
178 | | impl Layout { |
179 | | /// Returns Red channel index |
180 | | #[inline(always)] |
181 | 0 | pub const fn r_i(self) -> usize { |
182 | 0 | match self { |
183 | 0 | Layout::Rgb => 0, |
184 | 0 | Layout::Rgba => 0, |
185 | 0 | Layout::Gray => unimplemented!(), |
186 | 0 | Layout::GrayAlpha => unimplemented!(), |
187 | 0 | _ => unimplemented!(), |
188 | | } |
189 | 0 | } |
190 | | |
191 | | /// Returns Green channel index |
192 | | #[inline(always)] |
193 | 0 | pub const fn g_i(self) -> usize { |
194 | 0 | match self { |
195 | 0 | Layout::Rgb => 1, |
196 | 0 | Layout::Rgba => 1, |
197 | 0 | Layout::Gray => unimplemented!(), |
198 | 0 | Layout::GrayAlpha => unimplemented!(), |
199 | 0 | _ => unimplemented!(), |
200 | | } |
201 | 0 | } |
202 | | |
203 | | /// Returns Blue channel index |
204 | | #[inline(always)] |
205 | 0 | pub const fn b_i(self) -> usize { |
206 | 0 | match self { |
207 | 0 | Layout::Rgb => 2, |
208 | 0 | Layout::Rgba => 2, |
209 | 0 | Layout::Gray => unimplemented!(), |
210 | 0 | Layout::GrayAlpha => unimplemented!(), |
211 | 0 | _ => unimplemented!(), |
212 | | } |
213 | 0 | } |
214 | | |
215 | | #[inline(always)] |
216 | 0 | pub const fn a_i(self) -> usize { |
217 | 0 | match self { |
218 | 0 | Layout::Rgb => unimplemented!(), |
219 | 0 | Layout::Rgba => 3, |
220 | 0 | Layout::Gray => unimplemented!(), |
221 | 0 | Layout::GrayAlpha => 1, |
222 | 0 | _ => unimplemented!(), |
223 | | } |
224 | 0 | } |
225 | | |
226 | | #[inline(always)] |
227 | 0 | pub const fn has_alpha(self) -> bool { |
228 | 0 | match self { |
229 | 0 | Layout::Rgb => false, |
230 | 0 | Layout::Rgba => true, |
231 | 0 | Layout::Gray => false, |
232 | 0 | Layout::GrayAlpha => true, |
233 | 0 | _ => false, |
234 | | } |
235 | 0 | } |
236 | | |
237 | | #[inline] |
238 | 0 | pub const fn channels(self) -> usize { |
239 | 0 | match self { |
240 | 0 | Layout::Rgb => 3, |
241 | 0 | Layout::Rgba => 4, |
242 | 0 | Layout::Gray => 1, |
243 | 0 | Layout::GrayAlpha => 2, |
244 | 0 | Layout::Inks5 => 5, |
245 | 0 | Layout::Inks6 => 6, |
246 | 0 | Layout::Inks7 => 7, |
247 | 0 | Layout::Inks8 => 8, |
248 | 0 | Layout::Inks9 => 9, |
249 | 0 | Layout::Inks10 => 10, |
250 | 0 | Layout::Inks11 => 11, |
251 | 0 | Layout::Inks12 => 12, |
252 | 0 | Layout::Inks13 => 13, |
253 | 0 | Layout::Inks14 => 14, |
254 | 0 | Layout::Inks15 => 15, |
255 | | } |
256 | 0 | } |
257 | | |
258 | 0 | pub(crate) fn from_inks(inks: usize) -> Self { |
259 | 0 | match inks { |
260 | 0 | 1 => Layout::Gray, |
261 | 0 | 2 => Layout::GrayAlpha, |
262 | 0 | 3 => Layout::Rgb, |
263 | 0 | 4 => Layout::Rgba, |
264 | 0 | 5 => Layout::Inks5, |
265 | 0 | 6 => Layout::Inks6, |
266 | 0 | 7 => Layout::Inks7, |
267 | 0 | 8 => Layout::Inks8, |
268 | 0 | 9 => Layout::Inks9, |
269 | 0 | 10 => Layout::Inks10, |
270 | 0 | 11 => Layout::Inks11, |
271 | 0 | 12 => Layout::Inks12, |
272 | 0 | 13 => Layout::Inks13, |
273 | 0 | 14 => Layout::Inks14, |
274 | 0 | 15 => Layout::Inks15, |
275 | 0 | _ => unreachable!("Impossible amount of inks"), |
276 | | } |
277 | 0 | } |
278 | | } |
279 | | |
280 | | impl From<u8> for Layout { |
281 | 0 | fn from(value: u8) -> Self { |
282 | 0 | match value { |
283 | 0 | 0 => Layout::Rgb, |
284 | 0 | 1 => Layout::Rgba, |
285 | 0 | 2 => Layout::Gray, |
286 | 0 | 3 => Layout::GrayAlpha, |
287 | 0 | _ => unimplemented!(), |
288 | | } |
289 | 0 | } |
290 | | } |
291 | | |
292 | | impl Layout { |
293 | | #[inline(always)] |
294 | 0 | pub const fn resolve(value: u8) -> Self { |
295 | 0 | match value { |
296 | 0 | 0 => Layout::Rgb, |
297 | 0 | 1 => Layout::Rgba, |
298 | 0 | 2 => Layout::Gray, |
299 | 0 | 3 => Layout::GrayAlpha, |
300 | 0 | 4 => Layout::Inks5, |
301 | 0 | 5 => Layout::Inks6, |
302 | 0 | 6 => Layout::Inks7, |
303 | 0 | 7 => Layout::Inks8, |
304 | 0 | 8 => Layout::Inks9, |
305 | 0 | 9 => Layout::Inks10, |
306 | 0 | 10 => Layout::Inks11, |
307 | 0 | 11 => Layout::Inks12, |
308 | 0 | 12 => Layout::Inks13, |
309 | 0 | 13 => Layout::Inks14, |
310 | 0 | 14 => Layout::Inks15, |
311 | 0 | _ => unimplemented!(), |
312 | | } |
313 | 0 | } |
314 | | } |
315 | | |
316 | | #[doc(hidden)] |
317 | | pub trait PointeeSizeExpressible { |
318 | | fn _as_usize(self) -> usize; |
319 | | const FINITE: bool; |
320 | | const NOT_FINITE_GAMMA_TABLE_SIZE: usize; |
321 | | const NOT_FINITE_LINEAR_TABLE_SIZE: usize; |
322 | | const IS_U8: bool; |
323 | | const IS_U16: bool; |
324 | | } |
325 | | |
326 | | impl PointeeSizeExpressible for u8 { |
327 | | #[inline(always)] |
328 | 0 | fn _as_usize(self) -> usize { |
329 | 0 | self as usize |
330 | 0 | } |
331 | | |
332 | | const FINITE: bool = true; |
333 | | const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1; |
334 | | const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1; |
335 | | const IS_U8: bool = true; |
336 | | const IS_U16: bool = false; |
337 | | } |
338 | | |
339 | | impl PointeeSizeExpressible for u16 { |
340 | | #[inline(always)] |
341 | 0 | fn _as_usize(self) -> usize { |
342 | 0 | self as usize |
343 | 0 | } |
344 | | |
345 | | const FINITE: bool = true; |
346 | | |
347 | | const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 1; |
348 | | const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1; |
349 | | |
350 | | const IS_U8: bool = false; |
351 | | const IS_U16: bool = true; |
352 | | } |
353 | | |
354 | | impl PointeeSizeExpressible for f32 { |
355 | | #[inline(always)] |
356 | 0 | fn _as_usize(self) -> usize { |
357 | | const MAX_14_BIT: f32 = ((1 << 14u32) - 1) as f32; |
358 | 0 | ((self * MAX_14_BIT).max(0f32).min(MAX_14_BIT) as u16) as usize |
359 | 0 | } |
360 | | |
361 | | const FINITE: bool = false; |
362 | | |
363 | | const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 32768; |
364 | | const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 14u32; |
365 | | const IS_U8: bool = false; |
366 | | const IS_U16: bool = false; |
367 | | } |
368 | | |
369 | | impl PointeeSizeExpressible for f64 { |
370 | | #[inline(always)] |
371 | 0 | fn _as_usize(self) -> usize { |
372 | | const MAX_16_BIT: f64 = ((1 << 16u32) - 1) as f64; |
373 | 0 | ((self * MAX_16_BIT).max(0.).min(MAX_16_BIT) as u16) as usize |
374 | 0 | } |
375 | | |
376 | | const FINITE: bool = false; |
377 | | |
378 | | const NOT_FINITE_GAMMA_TABLE_SIZE: usize = 65536; |
379 | | const NOT_FINITE_LINEAR_TABLE_SIZE: usize = 1 << 16; |
380 | | const IS_U8: bool = false; |
381 | | const IS_U16: bool = false; |
382 | | } |
383 | | |
384 | | impl ColorProfile { |
385 | | /// Checks if profile is valid *Matrix Shaper* profile |
386 | 0 | pub fn is_matrix_shaper(&self) -> bool { |
387 | 0 | self.color_space == DataColorSpace::Rgb |
388 | 0 | && self.red_colorant != Xyzd::default() |
389 | 0 | && self.green_colorant != Xyzd::default() |
390 | 0 | && self.blue_colorant != Xyzd::default() |
391 | 0 | && self.red_trc.is_some() |
392 | 0 | && self.green_trc.is_some() |
393 | 0 | && self.blue_trc.is_some() |
394 | 0 | } |
395 | | |
396 | | /// Creates transform between source and destination profile |
397 | | /// Use for 16 bit-depth data bit-depth only. |
398 | 0 | pub fn create_transform_16bit( |
399 | 0 | &self, |
400 | 0 | src_layout: Layout, |
401 | 0 | dst_pr: &ColorProfile, |
402 | 0 | dst_layout: Layout, |
403 | 0 | options: TransformOptions, |
404 | 0 | ) -> Result<Box<Transform16BitExecutor>, CmsError> { |
405 | 0 | self.create_transform_nbit::<u16, 16, 65536, 65536>(src_layout, dst_pr, dst_layout, options) |
406 | 0 | } |
407 | | |
408 | | /// Creates transform between source and destination profile |
409 | | /// Use for 12 bit-depth data bit-depth only. |
410 | 0 | pub fn create_transform_12bit( |
411 | 0 | &self, |
412 | 0 | src_layout: Layout, |
413 | 0 | dst_pr: &ColorProfile, |
414 | 0 | dst_layout: Layout, |
415 | 0 | options: TransformOptions, |
416 | 0 | ) -> Result<Box<Transform16BitExecutor>, CmsError> { |
417 | 0 | self.create_transform_nbit::<u16, 12, 65536, 16384>(src_layout, dst_pr, dst_layout, options) |
418 | 0 | } |
419 | | |
420 | | /// Creates transform between source and destination profile |
421 | | /// Use for 10 bit-depth data bit-depth only. |
422 | 0 | pub fn create_transform_10bit( |
423 | 0 | &self, |
424 | 0 | src_layout: Layout, |
425 | 0 | dst_pr: &ColorProfile, |
426 | 0 | dst_layout: Layout, |
427 | 0 | options: TransformOptions, |
428 | 0 | ) -> Result<Box<Transform16BitExecutor>, CmsError> { |
429 | 0 | self.create_transform_nbit::<u16, 10, 65536, 8192>(src_layout, dst_pr, dst_layout, options) |
430 | 0 | } |
431 | | |
432 | | /// Creates transform between source and destination profile |
433 | | /// Data has to be normalized into [0, 1] range. |
434 | | /// ICC profiles and LUT tables do not exist in infinite precision. |
435 | | /// Thus, this implementation considers `f32` as 14-bit values. |
436 | | /// Floating point transformer works in extended mode, that means returned data might be negative |
437 | | /// or more than 1. |
438 | 0 | pub fn create_transform_f32( |
439 | 0 | &self, |
440 | 0 | src_layout: Layout, |
441 | 0 | dst_pr: &ColorProfile, |
442 | 0 | dst_layout: Layout, |
443 | 0 | options: TransformOptions, |
444 | 0 | ) -> Result<Box<TransformF32BitExecutor>, CmsError> { |
445 | 0 | self.create_transform_nbit::<f32, 1, 65536, 32768>(src_layout, dst_pr, dst_layout, options) |
446 | 0 | } |
447 | | |
448 | | /// Creates transform between source and destination profile |
449 | | /// Data has to be normalized into [0, 1] range. |
450 | | /// ICC profiles and LUT tables do not exist in infinite precision. |
451 | | /// Thus, this implementation considers `f64` as 16-bit values. |
452 | | /// Floating point transformer works in extended mode, that means returned data might be negative |
453 | | /// or more than 1. |
454 | 0 | pub fn create_transform_f64( |
455 | 0 | &self, |
456 | 0 | src_layout: Layout, |
457 | 0 | dst_pr: &ColorProfile, |
458 | 0 | dst_layout: Layout, |
459 | 0 | options: TransformOptions, |
460 | 0 | ) -> Result<Box<TransformF64BitExecutor>, CmsError> { |
461 | 0 | self.create_transform_nbit::<f64, 1, 65536, 65536>(src_layout, dst_pr, dst_layout, options) |
462 | 0 | } |
463 | | |
464 | 0 | fn create_transform_nbit< |
465 | 0 | T: Copy |
466 | 0 | + Default |
467 | 0 | + AsPrimitive<usize> |
468 | 0 | + PointeeSizeExpressible |
469 | 0 | + Send |
470 | 0 | + Sync |
471 | 0 | + AsPrimitive<f32> |
472 | 0 | + RgbXyzFactory<T> |
473 | 0 | + RgbXyzFactoryOpt<T> |
474 | 0 | + GammaLutInterpolate, |
475 | 0 | const BIT_DEPTH: usize, |
476 | 0 | const LINEAR_CAP: usize, |
477 | 0 | const GAMMA_CAP: usize, |
478 | 0 | >( |
479 | 0 | &self, |
480 | 0 | src_layout: Layout, |
481 | 0 | dst_pr: &ColorProfile, |
482 | 0 | dst_layout: Layout, |
483 | 0 | options: TransformOptions, |
484 | 0 | ) -> Result<Box<dyn TransformExecutor<T> + Send + Sync>, CmsError> |
485 | 0 | where |
486 | 0 | f32: AsPrimitive<T>, |
487 | 0 | u32: AsPrimitive<T>, |
488 | 0 | (): LutBarycentricReduction<T, u8>, |
489 | 0 | (): LutBarycentricReduction<T, u16>, |
490 | | { |
491 | 0 | if self.color_space == DataColorSpace::Rgb |
492 | 0 | && dst_pr.pcs == DataColorSpace::Xyz |
493 | 0 | && dst_pr.color_space == DataColorSpace::Rgb |
494 | 0 | && self.pcs == DataColorSpace::Xyz |
495 | 0 | && self.is_matrix_shaper() |
496 | 0 | && dst_pr.is_matrix_shaper() |
497 | | { |
498 | 0 | if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha { |
499 | 0 | return Err(CmsError::InvalidLayout); |
500 | 0 | } |
501 | 0 | if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha { |
502 | 0 | return Err(CmsError::InvalidLayout); |
503 | 0 | } |
504 | | |
505 | 0 | if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() { |
506 | 0 | return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>( |
507 | 0 | src_layout, self, dst_layout, dst_pr, options, |
508 | | ); |
509 | 0 | } |
510 | | |
511 | 0 | let transform = self.transform_matrix(dst_pr); |
512 | | |
513 | 0 | if !T::FINITE && options.allow_extended_range_rgb_xyz { |
514 | 0 | if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() { |
515 | 0 | if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() { |
516 | | use crate::conversions::{ |
517 | | TransformShaperFloatInOut, make_rgb_xyz_rgb_transform_float_in_out, |
518 | | }; |
519 | 0 | let p = TransformShaperFloatInOut { |
520 | 0 | linear_evaluator, |
521 | 0 | gamma_evaluator, |
522 | 0 | adaptation_matrix: transform.to_f32(), |
523 | 0 | phantom_data: PhantomData, |
524 | 0 | }; |
525 | 0 | return make_rgb_xyz_rgb_transform_float_in_out::<T>( |
526 | 0 | src_layout, dst_layout, p, BIT_DEPTH, |
527 | | ); |
528 | 0 | } |
529 | | |
530 | 0 | let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
531 | 0 | options.allow_use_cicp_transfer, |
532 | 0 | )?; |
533 | 0 | let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
534 | 0 | options.allow_use_cicp_transfer, |
535 | 0 | )?; |
536 | 0 | let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
537 | 0 | options.allow_use_cicp_transfer, |
538 | 0 | )?; |
539 | | |
540 | | use crate::conversions::{ |
541 | | TransformShaperRgbFloat, make_rgb_xyz_rgb_transform_float, |
542 | | }; |
543 | 0 | let p = TransformShaperRgbFloat { |
544 | 0 | r_linear: lin_r, |
545 | 0 | g_linear: lin_g, |
546 | 0 | b_linear: lin_b, |
547 | 0 | gamma_evaluator, |
548 | 0 | adaptation_matrix: transform.to_f32(), |
549 | 0 | phantom_data: PhantomData, |
550 | 0 | }; |
551 | 0 | return make_rgb_xyz_rgb_transform_float::<T, LINEAR_CAP>( |
552 | 0 | src_layout, dst_layout, p, BIT_DEPTH, |
553 | | ); |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | 0 | if self.are_all_trc_the_same() && dst_pr.are_all_trc_the_same() { |
558 | 0 | let linear = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
559 | 0 | options.allow_use_cicp_transfer, |
560 | 0 | )?; |
561 | | |
562 | 0 | let gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
563 | 0 | &dst_pr.red_trc, |
564 | 0 | options.allow_use_cicp_transfer, |
565 | 0 | )?; |
566 | | |
567 | 0 | let profile_transform = crate::conversions::TransformMatrixShaperOptimized { |
568 | 0 | linear, |
569 | 0 | gamma, |
570 | 0 | adaptation_matrix: transform.to_f32(), |
571 | 0 | }; |
572 | | |
573 | 0 | return T::make_optimized_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>( |
574 | 0 | src_layout, |
575 | 0 | dst_layout, |
576 | 0 | profile_transform, |
577 | 0 | options, |
578 | | ); |
579 | 0 | } |
580 | | |
581 | 0 | let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
582 | 0 | options.allow_use_cicp_transfer, |
583 | 0 | )?; |
584 | 0 | let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
585 | 0 | options.allow_use_cicp_transfer, |
586 | 0 | )?; |
587 | 0 | let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
588 | 0 | options.allow_use_cicp_transfer, |
589 | 0 | )?; |
590 | | |
591 | 0 | let gamma_r = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
592 | 0 | &dst_pr.red_trc, |
593 | 0 | options.allow_use_cicp_transfer, |
594 | 0 | )?; |
595 | 0 | let gamma_g = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
596 | 0 | &dst_pr.green_trc, |
597 | 0 | options.allow_use_cicp_transfer, |
598 | 0 | )?; |
599 | 0 | let gamma_b = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
600 | 0 | &dst_pr.blue_trc, |
601 | 0 | options.allow_use_cicp_transfer, |
602 | 0 | )?; |
603 | | |
604 | 0 | let profile_transform = TransformMatrixShaper { |
605 | 0 | r_linear: lin_r, |
606 | 0 | g_linear: lin_g, |
607 | 0 | b_linear: lin_b, |
608 | 0 | r_gamma: gamma_r, |
609 | 0 | g_gamma: gamma_g, |
610 | 0 | b_gamma: gamma_b, |
611 | 0 | adaptation_matrix: transform.to_f32(), |
612 | 0 | }; |
613 | | |
614 | 0 | T::make_transform::<LINEAR_CAP, GAMMA_CAP, BIT_DEPTH>( |
615 | 0 | src_layout, |
616 | 0 | dst_layout, |
617 | 0 | profile_transform, |
618 | 0 | options, |
619 | | ) |
620 | 0 | } else if (self.color_space == DataColorSpace::Gray && self.gray_trc.is_some()) |
621 | 0 | && (dst_pr.color_space == DataColorSpace::Rgb |
622 | 0 | || (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some())) |
623 | 0 | && self.pcs == DataColorSpace::Xyz |
624 | 0 | && dst_pr.pcs == DataColorSpace::Xyz |
625 | | { |
626 | 0 | if src_layout != Layout::GrayAlpha && src_layout != Layout::Gray { |
627 | 0 | return Err(CmsError::InvalidLayout); |
628 | 0 | } |
629 | | |
630 | 0 | if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() { |
631 | 0 | return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>( |
632 | 0 | src_layout, self, dst_layout, dst_pr, options, |
633 | | ); |
634 | 0 | } |
635 | | |
636 | 0 | let gray_linear = self.build_gray_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>()?; |
637 | | |
638 | 0 | if dst_pr.color_space == DataColorSpace::Gray { |
639 | 0 | if !T::FINITE && options.allow_extended_range_rgb_xyz { |
640 | 0 | if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() { |
641 | 0 | if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() { |
642 | | // Gray -> Gray case extended range |
643 | | use crate::conversions::make_gray_to_one_trc_extended; |
644 | 0 | return make_gray_to_one_trc_extended::<T>( |
645 | 0 | src_layout, |
646 | 0 | dst_layout, |
647 | 0 | linear_evaluator, |
648 | 0 | gamma_evaluator, |
649 | | BIT_DEPTH, |
650 | | ); |
651 | 0 | } |
652 | 0 | } |
653 | 0 | } |
654 | | |
655 | | // Gray -> Gray case |
656 | 0 | let gray_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
657 | 0 | &dst_pr.gray_trc, |
658 | 0 | options.allow_use_cicp_transfer, |
659 | 0 | )?; |
660 | | |
661 | 0 | make_gray_to_x::<T, LINEAR_CAP>( |
662 | 0 | src_layout, |
663 | 0 | dst_layout, |
664 | 0 | &gray_linear, |
665 | 0 | &gray_gamma, |
666 | | BIT_DEPTH, |
667 | | GAMMA_CAP, |
668 | | ) |
669 | | } else { |
670 | | #[allow(clippy::collapsible_if)] |
671 | 0 | if dst_pr.are_all_trc_the_same() { |
672 | 0 | if !T::FINITE && options.allow_extended_range_rgb_xyz { |
673 | 0 | if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() { |
674 | 0 | if let Some(linear_evaluator) = |
675 | 0 | self.try_extended_linearizing_evaluator() |
676 | | { |
677 | | // Gray -> RGB where all TRC is the same with extended range |
678 | | use crate::conversions::make_gray_to_one_trc_extended; |
679 | 0 | return make_gray_to_one_trc_extended::<T>( |
680 | 0 | src_layout, |
681 | 0 | dst_layout, |
682 | 0 | linear_evaluator, |
683 | 0 | gamma_evaluator, |
684 | | BIT_DEPTH, |
685 | | ); |
686 | 0 | } |
687 | 0 | } |
688 | 0 | } |
689 | | |
690 | | // Gray -> RGB where all TRC is the same |
691 | 0 | let rgb_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
692 | 0 | &dst_pr.red_trc, |
693 | 0 | options.allow_use_cicp_transfer, |
694 | 0 | )?; |
695 | | |
696 | 0 | make_gray_to_x::<T, LINEAR_CAP>( |
697 | 0 | src_layout, |
698 | 0 | dst_layout, |
699 | 0 | &gray_linear, |
700 | 0 | &rgb_gamma, |
701 | | BIT_DEPTH, |
702 | | GAMMA_CAP, |
703 | | ) |
704 | | } else { |
705 | | // Gray -> RGB where all TRC is NOT the same |
706 | 0 | if !T::FINITE && options.allow_extended_range_rgb_xyz { |
707 | 0 | if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() { |
708 | 0 | if let Some(linear_evaluator) = |
709 | 0 | self.try_extended_linearizing_evaluator() |
710 | | { |
711 | | // Gray -> RGB where all TRC is NOT the same with extended range |
712 | | |
713 | | use crate::conversions::make_gray_to_rgb_extended; |
714 | 0 | return make_gray_to_rgb_extended::<T>( |
715 | 0 | src_layout, |
716 | 0 | dst_layout, |
717 | 0 | linear_evaluator, |
718 | 0 | gamma_evaluator, |
719 | | BIT_DEPTH, |
720 | | ); |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | | |
725 | 0 | let red_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
726 | 0 | &dst_pr.red_trc, |
727 | 0 | options.allow_use_cicp_transfer, |
728 | 0 | )?; |
729 | 0 | let green_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
730 | 0 | &dst_pr.green_trc, |
731 | 0 | options.allow_use_cicp_transfer, |
732 | 0 | )?; |
733 | 0 | let blue_gamma = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
734 | 0 | &dst_pr.blue_trc, |
735 | 0 | options.allow_use_cicp_transfer, |
736 | 0 | )?; |
737 | | |
738 | 0 | let mut gray_linear2 = Box::new([0f32; 65536]); |
739 | 0 | for (dst, src) in gray_linear2.iter_mut().zip(gray_linear.iter()) { |
740 | 0 | *dst = *src; |
741 | 0 | } |
742 | | |
743 | 0 | make_gray_to_unfused::<T, LINEAR_CAP>( |
744 | 0 | src_layout, |
745 | 0 | dst_layout, |
746 | 0 | gray_linear2, |
747 | 0 | red_gamma, |
748 | 0 | green_gamma, |
749 | 0 | blue_gamma, |
750 | | BIT_DEPTH, |
751 | | GAMMA_CAP, |
752 | | ) |
753 | | } |
754 | | } |
755 | 0 | } else if self.color_space == DataColorSpace::Rgb |
756 | 0 | && (dst_pr.color_space == DataColorSpace::Gray && dst_pr.gray_trc.is_some()) |
757 | 0 | && dst_pr.pcs == DataColorSpace::Xyz |
758 | 0 | && self.pcs == DataColorSpace::Xyz |
759 | | { |
760 | 0 | if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha { |
761 | 0 | return Err(CmsError::InvalidLayout); |
762 | 0 | } |
763 | 0 | if dst_layout != Layout::Gray && dst_layout != Layout::GrayAlpha { |
764 | 0 | return Err(CmsError::InvalidLayout); |
765 | 0 | } |
766 | | |
767 | 0 | if self.has_device_to_pcs_lut() || dst_pr.has_pcs_to_device_lut() { |
768 | 0 | return make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>( |
769 | 0 | src_layout, self, dst_layout, dst_pr, options, |
770 | | ); |
771 | 0 | } |
772 | | |
773 | 0 | let transform = self.transform_matrix(dst_pr).to_f32(); |
774 | | |
775 | 0 | let vector = Vector3f { |
776 | 0 | v: [transform.v[1][0], transform.v[1][1], transform.v[1][2]], |
777 | 0 | }; |
778 | | |
779 | 0 | if !T::FINITE && options.allow_extended_range_rgb_xyz { |
780 | 0 | if let Some(gamma_evaluator) = dst_pr.try_extended_gamma_evaluator() { |
781 | 0 | if let Some(linear_evaluator) = self.try_extended_linearizing_evaluator() { |
782 | | use crate::conversions::make_rgb_to_gray_extended; |
783 | 0 | return make_rgb_to_gray_extended::<T>( |
784 | 0 | src_layout, |
785 | 0 | dst_layout, |
786 | 0 | linear_evaluator, |
787 | 0 | gamma_evaluator, |
788 | 0 | vector, |
789 | | BIT_DEPTH, |
790 | | ); |
791 | 0 | } |
792 | 0 | } |
793 | 0 | } |
794 | | |
795 | 0 | let lin_r = self.build_r_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
796 | 0 | options.allow_use_cicp_transfer, |
797 | 0 | )?; |
798 | 0 | let lin_g = self.build_g_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
799 | 0 | options.allow_use_cicp_transfer, |
800 | 0 | )?; |
801 | 0 | let lin_b = self.build_b_linearize_table::<T, LINEAR_CAP, BIT_DEPTH>( |
802 | 0 | options.allow_use_cicp_transfer, |
803 | 0 | )?; |
804 | 0 | let gray_linear = dst_pr.build_gamma_table::<T, 65536, GAMMA_CAP, BIT_DEPTH>( |
805 | 0 | &dst_pr.gray_trc, |
806 | 0 | options.allow_use_cicp_transfer, |
807 | 0 | )?; |
808 | | |
809 | 0 | let trc_box = ToneReproductionRgbToGray::<T, LINEAR_CAP> { |
810 | 0 | r_linear: lin_r, |
811 | 0 | g_linear: lin_g, |
812 | 0 | b_linear: lin_b, |
813 | 0 | gray_gamma: gray_linear, |
814 | 0 | }; |
815 | | |
816 | 0 | make_rgb_to_gray::<T, LINEAR_CAP>( |
817 | 0 | src_layout, dst_layout, trc_box, vector, GAMMA_CAP, BIT_DEPTH, |
818 | | ) |
819 | 0 | } else if (self.color_space.is_three_channels() |
820 | 0 | || self.color_space == DataColorSpace::Cmyk |
821 | 0 | || self.color_space == DataColorSpace::Color4) |
822 | 0 | && (dst_pr.color_space.is_three_channels() |
823 | 0 | || dst_pr.color_space == DataColorSpace::Cmyk |
824 | 0 | || dst_pr.color_space == DataColorSpace::Color4) |
825 | 0 | && (dst_pr.pcs == DataColorSpace::Xyz || dst_pr.pcs == DataColorSpace::Lab) |
826 | 0 | && (self.pcs == DataColorSpace::Xyz || self.pcs == DataColorSpace::Lab) |
827 | | { |
828 | 0 | if src_layout == Layout::Gray || src_layout == Layout::GrayAlpha { |
829 | 0 | return Err(CmsError::InvalidLayout); |
830 | 0 | } |
831 | 0 | if dst_layout == Layout::Gray || dst_layout == Layout::GrayAlpha { |
832 | 0 | return Err(CmsError::InvalidLayout); |
833 | 0 | } |
834 | 0 | make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>( |
835 | 0 | src_layout, self, dst_layout, dst_pr, options, |
836 | | ) |
837 | | } else { |
838 | 0 | make_lut_transform::<T, BIT_DEPTH, LINEAR_CAP, GAMMA_CAP>( |
839 | 0 | src_layout, self, dst_layout, dst_pr, options, |
840 | | ) |
841 | | } |
842 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<f64, 1, 65536, 65536> Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<f32, 1, 65536, 32768> Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<u8, 8, 256, 4096> Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<u16, 16, 65536, 65536> Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<u16, 10, 65536, 8192> Unexecuted instantiation: <moxcms::profile::ColorProfile>::create_transform_nbit::<u16, 12, 65536, 16384> |
843 | | |
844 | | /// Creates transform between source and destination profile |
845 | | /// Only 8 bit is supported. |
846 | 0 | pub fn create_transform_8bit( |
847 | 0 | &self, |
848 | 0 | src_layout: Layout, |
849 | 0 | dst_pr: &ColorProfile, |
850 | 0 | dst_layout: Layout, |
851 | 0 | options: TransformOptions, |
852 | 0 | ) -> Result<Box<Transform8BitExecutor>, CmsError> { |
853 | 0 | self.create_transform_nbit::<u8, 8, 256, 4096>(src_layout, dst_pr, dst_layout, options) |
854 | 0 | } |
855 | | |
856 | 0 | pub(crate) fn get_device_to_pcs(&self, intent: RenderingIntent) -> Option<&LutWarehouse> { |
857 | 0 | match intent { |
858 | 0 | RenderingIntent::AbsoluteColorimetric => self.lut_a_to_b_colorimetric.as_ref(), |
859 | 0 | RenderingIntent::Saturation => self.lut_a_to_b_saturation.as_ref(), |
860 | 0 | RenderingIntent::RelativeColorimetric => self.lut_a_to_b_colorimetric.as_ref(), |
861 | 0 | RenderingIntent::Perceptual => self.lut_a_to_b_perceptual.as_ref(), |
862 | | } |
863 | 0 | } |
864 | | |
865 | 0 | pub(crate) fn get_pcs_to_device(&self, intent: RenderingIntent) -> Option<&LutWarehouse> { |
866 | 0 | match intent { |
867 | 0 | RenderingIntent::AbsoluteColorimetric => self.lut_b_to_a_colorimetric.as_ref(), |
868 | 0 | RenderingIntent::Saturation => self.lut_b_to_a_saturation.as_ref(), |
869 | 0 | RenderingIntent::RelativeColorimetric => self.lut_b_to_a_colorimetric.as_ref(), |
870 | 0 | RenderingIntent::Perceptual => self.lut_b_to_a_perceptual.as_ref(), |
871 | | } |
872 | 0 | } |
873 | | } |
874 | | |
875 | | #[cfg(test)] |
876 | | mod tests { |
877 | | use crate::{ColorProfile, DataColorSpace, Layout, RenderingIntent, TransformOptions}; |
878 | | use rand::Rng; |
879 | | |
880 | | #[test] |
881 | | fn test_transform_rgb8() { |
882 | | let mut srgb_profile = ColorProfile::new_srgb(); |
883 | | let bt2020_profile = ColorProfile::new_bt2020(); |
884 | | let random_point_x = rand::rng().random_range(0..255); |
885 | | let transform = bt2020_profile |
886 | | .create_transform_8bit( |
887 | | Layout::Rgb, |
888 | | &srgb_profile, |
889 | | Layout::Rgb, |
890 | | TransformOptions::default(), |
891 | | ) |
892 | | .unwrap(); |
893 | | let src = vec![random_point_x; 256 * 256 * 3]; |
894 | | let mut dst = vec![random_point_x; 256 * 256 * 3]; |
895 | | transform.transform(&src, &mut dst).unwrap(); |
896 | | |
897 | | let transform = bt2020_profile |
898 | | .create_transform_8bit( |
899 | | Layout::Rgb, |
900 | | &srgb_profile, |
901 | | Layout::Rgb, |
902 | | TransformOptions { |
903 | | ..TransformOptions::default() |
904 | | }, |
905 | | ) |
906 | | .unwrap(); |
907 | | transform.transform(&src, &mut dst).unwrap(); |
908 | | srgb_profile.rendering_intent = RenderingIntent::RelativeColorimetric; |
909 | | let transform = bt2020_profile |
910 | | .create_transform_8bit( |
911 | | Layout::Rgb, |
912 | | &srgb_profile, |
913 | | Layout::Rgb, |
914 | | TransformOptions { |
915 | | ..TransformOptions::default() |
916 | | }, |
917 | | ) |
918 | | .unwrap(); |
919 | | transform.transform(&src, &mut dst).unwrap(); |
920 | | srgb_profile.rendering_intent = RenderingIntent::Saturation; |
921 | | let transform = bt2020_profile |
922 | | .create_transform_8bit( |
923 | | Layout::Rgb, |
924 | | &srgb_profile, |
925 | | Layout::Rgb, |
926 | | TransformOptions { |
927 | | ..TransformOptions::default() |
928 | | }, |
929 | | ) |
930 | | .unwrap(); |
931 | | transform.transform(&src, &mut dst).unwrap(); |
932 | | } |
933 | | |
934 | | #[test] |
935 | | fn test_transform_rgba8() { |
936 | | let srgb_profile = ColorProfile::new_srgb(); |
937 | | let bt2020_profile = ColorProfile::new_bt2020(); |
938 | | let random_point_x = rand::rng().random_range(0..255); |
939 | | let transform = bt2020_profile |
940 | | .create_transform_8bit( |
941 | | Layout::Rgba, |
942 | | &srgb_profile, |
943 | | Layout::Rgba, |
944 | | TransformOptions::default(), |
945 | | ) |
946 | | .unwrap(); |
947 | | let src = vec![random_point_x; 256 * 256 * 4]; |
948 | | let mut dst = vec![random_point_x; 256 * 256 * 4]; |
949 | | transform.transform(&src, &mut dst).unwrap(); |
950 | | } |
951 | | |
952 | | #[test] |
953 | | fn test_transform_gray_to_rgb8() { |
954 | | let gray_profile = ColorProfile::new_gray_with_gamma(2.2f32); |
955 | | let bt2020_profile = ColorProfile::new_bt2020(); |
956 | | let random_point_x = rand::rng().random_range(0..255); |
957 | | let transform = gray_profile |
958 | | .create_transform_8bit( |
959 | | Layout::Gray, |
960 | | &bt2020_profile, |
961 | | Layout::Rgb, |
962 | | TransformOptions::default(), |
963 | | ) |
964 | | .unwrap(); |
965 | | let src = vec![random_point_x; 256 * 256]; |
966 | | let mut dst = vec![random_point_x; 256 * 256 * 3]; |
967 | | transform.transform(&src, &mut dst).unwrap(); |
968 | | } |
969 | | |
970 | | #[test] |
971 | | fn test_transform_gray_to_rgba8() { |
972 | | let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32); |
973 | | let bt2020_profile = ColorProfile::new_bt2020(); |
974 | | let random_point_x = rand::rng().random_range(0..255); |
975 | | let transform = srgb_profile |
976 | | .create_transform_8bit( |
977 | | Layout::Gray, |
978 | | &bt2020_profile, |
979 | | Layout::Rgba, |
980 | | TransformOptions::default(), |
981 | | ) |
982 | | .unwrap(); |
983 | | let src = vec![random_point_x; 256 * 256]; |
984 | | let mut dst = vec![random_point_x; 256 * 256 * 4]; |
985 | | transform.transform(&src, &mut dst).unwrap(); |
986 | | } |
987 | | |
988 | | #[test] |
989 | | fn test_transform_gray_to_gray_alpha8() { |
990 | | let srgb_profile = ColorProfile::new_gray_with_gamma(2.2f32); |
991 | | let bt2020_profile = ColorProfile::new_bt2020(); |
992 | | let random_point_x = rand::rng().random_range(0..255); |
993 | | let transform = srgb_profile |
994 | | .create_transform_8bit( |
995 | | Layout::Gray, |
996 | | &bt2020_profile, |
997 | | Layout::GrayAlpha, |
998 | | TransformOptions::default(), |
999 | | ) |
1000 | | .unwrap(); |
1001 | | let src = vec![random_point_x; 256 * 256]; |
1002 | | let mut dst = vec![random_point_x; 256 * 256 * 2]; |
1003 | | transform.transform(&src, &mut dst).unwrap(); |
1004 | | } |
1005 | | |
1006 | | #[test] |
1007 | | fn test_transform_rgb10() { |
1008 | | let srgb_profile = ColorProfile::new_srgb(); |
1009 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1010 | | let random_point_x = rand::rng().random_range(0..((1 << 10) - 1)); |
1011 | | let transform = bt2020_profile |
1012 | | .create_transform_10bit( |
1013 | | Layout::Rgb, |
1014 | | &srgb_profile, |
1015 | | Layout::Rgb, |
1016 | | TransformOptions::default(), |
1017 | | ) |
1018 | | .unwrap(); |
1019 | | let src = vec![random_point_x; 256 * 256 * 3]; |
1020 | | let mut dst = vec![random_point_x; 256 * 256 * 3]; |
1021 | | transform.transform(&src, &mut dst).unwrap(); |
1022 | | } |
1023 | | |
1024 | | #[test] |
1025 | | fn test_transform_rgb12() { |
1026 | | let srgb_profile = ColorProfile::new_srgb(); |
1027 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1028 | | let random_point_x = rand::rng().random_range(0..((1 << 12) - 1)); |
1029 | | let transform = bt2020_profile |
1030 | | .create_transform_12bit( |
1031 | | Layout::Rgb, |
1032 | | &srgb_profile, |
1033 | | Layout::Rgb, |
1034 | | TransformOptions::default(), |
1035 | | ) |
1036 | | .unwrap(); |
1037 | | let src = vec![random_point_x; 256 * 256 * 3]; |
1038 | | let mut dst = vec![random_point_x; 256 * 256 * 3]; |
1039 | | transform.transform(&src, &mut dst).unwrap(); |
1040 | | } |
1041 | | |
1042 | | #[test] |
1043 | | fn test_transform_rgb16() { |
1044 | | let srgb_profile = ColorProfile::new_srgb(); |
1045 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1046 | | let random_point_x = rand::rng().random_range(0..((1u32 << 16u32) - 1u32)) as u16; |
1047 | | let transform = bt2020_profile |
1048 | | .create_transform_16bit( |
1049 | | Layout::Rgb, |
1050 | | &srgb_profile, |
1051 | | Layout::Rgb, |
1052 | | TransformOptions::default(), |
1053 | | ) |
1054 | | .unwrap(); |
1055 | | let src = vec![random_point_x; 256 * 256 * 3]; |
1056 | | let mut dst = vec![random_point_x; 256 * 256 * 3]; |
1057 | | transform.transform(&src, &mut dst).unwrap(); |
1058 | | } |
1059 | | |
1060 | | #[test] |
1061 | | fn test_transform_round_trip_rgb8() { |
1062 | | let srgb_profile = ColorProfile::new_srgb(); |
1063 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1064 | | let transform = srgb_profile |
1065 | | .create_transform_8bit( |
1066 | | Layout::Rgb, |
1067 | | &bt2020_profile, |
1068 | | Layout::Rgb, |
1069 | | TransformOptions::default(), |
1070 | | ) |
1071 | | .unwrap(); |
1072 | | let mut src = vec![0u8; 256 * 256 * 3]; |
1073 | | for dst in src.chunks_exact_mut(3) { |
1074 | | dst[0] = 175; |
1075 | | dst[1] = 75; |
1076 | | dst[2] = 13; |
1077 | | } |
1078 | | let mut dst = vec![0u8; 256 * 256 * 3]; |
1079 | | transform.transform(&src, &mut dst).unwrap(); |
1080 | | |
1081 | | let transform_inverse = bt2020_profile |
1082 | | .create_transform_8bit( |
1083 | | Layout::Rgb, |
1084 | | &srgb_profile, |
1085 | | Layout::Rgb, |
1086 | | TransformOptions::default(), |
1087 | | ) |
1088 | | .unwrap(); |
1089 | | |
1090 | | transform_inverse.transform(&dst, &mut src).unwrap(); |
1091 | | |
1092 | | for src in src.chunks_exact_mut(3) { |
1093 | | let diff0 = (src[0] as i32 - 175).abs(); |
1094 | | let diff1 = (src[1] as i32 - 75).abs(); |
1095 | | let diff2 = (src[2] as i32 - 13).abs(); |
1096 | | assert!( |
1097 | | diff0 < 3, |
1098 | | "On channel 0 difference should be less than 3, but it was {diff0}" |
1099 | | ); |
1100 | | assert!( |
1101 | | diff1 < 3, |
1102 | | "On channel 1 difference should be less than 3, but it was {diff1}" |
1103 | | ); |
1104 | | assert!( |
1105 | | diff2 < 3, |
1106 | | "On channel 2 difference should be less than 3, but it was {diff2}" |
1107 | | ); |
1108 | | } |
1109 | | } |
1110 | | |
1111 | | #[test] |
1112 | | fn test_transform_round_trip_rgb10() { |
1113 | | let srgb_profile = ColorProfile::new_srgb(); |
1114 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1115 | | let transform = srgb_profile |
1116 | | .create_transform_10bit( |
1117 | | Layout::Rgb, |
1118 | | &bt2020_profile, |
1119 | | Layout::Rgb, |
1120 | | TransformOptions::default(), |
1121 | | ) |
1122 | | .unwrap(); |
1123 | | let mut src = vec![0u16; 256 * 256 * 3]; |
1124 | | for dst in src.chunks_exact_mut(3) { |
1125 | | dst[0] = 175; |
1126 | | dst[1] = 256; |
1127 | | dst[2] = 512; |
1128 | | } |
1129 | | let mut dst = vec![0u16; 256 * 256 * 3]; |
1130 | | transform.transform(&src, &mut dst).unwrap(); |
1131 | | |
1132 | | let transform_inverse = bt2020_profile |
1133 | | .create_transform_10bit( |
1134 | | Layout::Rgb, |
1135 | | &srgb_profile, |
1136 | | Layout::Rgb, |
1137 | | TransformOptions::default(), |
1138 | | ) |
1139 | | .unwrap(); |
1140 | | |
1141 | | transform_inverse.transform(&dst, &mut src).unwrap(); |
1142 | | |
1143 | | for src in src.chunks_exact_mut(3) { |
1144 | | let diff0 = (src[0] as i32 - 175).abs(); |
1145 | | let diff1 = (src[1] as i32 - 256).abs(); |
1146 | | let diff2 = (src[2] as i32 - 512).abs(); |
1147 | | assert!( |
1148 | | diff0 < 15, |
1149 | | "On channel 0 difference should be less than 15, but it was {diff0}" |
1150 | | ); |
1151 | | assert!( |
1152 | | diff1 < 15, |
1153 | | "On channel 1 difference should be less than 15, but it was {diff1}" |
1154 | | ); |
1155 | | assert!( |
1156 | | diff2 < 15, |
1157 | | "On channel 2 difference should be less than 15, but it was {diff2}" |
1158 | | ); |
1159 | | } |
1160 | | } |
1161 | | |
1162 | | #[test] |
1163 | | fn test_transform_round_trip_rgb12() { |
1164 | | let srgb_profile = ColorProfile::new_srgb(); |
1165 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1166 | | let transform = srgb_profile |
1167 | | .create_transform_12bit( |
1168 | | Layout::Rgb, |
1169 | | &bt2020_profile, |
1170 | | Layout::Rgb, |
1171 | | TransformOptions::default(), |
1172 | | ) |
1173 | | .unwrap(); |
1174 | | let mut src = vec![0u16; 256 * 256 * 3]; |
1175 | | for dst in src.chunks_exact_mut(3) { |
1176 | | dst[0] = 1750; |
1177 | | dst[1] = 2560; |
1178 | | dst[2] = 3143; |
1179 | | } |
1180 | | let mut dst = vec![0u16; 256 * 256 * 3]; |
1181 | | transform.transform(&src, &mut dst).unwrap(); |
1182 | | |
1183 | | let transform_inverse = bt2020_profile |
1184 | | .create_transform_12bit( |
1185 | | Layout::Rgb, |
1186 | | &srgb_profile, |
1187 | | Layout::Rgb, |
1188 | | TransformOptions::default(), |
1189 | | ) |
1190 | | .unwrap(); |
1191 | | |
1192 | | transform_inverse.transform(&dst, &mut src).unwrap(); |
1193 | | |
1194 | | for src in src.chunks_exact_mut(3) { |
1195 | | let diff0 = (src[0] as i32 - 1750).abs(); |
1196 | | let diff1 = (src[1] as i32 - 2560).abs(); |
1197 | | let diff2 = (src[2] as i32 - 3143).abs(); |
1198 | | assert!( |
1199 | | diff0 < 25, |
1200 | | "On channel 0 difference should be less than 25, but it was {diff0}" |
1201 | | ); |
1202 | | assert!( |
1203 | | diff1 < 25, |
1204 | | "On channel 1 difference should be less than 25, but it was {diff1}" |
1205 | | ); |
1206 | | assert!( |
1207 | | diff2 < 25, |
1208 | | "On channel 2 difference should be less than 25, but it was {diff2}" |
1209 | | ); |
1210 | | } |
1211 | | } |
1212 | | |
1213 | | #[test] |
1214 | | fn test_transform_round_trip_rgb16() { |
1215 | | let srgb_profile = ColorProfile::new_srgb(); |
1216 | | let bt2020_profile = ColorProfile::new_bt2020(); |
1217 | | let transform = srgb_profile |
1218 | | .create_transform_16bit( |
1219 | | Layout::Rgb, |
1220 | | &bt2020_profile, |
1221 | | Layout::Rgb, |
1222 | | TransformOptions::default(), |
1223 | | ) |
1224 | | .unwrap(); |
1225 | | let mut src = vec![0u16; 256 * 256 * 3]; |
1226 | | for dst in src.chunks_exact_mut(3) { |
1227 | | dst[0] = 1760; |
1228 | | dst[1] = 2560; |
1229 | | dst[2] = 5120; |
1230 | | } |
1231 | | let mut dst = vec![0u16; 256 * 256 * 3]; |
1232 | | transform.transform(&src, &mut dst).unwrap(); |
1233 | | |
1234 | | let transform_inverse = bt2020_profile |
1235 | | .create_transform_16bit( |
1236 | | Layout::Rgb, |
1237 | | &srgb_profile, |
1238 | | Layout::Rgb, |
1239 | | TransformOptions::default(), |
1240 | | ) |
1241 | | .unwrap(); |
1242 | | |
1243 | | transform_inverse.transform(&dst, &mut src).unwrap(); |
1244 | | |
1245 | | for src in src.chunks_exact_mut(3) { |
1246 | | let diff0 = (src[0] as i32 - 1760).abs(); |
1247 | | let diff1 = (src[1] as i32 - 2560).abs(); |
1248 | | let diff2 = (src[2] as i32 - 5120).abs(); |
1249 | | assert!( |
1250 | | diff0 < 35, |
1251 | | "On channel 0 difference should be less than 35, but it was {diff0}" |
1252 | | ); |
1253 | | assert!( |
1254 | | diff1 < 35, |
1255 | | "On channel 1 difference should be less than 35, but it was {diff1}" |
1256 | | ); |
1257 | | assert!( |
1258 | | diff2 < 35, |
1259 | | "On channel 2 difference should be less than 35, but it was {diff2}" |
1260 | | ); |
1261 | | } |
1262 | | } |
1263 | | |
1264 | | #[test] |
1265 | | fn test_transform_rgb_to_gray_extended() { |
1266 | | let srgb = ColorProfile::new_srgb(); |
1267 | | let mut gray_profile = ColorProfile::new_gray_with_gamma(1.0); |
1268 | | gray_profile.color_space = DataColorSpace::Gray; |
1269 | | gray_profile.gray_trc = srgb.red_trc.clone(); |
1270 | | let mut test_profile = vec![0.; 4]; |
1271 | | test_profile[2] = 1.; |
1272 | | let mut dst = vec![0.; 1]; |
1273 | | |
1274 | | let mut inverse = vec![0.; 4]; |
1275 | | |
1276 | | let cvt0 = srgb |
1277 | | .create_transform_f32( |
1278 | | Layout::Rgba, |
1279 | | &gray_profile, |
1280 | | Layout::Gray, |
1281 | | TransformOptions { |
1282 | | allow_extended_range_rgb_xyz: true, |
1283 | | ..Default::default() |
1284 | | }, |
1285 | | ) |
1286 | | .unwrap(); |
1287 | | cvt0.transform(&test_profile, &mut dst).unwrap(); |
1288 | | assert!((dst[0] - 0.273046) < 1e-4); |
1289 | | |
1290 | | let cvt_inverse = gray_profile |
1291 | | .create_transform_f32( |
1292 | | Layout::Gray, |
1293 | | &srgb, |
1294 | | Layout::Rgba, |
1295 | | TransformOptions { |
1296 | | allow_extended_range_rgb_xyz: false, |
1297 | | ..Default::default() |
1298 | | }, |
1299 | | ) |
1300 | | .unwrap(); |
1301 | | cvt_inverse.transform(&dst, &mut inverse).unwrap(); |
1302 | | assert!((inverse[0] - 0.273002833) < 1e-4); |
1303 | | |
1304 | | let cvt1 = srgb |
1305 | | .create_transform_f32( |
1306 | | Layout::Rgba, |
1307 | | &gray_profile, |
1308 | | Layout::Gray, |
1309 | | TransformOptions { |
1310 | | allow_extended_range_rgb_xyz: false, |
1311 | | ..Default::default() |
1312 | | }, |
1313 | | ) |
1314 | | .unwrap(); |
1315 | | cvt1.transform(&test_profile, &mut dst).unwrap(); |
1316 | | assert!((dst[0] - 0.27307168) < 1e-5); |
1317 | | |
1318 | | inverse.fill(0.); |
1319 | | |
1320 | | let cvt_inverse = gray_profile |
1321 | | .create_transform_f32( |
1322 | | Layout::Gray, |
1323 | | &srgb, |
1324 | | Layout::Rgba, |
1325 | | TransformOptions { |
1326 | | allow_extended_range_rgb_xyz: true, |
1327 | | ..Default::default() |
1328 | | }, |
1329 | | ) |
1330 | | .unwrap(); |
1331 | | cvt_inverse.transform(&dst, &mut inverse).unwrap(); |
1332 | | assert!((inverse[0] - 0.273002833) < 1e-4); |
1333 | | } |
1334 | | } |