pub fn cmyk_to_rgb24(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 3);
for i in 0..pixels {
let c = src[i * 4] as u32;
let m = src[i * 4 + 1] as u32;
let y = src[i * 4 + 2] as u32;
let k = src[i * 4 + 3] as u32;
let kc = 255 - k;
dst[i * 3] = (((255 - c) * kc) / 255) as u8;
dst[i * 3 + 1] = (((255 - m) * kc) / 255) as u8;
dst[i * 3 + 2] = (((255 - y) * kc) / 255) as u8;
}
}
pub fn cmyk_to_rgba(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 4);
for i in 0..pixels {
let c = src[i * 4] as u32;
let m = src[i * 4 + 1] as u32;
let y = src[i * 4 + 2] as u32;
let k = src[i * 4 + 3] as u32;
let kc = 255 - k;
dst[i * 4] = (((255 - c) * kc) / 255) as u8;
dst[i * 4 + 1] = (((255 - m) * kc) / 255) as u8;
dst[i * 4 + 2] = (((255 - y) * kc) / 255) as u8;
dst[i * 4 + 3] = 255;
}
}
pub fn rgb24_to_cmyk(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 3 && dst.len() >= pixels * 4);
for i in 0..pixels {
let r = src[i * 3] as u32;
let g = src[i * 3 + 1] as u32;
let b = src[i * 3 + 2] as u32;
let k = 255 - r.max(g).max(b);
if k == 255 {
dst[i * 4] = 0;
dst[i * 4 + 1] = 0;
dst[i * 4 + 2] = 0;
dst[i * 4 + 3] = 255;
continue;
}
let denom = 255 - k;
dst[i * 4] = (((255 - r - k) * 255) / denom) as u8;
dst[i * 4 + 1] = (((255 - g - k) * 255) / denom) as u8;
dst[i * 4 + 2] = (((255 - b - k) * 255) / denom) as u8;
dst[i * 4 + 3] = k as u8;
}
}
pub fn rgba_to_cmyk(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 4);
for i in 0..pixels {
let r = src[i * 4] as u32;
let g = src[i * 4 + 1] as u32;
let b = src[i * 4 + 2] as u32;
let k = 255 - r.max(g).max(b);
if k == 255 {
dst[i * 4] = 0;
dst[i * 4 + 1] = 0;
dst[i * 4 + 2] = 0;
dst[i * 4 + 3] = 255;
continue;
}
let denom = 255 - k;
dst[i * 4] = (((255 - r - k) * 255) / denom) as u8;
dst[i * 4 + 1] = (((255 - g - k) * 255) / denom) as u8;
dst[i * 4 + 2] = (((255 - b - k) * 255) / denom) as u8;
dst[i * 4 + 3] = k as u8;
}
}
pub fn cmyk_complement(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 4);
for (d, &s) in dst[..pixels * 4].iter_mut().zip(src[..pixels * 4].iter()) {
*d = !s;
}
}
pub fn cmyk_inverted_to_rgb24(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 3);
for i in 0..pixels {
let c = src[i * 4] as u32;
let m = src[i * 4 + 1] as u32;
let y = src[i * 4 + 2] as u32;
let k = src[i * 4 + 3] as u32;
dst[i * 3] = ((c * k) / 255) as u8;
dst[i * 3 + 1] = ((m * k) / 255) as u8;
dst[i * 3 + 2] = ((y * k) / 255) as u8;
}
}
pub fn cmyk_inverted_to_rgba(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels * 4 && dst.len() >= pixels * 4);
for i in 0..pixels {
let c = src[i * 4] as u32;
let m = src[i * 4 + 1] as u32;
let y = src[i * 4 + 2] as u32;
let k = src[i * 4 + 3] as u32;
dst[i * 4] = ((c * k) / 255) as u8;
dst[i * 4 + 1] = ((m * k) / 255) as u8;
dst[i * 4 + 2] = ((y * k) / 255) as u8;
dst[i * 4 + 3] = 255;
}
}
pub fn rgb24_to_cmyk_inverted(src: &[u8], dst: &mut [u8], pixels: usize) {
rgb24_to_cmyk(src, dst, pixels);
for b in dst[..pixels * 4].iter_mut() {
*b = !*b;
}
}
pub fn rgba_to_cmyk_inverted(src: &[u8], dst: &mut [u8], pixels: usize) {
rgba_to_cmyk(src, dst, pixels);
for b in dst[..pixels * 4].iter_mut() {
*b = !*b;
}
}
pub fn gray8_to_cmyk(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels && dst.len() >= pixels * 4);
for (i, &v) in src.iter().enumerate().take(pixels) {
dst[i * 4] = 0;
dst[i * 4 + 1] = 0;
dst[i * 4 + 2] = 0;
dst[i * 4 + 3] = 255 - v;
}
}
pub fn gray8_to_cmyk_inverted(src: &[u8], dst: &mut [u8], pixels: usize) {
debug_assert!(src.len() >= pixels && dst.len() >= pixels * 4);
for (i, &v) in src.iter().enumerate().take(pixels) {
dst[i * 4] = 255;
dst[i * 4 + 1] = 255;
dst[i * 4 + 2] = 255;
dst[i * 4 + 3] = v;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pure_black() {
let src = [0u8, 0, 0, 255];
let mut dst = [0u8; 3];
cmyk_to_rgb24(&src, &mut dst, 1);
assert_eq!(dst, [0, 0, 0]);
}
#[test]
fn pure_white() {
let src = [0u8, 0, 0, 0];
let mut dst = [0u8; 3];
cmyk_to_rgb24(&src, &mut dst, 1);
assert_eq!(dst, [255, 255, 255]);
}
#[test]
fn pure_cyan() {
let src = [255u8, 0, 0, 0];
let mut dst = [0u8; 3];
cmyk_to_rgb24(&src, &mut dst, 1);
assert_eq!(dst, [0, 255, 255]);
}
#[test]
fn pure_magenta() {
let src = [0u8, 255, 0, 0];
let mut dst = [0u8; 3];
cmyk_to_rgb24(&src, &mut dst, 1);
assert_eq!(dst, [255, 0, 255]);
}
#[test]
fn pure_yellow() {
let src = [0u8, 0, 255, 0];
let mut dst = [0u8; 3];
cmyk_to_rgb24(&src, &mut dst, 1);
assert_eq!(dst, [255, 255, 0]);
}
#[test]
fn rgb_to_cmyk_basics() {
let src = [255u8, 255, 255];
let mut dst = [0u8; 4];
rgb24_to_cmyk(&src, &mut dst, 1);
assert_eq!(dst, [0, 0, 0, 0]);
let src = [0u8, 0, 0];
let mut dst = [0u8; 4];
rgb24_to_cmyk(&src, &mut dst, 1);
assert_eq!(dst, [0, 0, 0, 255]);
let src = [255u8, 0, 0];
let mut dst = [0u8; 4];
rgb24_to_cmyk(&src, &mut dst, 1);
assert_eq!(dst, [0, 255, 255, 0]);
}
#[test]
fn roundtrip_rgb_cmyk_rgb() {
let colours = [
[0u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[128, 128, 128],
[50, 200, 100],
[17, 34, 51],
[240, 15, 5],
];
for rgb in &colours {
let mut cmyk = [0u8; 4];
rgb24_to_cmyk(rgb, &mut cmyk, 1);
let mut back = [0u8; 3];
cmyk_to_rgb24(&cmyk, &mut back, 1);
assert_eq!(&back, rgb, "round-trip failed for {rgb:?} via {cmyk:?}");
}
}
#[test]
fn rgba_variant_matches_rgb24() {
let src_rgba = [200u8, 100, 50, 255];
let src_rgb = [200u8, 100, 50];
let mut a = [0u8; 4];
let mut b = [0u8; 4];
rgba_to_cmyk(&src_rgba, &mut a, 1);
rgb24_to_cmyk(&src_rgb, &mut b, 1);
assert_eq!(a, b);
}
#[test]
fn cmyk_to_rgba_always_opaque() {
let src = [40u8, 0, 100, 30];
let mut rgba = [0u8; 4];
cmyk_to_rgba(&src, &mut rgba, 1);
assert_eq!(rgba[3], 255);
}
#[test]
fn complement_is_self_inverse_bijection() {
let src: Vec<u8> = (0..=255u8).chain(0..=255).collect();
let px = src.len() / 4;
let mut inv = vec![0u8; src.len()];
cmyk_complement(&src, &mut inv, px);
for (a, b) in src.iter().zip(inv.iter()) {
assert_eq!(*a, !*b);
}
let mut back = vec![0u8; src.len()];
cmyk_complement(&inv, &mut back, px);
assert_eq!(back, src);
}
#[test]
fn inverted_decode_equals_complement_then_regular() {
for seed in 0..64u32 {
let q = [
(seed * 37 + 1) as u8,
(seed * 91 + 5) as u8,
(seed * 53 + 11) as u8,
(seed * 17 + 3) as u8,
];
let mut direct = [0u8; 3];
cmyk_inverted_to_rgb24(&q, &mut direct, 1);
let comp: Vec<u8> = q.iter().map(|&b| !b).collect();
let mut staged = [0u8; 3];
cmyk_to_rgb24(&comp, &mut staged, 1);
assert_eq!(direct, staged.as_slice(), "quad {q:?}");
let mut rgba = [0u8; 4];
cmyk_inverted_to_rgba(&q, &mut rgba, 1);
assert_eq!(&rgba[..3], &direct);
assert_eq!(rgba[3], 255);
}
}
#[test]
fn inverted_anchor_values() {
let mut dst = [0u8; 3];
cmyk_inverted_to_rgb24(&[255, 255, 255, 255], &mut dst, 1);
assert_eq!(dst, [255, 255, 255]);
cmyk_inverted_to_rgb24(&[255, 255, 255, 0], &mut dst, 1);
assert_eq!(dst, [0, 0, 0]);
cmyk_inverted_to_rgb24(&[0, 255, 255, 255], &mut dst, 1);
assert_eq!(dst, [0, 255, 255]);
}
#[test]
fn roundtrip_rgb_inverted_cmyk_rgb() {
let colours = [
[0u8, 0, 0],
[255, 255, 255],
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[128, 128, 128],
[50, 200, 100],
[17, 34, 51],
[240, 15, 5],
];
for rgb in &colours {
let mut q = [0u8; 4];
rgb24_to_cmyk_inverted(rgb, &mut q, 1);
let mut back = [0u8; 3];
cmyk_inverted_to_rgb24(&q, &mut back, 1);
assert_eq!(&back, rgb, "round-trip failed for {rgb:?} via {q:?}");
}
let mut a = [0u8; 4];
let mut b = [0u8; 4];
rgba_to_cmyk_inverted(&[200, 100, 50, 77], &mut a, 1);
rgb24_to_cmyk_inverted(&[200, 100, 50], &mut b, 1);
assert_eq!(a, b);
}
}