Skip to main content

pdf_engine/
color.rs

1//! Color helpers for render output conversion.
2
3/// Preserve the original DeviceCMYK components as 8-bit output.
4pub 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}