Search code examples
javascripthtmlcanvassmoothingbrush

Drawing soft brush


I'm trying to create a smooth brush in HTML5, an example is below.

Paint.net example of smooth brush

This is what I tried, it's something. But it's not as smooth as the image above.

Editor.Drawing.Context.globalAlpha = 0.3;
var amount = 3;

for(var t = -amount; t <= amount; t += 3) {
    for(var n = -amount; n <= amount; n += 3) {
        Editor.Drawing.Context.drawImage(Editor.Drawing.ClipCanvas, -(n-1), -(t-1));
    }
}

And it looks like this.

My method of creating a smooth brush


Solution

  • Using brushes

    Choose a brush, this can be an image with predefined brushes or you can make one using an off-screen canvas and draw a radial gradient into it. For simplicity I made a simple image brush such as these:

    Brush 60 Brush 40 Brush 30

    Then for each new point drawn to the canvas:

    • Calculate the diff between the previous and current point
    • Calculate the length of the line so we can use an absolute step value independent of length
    • Iterate over the length using a normalized value and the previously calculated step value

    The step value can be anything that looks good as a result - it largely depends on the smoothness of the brush as well as its general size (smoother brushes will require smaller steps to blend into each other).

    For this demo I used brush-width, the smaller values that are used, the more brushes will be drawn along the line, nicer result, but can also slow down the program, so find a value that compromises quality and speed.

    For example:

    This will be called every time a new point is registered when drawing:

    function brushLine(ctx, x1, y1, x2, y2) {
    
        var diffX = Math.abs(x2 - x1),                       // calc diffs
            diffY = Math.abs(y2 - y1),
            dist = Math.sqrt(diffX * diffX + diffY * diffY), // find length
            step = 20 / (dist ? dist : 1),                   // "resolution"
            i = 0,                                           // iterator for length
            t = 0,                                           // t [0, 1]
            b, x, y;
    
        while (i <= dist) {
          t = Math.max(0, Math.min(1, i / dist));
          x = x1 + (x2 - x1) * t;
          y = y1 + (y2 - y1) * t;
          b = (Math.random() * 3) | 0;
          ctx.drawImage(brush, x - bw * 0.5, y - bh * 0.5);  // draw brush
          i += step;
        }
    }
    

    Demo

    var brush = new Image();
    brush.onload = ready;
    brush.src = "//i.sstatic.net/HsbVA.png";
    
    function ready() {
    
      var c = document.querySelector("canvas"),
          ctx = c.getContext("2d"),
          isDown = false, px, py, 
          bw = this.width, bh = this.height;
    
      c.onmousedown = c.ontouchstart = function(e) {
        isDown = true;
        var pos = getPos(e);
        px = pos.x;
        py = pos.y;
      };
    
      window.onmousemove = window.ontouchmove = function(e) {
        if (isDown) draw(e);
      };
    
      window.onmouseup = window.ontouchend = function(e) {
        e.preventDefault();
        isDown = false
      };
    
      function getPos(e) {
        e.preventDefault();
        if (e.touches) e = e.touches[0];
        var r = c.getBoundingClientRect();
        return {
          x: e.clientX - r.left,
          y: e.clientY - r.top
        }
      }
    
      function draw(e) {
        var pos = getPos(e);
        brushLine(ctx, px, py, pos.x, pos.y);
        px = pos.x;
        py = pos.y;
      }
    
      function brushLine(ctx, x1, y1, x2, y2) {
    
        var diffX = Math.abs(x2 - x1),
          diffY = Math.abs(y2 - y1),
          dist = Math.sqrt(diffX * diffX + diffY * diffY),
          step = bw / (dist ? dist : 1),
          i = 0,
          t = 0,
          b, x, y;
    
        while (i <= dist) {
          t = Math.max(0, Math.min(1, i / dist));
          x = x1 + (x2 - x1) * t;
          y = y1 + (y2 - y1) * t;
          b = (Math.random() * 3) | 0;
          ctx.drawImage(brush, x - bw * 0.5, y - bh * 0.5);
          i += step
        }
      }
    }
    body {background: #777}
    canvas {background: #fff;cursor:crosshair}
    <canvas width=630 height=500></canvas>

    You can use this technique to simulate a variety of brushes.

    Tip: with a small modification you can also variate the width depending on velocity to increase realism (not shown).