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