Reza Amya
Reza Amya

Reputation: 1724

how to draw smooth lines on canvas without clearing it?

I have a canvas that is adding dynamically to the page on on load. I want to draw user's mouse path on the canvas, but I found that if I clear the canvas before drawing, it will draw smooth lines, otherwise, it will draw ugly lines like following screenshot! enter image description here

To test the problem, please uncomment first line of draw_on_canvas function in the code to see the difference.

$(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>

You can see an example of my code here with two canvas.

I have tried context.lineJoin = "round"; and context.lineCap = 'round';, but the result didn't change.

Is it normal canvas behavior or I should set something?

Upvotes: 3

Views: 1623

Answers (1)

Kaiido
Kaiido

Reputation: 136708

how to draw smooth lines on canvas without clearing it?

You don't. Clearing and redrawing is the way to go.

Is it normal canvas behavior

Yes completely. Unless you perform some action that should clear the canvas, it won't be cleared. So when you draw multiple times over the same area using semi transparent color, the pixels will become darker and darker.

Don't be afraid of performances, having to deal with the compositing of previous drawings may even be slower than drawing a single more complex path.

One thing you can do to improve performances is to use a single Path, so that at every frame only a single paint operation occurs:

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>

If you need to have different path styles, then you can create one path per style.

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>

Upvotes: 1

Related Questions