iced-rs / iced

A cross-platform GUI library for Rust, inspired by Elm
https://iced.rs
MIT License
24.79k stars 1.17k forks source link

Incorrect line rendering with very sharp angles #2567

Open edwloef opened 2 months ago

edwloef commented 2 months ago

Is your issue REALLY a bug?

Is there an existing issue for this?

Is this issue related to iced?

What happened?

In the following code example, the second path is rendered incorrectly:

struct State;

#[derive(Debug)]
enum Message {}

impl State {
    fn new() -> Self {
        Self
    }

    fn update(&mut self, _: Message) {}

    fn view(&self) -> iced::Element<'_, Message> {
        iced::Element::from(
            iced::widget::Canvas::new(self)
                .width(iced::Length::Fill)
                .height(iced::Length::Fill),
        )
    }
}

impl Default for State {
    fn default() -> Self {
        Self::new()
    }
}

impl iced::widget::canvas::Program<Message> for State {
    type State = ();

    fn draw(
        &self,
        _state: &Self::State,
        renderer: &iced::Renderer,
        theme: &iced::Theme,
        bounds: iced::Rectangle,
        _cursor: iced::mouse::Cursor,
    ) -> Vec<iced::widget::canvas::Geometry> {
        let mut frame = iced::widget::canvas::Frame::new(renderer, bounds.size());

        let path1 = iced::widget::canvas::Path::new(|path| {
            path.line_to(iced::Point::new(100.0, 100.0));
            path.line_to(iced::Point::new(100.0, 200.0));
        });
        frame.stroke(
            &path1,
            iced::widget::canvas::Stroke::default().with_color(theme.palette().text),
        );

        let path2 = iced::widget::canvas::Path::new(|path| {
            path.line_to(iced::Point::new(200.0, 100.0));
            path.line_to(iced::Point::new(200.0, 200.0));
            path.line_to(iced::Point::new(201.0, 100.0));
        });
        frame.stroke(
            &path2,
            iced::widget::canvas::Stroke::default().with_color(theme.palette().text),
        );

        vec![frame.into_geometry()]
    }
}

fn main() -> iced::Result {
    iced::application("", State::update, State::view).run()
}

image

This occurs on both the crates.io release and the master branch.

What is the expected behavior?

The right path shouldn't be cut off higher than the left path.

Version

master

Operating System

Linux

Do you have any log output?

No response

edwloef commented 2 months ago

Additional info: this only occurs with the wgpu backend. Using tiny-skia results in the lines rendering as expected.

edwloef commented 2 months ago

Even more additional info: this is probably down to floating-point precision. The output of the following code snippet illustrates that:

        let path1 = iced::widget::canvas::Path::new(|path| {
            path.line_to(iced::Point::new(200.0, 100.0));
            path.line_to(iced::Point::new(200.0, 200.0));
            path.line_to(iced::Point::new(201.00004, 100.0));
        });
        frame.stroke(
            &path1,
            iced::widget::canvas::Stroke::default().with_color(theme.palette().text),
        );

        let path2 = iced::widget::canvas::Path::new(|path| {
            path.line_to(iced::Point::new(200.0, 300.0));
            path.line_to(iced::Point::new(200.0, 400.0));
            path.line_to(iced::Point::new(201.00003, 300.0));
        });
        frame.stroke(
            &path2,
            iced::widget::canvas::Stroke::default().with_color(theme.palette().text),
        );

this code snippet also creates some interesting results:

        let path2 = iced::widget::canvas::Path::new(|path| {
            path.line_to(iced::Point::new(200.0, 400.0));
            path.line_to(iced::Point::new(200.0, 600.0));
            path.line_to(iced::Point::new(202.0, 400.0));
        });
        frame.stroke(
            &path2,
            iced::widget::canvas::Stroke::default().with_color(theme.palette().text),
        );