1pub fn preserve_device_cmyk(c: f32, m: f32, y: f32, k: f32) -> [u8; 4] {
5 [
6 (c.clamp(0.0, 1.0) * 255.0) as u8,
7 (m.clamp(0.0, 1.0) * 255.0) as u8,
8 (y.clamp(0.0, 1.0) * 255.0) as u8,
9 (k.clamp(0.0, 1.0) * 255.0) as u8,
10 ]
11}
12
13pub(crate) fn rgba_to_cmyk_pixel(rgba: &[u8]) -> [u8; 4] {
14 let alpha = rgba[3] as f32 / 255.0;
15 let bg = 1.0 - alpha;
16 let r = (rgba[0] as f32 / 255.0) * alpha + bg;
17 let g = (rgba[1] as f32 / 255.0) * alpha + bg;
18 let b = (rgba[2] as f32 / 255.0) * alpha + bg;
19 let k = 1.0 - r.max(g).max(b);
20 let (c, m, y) = if k >= 1.0 - f32::EPSILON {
21 (0.0_f32, 0.0_f32, 0.0_f32)
22 } else {
23 let inv = 1.0 - k;
24 ((inv - r) / inv, (inv - g) / inv, (inv - b) / inv)
25 };
26
27 [
28 (c.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
29 (m.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
30 (y.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
31 (k.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
32 ]
33}
34
35pub(crate) fn rgba_to_cmyk_buffer(rgba: &[u8]) -> Vec<u8> {
36 let mut out = Vec::with_capacity(rgba.len());
37 for px in rgba.chunks_exact(4) {
38 out.extend_from_slice(&rgba_to_cmyk_pixel(px));
39 }
40 out
41}
42
43pub(crate) fn blend_cmyk(bottom: [u8; 4], top: [u8; 4], alpha: u8) -> [u8; 4] {
44 let alpha = alpha as f32 / 255.0;
45 let inv_alpha = 1.0 - alpha;
46 let mut out = [0; 4];
47 for (idx, component) in out.iter_mut().enumerate() {
48 let blended = bottom[idx] as f32 * inv_alpha + top[idx] as f32 * alpha;
49 *component = blended.clamp(0.0, 255.0).round() as u8;
50 }
51 out
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn preserve_device_cmyk_truncates_to_u8() {
60 assert_eq!(
61 preserve_device_cmyk(0.25, 0.5, 0.75, 1.0),
62 [63, 127, 191, 255]
63 );
64 }
65
66 #[test]
67 fn blend_cmyk_half_alpha() {
68 assert_eq!(
69 blend_cmyk([0, 0, 0, 0], [255, 128, 64, 32], 128),
70 [128, 64, 32, 16]
71 );
72 }
73}