3

I have that drawing logic:

Draw = function(canvas, ctx, mousePosition) {

    var grad = ctx.createLinearGradient(0, 0, canvas[0].width, 0);
    grad.addColorStop(0, currentLineColor);
    grad.addColorStop(1, currentLineColor);

    ctx.lineWidth = currentLineWidth;
    ctx.strokeStyle = grad;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.globalAlpha = 0.4;

    ctx.beginPath();
    ctx.moveTo(lastMousePosition.x, lastMousePosition.y);
    ctx.lineTo(mousePosition.x, mousePosition.y);
    ctx.stroke();
};

When i set the globalAlpha to set opacity in this code a see dots in my drawn line. This logic is attached to mosemove event.

andrewgda
  • 153
  • 1
  • 10

1 Answers1

6

Yes, that is to be expected as each line is overdrawn at the connection points, and their alpha data will add up.

I would suggest the following approach and attach a proof-of-concept demo at the end, feel free to adopt that code to your project:

  1. Create two canvases, one main and one draft on top
  2. Set the alpha directly on the top element using CSS (opacity) and always keep globalAlpha=1
  3. For each stroke (pen down, pen up) draw on draft canvas (use lines between each point)
  4. On pen up, set globalAlpha on main canvas equal the CSS opacity of top canvas
  5. Draw top canvas to main canvas using drawImage().
  6. Clear top canvas, eat, sleep, repeat (from 3).

Proof-of-concept

var draft = document.getElementById("draft");
var main = document.getElementById("main");
var ctx = draft.getContext("2d");
var mctx = main.getContext("2d");
var isDown = false, prev, alpha = 0.4;

// setup pen
ctx.strokeStyle = "rgb(0,200,127)";
ctx.lineWidth = 16;
ctx.lineCap = "round";                              // important to make lines cont.

// set up alpha
draft.style.opacity = alpha;                        // CSS alpha for draft
mctx.globalAlpha = alpha;                           // context alpha for main

draft.onmousedown = function(e){
  isDown = true; 
  prev = getXY(e);                                  // set prev. point as start
};

window.onmousemove = function(e){
  if (!isDown) return;
  var point = getXY(e);
  ctx.beginPath();                                  // new path
  ctx.moveTo(prev.x, prev.y);                       // start at prev. point
  ctx.lineTo(point.x, point.y);                     // line to new point
  ctx.stroke();                                     // stroke
  prev = point;                                     // update prev. point
};

window.onmouseup = function(){
  isDown = false;                                   // when up:
  mctx.drawImage(draft, 0, 0);                      // copy drawing to main
  ctx.clearRect(0, 0, draft.width, draft.height);   // clear draft
};

function getXY(e) {
  var r = draft.getBoundingClientRect();
  return {x: e.clientX - r.left, y: e.clientY - r.top}
}
#draft {cursor:crosshair}
.sandwich {position:relative}
.sandwich>canvas {position:absolute;left:0;top:0}
<div class="sandwich">
  <canvas id="main" width=600 height=600></canvas>
  <canvas id="draft" width=600 height=600></canvas>
</div>
  • This is fantastic and works perfectly. I have one question, is it possible to right click and save the image? Currently it returns an empty image, because I guess the empty canvas is returned rather than the main one? – almost a beginner Feb 03 '17 at 07:20
  • 1
    @almostabeginner it's a good question, almost deserves a separate post :) but a quick workaround is to set draft canvas' [pointer-events](https://devdocs.io/css/pointer-events) to none, attach the canvas specific event handlers to the main one but keep using the draft context to draw. –  Feb 04 '17 at 19:47
  • Quite impressive, this seems like a really good solution. Thank you ! – Nik May 24 '19 at 23:22