moxcms/
reader.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 3/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 */
29use crate::helpers::{read_matrix_3d, read_vector_3d};
30use crate::profile::LutDataType;
31use crate::safe_math::{SafeAdd, SafeMul, SafePowi};
32use crate::tag::{TAG_SIZE, TagTypeDefinition};
33use crate::{
34    CicpColorPrimaries, CicpProfile, CmsError, ColorDateTime, ColorProfile, DescriptionString,
35    LocalizableString, LutMultidimensionalType, LutStore, LutType, LutWarehouse, Matrix3d,
36    Matrix3f, MatrixCoefficients, Measurement, MeasurementGeometry, ParsingOptions, ProfileText,
37    StandardIlluminant, StandardObserver, TechnologySignatures, ToneReprCurve,
38    TransferCharacteristics, Vector3d, ViewingConditions, Xyz, Xyzd,
39};
40
41/// Produces the nearest float to `a` with a maximum error of 1/1024 which
42/// happens for large values like 0x40000040.
43#[inline]
44pub(crate) const fn s15_fixed16_number_to_float(a: i32) -> f32 {
45    a as f32 / 65536.
46}
47
48#[inline]
49pub(crate) const fn s15_fixed16_number_to_double(a: i32) -> f64 {
50    a as f64 / 65536.
51}
52
53#[inline]
54pub(crate) const fn uint16_number_to_float(a: u32) -> f32 {
55    a as f32 / 65536.
56}
57
58#[inline]
59pub(crate) const fn uint16_number_to_float_fast(a: u32) -> f32 {
60    a as f32 * (1. / 65536.)
61}
62
63// #[inline]
64// pub(crate) fn uint8_number_to_float(a: u8) -> f32 {
65//     a as f32 / 255.0
66// }
67
68#[inline]
69pub(crate) fn uint8_number_to_float_fast(a: u8) -> f32 {
70    a as f32 * (1. / 255.0)
71}
72
73fn utf16be_to_utf16(slice: &[u8]) -> Vec<u16> {
74    let mut vec = vec![0u16; slice.len() / 2];
75    for (dst, chunk) in vec.iter_mut().zip(slice.chunks_exact(2)) {
76        *dst = u16::from_be_bytes([chunk[0], chunk[1]]);
77    }
78    vec
79}
80
81impl ColorProfile {
82    #[inline]
83    pub(crate) fn read_lut_type(
84        slice: &[u8],
85        entry: usize,
86        tag_size: usize,
87    ) -> Result<LutType, CmsError> {
88        let tag_size = if tag_size == 0 { TAG_SIZE } else { tag_size };
89        let last_tag_offset = tag_size.safe_add(entry)?;
90        if last_tag_offset > slice.len() {
91            return Err(CmsError::InvalidProfile);
92        }
93        let tag = &slice[entry..last_tag_offset];
94        if tag.len() < 48 {
95            return Err(CmsError::InvalidProfile);
96        }
97        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
98        LutType::try_from(tag_type)
99    }
100
101    #[inline]
102    pub(crate) fn read_viewing_conditions(
103        slice: &[u8],
104        entry: usize,
105        tag_size: usize,
106    ) -> Result<Option<ViewingConditions>, CmsError> {
107        if tag_size < 36 {
108            return Ok(None);
109        }
110        if slice.len() < entry.safe_add(36)? {
111            return Err(CmsError::InvalidProfile);
112        }
113        let tag = &slice[entry..entry.safe_add(36)?];
114        let tag_type =
115            TagTypeDefinition::from(u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]));
116        // Ignore unknown
117        if tag_type != TagTypeDefinition::DefViewingConditions {
118            return Ok(None);
119        }
120        let illuminant_x = i32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
121        let illuminant_y = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
122        let illuminant_z = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
123
124        let surround_x = i32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]);
125        let surround_y = i32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]);
126        let surround_z = i32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]);
127
128        let illuminant_type = u32::from_be_bytes([tag[32], tag[33], tag[34], tag[35]]);
129
130        let illuminant = Xyz::new(
131            s15_fixed16_number_to_float(illuminant_x),
132            s15_fixed16_number_to_float(illuminant_y),
133            s15_fixed16_number_to_float(illuminant_z),
134        );
135
136        let surround = Xyz::new(
137            s15_fixed16_number_to_float(surround_x),
138            s15_fixed16_number_to_float(surround_y),
139            s15_fixed16_number_to_float(surround_z),
140        );
141
142        let observer = StandardObserver::from(illuminant_type);
143
144        Ok(Some(ViewingConditions {
145            illuminant,
146            surround,
147            observer,
148        }))
149    }
150
151    pub(crate) fn read_string_tag(
152        slice: &[u8],
153        entry: usize,
154        tag_size: usize,
155    ) -> Result<Option<ProfileText>, CmsError> {
156        let tag_size = if tag_size == 0 { TAG_SIZE } else { tag_size };
157        if tag_size < 4 {
158            return Ok(None);
159        }
160        let last_tag_offset = tag_size.safe_add(entry)?;
161        if last_tag_offset > slice.len() {
162            return Err(CmsError::InvalidProfile);
163        }
164        let tag = &slice[entry..last_tag_offset];
165        if tag.len() < 8 {
166            return Ok(None);
167        }
168        let tag_type =
169            TagTypeDefinition::from(u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]));
170        // Ignore unknown
171        if tag_type == TagTypeDefinition::Text {
172            let sliced_from_to_end = &tag[8..tag.len()];
173            let str = String::from_utf8_lossy(sliced_from_to_end);
174            return Ok(Some(ProfileText::PlainString(str.to_string())));
175        } else if tag_type == TagTypeDefinition::MultiLocalizedUnicode {
176            if tag.len() < 28 {
177                return Err(CmsError::InvalidProfile);
178            }
179            // let record_size = u32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]) as usize;
180            // // Record size is reserved to be 12.
181            // if record_size != 12 {
182            //     return Err(CmsError::InvalidIcc);
183            // }
184            let records_count = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
185            let primary_language_code = String::from_utf8_lossy(&[tag[16], tag[17]]).to_string();
186            let primary_country_code = String::from_utf8_lossy(&[tag[18], tag[19]]).to_string();
187            let first_string_record_length =
188                u32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]) as usize;
189            let first_record_offset =
190                u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]) as usize;
191
192            if tag.len() < first_record_offset.safe_add(first_string_record_length)? {
193                return Ok(None);
194            }
195
196            let resliced =
197                &tag[first_record_offset..first_record_offset + first_string_record_length];
198            let cvt = utf16be_to_utf16(resliced);
199            let string_record = String::from_utf16_lossy(&cvt);
200
201            let mut records = vec![LocalizableString {
202                language: primary_language_code,
203                country: primary_country_code,
204                value: string_record,
205            }];
206
207            for record in 1..records_count {
208                // Localizable header must be at least 12 bytes
209                let localizable_header_offset = if record == 1 {
210                    28
211                } else {
212                    28 + 12 * (record - 1)
213                };
214                if tag.len() < localizable_header_offset + 12 {
215                    return Err(CmsError::InvalidProfile);
216                }
217                let choked = &tag[localizable_header_offset..localizable_header_offset + 12];
218
219                let language_code = String::from_utf8_lossy(&[choked[0], choked[1]]).to_string();
220                let country_code = String::from_utf8_lossy(&[choked[2], choked[3]]).to_string();
221                let record_length =
222                    u32::from_be_bytes([choked[4], choked[5], choked[6], choked[7]]) as usize;
223                let string_offset =
224                    u32::from_be_bytes([choked[8], choked[9], choked[10], choked[11]]) as usize;
225
226                if tag.len() < string_offset.safe_add(record_length)? {
227                    return Ok(None);
228                }
229                let resliced = &tag[string_offset..string_offset + record_length];
230                let cvt = utf16be_to_utf16(resliced);
231                let string_record = String::from_utf16_lossy(&cvt);
232                records.push(LocalizableString {
233                    country: country_code,
234                    language: language_code,
235                    value: string_record,
236                });
237            }
238
239            return Ok(Some(ProfileText::Localizable(records)));
240        } else if tag_type == TagTypeDefinition::Description {
241            if tag.len() < 12 {
242                return Err(CmsError::InvalidProfile);
243            }
244            let ascii_length = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
245            if tag.len() < 12.safe_add(ascii_length)? {
246                return Err(CmsError::InvalidProfile);
247            }
248            let sliced = &tag[12..12 + ascii_length];
249            let ascii_string = String::from_utf8_lossy(sliced).to_string();
250
251            let mut last_position = 12 + ascii_length;
252            if tag.len() < last_position + 8 {
253                return Err(CmsError::InvalidProfile);
254            }
255            let uc = &tag[last_position..last_position + 8];
256            let unicode_code = u32::from_be_bytes([uc[0], uc[1], uc[2], uc[3]]);
257            let unicode_length = u32::from_be_bytes([uc[4], uc[5], uc[6], uc[7]]) as usize * 2;
258            if tag.len() < unicode_length.safe_add(8)?.safe_add(last_position)? {
259                return Ok(None);
260            }
261
262            last_position += 8;
263            let uc = &tag[last_position..last_position + unicode_length];
264            let wc = utf16be_to_utf16(uc);
265            let unicode_string = String::from_utf16_lossy(&wc).to_string();
266
267            // last_position += unicode_length;
268            //
269            // if tag.len() < last_position + 2 {
270            //     return Err(CmsError::InvalidIcc);
271            // }
272
273            // let uc = &tag[last_position..last_position + 2];
274            // let script_code = uc[0];
275            // let mac_length = uc[1] as usize;
276            // last_position += 2;
277            // if tag.len() < last_position + mac_length {
278            //     return Err(CmsError::InvalidIcc);
279            // }
280            //
281            // let uc = &tag[last_position..last_position + unicode_length];
282            // let wc = utf16be_to_utf16(uc);
283            // let mac_string = String::from_utf16_lossy(&wc).to_string();
284
285            return Ok(Some(ProfileText::Description(DescriptionString {
286                ascii_string,
287                unicode_language_code: unicode_code,
288                unicode_string,
289                mac_string: "".to_string(),
290                script_code_code: -1,
291            })));
292        }
293        Ok(None)
294    }
295
296    #[must_use]
297    #[inline]
298    fn read_lut_table_f32(table: &[u8], lut_type: LutType) -> LutStore {
299        if lut_type == LutType::Lut16 {
300            let mut clut = vec![0u16; table.len() / 2];
301            for (src, dst) in table.chunks_exact(2).zip(clut.iter_mut()) {
302                *dst = u16::from_be_bytes([src[0], src[1]]);
303            }
304            LutStore::Store16(clut)
305        } else if lut_type == LutType::Lut8 {
306            let mut clut = vec![0u8; table.len()];
307            for (&src, dst) in table.iter().zip(clut.iter_mut()) {
308                *dst = src;
309            }
310            LutStore::Store8(clut)
311        } else {
312            unreachable!("This should never happen, report to https://github.com/awxkee/moxcms")
313        }
314    }
315
316    #[inline]
317    fn read_nested_tone_curves(
318        slice: &[u8],
319        offset: usize,
320        length: usize,
321        options: &ParsingOptions,
322    ) -> Result<Option<Vec<ToneReprCurve>>, CmsError> {
323        let mut curve_offset: usize = offset;
324        let mut curves = Vec::new();
325        for _ in 0..length {
326            if slice.len() < curve_offset.safe_add(12)? {
327                return Err(CmsError::InvalidProfile);
328            }
329            let mut tag_size = 0usize;
330            let new_curve = Self::read_trc_tag(slice, curve_offset, 0, &mut tag_size, options)?;
331            match new_curve {
332                None => return Err(CmsError::InvalidProfile),
333                Some(curve) => curves.push(curve),
334            }
335            curve_offset += tag_size;
336            // 4 byte aligned
337            if curve_offset % 4 != 0 {
338                curve_offset += 4 - curve_offset % 4;
339            }
340        }
341        Ok(Some(curves))
342    }
343
344    #[inline]
345    pub(crate) fn read_lut_abm_type(
346        slice: &[u8],
347        entry: usize,
348        tag_size: usize,
349        to_pcs: bool,
350        options: &ParsingOptions,
351    ) -> Result<Option<LutWarehouse>, CmsError> {
352        if tag_size < 48 {
353            return Ok(None);
354        }
355        let last_tag_offset = tag_size.safe_add(entry)?;
356        if last_tag_offset > slice.len() {
357            return Err(CmsError::InvalidProfile);
358        }
359        let tag = &slice[entry..last_tag_offset];
360        if tag.len() < 48 {
361            return Err(CmsError::InvalidProfile);
362        }
363        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
364        let tag_type_definition = TagTypeDefinition::from(tag_type);
365        if tag_type_definition != TagTypeDefinition::MabLut
366            && tag_type_definition != TagTypeDefinition::MbaLut
367        {
368            return Ok(None);
369        }
370        let in_channels = tag[8];
371        let out_channels = tag[9];
372        if in_channels > 4 && out_channels > 4 {
373            return Ok(None);
374        }
375        let a_curve_offset = u32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]) as usize;
376        let clut_offset = u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]) as usize;
377        let m_curve_offset = u32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]) as usize;
378        let matrix_offset = u32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]) as usize;
379        let b_curve_offset = u32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]) as usize;
380
381        let transform: Matrix3d;
382        let bias: Vector3d;
383        if matrix_offset != 0 {
384            let matrix_end = matrix_offset.safe_add(12 * 4)?;
385            if tag.len() < matrix_end {
386                return Err(CmsError::InvalidProfile);
387            }
388
389            let m_tag = &tag[matrix_offset..matrix_end];
390
391            bias = read_vector_3d(&m_tag[36..48])?;
392            transform = read_matrix_3d(m_tag)?;
393        } else {
394            transform = Matrix3d::IDENTITY;
395            bias = Vector3d::default();
396        }
397
398        let mut grid_points: [u8; 16] = [0; 16];
399
400        let clut_table: Option<LutStore> = if clut_offset != 0 {
401            // Check if CLUT formed correctly
402            if clut_offset.safe_add(20)? > tag.len() {
403                return Err(CmsError::InvalidProfile);
404            }
405
406            let clut_sizes_slice = &tag[clut_offset..clut_offset.safe_add(16)?];
407            for (&s, v) in clut_sizes_slice.iter().zip(grid_points.iter_mut()) {
408                *v = s;
409            }
410
411            let mut clut_size = 1u32;
412            for &i in grid_points.iter().take(in_channels as usize) {
413                clut_size *= i as u32;
414            }
415            clut_size *= out_channels as u32;
416
417            if clut_size == 0 {
418                return Err(CmsError::InvalidProfile);
419            }
420
421            if clut_size > 10_000_000 {
422                return Err(CmsError::InvalidProfile);
423            }
424
425            let clut_offset20 = clut_offset.safe_add(20)?;
426
427            let clut_header = &tag[clut_offset..clut_offset20];
428            let entry_size = clut_header[16];
429            if entry_size != 1 && entry_size != 2 {
430                return Err(CmsError::InvalidProfile);
431            }
432
433            let clut_end =
434                clut_offset20.safe_add(clut_size.safe_mul(entry_size as u32)? as usize)?;
435
436            if tag.len() < clut_end {
437                return Err(CmsError::InvalidProfile);
438            }
439
440            let shaped_clut_table = &tag[clut_offset20..clut_end];
441            Some(Self::read_lut_table_f32(
442                shaped_clut_table,
443                if entry_size == 1 {
444                    LutType::Lut8
445                } else {
446                    LutType::Lut16
447                },
448            ))
449        } else {
450            None
451        };
452
453        let a_curves = if a_curve_offset == 0 {
454            Vec::new()
455        } else {
456            Self::read_nested_tone_curves(
457                tag,
458                a_curve_offset,
459                if to_pcs {
460                    in_channels as usize
461                } else {
462                    out_channels as usize
463                },
464                options,
465            )?
466            .ok_or(CmsError::InvalidProfile)?
467        };
468
469        let m_curves = if m_curve_offset == 0 {
470            Vec::new()
471        } else {
472            Self::read_nested_tone_curves(
473                tag,
474                m_curve_offset,
475                if to_pcs {
476                    out_channels as usize
477                } else {
478                    in_channels as usize
479                },
480                options,
481            )?
482            .ok_or(CmsError::InvalidProfile)?
483        };
484
485        let b_curves = if b_curve_offset == 0 {
486            Vec::new()
487        } else {
488            Self::read_nested_tone_curves(
489                tag,
490                b_curve_offset,
491                if to_pcs {
492                    out_channels as usize
493                } else {
494                    in_channels as usize
495                },
496                options,
497            )?
498            .ok_or(CmsError::InvalidProfile)?
499        };
500
501        let wh = LutWarehouse::Multidimensional(LutMultidimensionalType {
502            num_input_channels: in_channels,
503            num_output_channels: out_channels,
504            matrix: transform,
505            clut: clut_table,
506            a_curves,
507            b_curves,
508            m_curves,
509            grid_points,
510            bias,
511        });
512        Ok(Some(wh))
513    }
514
515    #[inline]
516    pub(crate) fn read_lut_a_to_b_type(
517        slice: &[u8],
518        entry: usize,
519        tag_size: usize,
520        parsing_options: &ParsingOptions,
521    ) -> Result<Option<LutWarehouse>, CmsError> {
522        if tag_size < 48 {
523            return Ok(None);
524        }
525        let last_tag_offset = tag_size.safe_add(entry)?;
526        if last_tag_offset > slice.len() {
527            return Err(CmsError::InvalidProfile);
528        }
529        let tag = &slice[entry..last_tag_offset];
530        if tag.len() < 48 {
531            return Err(CmsError::InvalidProfile);
532        }
533        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
534        let lut_type = LutType::try_from(tag_type)?;
535        assert!(lut_type == LutType::Lut8 || lut_type == LutType::Lut16);
536
537        if lut_type == LutType::Lut16 && tag.len() < 52 {
538            return Err(CmsError::InvalidProfile);
539        }
540
541        let num_input_table_entries: u16 = match lut_type {
542            LutType::Lut8 => 256,
543            LutType::Lut16 => u16::from_be_bytes([tag[48], tag[49]]),
544            _ => unreachable!(),
545        };
546        let num_output_table_entries: u16 = match lut_type {
547            LutType::Lut8 => 256,
548            LutType::Lut16 => u16::from_be_bytes([tag[50], tag[51]]),
549            _ => unreachable!(),
550        };
551
552        if !(2..=4096).contains(&num_input_table_entries)
553            || !(2..=4096).contains(&num_output_table_entries)
554        {
555            return Err(CmsError::InvalidProfile);
556        }
557
558        let input_offset: usize = match lut_type {
559            LutType::Lut8 => 48,
560            LutType::Lut16 => 52,
561            _ => unreachable!(),
562        };
563        let entry_size: usize = match lut_type {
564            LutType::Lut8 => 1,
565            LutType::Lut16 => 2,
566            _ => unreachable!(),
567        };
568
569        let in_chan = tag[8];
570        let out_chan = tag[9];
571        let is_3_to_4 = in_chan == 3 || out_chan == 4;
572        let is_4_to_3 = in_chan == 4 || out_chan == 3;
573        if !is_3_to_4 && !is_4_to_3 {
574            return Err(CmsError::InvalidProfile);
575        }
576        let grid_points = tag[10];
577        let clut_size = (grid_points as u32).safe_powi(in_chan as u32)? as usize;
578
579        if !(1..=parsing_options.max_allowed_clut_size).contains(&clut_size) {
580            return Err(CmsError::InvalidProfile);
581        }
582
583        assert!(tag.len() >= 48);
584
585        let transform = read_matrix_3d(&tag[12..48])?;
586
587        let lut_input_size = num_input_table_entries.safe_mul(in_chan as u16)? as usize;
588
589        let linearization_table_end = lut_input_size
590            .safe_mul(entry_size)?
591            .safe_add(input_offset)?;
592        if tag.len() < linearization_table_end {
593            return Err(CmsError::InvalidProfile);
594        }
595        let shaped_input_table = &tag[input_offset..linearization_table_end];
596        let linearization_table = Self::read_lut_table_f32(shaped_input_table, lut_type);
597
598        let clut_offset = linearization_table_end;
599
600        let clut_data_size = clut_size
601            .safe_mul(out_chan as usize)?
602            .safe_mul(entry_size)?;
603
604        if tag.len() < clut_offset.safe_add(clut_data_size)? {
605            return Err(CmsError::InvalidProfile);
606        }
607
608        let shaped_clut_table = &tag[clut_offset..clut_offset + clut_data_size];
609        let clut_table = Self::read_lut_table_f32(shaped_clut_table, lut_type);
610
611        let output_offset = clut_offset.safe_add(clut_data_size)?;
612
613        let output_size = (num_output_table_entries as usize).safe_mul(out_chan as usize)?;
614
615        let shaped_output_table =
616            &tag[output_offset..output_offset.safe_add(output_size.safe_mul(entry_size)?)?];
617        let gamma_table = Self::read_lut_table_f32(shaped_output_table, lut_type);
618
619        let wh = LutWarehouse::Lut(LutDataType {
620            num_input_table_entries,
621            num_output_table_entries,
622            num_input_channels: in_chan,
623            num_output_channels: out_chan,
624            num_clut_grid_points: grid_points,
625            matrix: transform,
626            input_table: linearization_table,
627            clut_table,
628            output_table: gamma_table,
629            lut_type,
630        });
631        Ok(Some(wh))
632    }
633
634    pub(crate) fn read_lut_tag(
635        slice: &[u8],
636        tag_entry: u32,
637        tag_size: usize,
638        parsing_options: &ParsingOptions,
639    ) -> Result<Option<LutWarehouse>, CmsError> {
640        let lut_type = Self::read_lut_type(slice, tag_entry as usize, tag_size)?;
641        Ok(if lut_type == LutType::Lut8 || lut_type == LutType::Lut16 {
642            Self::read_lut_a_to_b_type(slice, tag_entry as usize, tag_size, parsing_options)?
643        } else if lut_type == LutType::LutMba || lut_type == LutType::LutMab {
644            Self::read_lut_abm_type(
645                slice,
646                tag_entry as usize,
647                tag_size,
648                lut_type == LutType::LutMab,
649                parsing_options,
650            )?
651        } else {
652            None
653        })
654    }
655
656    pub(crate) fn read_trc_tag_s(
657        slice: &[u8],
658        entry: usize,
659        tag_size: usize,
660        options: &ParsingOptions,
661    ) -> Result<Option<ToneReprCurve>, CmsError> {
662        let mut _empty = 0usize;
663        Self::read_trc_tag(slice, entry, tag_size, &mut _empty, options)
664    }
665
666    pub(crate) fn read_trc_tag(
667        slice: &[u8],
668        entry: usize,
669        tag_size: usize,
670        read_size: &mut usize,
671        options: &ParsingOptions,
672    ) -> Result<Option<ToneReprCurve>, CmsError> {
673        if slice.len() < entry.safe_add(4)? {
674            return Ok(None);
675        }
676        let small_tag = &slice[entry..entry + 4];
677        // We require always recognize tone curves.
678        let curve_type = TagTypeDefinition::from(u32::from_be_bytes([
679            small_tag[0],
680            small_tag[1],
681            small_tag[2],
682            small_tag[3],
683        ]));
684        if tag_size != 0 && tag_size < TAG_SIZE {
685            return Ok(None);
686        }
687        let last_tag_offset = if tag_size != 0 {
688            tag_size + entry
689        } else {
690            slice.len()
691        };
692        if last_tag_offset > slice.len() {
693            return Err(CmsError::MalformedTrcCurve("Data exhausted".to_string()));
694        }
695        let tag = &slice[entry..last_tag_offset];
696        if tag.len() < TAG_SIZE {
697            return Err(CmsError::MalformedTrcCurve("Data exhausted".to_string()));
698        }
699        if curve_type == TagTypeDefinition::LutToneCurve {
700            let entry_count = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]) as usize;
701            if entry_count == 0 {
702                return Ok(Some(ToneReprCurve::Lut(vec![])));
703            }
704            if entry_count > options.max_allowed_trc_size {
705                return Err(CmsError::CurveLutIsTooLarge);
706            }
707            let curve_end = entry_count.safe_mul(size_of::<u16>())?.safe_add(12)?;
708            if tag.len() < curve_end {
709                return Err(CmsError::MalformedTrcCurve(
710                    "Curve end ends to early".to_string(),
711                ));
712            }
713            let curve_sliced = &tag[12..curve_end];
714            let mut curve_values = vec![0u16; entry_count];
715            for (value, curve_value) in curve_sliced.chunks_exact(2).zip(curve_values.iter_mut()) {
716                let gamma_s15 = u16::from_be_bytes([value[0], value[1]]);
717                *curve_value = gamma_s15;
718            }
719            *read_size = curve_end;
720            Ok(Some(ToneReprCurve::Lut(curve_values)))
721        } else if curve_type == TagTypeDefinition::ParametricToneCurve {
722            let entry_count = u16::from_be_bytes([tag[8], tag[9]]) as usize;
723            if entry_count > 4 {
724                return Err(CmsError::MalformedTrcCurve(
725                    "Parametric curve has unknown entries count".to_string(),
726                ));
727            }
728
729            const COUNT_TO_LENGTH: [usize; 5] = [1, 3, 4, 5, 7]; //PARAMETRIC_CURVE_TYPE
730
731            if tag.len() < 12 + COUNT_TO_LENGTH[entry_count] * size_of::<u32>() {
732                return Err(CmsError::MalformedTrcCurve(
733                    "Parametric curve has unknown entries count exhaust data too early".to_string(),
734                ));
735            }
736            let curve_sliced = &tag[12..12 + COUNT_TO_LENGTH[entry_count] * size_of::<u32>()];
737            let mut params = vec![0f32; COUNT_TO_LENGTH[entry_count]];
738            for (value, param_value) in curve_sliced.chunks_exact(4).zip(params.iter_mut()) {
739                let parametric_value = i32::from_be_bytes([value[0], value[1], value[2], value[3]]);
740                *param_value = s15_fixed16_number_to_float(parametric_value);
741            }
742            if entry_count == 1 || entry_count == 2 {
743                // we have a type 1 or type 2 function that has a division by `a`
744                let a: f32 = params[1];
745                if a == 0.0 {
746                    return Err(CmsError::ParametricCurveZeroDivision);
747                }
748            }
749            *read_size = 12 + COUNT_TO_LENGTH[entry_count] * 4;
750            Ok(Some(ToneReprCurve::Parametric(params)))
751        } else {
752            Err(CmsError::MalformedTrcCurve(
753                "Unknown parametric curve tag".to_string(),
754            ))
755        }
756    }
757
758    #[inline]
759    pub(crate) fn read_chad_tag(
760        slice: &[u8],
761        entry: usize,
762        tag_size: usize,
763    ) -> Result<Option<Matrix3d>, CmsError> {
764        let last_tag_offset = tag_size.safe_add(entry)?;
765        if last_tag_offset > slice.len() {
766            return Err(CmsError::InvalidProfile);
767        }
768        if slice[entry..].len() < 8 {
769            return Err(CmsError::InvalidProfile);
770        }
771        if tag_size < 8 {
772            return Ok(None);
773        }
774        if (tag_size - 8) / 4 != 9 {
775            return Ok(None);
776        }
777        let tag0 = &slice[entry..entry.safe_add(8)?];
778        let c_type =
779            TagTypeDefinition::from(u32::from_be_bytes([tag0[0], tag0[1], tag0[2], tag0[3]]));
780        if c_type != TagTypeDefinition::S15Fixed16Array {
781            return Err(CmsError::InvalidProfile);
782        }
783        if slice.len() < 9 * size_of::<u32>() + 8 {
784            return Err(CmsError::InvalidProfile);
785        }
786        let tag = &slice[entry + 8..last_tag_offset];
787        if tag.len() != size_of::<Matrix3f>() {
788            return Err(CmsError::InvalidProfile);
789        }
790        let matrix = read_matrix_3d(tag)?;
791        Ok(Some(matrix))
792    }
793
794    #[inline]
795    pub(crate) fn read_tech_tag(
796        slice: &[u8],
797        entry: usize,
798        tag_size: usize,
799    ) -> Result<Option<TechnologySignatures>, CmsError> {
800        if tag_size < TAG_SIZE {
801            return Err(CmsError::InvalidProfile);
802        }
803        let last_tag_offset = tag_size.safe_add(entry)?;
804        if last_tag_offset > slice.len() {
805            return Err(CmsError::InvalidProfile);
806        }
807        let tag = &slice[entry..entry.safe_add(12)?];
808        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
809        let def = TagTypeDefinition::from(tag_type);
810        if def == TagTypeDefinition::Signature {
811            let sig = u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
812            let tech_sig = TechnologySignatures::from(sig);
813            return Ok(Some(tech_sig));
814        }
815        Ok(None)
816    }
817
818    #[inline]
819    pub(crate) fn read_date_time_tag(
820        slice: &[u8],
821        entry: usize,
822        tag_size: usize,
823    ) -> Result<Option<ColorDateTime>, CmsError> {
824        if tag_size < 20 {
825            return Ok(None);
826        }
827        let last_tag_offset = tag_size.safe_add(entry)?;
828        if last_tag_offset > slice.len() {
829            return Err(CmsError::InvalidProfile);
830        }
831        let tag = &slice[entry..entry.safe_add(20)?];
832        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
833        let def = TagTypeDefinition::from(tag_type);
834        if def == TagTypeDefinition::DateTime {
835            let tag_value = &slice[8..20];
836            let time = ColorDateTime::new_from_slice(tag_value)?;
837            return Ok(Some(time));
838        }
839        Ok(None)
840    }
841
842    #[inline]
843    pub(crate) fn read_meas_tag(
844        slice: &[u8],
845        entry: usize,
846        tag_size: usize,
847    ) -> Result<Option<Measurement>, CmsError> {
848        if tag_size < TAG_SIZE {
849            return Ok(None);
850        }
851        let last_tag_offset = tag_size.safe_add(entry)?;
852        if last_tag_offset > slice.len() {
853            return Err(CmsError::InvalidProfile);
854        }
855        let tag = &slice[entry..entry + 12];
856        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
857        let def = TagTypeDefinition::from(tag_type);
858        if def != TagTypeDefinition::Measurement {
859            return Ok(None);
860        }
861        if 36 > slice.len() {
862            return Err(CmsError::InvalidProfile);
863        }
864        let tag = &slice[entry..entry + 36];
865        let observer =
866            StandardObserver::from(u32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]));
867        let q15_16_x = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
868        let q15_16_y = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
869        let q15_16_z = i32::from_be_bytes([tag[20], tag[21], tag[22], tag[23]]);
870        let x = s15_fixed16_number_to_float(q15_16_x);
871        let y = s15_fixed16_number_to_float(q15_16_y);
872        let z = s15_fixed16_number_to_float(q15_16_z);
873        let xyz = Xyz::new(x, y, z);
874        let geometry =
875            MeasurementGeometry::from(u32::from_be_bytes([tag[24], tag[25], tag[26], tag[27]]));
876        let flare =
877            uint16_number_to_float(u32::from_be_bytes([tag[28], tag[29], tag[30], tag[31]]));
878        let illuminant =
879            StandardIlluminant::from(u32::from_be_bytes([tag[32], tag[33], tag[34], tag[35]]));
880        Ok(Some(Measurement {
881            flare,
882            illuminant,
883            geometry,
884            observer,
885            backing: xyz,
886        }))
887    }
888
889    #[inline]
890    pub(crate) fn read_xyz_tag(
891        slice: &[u8],
892        entry: usize,
893        tag_size: usize,
894    ) -> Result<Xyzd, CmsError> {
895        if tag_size < TAG_SIZE {
896            return Ok(Xyzd::default());
897        }
898        let last_tag_offset = tag_size.safe_add(entry)?;
899        if last_tag_offset > slice.len() {
900            return Err(CmsError::InvalidProfile);
901        }
902        let tag = &slice[entry..entry + 12];
903        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
904        let def = TagTypeDefinition::from(tag_type);
905        if def != TagTypeDefinition::Xyz {
906            return Ok(Xyzd::default());
907        }
908
909        let tag = &slice[entry..last_tag_offset];
910        if tag.len() < 20 {
911            return Err(CmsError::InvalidProfile);
912        }
913        let q15_16_x = i32::from_be_bytes([tag[8], tag[9], tag[10], tag[11]]);
914        let q15_16_y = i32::from_be_bytes([tag[12], tag[13], tag[14], tag[15]]);
915        let q15_16_z = i32::from_be_bytes([tag[16], tag[17], tag[18], tag[19]]);
916        let x = s15_fixed16_number_to_double(q15_16_x);
917        let y = s15_fixed16_number_to_double(q15_16_y);
918        let z = s15_fixed16_number_to_double(q15_16_z);
919        Ok(Xyzd { x, y, z })
920    }
921
922    #[inline]
923    pub(crate) fn read_cicp_tag(
924        slice: &[u8],
925        entry: usize,
926        tag_size: usize,
927    ) -> Result<Option<CicpProfile>, CmsError> {
928        if tag_size < TAG_SIZE {
929            return Ok(None);
930        }
931        let last_tag_offset = tag_size.safe_add(entry)?;
932        if last_tag_offset > slice.len() {
933            return Err(CmsError::InvalidProfile);
934        }
935        let tag = &slice[entry..last_tag_offset];
936        if tag.len() < 12 {
937            return Err(CmsError::InvalidProfile);
938        }
939        let tag_type = u32::from_be_bytes([tag[0], tag[1], tag[2], tag[3]]);
940        let def = TagTypeDefinition::from(tag_type);
941        if def != TagTypeDefinition::Cicp {
942            return Ok(None);
943        }
944        let primaries = CicpColorPrimaries::try_from(tag[8])?;
945        let transfer_characteristics = TransferCharacteristics::try_from(tag[9])?;
946        let matrix_coefficients = MatrixCoefficients::try_from(tag[10])?;
947        let full_range = tag[11] == 1;
948        Ok(Some(CicpProfile {
949            color_primaries: primaries,
950            transfer_characteristics,
951            matrix_coefficients,
952            full_range,
953        }))
954    }
955}