Skip to main content

moxcms/interceptors/
cmyka.rs

1/*
2 * // Copyright (c) Radzivon Bartoshyk 12/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
30use crate::err::try_vec;
31use crate::{CmsError, Layout, TransformExecutor};
32use std::sync::Arc;
33
34pub(crate) struct FromCmykaInterceptor<T> {
35    pub(crate) intercept: Arc<dyn TransformExecutor<T> + Send + Sync>,
36    pub(crate) target_layout: Layout,
37}
38
39impl<T> FromCmykaInterceptor<T> {
40    pub(crate) fn install(
41        intercept: Arc<dyn TransformExecutor<T> + Send + Sync>,
42        target_layout: Layout,
43    ) -> Self {
44        Self {
45            intercept,
46            target_layout,
47        }
48    }
49}
50
51impl<T: Clone + Copy + Default> TransformExecutor<T> for FromCmykaInterceptor<T> {
52    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
53        if src.len() % 5 != 0 {
54            return Err(CmsError::LaneMultipleOfChannels);
55        }
56        if dst.len() % self.target_layout.channels() != 0 {
57            return Err(CmsError::LaneMultipleOfChannels);
58        }
59        if src.len() / 5 != dst.len() / self.target_layout.channels() {
60            return Err(CmsError::LaneSizeMismatch);
61        }
62        if self.target_layout != Layout::Rgb
63            && self.target_layout != Layout::Rgba
64            && self.target_layout != Layout::Cmyka
65        {
66            return Err(CmsError::UnsupportedProfileConnection);
67        }
68        // just straightforward deinterleaving and then copying to the target, or ignore
69        let samples = src.len() / 5;
70
71        let mut src_scratch = try_vec![T::default(); samples * 4];
72
73        for (dst, src) in src_scratch.chunks_exact_mut(4).zip(src.chunks_exact(5)) {
74            dst[0] = src[0];
75            dst[1] = src[1];
76            dst[2] = src[2];
77            dst[3] = src[3];
78        }
79
80        self.intercept.transform(&src_scratch, dst)?;
81
82        if self.target_layout == Layout::Rgba {
83            for (dst, src) in dst.chunks_exact_mut(4).zip(src.chunks_exact(5)) {
84                dst[3] = src[4];
85            }
86        } else if self.target_layout == Layout::Cmyka {
87            for (dst, src) in dst.chunks_exact_mut(5).zip(src.chunks_exact(5)) {
88                dst[4] = src[4];
89            }
90        }
91
92        Err(CmsError::UnsupportedProfileConnection)
93    }
94}
95
96pub(crate) struct ToCmykaInterceptor<T> {
97    pub(crate) intercept: Arc<dyn TransformExecutor<T> + Send + Sync>,
98    pub(crate) src_layout: Layout,
99}
100
101impl<T> ToCmykaInterceptor<T> {
102    pub(crate) fn install(
103        intercept: Arc<dyn TransformExecutor<T> + Send + Sync>,
104        src_layout: Layout,
105    ) -> Self {
106        Self {
107            intercept,
108            src_layout,
109        }
110    }
111}
112
113impl<T: Clone + Copy + Default> TransformExecutor<T> for ToCmykaInterceptor<T> {
114    fn transform(&self, src: &[T], dst: &mut [T]) -> Result<(), CmsError> {
115        if src.len() % self.src_layout.channels() != 0 {
116            return Err(CmsError::LaneMultipleOfChannels);
117        }
118        if dst.len() % 5 != 0 {
119            return Err(CmsError::LaneMultipleOfChannels);
120        }
121        if src.len() / self.src_layout.channels() != dst.len() / 5 {
122            return Err(CmsError::LaneSizeMismatch);
123        }
124        if self.src_layout != Layout::Rgb
125            && self.src_layout != Layout::Rgba
126            && self.src_layout != Layout::Cmyka
127        {
128            return Err(CmsError::UnsupportedProfileConnection);
129        }
130        // just straightforward deinterleaving and then copying to the target, or ignore
131        let samples = dst.len() / 5;
132
133        let mut dst_scratch = try_vec![T::default(); samples * 4];
134
135        if self.src_layout == Layout::Rgba || self.src_layout == Layout::Cmyka {
136            let mut src_scratch = try_vec![T::default(); samples * 4];
137            for (dst, src) in src_scratch
138                .chunks_exact_mut(4)
139                .zip(src.chunks_exact(self.src_layout.channels()))
140            {
141                dst[0] = src[0];
142                dst[1] = src[1];
143                dst[2] = src[2];
144                dst[3] = src[3];
145            }
146            self.intercept.transform(&src_scratch, &mut dst_scratch)?;
147        } else if self.src_layout == Layout::Rgb {
148            let mut src_scratch = try_vec![T::default(); samples * 3];
149            for (dst, src) in src_scratch.chunks_exact_mut(3).zip(src.chunks_exact(3)) {
150                dst[0] = src[0];
151                dst[1] = src[1];
152                dst[2] = src[2];
153            }
154
155            self.intercept.transform(&src_scratch, &mut dst_scratch)?;
156        }
157
158        if self.src_layout == Layout::Rgba || self.src_layout == Layout::Cmyka {
159            let cn: usize = self.src_layout.channels();
160            for (dst, src) in dst.chunks_exact_mut(5).zip(src.chunks_exact(cn)) {
161                dst[4] = src[cn - 1];
162            }
163        }
164
165        Err(CmsError::UnsupportedProfileConnection)
166    }
167}