为什么在 Canvas 中的绘画应用程序中使用了 beginPath 方法?

Why beginPath method used in the paint application in Canvas?

这是关于 Canvas 的问题。这是一个用 rust 编写的示例绘画应用程序,可编译为 WebAssembly。它使用 canvas 进行绘图。当鼠标移动并在 mouseUp 停止时,它在 mouseDown 事件开始用铅笔绘图。 Here 是 运行.

mouseMove 事件中,示例具有函数

        context.line_to(event.offset_x() as f64, event.offset_y() as f64);
        context.stroke();
        context.begin_path(); //whats the use of this?
        context.move_to(event.offset_x() as f64, event.offset_y() as f64); //whats the use of this?

为什么我们有最后两个 begin_pathmove_to 函数调用? line_to 随着鼠标的移动,已经从头到尾画了一条线,然后从尾到下一点画了一条线。 begin_pathmove_to 有什么用?

#[wasm_bindgen]
pub fn greet() -> Result<(), JsValue> {
    utils::set_panic_hook();

    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document
        .create_element("canvas")?
        .dyn_into::<web_sys::HtmlCanvasElement>()?;
    document.body().unwrap().append_child(&canvas)?;
    canvas.set_width(640);
    canvas.set_height(480);
    canvas.style().set_property("border", "solid")?;
    let context = canvas
        .get_context("2d")?
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()?;
    let context = Rc::new(context);
    let pressed = Rc::new(Cell::new(false));
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            context.begin_path();
            context.move_to(event.offset_x() as f64, event.offset_y() as f64);
            pressed.set(true);
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            if pressed.get() {
                context.line_to(event.offset_x() as f64, event.offset_y() as f64);
                context.stroke();
                context.begin_path();
                context.move_to(event.offset_x() as f64, event.offset_y() as f64);
            }
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }
    {
        let context = context.clone();
        let pressed = pressed.clone();
        let closure = Closure::wrap(Box::new(move |event: web_sys::MouseEvent| {
            pressed.set(false);
            context.line_to(event.offset_x() as f64, event.offset_y() as f64);
            context.stroke();
        }) as Box<dyn FnMut(_)>);
        canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?;
        closure.forget();
    }

    Ok(())
}

它是为了每次鼠标移动画一条线。

未能调用 beginPath(),上下文的子路径仍将包含所有先前的绘制调用。因此,下次调用 stroke() 时,所有这些子路径都将被绘制,再次 ,覆盖之前的像素,从而产生丑陋的伪像。
然后调用moveTo只是将未来的小段初始化为下一个鼠标移动向量。

这是一个示例,其中 strokeStyle 每次新的鼠标移动都会更改。由于我们不调用 beginPath(),整个路径的颜色都发生了变化,而不仅仅是最后一段:

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
var hue = 0;

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  ctx.lineTo( x, y );
  hue += 5;
  ctx.strokeStyle = `hsl(${hue}deg,100%,50%)`;
  ctx.stroke();
}
canvas {
  border: 1px solid;
}
<canvas></canvas>

相比之下,调用 beginPath 时会发生以下情况:

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
var hue = 0;

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  ctx.lineTo( x, y );
  hue += 5;
  ctx.strokeStyle = `hsl(${hue}deg,100%,50%)`;
  ctx.stroke();
  ctx.beginPath();
  ctx.moveTo( x, y );
}
canvas {
  border: 1px solid;
}
<canvas></canvas>

但是,这可能会在线路交汇处产生问题,因为实际上没有接头。
因此,更好的方法是将所有向量存储在一个数组中,每帧清除整个上下文,并重绘完整路径:(抱歉,仍然在 JS 中,我的生锈是 rusted 不存在).

const canvas = document.querySelector( "canvas" );
const ctx = canvas.getContext("2d");
const vectors = [];

canvas.onmousemove = (evt) => {
  const rect = canvas.getBoundingClientRect();
  draw(evt.clientX - rect.left, evt.clientY - rect.top);
};

function draw(x, y) {
  vectors.push( { x, y } );
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  ctx.beginPath();
  vectors.forEach( ({x, y}) => ctx.lineTo( x, y ) );
  ctx.stroke();
}
canvas {
  border: 1px solid;
}
<canvas></canvas>