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