Reputation: 6094
This is a question regarding Canvas
. This is an example paint app written in rust
which compiles to WebAssembly
. It uses canvas for drawing. It starts drawing with pencil on mouseDown
event as mouse moves and stops on mouseUp
. Here is it running.
On the mouseMove
event the example have functions
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?
Why do we have the last two begin_path
and move_to
function call? line_to
already draw a line from start to end and then from the end to next point as the mouse moves. Whats the use of begin_path
and move_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(())
}
Upvotes: 0
Views: 99
Reputation: 136707
It's made in order to draw one line per mouse move.
Failing to call beginPath()
, the context's subpath would still contain all the previous draw calls. So the next time stroke()
is called, all these subpathes would be drawn, again, over the previous pixels, creating ugly artifacts.
The call to moveTo
is then just to initialize the future small segment to the next mouse move vector.
Here is an example where the strokeStyle
is changed every new mouse move. Since we don't call beginPath()
, the whole path has its colored changed, and not just the last segment:
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>
In comparison, here is what happens when calling 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>
However, this may create issues at line junctions, since there is actually no joint.
A better approach is thus to store all the vectors in an Array, clear the whole context every frame, and redraw the full path: (sorry, still in JS, my rust is rusted non existent).
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>
Upvotes: 1