1use 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#[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]
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 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 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 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 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 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 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 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 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]; 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 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}