/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 | | } |