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.88k stars 281 forks source link

[BUG] "attempt to subtract with overflow" When plotting polygons out of bounds #405

Open egordm opened 2 years ago

egordm commented 2 years ago

Describe the bug

The issue I am having is that while plotting polygons out of bounds, I get the following error:

thread 'main' panicked at 'attempt to subtract with overflow', .../plotters/plotters-backend/src/rasterizer/polygon.rs:98:54
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I have looked a bit into the issue, and while the error happens in polygon.rs:98:54, the actual problem is in the path.rs:38.

What seems to be happening is that as the polygon goes out of bounds, it is clipped to stay within the axes. This clipping introduces a lot of colinear points. The check for colinearity does not seem to catch all these cases.

If we the example listed in the reproduction section and look what compute_polygon_vertex does with the first three points:

The variables until line 38

let triple = [ // First three points clipped to the area and translated to backend coords
  [92, 728],
  [40, 728],
  [315, 728],
];

// Tangent and normal vector between triple[0] and triple[1]
let a_t = (-1, 0);
let a_n = (-0, -1);

// Tangent and normal vector between triple[1] and triple[2]
let b_t = (-1, 0);
let b_n = (0, 1);

Multiplying triple[1] with a_n and b_n yields different points and therefore passes the colinearity test on line 38. But, on line 68, x and y are not updated and therefore stay at INTY.

To Reproduce You can reproduce it using this example:

use plotters::prelude::*;

const OUT_FILE_NAME: &'static str = "plotters-doc-data/sample.png";
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root_area = BitMapBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area();
    root_area.fill(&WHITE)?;

    let mut cc = ChartBuilder::on(&root_area)
        .build_cartesian_2d(-10.0f64..10.0, -10.0f64..10.0)?;

    let pts = [
        (-8.9312, -20.0187),
        (-10.1495, -22.5886),
        (-11.1510, -24.7366),
        (-4.3868, -12.9378),
        (9.4977, 14.7489)
    ];

    cc.draw_series(LineSeries::new(pts, RED.stroke_width(2)))
        .unwrap();

    // To avoid the IO failure being ignored silently, we manually call the present function
    root_area.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");
    println!("Result has been saved to {}", OUT_FILE_NAME);
    Ok(())
}

#[test]
fn entry_point() {
    main().unwrap()
}

Possible fix See the pull request below.

Changing the colinearity test to the following fixes the issue. It compares the delta x / delta y for both the a and b vectors.

    // Check if 3 points are colinear. If so, just emit the point.
    if a_t.1 * b_t.0 == a_t.0 * b_t.1 {
        buf.push((b_p.0 as i32, b_p.1 as i32));
        return;
    }

Version Information I was able to reproduce it on 0.3.2 and on the current master branch.

Thanks in advance

egordm commented 2 years ago

@38

Thanks for the merge. Would it also be possible to release a new version of plotters_backend? I believe the issue can be closed afterward.