Skip to main content

moxcms/
writer.rs

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 */
29use crate::profile::{LutDataType, ProfileHeader};
30use crate::tag::{TAG_SIZE, Tag, TagTypeDefinition};
31use crate::trc::ToneReprCurve;
32use crate::{
33    CicpProfile, CmsError, ColorDateTime, ColorProfile, DataColorSpace, LocalizableString,
34    LutMultidimensionalType, LutStore, LutType, LutWarehouse, Matrix3d, ProfileClass,
35    ProfileSignature, ProfileText, ProfileVersion, Vector3d, ViewingConditions, Xyz, Xyzd,
36};
37
38pub(crate) trait FloatToFixedS15Fixed16 {
39    fn to_s15_fixed16(self) -> i32;
40}
41
42pub(crate) trait FloatToFixedU8Fixed8 {
43    fn to_u8_fixed8(self) -> u16;
44}
45
46// pub(crate) trait FloatToFixedU16 {
47//     fn to_fixed_u16(self) -> u16;
48// }
49
50// impl FloatToFixedU16 for f32 {
51//     #[inline]
52//     fn to_fixed_u16(self) -> u16 {
53//         const SCALE: f64 = (1 << 16) as f64;
54//         (self as f64 * SCALE + 0.5)
55//             .floor()
56//             .clamp(u16::MIN as f64, u16::MAX as f64) as u16
57//     }
58// }
59
60impl FloatToFixedS15Fixed16 for f32 {
61    #[inline]
62    fn to_s15_fixed16(self) -> i32 {
63        const SCALE: f64 = (1 << 16) as f64;
64        (self as f64 * SCALE + 0.5)
65            .floor()
66            .clamp(i32::MIN as f64, i32::MAX as f64) as i32
67    }
68}
69
70impl FloatToFixedS15Fixed16 for f64 {
71    #[inline]
72    fn to_s15_fixed16(self) -> i32 {
73        const SCALE: f64 = (1 << 16) as f64;
74        (self * SCALE + 0.5)
75            .floor()
76            .clamp(i32::MIN as f64, i32::MAX as f64) as i32
77    }
78}
79
80#[inline]
81fn write_u32_be(into: &mut Vec<u8>, value: u32) {
82    let bytes = value.to_be_bytes();
83    into.push(bytes[0]);
84    into.push(bytes[1]);
85    into.push(bytes[2]);
86    into.push(bytes[3]);
87}
88
89#[inline]
90pub(crate) fn write_u16_be(into: &mut Vec<u8>, value: u16) {
91    let bytes = value.to_be_bytes();
92    into.push(bytes[0]);
93    into.push(bytes[1]);
94}
95
96#[inline]
97fn write_i32_be(into: &mut Vec<u8>, value: i32) {
98    let bytes = value.to_be_bytes();
99    into.push(bytes[0]);
100    into.push(bytes[1]);
101    into.push(bytes[2]);
102    into.push(bytes[3]);
103}
104
105fn first_two_ascii_bytes(s: &String) -> [u8; 2] {
106    let bytes = s.as_bytes();
107    if bytes.len() >= 2 {
108        bytes[0..2].try_into().unwrap()
109    } else if bytes.len() == 1 {
110        let vec = vec![bytes[0], 0u8];
111        vec.try_into().unwrap()
112    } else {
113        let vec = vec![0u8, 0u8];
114        vec.try_into().unwrap()
115    }
116}
117
118/// Writes Multi Localized Unicode
119#[inline]
120fn write_mluc(into: &mut Vec<u8>, strings: &[LocalizableString]) -> usize {
121    assert!(!strings.is_empty());
122    let start = into.len();
123    let tag_def: u32 = TagTypeDefinition::MultiLocalizedUnicode.into();
124    write_u32_be(into, tag_def);
125    write_u32_be(into, 0);
126    let number_of_records = strings.len();
127    write_u32_be(into, number_of_records as u32);
128    write_u32_be(into, 12); // Record size, must be 12
129    let lang = first_two_ascii_bytes(&strings[0].language);
130    into.extend_from_slice(&lang);
131    let country = first_two_ascii_bytes(&strings[0].country);
132    into.extend_from_slice(&country);
133    let first_string_len = strings[0].value.len() * 2;
134    write_u32_be(into, first_string_len as u32);
135    let mut first_string_offset = 16 + 12 * strings.len();
136    write_u32_be(into, first_string_offset as u32);
137    first_string_offset += first_string_len;
138    for record in strings.iter().skip(1) {
139        let lang = first_two_ascii_bytes(&record.language);
140        into.extend_from_slice(&lang);
141        let country = first_two_ascii_bytes(&record.country);
142        into.extend_from_slice(&country);
143        let first_string_len = record.value.len() * 2;
144        write_u32_be(into, first_string_len as u32);
145        write_u32_be(into, first_string_offset as u32);
146        first_string_offset += first_string_len;
147    }
148    for record in strings.iter() {
149        for chunk in record.value.encode_utf16() {
150            write_u16_be(into, chunk);
151        }
152    }
153    let end = into.len();
154    end - start
155}
156
157#[inline]
158fn write_string_value(into: &mut Vec<u8>, text: &ProfileText) -> usize {
159    match text {
160        ProfileText::PlainString(text) => {
161            let vec = vec![LocalizableString {
162                language: "en".to_string(),
163                country: "US".to_string(),
164                value: text.clone(),
165            }];
166            write_mluc(into, &vec)
167        }
168        ProfileText::Localizable(localizable) => {
169            if localizable.is_empty() {
170                return 0;
171            }
172            write_mluc(into, localizable)
173        }
174        ProfileText::Description(description) => {
175            let value = if description.unicode_string.is_empty() {
176                description.ascii_string.clone()
177            } else {
178                description.unicode_string.clone()
179            };
180            let vec = vec![LocalizableString {
181                language: "en".to_string(),
182                country: "US".to_string(),
183                value,
184            }];
185            write_mluc(into, &vec)
186        }
187    }
188}
189
190#[inline]
191fn write_xyz_tag_value(into: &mut Vec<u8>, xyz: Xyzd) {
192    let tag_definition: u32 = TagTypeDefinition::Xyz.into();
193    write_u32_be(into, tag_definition);
194    write_u32_be(into, 0);
195    let x_fixed = xyz.x.to_s15_fixed16();
196    write_i32_be(into, x_fixed);
197    let y_fixed = xyz.y.to_s15_fixed16();
198    write_i32_be(into, y_fixed);
199    let z_fixed = xyz.z.to_s15_fixed16();
200    write_i32_be(into, z_fixed);
201}
202
203#[inline]
204fn write_tag_entry(into: &mut Vec<u8>, tag: Tag, tag_entry: usize, tag_size: usize) {
205    let tag_value: u32 = tag.into();
206    write_u32_be(into, tag_value);
207    write_u32_be(into, tag_entry as u32);
208    write_u32_be(into, tag_size as u32);
209}
210
211#[inline]
212fn write_xyz(into: &mut Vec<u8>, xyz: Xyz) {
213    let x_fixed = xyz.x.to_s15_fixed16();
214    write_i32_be(into, x_fixed);
215    let y_fixed = xyz.y.to_s15_fixed16();
216    write_i32_be(into, y_fixed);
217    let z_fixed = xyz.z.to_s15_fixed16();
218    write_i32_be(into, z_fixed);
219}
220
221#[inline]
222fn write_viewing_conditions_value(
223    into: &mut Vec<u8>,
224    viewing_conditions: &ViewingConditions,
225) -> usize {
226    let tag_definition: u32 = TagTypeDefinition::DefViewingConditions.into();
227    write_u32_be(into, tag_definition);
228    write_u32_be(into, 0);
229    write_xyz(into, viewing_conditions.illuminant);
230    write_xyz(into, viewing_conditions.surround);
231    write_u32_be(into, viewing_conditions.observer.into());
232    36
233}
234
235fn write_trc_entry(into: &mut Vec<u8>, trc: &ToneReprCurve) -> Result<usize, CmsError> {
236    match trc {
237        ToneReprCurve::Lut(lut) => {
238            let curv: u32 = TagTypeDefinition::LutToneCurve.into();
239            write_u32_be(into, curv);
240            write_u32_be(into, 0);
241            write_u32_be(into, lut.len() as u32);
242            for item in lut.iter() {
243                write_u16_be(into, *item);
244            }
245            Ok(12 + lut.len() * 2)
246        }
247        ToneReprCurve::Parametric(parametric_curve) => {
248            if parametric_curve.len() > 7
249                || parametric_curve.len() == 6
250                || parametric_curve.len() == 2
251            {
252                return Err(CmsError::InvalidProfile);
253            }
254            let para: u32 = TagTypeDefinition::ParametricToneCurve.into();
255            write_u32_be(into, para);
256            write_u32_be(into, 0);
257            if parametric_curve.len() == 1 {
258                write_u16_be(into, 0);
259            } else if parametric_curve.len() == 3 {
260                write_u16_be(into, 1);
261            } else if parametric_curve.len() == 4 {
262                write_u16_be(into, 2);
263            } else if parametric_curve.len() == 5 {
264                write_u16_be(into, 3);
265            } else if parametric_curve.len() == 7 {
266                write_u16_be(into, 4);
267            }
268            write_u16_be(into, 0);
269            for item in parametric_curve.iter() {
270                write_i32_be(into, item.to_s15_fixed16());
271            }
272            Ok(12 + 4 * parametric_curve.len())
273        }
274    }
275}
276
277#[inline]
278fn write_cicp_entry(into: &mut Vec<u8>, cicp: &CicpProfile) {
279    let cicp_tag: u32 = TagTypeDefinition::Cicp.into();
280    write_u32_be(into, cicp_tag);
281    write_u32_be(into, 0);
282    into.push(cicp.color_primaries as u8);
283    into.push(cicp.transfer_characteristics as u8);
284    into.push(cicp.matrix_coefficients as u8);
285    into.push(if cicp.full_range { 1 } else { 0 });
286}
287
288fn write_chad(into: &mut Vec<u8>, matrix: Matrix3d) {
289    let arr_type: u32 = TagTypeDefinition::S15Fixed16Array.into();
290    write_u32_be(into, arr_type);
291    write_u32_be(into, 0);
292    write_matrix3d(into, matrix);
293}
294
295#[inline]
296fn write_matrix3d(into: &mut Vec<u8>, v: Matrix3d) {
297    write_i32_be(into, v.v[0][0].to_s15_fixed16());
298    write_i32_be(into, v.v[0][1].to_s15_fixed16());
299    write_i32_be(into, v.v[0][2].to_s15_fixed16());
300
301    write_i32_be(into, v.v[1][0].to_s15_fixed16());
302    write_i32_be(into, v.v[1][1].to_s15_fixed16());
303    write_i32_be(into, v.v[1][2].to_s15_fixed16());
304
305    write_i32_be(into, v.v[2][0].to_s15_fixed16());
306    write_i32_be(into, v.v[2][1].to_s15_fixed16());
307    write_i32_be(into, v.v[2][2].to_s15_fixed16());
308}
309
310#[inline]
311fn write_vector3d(into: &mut Vec<u8>, v: Vector3d) {
312    write_i32_be(into, v.v[0].to_s15_fixed16());
313    write_i32_be(into, v.v[1].to_s15_fixed16());
314    write_i32_be(into, v.v[2].to_s15_fixed16());
315}
316
317#[inline]
318fn write_lut_entry(into: &mut Vec<u8>, lut: &LutDataType) -> Result<usize, CmsError> {
319    if !lut.has_same_kind() {
320        return Err(CmsError::InvalidProfile);
321    }
322    let start = into.len();
323    let lut16_tag: u32 = match &lut.input_table {
324        LutStore::Store8(_) => LutType::Lut8.into(),
325        LutStore::Store16(_) => LutType::Lut16.into(),
326    };
327    write_u32_be(into, lut16_tag);
328    write_u32_be(into, 0);
329    into.push(lut.num_input_channels);
330    into.push(lut.num_output_channels);
331    into.push(lut.num_clut_grid_points);
332    into.push(0);
333    write_matrix3d(into, lut.matrix);
334    write_u16_be(into, lut.num_input_table_entries);
335    write_u16_be(into, lut.num_output_table_entries);
336    match &lut.input_table {
337        LutStore::Store8(input_table) => {
338            for &item in input_table.iter() {
339                into.push(item);
340            }
341        }
342        LutStore::Store16(input_table) => {
343            for &item in input_table.iter() {
344                write_u16_be(into, item);
345            }
346        }
347    }
348    match &lut.clut_table {
349        LutStore::Store8(input_table) => {
350            for &item in input_table.iter() {
351                into.push(item);
352            }
353        }
354        LutStore::Store16(input_table) => {
355            for &item in input_table.iter() {
356                write_u16_be(into, item);
357            }
358        }
359    }
360    match &lut.output_table {
361        LutStore::Store8(input_table) => {
362            for &item in input_table.iter() {
363                into.push(item);
364            }
365        }
366        LutStore::Store16(input_table) => {
367            for &item in input_table.iter() {
368                write_u16_be(into, item);
369            }
370        }
371    }
372    let end = into.len();
373    Ok(end - start)
374}
375
376#[inline]
377fn write_mab_entry(
378    into: &mut Vec<u8>,
379    lut: &LutMultidimensionalType,
380    is_a_to_b: bool,
381) -> Result<usize, CmsError> {
382    let start = into.len();
383    let lut16_tag: u32 = if is_a_to_b {
384        LutType::LutMab.into()
385    } else {
386        LutType::LutMba.into()
387    };
388    write_u32_be(into, lut16_tag);
389    write_u32_be(into, 0);
390    into.push(lut.num_input_channels);
391    into.push(lut.num_output_channels);
392    write_u16_be(into, 0);
393    let mut working_offset = 32usize;
394
395    let mut data = Vec::new();
396
397    // Offset to "B curves"
398    if !lut.b_curves.is_empty() {
399        while working_offset % 4 != 0 {
400            data.push(0);
401            working_offset += 1;
402        }
403
404        write_u32_be(into, working_offset as u32);
405
406        for trc in lut.b_curves.iter() {
407            let curve_size = write_trc_entry(&mut data, trc)?;
408            working_offset += curve_size;
409            while working_offset % 4 != 0 {
410                data.push(0);
411                working_offset += 1;
412            }
413        }
414    } else {
415        write_u32_be(into, 0);
416    }
417
418    // Offset to matrix
419    if !lut.m_curves.is_empty() {
420        while working_offset % 4 != 0 {
421            data.push(0);
422            working_offset += 1;
423        }
424
425        write_u32_be(into, working_offset as u32);
426        write_matrix3d(&mut data, lut.matrix);
427        write_vector3d(&mut data, lut.bias);
428        working_offset += 9 * 4 + 3 * 4;
429        // Offset to "M curves"
430        write_u32_be(into, working_offset as u32);
431        for trc in lut.m_curves.iter() {
432            let curve_size = write_trc_entry(&mut data, trc)?;
433            working_offset += curve_size;
434            while working_offset % 4 != 0 {
435                data.push(0);
436                working_offset += 1;
437            }
438        }
439    } else {
440        // Offset to matrix
441        write_u32_be(into, 0);
442        // Offset to "M curves"
443        write_u32_be(into, 0);
444    }
445
446    let mut clut_start = data.len();
447
448    // Offset to CLUT
449    if let Some(clut) = &lut.clut {
450        while working_offset % 4 != 0 {
451            data.push(0);
452            working_offset += 1;
453        }
454
455        clut_start = data.len();
456
457        write_u32_be(into, working_offset as u32);
458
459        // Writing CLUT
460        for &pt in lut.grid_points.iter() {
461            data.push(pt);
462        }
463        data.push(match clut {
464            LutStore::Store8(_) => 1,
465            LutStore::Store16(_) => 2,
466        }); // Entry size
467        data.push(0);
468        data.push(0);
469        data.push(0);
470        match clut {
471            LutStore::Store8(store) => {
472                for &element in store.iter() {
473                    data.push(element)
474                }
475            }
476            LutStore::Store16(store) => {
477                for &element in store.iter() {
478                    write_u16_be(&mut data, element);
479                }
480            }
481        }
482    } else {
483        write_u32_be(into, 0);
484    }
485
486    let clut_size = data.len() - clut_start;
487    working_offset += clut_size;
488
489    // Offset to "A curves"
490    if !lut.a_curves.is_empty() {
491        while working_offset % 4 != 0 {
492            data.push(0);
493            working_offset += 1;
494        }
495
496        write_u32_be(into, working_offset as u32);
497
498        for trc in lut.a_curves.iter() {
499            let curve_size = write_trc_entry(&mut data, trc)?;
500            working_offset += curve_size;
501            while working_offset % 4 != 0 {
502                data.push(0);
503                working_offset += 1;
504            }
505        }
506    } else {
507        write_u32_be(into, 0);
508    }
509
510    into.extend(data);
511
512    let end = into.len();
513    Ok(end - start)
514}
515
516fn write_lut(into: &mut Vec<u8>, lut: &LutWarehouse, is_a_to_b: bool) -> Result<usize, CmsError> {
517    match lut {
518        LutWarehouse::Lut(lut) => Ok(write_lut_entry(into, lut)?),
519        LutWarehouse::Multidimensional(mab) => write_mab_entry(into, mab, is_a_to_b),
520    }
521}
522
523impl ProfileHeader {
524    fn encode(&self) -> Vec<u8> {
525        let mut encoder: Vec<u8> = Vec::with_capacity(size_of::<ProfileHeader>());
526        write_u32_be(&mut encoder, self.size); // Size
527        write_u32_be(&mut encoder, 0); // CMM Type
528        write_u32_be(&mut encoder, self.version.into()); // Version Number Type
529        write_u32_be(&mut encoder, self.profile_class.into()); // Profile class
530        write_u32_be(&mut encoder, self.data_color_space.into()); // Data color space
531        write_u32_be(&mut encoder, self.pcs.into()); // PCS
532        self.creation_date_time.encode(&mut encoder); // Date time
533        write_u32_be(&mut encoder, self.signature.into()); // Profile signature
534        write_u32_be(&mut encoder, self.platform);
535        write_u32_be(&mut encoder, self.flags);
536        write_u32_be(&mut encoder, self.device_manufacturer);
537        write_u32_be(&mut encoder, self.device_model);
538        for &i in self.device_attributes.iter() {
539            encoder.push(i);
540        }
541        write_u32_be(&mut encoder, self.rendering_intent.into());
542        write_i32_be(&mut encoder, self.illuminant.x.to_s15_fixed16());
543        write_i32_be(&mut encoder, self.illuminant.y.to_s15_fixed16());
544        write_i32_be(&mut encoder, self.illuminant.z.to_s15_fixed16());
545        write_u32_be(&mut encoder, self.creator);
546        for &i in self.profile_id.iter() {
547            encoder.push(i);
548        }
549        for &i in self.reserved.iter() {
550            encoder.push(i);
551        }
552        write_u32_be(&mut encoder, self.tag_count);
553        encoder
554    }
555}
556
557impl ColorProfile {
558    fn writable_tags_count(&self) -> usize {
559        let mut tags_count = 0usize;
560        if self.red_colorant != Xyzd::default() {
561            tags_count += 1;
562        }
563        if self.green_colorant != Xyzd::default() {
564            tags_count += 1;
565        }
566        if self.blue_colorant != Xyzd::default() {
567            tags_count += 1;
568        }
569        if self.red_trc.is_some() {
570            tags_count += 1;
571        }
572        if self.green_trc.is_some() {
573            tags_count += 1;
574        }
575        if self.blue_trc.is_some() {
576            tags_count += 1;
577        }
578        if self.gray_trc.is_some() {
579            tags_count += 1;
580        }
581        if self.cicp.is_some() {
582            tags_count += 1;
583        }
584        if self.media_white_point.is_some() {
585            tags_count += 1;
586        }
587        if self.gamut.is_some() {
588            tags_count += 1;
589        }
590        if self.chromatic_adaptation.is_some() {
591            tags_count += 1;
592        }
593        if self.lut_a_to_b_perceptual.is_some() {
594            tags_count += 1;
595        }
596        if self.lut_a_to_b_colorimetric.is_some() {
597            tags_count += 1;
598        }
599        if self.lut_a_to_b_saturation.is_some() {
600            tags_count += 1;
601        }
602        if self.lut_b_to_a_perceptual.is_some() {
603            tags_count += 1;
604        }
605        if self.lut_b_to_a_colorimetric.is_some() {
606            tags_count += 1;
607        }
608        if self.lut_b_to_a_saturation.is_some() {
609            tags_count += 1;
610        }
611        if self.luminance.is_some() {
612            tags_count += 1;
613        }
614        if let Some(description) = &self.description {
615            if description.has_values() {
616                tags_count += 1;
617            }
618        }
619        if let Some(copyright) = &self.copyright {
620            if copyright.has_values() {
621                tags_count += 1;
622            }
623        }
624        if self.viewing_conditions.is_some() {
625            tags_count += 1;
626        }
627        if let Some(vd) = &self.viewing_conditions_description {
628            if vd.has_values() {
629                tags_count += 1;
630            }
631        }
632        if let Some(vd) = &self.device_model {
633            if vd.has_values() {
634                tags_count += 1;
635            }
636        }
637        if let Some(vd) = &self.device_manufacturer {
638            if vd.has_values() {
639                tags_count += 1;
640            }
641        }
642        tags_count
643    }
644
645    /// Encodes profile
646    pub fn encode(&self) -> Result<Vec<u8>, CmsError> {
647        let mut entries = Vec::new();
648        let tags_count = self.writable_tags_count();
649        let mut tags = Vec::with_capacity(TAG_SIZE * tags_count);
650        let mut base_offset = size_of::<ProfileHeader>() + TAG_SIZE * tags_count;
651        if self.red_colorant != Xyzd::default() {
652            write_tag_entry(&mut tags, Tag::RedXyz, base_offset, 20);
653            write_xyz_tag_value(&mut entries, self.red_colorant);
654            base_offset += 20;
655        }
656        if self.green_colorant != Xyzd::default() {
657            write_tag_entry(&mut tags, Tag::GreenXyz, base_offset, 20);
658            write_xyz_tag_value(&mut entries, self.green_colorant);
659            base_offset += 20;
660        }
661        if self.blue_colorant != Xyzd::default() {
662            write_tag_entry(&mut tags, Tag::BlueXyz, base_offset, 20);
663            write_xyz_tag_value(&mut entries, self.blue_colorant);
664            base_offset += 20;
665        }
666        if let Some(chad) = self.chromatic_adaptation {
667            write_tag_entry(&mut tags, Tag::ChromaticAdaptation, base_offset, 8 + 9 * 4);
668            write_chad(&mut entries, chad);
669            base_offset += 8 + 9 * 4;
670        }
671        if let Some(trc) = &self.red_trc {
672            let entry_size = write_trc_entry(&mut entries, trc)?;
673            write_tag_entry(&mut tags, Tag::RedToneReproduction, base_offset, entry_size);
674            base_offset += entry_size;
675        }
676        if let Some(trc) = &self.green_trc {
677            let entry_size = write_trc_entry(&mut entries, trc)?;
678            write_tag_entry(
679                &mut tags,
680                Tag::GreenToneReproduction,
681                base_offset,
682                entry_size,
683            );
684            base_offset += entry_size;
685        }
686        if let Some(trc) = &self.blue_trc {
687            let entry_size = write_trc_entry(&mut entries, trc)?;
688            write_tag_entry(
689                &mut tags,
690                Tag::BlueToneReproduction,
691                base_offset,
692                entry_size,
693            );
694            base_offset += entry_size;
695        }
696        if let Some(trc) = &self.gray_trc {
697            let entry_size = write_trc_entry(&mut entries, trc)?;
698            write_tag_entry(
699                &mut tags,
700                Tag::GreyToneReproduction,
701                base_offset,
702                entry_size,
703            );
704            base_offset += entry_size;
705        }
706
707        if let Some(media_wp) = self.media_white_point {
708            write_tag_entry(&mut tags, Tag::MediaWhitePoint, base_offset, 20);
709            write_xyz_tag_value(&mut entries, media_wp);
710            base_offset += 20;
711        }
712
713        let has_cicp = self.cicp.is_some();
714
715        // This tag may be present when the data colour space in the profile header is RGB, YCbCr, or XYZ, and the
716        // profile class in the profile header is Input or Display. The tag shall not be present for other data colour spaces
717        // or profile classes indicated in the profile header.
718
719        if let Some(cicp) = &self.cicp {
720            if (self.profile_class == ProfileClass::InputDevice
721                || self.profile_class == ProfileClass::DisplayDevice)
722                && (self.color_space == DataColorSpace::Rgb
723                    || self.color_space == DataColorSpace::YCbr
724                    || self.color_space == DataColorSpace::Xyz)
725            {
726                write_tag_entry(&mut tags, Tag::CodeIndependentPoints, base_offset, 12);
727                write_cicp_entry(&mut entries, cicp);
728                base_offset += 12;
729            }
730        }
731
732        if let Some(lut) = &self.lut_a_to_b_perceptual {
733            let entry_size = write_lut(&mut entries, lut, true)?;
734            write_tag_entry(
735                &mut tags,
736                Tag::DeviceToPcsLutPerceptual,
737                base_offset,
738                entry_size,
739            );
740            base_offset += entry_size;
741        }
742
743        if let Some(lut) = &self.lut_a_to_b_colorimetric {
744            let entry_size = write_lut(&mut entries, lut, true)?;
745            write_tag_entry(
746                &mut tags,
747                Tag::DeviceToPcsLutColorimetric,
748                base_offset,
749                entry_size,
750            );
751            base_offset += entry_size;
752        }
753
754        if let Some(lut) = &self.lut_a_to_b_saturation {
755            let entry_size = write_lut(&mut entries, lut, true)?;
756            write_tag_entry(
757                &mut tags,
758                Tag::DeviceToPcsLutSaturation,
759                base_offset,
760                entry_size,
761            );
762            base_offset += entry_size;
763        }
764
765        if let Some(lut) = &self.lut_b_to_a_perceptual {
766            let entry_size = write_lut(&mut entries, lut, false)?;
767            write_tag_entry(
768                &mut tags,
769                Tag::PcsToDeviceLutPerceptual,
770                base_offset,
771                entry_size,
772            );
773            base_offset += entry_size;
774        }
775
776        if let Some(lut) = &self.lut_b_to_a_colorimetric {
777            let entry_size = write_lut(&mut entries, lut, false)?;
778            write_tag_entry(
779                &mut tags,
780                Tag::PcsToDeviceLutColorimetric,
781                base_offset,
782                entry_size,
783            );
784            base_offset += entry_size;
785        }
786
787        if let Some(lut) = &self.lut_b_to_a_saturation {
788            let entry_size = write_lut(&mut entries, lut, false)?;
789            write_tag_entry(
790                &mut tags,
791                Tag::PcsToDeviceLutSaturation,
792                base_offset,
793                entry_size,
794            );
795            base_offset += entry_size;
796        }
797
798        if let Some(lut) = &self.gamut {
799            let entry_size = write_lut(&mut entries, lut, false)?;
800            write_tag_entry(&mut tags, Tag::Gamut, base_offset, entry_size);
801            base_offset += entry_size;
802        }
803
804        if let Some(luminance) = self.luminance {
805            write_tag_entry(&mut tags, Tag::Luminance, base_offset, 20);
806            write_xyz_tag_value(&mut entries, luminance);
807            base_offset += 20;
808        }
809
810        if let Some(description) = &self.description {
811            if description.has_values() {
812                let entry_size = write_string_value(&mut entries, description);
813                write_tag_entry(&mut tags, Tag::ProfileDescription, base_offset, entry_size);
814                base_offset += entry_size;
815            }
816        }
817
818        if let Some(copyright) = &self.copyright {
819            if copyright.has_values() {
820                let entry_size = write_string_value(&mut entries, copyright);
821                write_tag_entry(&mut tags, Tag::Copyright, base_offset, entry_size);
822                base_offset += entry_size;
823            }
824        }
825
826        if let Some(vc) = &self.viewing_conditions {
827            let entry_size = write_viewing_conditions_value(&mut entries, vc);
828            write_tag_entry(&mut tags, Tag::ObserverConditions, base_offset, entry_size);
829            base_offset += entry_size;
830        }
831
832        if let Some(vd) = &self.viewing_conditions_description {
833            if vd.has_values() {
834                let entry_size = write_string_value(&mut entries, vd);
835                write_tag_entry(
836                    &mut tags,
837                    Tag::ViewingConditionsDescription,
838                    base_offset,
839                    entry_size,
840                );
841                base_offset += entry_size;
842            }
843        }
844
845        if let Some(vd) = &self.device_model {
846            if vd.has_values() {
847                let entry_size = write_string_value(&mut entries, vd);
848                write_tag_entry(&mut tags, Tag::DeviceModel, base_offset, entry_size);
849                base_offset += entry_size;
850            }
851        }
852
853        if let Some(vd) = &self.device_manufacturer {
854            if vd.has_values() {
855                let entry_size = write_string_value(&mut entries, vd);
856                write_tag_entry(&mut tags, Tag::DeviceManufacturer, base_offset, entry_size);
857                // base_offset += entry_size;
858            }
859        }
860
861        tags.extend(entries);
862
863        let profile_header = ProfileHeader {
864            size: size_of::<ProfileHeader>() as u32 + tags.len() as u32,
865            pcs: self.pcs,
866            profile_class: self.profile_class,
867            rendering_intent: self.rendering_intent,
868            cmm_type: 0,
869            version: if has_cicp {
870                ProfileVersion::V4_3
871            } else if self.version_internal < ProfileVersion::V4_0 {
872                ProfileVersion::V4_0
873            } else {
874                self.version_internal
875            },
876            data_color_space: self.color_space,
877            creation_date_time: ColorDateTime::now(),
878            signature: ProfileSignature::Acsp,
879            platform: 0u32,
880            flags: 0u32,
881            device_manufacturer: 0u32,
882            device_model: 0u32,
883            device_attributes: [0u8; 8],
884            illuminant: self.white_point.to_xyz(),
885            creator: 0u32,
886            profile_id: [0u8; 16],
887            reserved: [0u8; 28],
888            tag_count: tags_count as u32,
889        };
890        let mut header = profile_header.encode();
891        header.extend(tags);
892        Ok(header)
893    }
894}
895
896impl FloatToFixedU8Fixed8 for f32 {
897    #[inline]
898    fn to_u8_fixed8(self) -> u16 {
899        if self > 255.0 + 255.0 / 256f32 {
900            0xffffu16
901        } else if self < 0.0 {
902            0u16
903        } else {
904            (self * 256.0 + 0.5).floor() as u16
905        }
906    }
907}
908
909#[cfg(test)]
910mod tests {
911    use super::*;
912
913    #[test]
914    fn to_u8_fixed8() {
915        assert_eq!(0, 0f32.to_u8_fixed8());
916        assert_eq!(0x0100, 1f32.to_u8_fixed8());
917        assert_eq!(u16::MAX, (255f32 + (255f32 / 256f32)).to_u8_fixed8());
918    }
919
920    #[test]
921    fn to_s15_fixed16() {
922        assert_eq!(0x80000000u32 as i32, (-32768f32).to_s15_fixed16());
923        assert_eq!(0, 0f32.to_s15_fixed16());
924        assert_eq!(0x10000, 1.0f32.to_s15_fixed16());
925        assert_eq!(
926            i32::MAX,
927            (32767f32 + (65535f32 / 65536f32)).to_s15_fixed16()
928        );
929    }
930}