Create a realistic pencil tool for a painting app with HTML5 Canvas

18,524

You could try something like the following demo

Live Demo

Your most likely using moveTo and lineTo to create the paths, if you do it that way the properties will be shared for the path until you close the path. So everytime you change the thickness youd need to call closePath and then beginPath again.

In my example I use Bresenham's line algorithm to plot the points. Basically onmousedown it starts painting. Then onmousemove it compares the current coordinates with the last coordinates and plots all of the points between. Its also using fillRect to paint. Based on how fast your moving the line will be thicker or thinner.

Heres the code for the drawing function

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    painting = false,
    lastX = 0,
    lastY = 0,
    lineThickness = 1;

canvas.width = canvas.height = 600;
ctx.fillRect(0, 0, 600, 600);

canvas.onmousedown = function(e) {
    painting = true;
    ctx.fillStyle = "#ffffff";
    lastX = e.pageX - this.offsetLeft;
    lastY = e.pageY - this.offsetTop;
};

canvas.onmouseup = function(e){
    painting = false;
}

canvas.onmousemove = function(e) {
    if (painting) {
        mouseX = e.pageX - this.offsetLeft;
        mouseY = e.pageY - this.offsetTop;

        // find all points between        
        var x1 = mouseX,
            x2 = lastX,
            y1 = mouseY,
            y2 = lastY;


        var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
        if (steep){
            var x = x1;
            x1 = y1;
            y1 = x;

            var y = y2;
            y2 = x2;
            x2 = y;
        }
        if (x1 > x2) {
            var x = x1;
            x1 = x2;
            x2 = x;

            var y = y1;
            y1 = y2;
            y2 = y;
        }

        var dx = x2 - x1,
            dy = Math.abs(y2 - y1),
            error = 0,
            de = dy / dx,
            yStep = -1,
            y = y1;

        if (y1 < y2) {
            yStep = 1;
        }

        lineThickness = 5 - Math.sqrt((x2 - x1) *(x2-x1) + (y2 - y1) * (y2-y1))/10;
        if(lineThickness < 1){
            lineThickness = 1;   
        }

        for (var x = x1; x < x2; x++) {
            if (steep) {
                ctx.fillRect(y, x, lineThickness , lineThickness );
            } else {
                ctx.fillRect(x, y, lineThickness , lineThickness );
            }

            error += de;
            if (error >= 0.5) {
                y += yStep;
                error -= 1.0;
            }
        }



        lastX = mouseX;
        lastY = mouseY;

    }
}

Share:
18,524
jazzytomato
Author by

jazzytomato

Programmers know the benefits of everything and the tradeoffs of nothing

Updated on June 21, 2022

Comments

  • jazzytomato
    jazzytomato almost 2 years

    First I want to say that I made a lot of research and tries myself without any success.

    I am working on a MSPaint-like application using Canvas, and I would like to create a pencil tool which looks realistic like handmade drawings... Here is an example in the link below with the default tool : http://www.onemotion.com/flash/sketch-paint/

    I tried to play with mousespeed and linewidth properties but it is not working well (the entire line enlarge and shrink as I move the mouse). I have no idea of an algorithm acting on pixel raw data.

    Do you know something existing or a suitable algorithm to apply ? Thank you very much for your help

    EDIT

    I decided to add the solution I've chosen because it seems to interest lot of people. So, the best thing I found so far is to draw an image onto the canvas, using the technique explained here : http://css.dzone.com/articles/sketching-html5-canvas-and. It works like a charm, the result is really convincing and this is quite easy to implement. Try it out here : http://tricedesigns.com/portfolio/sketch/brush.html#

  • Beginner
    Beginner over 10 years
    @Loktar i have tried to implement same thing using jquery mobile. I am able to obtain touchevents, but unable to draw the line. can u please help in it? thanks:)
  • Melvin Roest
    Melvin Roest over 4 years
    I had a hard time figuring out how this works. I realized that it's quite easy to see. Selectively toggle out: if (steep){, if (x1 > x2) { and also do else if (x1 > x2) { (for experimentation) and also toggle if (y1 < y2) { yStep = 1; } and you'll see visually what the algorithm is doing.
  • Tom Parke
    Tom Parke over 3 years
    I'd like to let you know that you've created one of the best feeling pencil tools on the internet (much better than a lot of sites that have drawing tools).
  • Loktar
    Loktar over 3 years
    Wow @TomParke I really appreciate that! Maybe I'll wrap it into a component or something and throw it on Github.