plotters-rs / plotters

A rust drawing library for high quality data plotting for both WASM and native, statically and realtimely 🦀 📈🚀
https://plotters-rs.github.io/home/
MIT License
3.69k stars 270 forks source link

[BUG] GIF output has poor palette selection #273

Closed simon-frankau closed 2 years ago

simon-frankau commented 2 years ago

Describe the bug

When I tried to produce a plot with a single highlighted point using the gif backend (as part of an animated gif), the highlighted point did not come out in the colour I expected - I asked for bright red, and got a very dark red.

My theory is that the gif is generated by the gif package, which uses the NeuQuant algorithm, which is primarily intended to quantize images with many colours. It uses a "samping factor" to choose what fraction of pixels to sample, which I suspect makes it possible to ignore or under-weigh bright little highlights when constructing the palette.

Plotters uses a "speed" of 10 (https://docs.rs/plotters-bitmap/0.3.0/src/plotters_bitmap/bitmap.rs.html#86), as recommended. I suspect this is just the sampling factor, although I've not traced it through to the gif crate source. This makes it possible to miss sampling areas of ~10 pixels.

I imagine this might seem like a bit of a corner case, but it's surprisingly confusing when you create a plot with a single highlighted point and it just isn't highlighted!

I see a few possible responses:

You may see others. To be honest, I'd probably go with calling it an issue with the gif crate, but I want to give you first refusal. :)

If any of the other solutions appeal, I'd be happy to work on the PR.

To Reproduce

Minimised code:

use plotters::prelude::*;                                                                                                         

const OUT_FILE_NAME: &str = "out.gif";                                                                                            
const FRAME_DELAY: u32 = 10;                                                                                                      

fn main() -> Result<(), Box<dyn std::error::Error>> {                                                                             
    let backend = BitMapBackend::gif(OUT_FILE_NAME, (1024, 1024), FRAME_DELAY)?;                                                  
    let drawing_area = backend.into_drawing_area();                                                                               
    drawing_area.fill(&WHITE)?;                                                                                                   
    let mut scatter_ctx = ChartBuilder::on(&drawing_area)                                                                         
        .x_label_area_size(40)                                                                                                    
        .y_label_area_size(40)                                                                                                    
        .build_cartesian_2d(-1f64..1f64, -1f64..1f64)?;                                                                           
    scatter_ctx                                                                                                                   
        .configure_mesh()                                                                                                         
        .disable_x_mesh()                                                                                                         
        .disable_y_mesh()                                                                                                         
        .draw()?;                                                                                                                 
    scatter_ctx.draw_series([Circle::new(     
![out](https://user-images.githubusercontent.com/6563758/126066032-a82efac8-97c1-47f4-b2a6-e2b6caf428ce.gif)

        (0.25, 0.1),                                                                                                              
        5,                                                                                                                        
        RED.filled(),                                                                                                             
    )])?;                                                                                                                         
    drawing_area.present()?;                                                                                                      
    Ok(())                                                                                                                        
}

Output gif attached: out

Version Information Not quite sure how best to answer this, but my Cargo.lock has:

[[package]]
name = "plotters"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a"

Thanks, Simon.

simon-frankau commented 2 years ago

I've also raised https://github.com/image-rs/image-gif/issues/109, in case they feel it should be fixed at that layer.

simon-frankau commented 2 years ago

This has been fixed upstream in image-gif, and I've just checked that cargo update picks it up. It does, so it's all done.