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