canvas - 为什么在 Canvas 的绘制应用程序中使用 beginPath 方法?
问题描述
这是一个关于Canvas
. 这是一个编写的示例绘画应用程序,rust
可编译为WebAssembly
. 它使用画布进行绘图。当鼠标移动和停止时,它开始用铅笔绘制mouseDown
事件mouseUp
。它正在运行。
如果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_path
和move_to
函数调用?line_to
当鼠标移动时,已经从开始到结束绘制一条线,然后从结束到下一个点。begin_path
和有什么用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(())
}
解决方案
它是为了每次鼠标移动画一条线。
调用失败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 中,我的 rust is 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>
推荐阅读
- r - R随机游走中的极限矩阵
- algorithm - T(n) = 2T(n/2) + log n 的解
- javascript - 我有一个唯一 id 列表,我想在其中为每个 id 数据实现一个计数器,每 10 分钟刷新一次
- image - 矢量图像上的分段分类?
- html - 提交按钮以重定向到另一个链接
- javascript - 如何使用 AJAX 从视图中将 id 发布到 ASP.Net 控制器?
- c# - 在数据网格视图中添加最新图标
- debugging - 为什么 64 位 gdb 在 ARM 32 代码中永远不会到达断点?
- javascript - React - 错误:App(...):渲染没有返回任何内容。这通常意味着缺少 return 语句。或者,不渲染任何内容,返回 null
- amazon-web-services - Pyspark s3 错误:java.lang.NoClassDefFoundError: com/amazonaws/AmazonServiceException