Coverage Report

Created: 2026-03-10 07:34

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