如何在画布上绘制平滑的线条而不清除它?

3
我有一个画布,在页面上动态添加。 我想在画布上绘制用户的鼠标路径,但我发现如果我在绘制之前清除画布,它会绘制平滑的线条,否则,它会绘制像以下截图那样的丑陋的线条! 请取消代码中draw_on_canvas函数的第一行注释以测试该问题。 enter image description here

$(document).ready(function() {
  //Create DRAWING environment
  var canvasWidth = 400;
  var canvasHeight = 200;
  var drawn_shape_list = [];
  var current_shape_info = {};
  var is_painting = false;

  function add_path_to_drawn_shape_list() {
    if (current_shape_info.path && current_shape_info.path.length > 0) {
      drawn_shape_list.push(current_shape_info);
    }

    current_shape_info = {};
  }

  function add_path(x, y) {
    current_shape_info.color = "#000000";
    current_shape_info.size = 2;

    if (!current_shape_info.path) {
      current_shape_info.path = [];
    }

    current_shape_info.path.push({
      "x": x,
      "y": y
    });
  }

  function draw_on_canvas() {
    //Uncomment following line to have smooth drawing!
    //context.clearRect(0, 0, context.canvas.width, context.canvas.height); //clear canvas
    context.strokeStyle = current_shape_info.color;
    context.lineWidth = current_shape_info.size;

    context.beginPath();
    context.moveTo(current_shape_info.path[0].x, current_shape_info.path[0].y);

    for (var i = 1; i < current_shape_info.path.length; i++) {
      context.lineTo(current_shape_info.path[i].x, current_shape_info.path[i].y);
    }
    context.stroke();
  }

  //Create canvas node
  var canvas_holder = document.getElementById('canvas_holder');
  canvas = document.createElement('canvas');
  canvas.setAttribute('width', canvasWidth);
  canvas.setAttribute('height', canvasHeight);
  canvas.setAttribute('id', 'whitboard_canvas');
  canvas_holder.appendChild(canvas);
  if (typeof G_vmlCanvasManager != 'undefined') {
    canvas = G_vmlCanvasManager.initElement(canvas);
  }
  context = canvas.getContext("2d");

  $('#canvas_holder').mousedown(function(e) {
    var mouseX = e.pageX - this.offsetLeft;
    var mouseY = e.pageY - this.offsetTop;

    is_painting = true;
    add_path(mouseX, mouseY, false);
    draw_on_canvas();
  });

  $('#canvas_holder').mousemove(function(e) {
    if (is_painting) {
      var mouseX = e.pageX - this.offsetLeft;
      var mouseY = e.pageY - this.offsetTop;

      var can = $('#whitboard_canvas');
      add_path(mouseX, mouseY, true);
      draw_on_canvas();
    }
  });

  $('#canvas_holder').mouseup(function(e) {
    is_painting = false;
    add_path_to_drawn_shape_list();
  });

  $('#canvas_holder').mouseleave(function(e) {
    is_painting = false;
    add_path_to_drawn_shape_list();
  });
});
#canvas_holder {
  border: solid 1px #eee;
}

canvas {
  border: solid 1px #ccc;
}
<HTML>

<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <div id="canvas_holder"></div>
</body>

</HTML>

你可以在这里查看我代码的示例,包含两个画布:点击这里
我尝试使用 context.lineJoin = "round"; 和 context.lineCap = 'round'; 来实现圆角线条,但结果并未改变。
这是正常的 canvas 行为还是需要进行其他设置呢?

BoxBlur blur = new BoxBlur(); 不是有效的JavaScript语句,同时 BoxBlur 在任何地方都没有被定义。 - nick zoum
1
画布不自动清除是正常的,是的。您看到的是路径在上一次绘制的基础上重新绘制的效果。您不想清除它的原因是什么? - Ry-
我需要从头开始绘制所有内容。你无论如何都会这样做。 - Keith
哦,现在应该更简单了,因为没有模糊步骤了。你只需要画最新的线而不是所有的线。 - Ry-
@RezaAmya,这不是我想说的,你现在正在重新绘制所有点,清除画布并不会影响它。这也是外观不平滑的原因,因为Alpha黑色叠加在Alpha黑色上,最终会形成黑色。此外 -> for (var i = 1; 也值得指出数组是从零开始的。就像@Ry指出的那样,只需绘制最新的线条即可。 - Keith
显示剩余4条评论
1个回答

1

如何在画布上绘制平滑的线条而不清除它?

你不能这样做。清除和重新绘制是正确的方法。

这是正常的画布行为吗?

完全是。除非你执行某些应该清除画布的操作,否则它不会被清除。因此,当你使用半透明颜色多次在同一区域绘制时,像素会变得越来越暗。

不要担心性能问题,处理先前绘制的合成甚至可能比绘制单个更复杂的路径还要慢。

你可以做的一件事情是使用单个路径来提高性能,这样每帧只会发生单个绘画操作:

const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const path = new Path2D();
const mouse = {};

function draw() {
  // clear all
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  // draw the single path
  ctx.stroke( path );
  // tell we need to redraw next frame
  mouse.dirty = false;
}

canvas.onmousedown = (evt) => {
  mouse.down = true;
  // always use the same path
  path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
  mouse.down = false;
};
document.onmousemove = (evt) => {
  if( mouse.down ) {
    const rect = canvas.getBoundingClientRect();
    path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
  }
  if( !mouse.dirty ) {
    mouse.dirty = true;
    requestAnimationFrame(draw);
  }
};
canvas { border: 1px solid }
<canvas id="canvas" width="500" height="500"></canvas>

如果您需要不同的路径样式,则可以为每种样式创建一个路径。

const canvas = document.getElementById( 'canvas' );
const ctx = canvas.getContext( '2d' );
const makePath = (color) => ({
  color,
  path2d: new Path2D()
});
const pathes = [makePath('black')];

const mouse = {};

function draw() {
  // clear all
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  pathes.forEach( (path) => {
    // draw the single path
    ctx.strokeStyle = path.color;
    ctx.stroke( path.path2d );
  } );
  // tell we need to redraw next frame
  mouse.dirty = false;
}

document.getElementById('inp').onchange = (evt) =>
  pathes.push( makePath( evt.target.value ) );

canvas.onmousedown = (evt) => {
  mouse.down = true;
  const path = pathes[ pathes.length - 1 ].path2d;
  // always use the same path
  path.moveTo( evt.offsetX, evt.offsetY );
};
document.onmouseup = (evt) => {
  mouse.down = false;
};
document.onmousemove = (evt) => {
  if( mouse.down ) {
    const rect = canvas.getBoundingClientRect();
    const path = pathes[ pathes.length - 1 ].path2d;
    path.lineTo( evt.clientX - rect.left, evt.clientY - rect.top );
  }
  if( !mouse.dirty ) {
    mouse.dirty = true;
    requestAnimationFrame(draw);
  }
};
canvas { border: 1px solid }
<input type="color" id="inp"><br>
<canvas id="canvas" width="500" height="500"></canvas>


谢谢你的回答。你提出的使用一条路径的解决方案真的很有趣,但我认为相对于使用一个带有多个点的路径来绘制多个路径,性能差异并不是很大!此外,我正在寻找我的问题的答案。性能很重要,但目前这个问题并不是关键点。 - Reza Amya
@Reza,是的,性能差异非常大。你可能用你的小绘图板没有注意到它,但当你每帧要绘制成千上万个对象时,你就会明白了。而且我已经回答了你问题的标题:“你不需要”。但我还在正文中添加了一点内容。 - Kaiido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接