colorspace 0.1.0

A crate for colorimetry covering spectral, RGB, XYZ, L*a'b' and conversions thereof
use colorspace::*;

// This example shows how to convert between color spaces step-by-step by building
// and using the necessary matrices directly
fn main() {
    // Read marcie into a Vec of u8 representing packed RGB triples.
    let (px_in_u8, width, height) = read_png("resources/marcie_sRGB.png");

    // We want to convert the u8 pixel data to a multitude of output color
    // spaces for viewing.
    // All colour spaces can be converted to and from XYZ, so we'll first
    // convert our u8 pixel data to RGBf32, then convert that to XYZ, then
    // convert the XYZ data to the desired output spaces, before finally
    // converting back to u8 for writing our output images

    // first get our input pixels as RGBf32
    // Note that if we had an f32 buffer here we could just cast the slice using
    // RGBf32::cast_slice() to avoid a copy.
    let px_srgb: Vec<_> = px_in_u8
        .chunks_exact(3)
        .map(|p| {
            rgbf32(
                p[0] as f32 / 255.0,
                p[1] as f32 / 255.0,
                p[2] as f32 / 255.0,
            )
        })
        .collect();

    // Next we'll need a matrix to go from sRGB to XYZ
    let srgb = &color_space_rgb::model_f32::SRGB;
    let xf_srgb_to_xyz = rgb_to_xyz_matrix(srgb.white, srgb);

    // Now convert. We need to first decode the sRGB pixels to linear instead
    // of display-referred values, then apply the matrix
    let px_xyz: Vec<_> = px_srgb
        .iter()
        .map(|p| {
            let p_linear = srgb.decode(*p);
            rgb_to_xyz(&xf_srgb_to_xyz, p_linear)
        })
        .collect();

    // Now we'll convert to both Adobe RGB and Alexa LogC. This is the reverse
    // process. Again, we'll first need matrices to go from XYZ to the desired
    // space.
    // In the case of P3, this involves a Chromatic Adaptation Transform as well
    // since sRGB and P3 have different white points. This is handled using CAT02
    // by the xyz_to_rgb_matrix() function.
    let srgb = &color_space_rgb::model_f32::SRGB;
    let adobe_rgb = &color_space_rgb::model_f32::ADOBE_RGB_1998;
    let alexa_wide = &color_space_rgb::model_f32::ALEXA_WIDE_GAMUT;
    let dci_p3 = &color_space_rgb::model_f32::DCI_P3;

    let xf_xyz_to_adobe_rgb = xyz_to_rgb_matrix(srgb.white, adobe_rgb);
    let xf_xyz_to_alexa_wide = xyz_to_rgb_matrix(srgb.white, alexa_wide);
    let xf_xyz_to_dci_p3 = xyz_to_rgb_matrix(srgb.white, dci_p3);

    // We then apply the matrix to each pixel before encoding it. In addition
    // we'll convert to RGBu8 for output
    let px_adobe_rgb_u8: Vec<RGBu8> = px_xyz
        .iter()
        .map(|p| {
            let p_adobe_rgb = xyz_to_rgb(&xf_xyz_to_adobe_rgb, *p);
            let p_adobe_rgb = adobe_rgb.encode(p_adobe_rgb);
            // convert to RGBu8. This automatically scales to [0..255] and clamps
            p_adobe_rgb.into()
        })
        .collect();

    let px_alexa_wide_u8: Vec<RGBu8> = px_xyz
        .iter()
        .map(|p| {
            let p_alexa_wide = xyz_to_rgb(&xf_xyz_to_alexa_wide, *p);
            let p_alexa_wide = alexa_wide.encode(p_alexa_wide);
            p_alexa_wide.into()
        })
        .collect();

    let px_dci_p3_u8: Vec<RGBu8> = px_xyz
        .iter()
        .map(|p| {
            let p_dci_p3 = xyz_to_rgb(&xf_xyz_to_dci_p3, *p);
            let p_dci_p3 = dci_p3.encode(p_dci_p3);
            p_dci_p3.into()
        })
        .collect();

    // write the images
    write_png(
        "marcie_AdobeRGB.png",
        width,
        height,
        rgbu8_slice_as_u8(&px_adobe_rgb_u8),
    );
    write_png(
        "marcie_AlexaWide.png",
        width,
        height,
        rgbu8_slice_as_u8(&px_alexa_wide_u8),
    );
    write_png(
        "marcie_DCI-P3.png",
        width,
        height,
        rgbu8_slice_as_u8(&px_dci_p3_u8),
    );
}

use std::fs::File;
fn read_png(filename: &str) -> (Vec<u8>, u32, u32) {
    let decoder = png::Decoder::new(File::open(filename).unwrap());
    let (info, mut reader) = decoder.read_info().unwrap();
    let (width, height) = reader.info().size();
    let mut px_in_u8 = vec![0; info.buffer_size()];
    reader.next_frame(&mut px_in_u8).unwrap();
    (px_in_u8, width, height)
}

fn write_png(filename: &str, width: u32, height: u32, pixels: &[u8]) {
    use std::io::BufWriter;
    let file = File::create(filename).unwrap();
    let ref mut w = BufWriter::new(file);
    let mut encoder = png::Encoder::new(w, width, height);
    encoder.set_color(png::ColorType::RGB);
    encoder.set_depth(png::BitDepth::Eight);
    let mut writer = encoder.write_header().unwrap();
    writer.write_image_data(pixels).unwrap();
}

pub fn rgbu8_slice_as_u8(slice: &[RGBu8]) -> &[u8] {
    unsafe {
        std::slice::from_raw_parts(slice.as_ptr() as *const u8, slice.len() * 3)
    }
}