BoxDragon / kolor

Color space conversion math made simple
38 stars 7 forks source link

Uncorrect result when converting from ACES_CG to ENCODED_BT_709 #12

Closed oumad closed 4 months ago

oumad commented 1 year ago

I'm trying to convert an EXR done in ACES_CG color space to png with encoded BT_709.

The result I'm expecting to get : toy_car

The actual result I'm getting using my code : toy_car_BT_709

As you can see we lose a lot of nuances in the colors and highlights.

This is the source EXR file : toy_car.zip

And this is the code I'm using for the conversion :

extern crate image as png;
// exr imports
extern crate exr;
extern crate kolor;

/// Converts one rgba exr with one layer to one png, or fail.
pub fn exr_convert() {
    use exr::prelude::*;
    use exr::prelude as exrs;

    let conversion = kolor::ColorConversion::new(
      kolor::spaces::ACES_CG,
      kolor::spaces::ENCODED_BT_709,
    );

    // read from the exr file directly into a new `png::RgbaImage` image without intermediate buffers
    let reader = exrs::read()
        .no_deep_data()
        .largest_resolution_level()
        .rgba_channels(
        |resolution, _channels: &RgbaChannels| -> png::RgbaImage {
                png::ImageBuffer::new(
                    resolution.width() as u32,
                    resolution.height() as u32
                )
            },
            // set each pixel in the png buffer from the exr file
            move |png_pixels, position, (r,g,b,a): (f32,f32,f32,f32)| { 
                let linear_color = kolor::Vec3::new(r,g,b);
                let srgb_color = conversion.convert(linear_color);
                png_pixels.put_pixel(
                    position.x() as u32, position.y() as u32,
                    png::Rgba([(srgb_color[0]* 255.0) as u8, (srgb_color[1]* 255.0) as u8, (srgb_color[2]* 255.0) as u8, (a * 255.0) as u8])
                );
            }
        )
        .first_valid_layer()
        .all_attributes();

    // an image that contains a single layer containing a png rgba buffer
    let image: Image<Layer<SpecificChannels<png::RgbaImage, RgbaChannels>>> = reader
        .from_file("F:/dev/testing/source/exr/toy_car.exr")
        .expect("run the `1_write_rgba` example to generate the required file");

    // save the png buffer to a png file
    let png_buffer = &image.layer_data.channel_data.pixels;
    png_buffer.save("F:/dev/testing/source/exr/toy_car_BT_709.png").unwrap();
    println!("created rgb png image")
}
oumad commented 1 year ago

I finally got a more correct result, but this time I'm using colstodian crate which is built on top of kolor.

This is the result : toy_car_aces_cg_to_srgb

This is the source target for comparison : toy_car_SOURCE_SRGB

The conversion version is a bit lighter than the target, maybe it's some parameter in tonemaping, but at least the colors behave more correctly, almost there !

The conversion code :

fn aces_cg_to_srgb(r:f32,g:f32,b:f32,a:f32) -> [u8; 3] {
  let rendered_col = color::acescg(r, g, b);
  let params = PerceptualTonemapperParams::default();
  let tonemapped: Color<AcesCg,Display> = PerceptualTonemapper::tonemap(rendered_col, params).convert();
  let encoded = tonemapped.convert::<EncodedSrgb>();
  return encoded.to_u8();
}
kabergstrom commented 4 months ago

Glad to hear you got the result you wanted. Let me know if there are any other issues :)