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