ImageOptim / mozjpeg-rust

Safe Rust wrapper for the MozJPEG library
https://lib.rs/mozjpeg
Other
75 stars 19 forks source link

How to achieve same quality as libjpeg? #13

Closed romanzes closed 4 years ago

romanzes commented 4 years ago

I'm working on a rendering utility that can save images as JPEGs with specified quality. For a weird reason, I need the images saved by my utility to look the same as the ones produced by libjpeg's cjpeg. As there is no convenient libjpeg bindings for Rust, I'm using mozjpeg-rust, but the output looks very different for the same quality. Is it possible to configure the compressor in mozjpeg-rust to make it look similar in terms of the amount of artefacts? This is an example of what the output images currently look like:

libjpeg

libjpeg_30

mozjpeg

mozjpeg_30

It's easy to notice that the mozjpeg one has much more artefacts than libjpeg.

The libjpeg image was produced by converting the image saved with 100 quality:

djpeg original.jpg | cjpeg -quality 30 > libjpeg_30.jpg

Original

original

And this is how I call mozjpeg-rust from my code:

    let mut compressor = Compress::new(ColorSpace::JCS_EXT_RGBA);
    compressor.set_quality(30.0);
    compressor.set_size(320, 240);
    compressor.set_mem_dest();
    compressor.start_compress();
    compressor.write_scanlines(&scanlines);
    compressor.finish_compress();

PS: if it's currently not possible in mozjpeg-rust, but possible in mozjpeg, that might help too.

kornelski commented 4 years ago

The difference is due to chroma subsampling.

compressor.components_mut() has component.h_samp_factor and .v_samp_factor

Search v_samp_factor here for more info https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/libjpeg.txt

romanzes commented 4 years ago

The difference is due to chroma subsampling.

compressor.components_mut() has component.h_samp_factor and .v_samp_factor

Search v_samp_factor here for more info https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/libjpeg.txt

Thanks for your reponse! Do I understand it right that I should disable subsampling in order to achieve the desired result? I modified my code in the following way:

    let mut compressor = Compress::new(ColorSpace::JCS_EXT_RGBA);
    compressor.set_quality(30.0);
    compressor.set_size(320, 240);
    let mut components = compressor.components_mut();
    for component in components {
        component.h_samp_factor = 1;
        component.v_samp_factor = 1;
    }
    compressor.set_mem_dest();
    compressor.start_compress();
    compressor.write_scanlines(&scanlines);
    compressor.finish_compress();

It led to a result that is closer to what I need, but still not quite the same:

converter_30

kornelski commented 4 years ago

The rest is up to quality setting.

MozJPEG defaults to quantization table optimized for photographic content, and not icons with sharp edges.

romanzes commented 4 years ago

The rest is up to quality setting.

MozJPEG defaults to quantization table optimized for photographic content, and not icons with sharp edges.

I tried specifying every possible value for -quant-table argument and no result was the same as libjpeg. This is the command I used:

djpeg original.jpg | cjpeg -quality 30 -quant-table <N> -sample 1x1 > libjpeg_30.jpg

However, then I noticed that -revert param does the trick:

djpeg original.jpg | cjpeg -revert -quality 30 > libjpeg_30.jpg

For those wondering how to do it in Rust code, you need to call Compress::set_fastest_defaults() method. It needs to be called before you set quality or other settings, otherwise it will overwrite them.

kornelski commented 4 years ago

Please note that -revert/fastest_defaults disables all compression improvements too. It completely turns off all MozJPEG features, as if didn't exist. You get plain libjpeg-turbo with an old encoder.

romanzes commented 4 years ago

Please note that -revert/fastest_defaults disables all compression improvements too. It completely turns off all MozJPEG features, as if didn't exist. You get plain libjpeg-turbo with an old encoder.

Thanks for clarifying that. At the current stage of my project this is exactly what I need, but later I'll be able to turn MozJPEG features back on as I won't need to match images produced by libjpeg anymore.