1

I was trying to create a sample paint application using HTML 5 canvas. Then I added a button to redraw what user had drawn earlier. I am not sure what I am doing wrong or may be completely wrong. When I click redraw button multiple times it generates some magical animation by drawing lines all over. Even though if I log the starting point of drawing the image its same every time.

Demo: http://jsfiddle.net/BW57H/6/

Steps to reproduce:

Draw some circle or rectangle or something by clicking the mouse and dragging it on the rectangular box. Then click reset and redraw , click redraw couple of times after that and see the result.

I am not sure what I have done. I have not read a lot about Canvas. But I am curious to know what is going on here. Thanks.

html

<body>
    <canvas id="paint" width="600px" height="300px"></canvas>


    <div id="controls">
        <button name="reset" id="reset">Reset</button>
        &nbsp;<button name="redraw" id="redraw">Re-Draw</button>
    </div>
</body>

css

#paint{
    border: solid;
}

js

$(document).ready(function(){


    var x, y, context, painter;

    var xCounter = 0 , yCounter = 0;
    var xarray = [];
    var yarray = [];

    function init(){
        while(document.getElementById("paint") === undefined){
            //do nothing
        }

        console.log("Loaded document now registering events for canvas");
        var canvas = document.getElementById("paint");

        context = canvas.getContext('2d'); 
        painter = new Painter();

        canvas.addEventListener('mousedown', capture, false);
        canvas.addEventListener('mouseup', capture, false);
        canvas.addEventListener('mousemove', capture, false);

        document.getElementById("reset").addEventListener("click",function(){ clearCanvas(canvas);}, false);

            document.getElementById("redraw").addEventListener("click",             function(){
                autoDraw();
            }, false);
    }

    function clearCanvas(canvas){
    context.save();

    // Use the identity matrix while clearing the canvas
    context.setTransform(1, 0, 0, 1, 0, 0);
    context.clearRect(0, 0, canvas.width, canvas.height);

    // Restore the transform
    context.restore();

    };

    function capture(event){

        if(event.which !== 1){
            return;
        }   

        x = event.layerX;
        y = event.layerY;

        switch(event.type){
            case 'mousedown':
                painter.startPaint(event);
                break;
            case 'mouseup':
                painter.endPaint(event);
                break;
            case 'mousemove':
                painter.paint(event);
                break;
        }


    };


    var Painter = function(){

        var self = this;
        self.paintStarted = false;

        self.startPaint  = function(event){
                self.resetRecordingParams();
                self.paintStarted = true;
                context.beginPath();
                context.moveTo(x,y);
                self.record(); 
        }

        self.endPaint = function(event){
                self.paintStarted = false;
                self.record();
                self.paint(event)
        }

        self.paint  = function(event){
            if(self.paintStarted){
                context.lineTo(x,y); 
                context.stroke(); 
                self.record();
            }
        }

        self.record = function(){
            xarray[xCounter++] = x;
            yarray[yCounter++] = y;
        }

        self.resetRecordingParams = function(){
            xarray = [];
            yarray = [];
            xCounter = 0;
            yCounter= 0;
        }

    return self;

    }


    function autoDraw(){
        context.beginPath();

        console.log('starting at: '+xarray[0]+','+yarray[0]);
        context.moveTo(xarray[0],yarray[0]);

        for (var i = 0; i < xarray.length; i++) {
            setTimeout(drawLineSlowly, 1000+(i*20), i); 
        }; 

    }

    function drawLineSlowly(i)
    {
        context.lineTo(xarray[i],yarray[i]); 
        context.stroke(); 

    }

    init();
});
inf3rno
  • 24,976
  • 11
  • 115
  • 197
webdev
  • 598
  • 5
  • 16
  • 3
    That "Links to jsfiddle.net must be accompanied by code" message isn't just for circumventing, you know. If jsfiddle is unavailable for any reason, this question loses what little value it may currently have. **Fix that.** – Matt Ball Jun 18 '13 at 03:14
  • 2
    I was in the middle of fixing that :) – webdev Jun 18 '13 at 03:15
  • No; all you've done is found a loophole. How about composing a higher-quality question instead of being clever? http://meta.stackexchange.com/q/149890/133242 – Matt Ball Jun 18 '13 at 03:21
  • while(document.getElementById("paint") === undefined) omfg, hahahaha :D – inf3rno Jun 18 '13 at 03:23
  • @inf3rno do not judge the code for any clean code stuff answer if u can the actual thing :P – webdev Jun 18 '13 at 03:46
  • @PawanChopra : I don't find the real words about you and your code... :D – inf3rno Jun 18 '13 at 03:53
  • @inf3rno You will never find any words :P. That is the difference between a good programmer and great programmer. Great programmers won't laugh at other's code :D – webdev Jun 18 '13 at 03:59
  • @PawanChopra , what do you think of my answer? If you have any questions, please let me know. – Max Jun 18 '13 at 04:00

2 Answers2

0

Obviously you don't stop the previous timeout loop before you start a new...

Use setInterval instead of setTimeout, and clearInterval by next push. So I think the problem is not with the canvas, just with your redraw animation. Btw it is strange because there is some difference between the redraw and the original draw...

var drawInterval = null;

function autoDraw(){
    if (drawInterval) {
        //here your can reset the previous - still running - redraw
        clearInterval(drawInterval);
    }

    context.beginPath();

    console.log('starting at: '+xarray[0]+','+yarray[0]);
    context.moveTo(xarray[0],yarray[0]);

    var i=0;
    setInterval(function (){
        ++i;
        if (i<xarray.length)
            drawLineSlowly(i);
        else
            clearInterval(drawInterval);
    },20);

}

note: There is still bug in the redraw, but at least it does not kill the browser... Btw the strange "animation" is because you does not check by redraw if you are currently drawing or not, so you start draw and redraw together and they interfere... You have to stop redraw when you start drawing.

inf3rno
  • 24,976
  • 11
  • 115
  • 197
  • 1
    I think because your solution doesn't actually address posters original question. – Max Jun 18 '13 at 03:58
  • I have not tried your solution but not sure who gave -1. Is it not working? – webdev Jun 18 '13 at 04:03
  • Ye but the original for loop + setTimeout kills the browser, because pushing the redraw button again and again will result countless calls of drawLineSlowly... – inf3rno Jun 18 '13 at 04:03
  • Ye that solution is the same with setTimeout... I have the same conditon in the beginning, but it stops the current redraw instead of waiting the end of it... – inf3rno Jun 18 '13 at 04:07
  • Btw I think it will be better to store the drawing duration for each point, and set it by redraw. I think canvas can do that. (never tried) – inf3rno Jun 18 '13 at 04:11
  • 2
    @PawanChopra : It is working, it is roughly the same you accepted. – inf3rno Jun 18 '13 at 04:12
  • 1
    Yea I tried it. It's kind of same answer. Thanks :) and ignore that while loop :P :D – webdev Jun 18 '13 at 04:14
  • 1
    I never saw such thing in javascript... Don't use that please, it eats the processor completely... Use event listeners instead of that, or throw an exception if the element does not exist! – inf3rno Jun 18 '13 at 04:18
  • Btw by domready the canvas element should already exist, so that loop will never run... But if you change the id of the canvas, it will result infinite loop, and your browser will freeze... – inf3rno Jun 18 '13 at 04:21
  • 1
    Don't worry that was just for fun. But I agree with you. should never use such code. – webdev Jun 18 '13 at 04:24
0

You don't have any kind of check to see whether or not you are already drawing, so here is your code with those changes commented, as well as the real-pixel-location fixes (http://jsfiddle.net/upgradellc/htJXy/1/):

$(document).ready(function(){
    var x, y, context, painter, canvas;

    var xCounter = 0 , yCounter = 0;
    var xarray = [];
    var yarray = [];

    function init(){
        while(document.getElementById("paint") === undefined){
            //do nothing
        }

        console.log("Loaded document now registering events for canvas");
        canvas = document.getElementById("paint");

        context = canvas.getContext('2d'); 
        painter = new Painter();

        canvas.addEventListener('mousedown', capture, false);
        canvas.addEventListener('mouseup', capture, false);
        canvas.addEventListener('mousemove', capture, false);

        document.getElementById("reset").addEventListener("click",function(){ clearCanvas(canvas);}, false);

            document.getElementById("redraw").addEventListener("click", function(){
                autoDraw();
            }, false);
    }

    function clearCanvas(canvas){
    context.save();

    // Use the identity matrix while clearing the canvas
    context.setTransform(1, 0, 0, 1, 0, 0);
    context.clearRect(0, 0, canvas.width, canvas.height);

    // Restore the transform
    context.restore();

    };

    function capture(event){  
        if(event.which !== 1){
            return;
        }   

        tempPos = getMousePos(canvas, event);
        x = tempPos.x;
        y = tempPos.y;

        switch(event.type){
            case 'mousedown':
                painter.startPaint(event);
                break;
            case 'mouseup':
                painter.endPaint(event);
                break;
            case 'mousemove':
                painter.paint(event);
                break;
        }

    };


    var Painter = function(){
        var self = this;
        self.paintStarted = false;
        //this keeps track of whether or not we are currently auto drawing
        self.currentlyAutoDrawing = false;

        self.startPaint  = function(event){
                self.resetRecordingParams();
                self.paintStarted = true;
                context.beginPath();
                context.moveTo(x,y);
                self.record(); 
        }

        self.endPaint = function(event){
                self.paintStarted = false;
                self.record();
                self.paint(event);
        }

        self.paint  = function(event){
            if(self.paintStarted){
                context.lineTo(x,y); 
                context.stroke(); 
                self.record();
            }
        }

        self.record = function(){
            xarray[xCounter++] = x;
            yarray[yCounter++] = y;
        }

        self.resetRecordingParams = function(){
            xarray = [];
            yarray = [];
            xCounter = 0;
            yCounter= 0;
        }

    return self;

    }


    function autoDraw(){
        context.beginPath();
        //If we are already auto-drawing, then we should just return instead of starting another drawing loop cycle
        if(painter.currentlyAutoDrawing){
            console.log("painter is already auto drawing");
            return;
        }
        painter.currentlyAutoDrawing = true;
        console.log('starting at: '+xarray[0]+','+yarray[0]);
        context.moveTo(xarray[0],yarray[0]);

        for (var i = 0; i < xarray.length; i++) {
            setTimeout(drawLineSlowly, 1000+(i*20), i); 
        }; 

    }

    function drawLineSlowly(i)
    {
        //when we reach the last element in the array, update painter with the fact that autodrawing is now complete
        if(xarray.length == i+1){
            painter.currentlyAutoDrawing=false;
        }
        console.log(xarray.length+" "+i);
        context.lineTo(xarray[i],yarray[i]); 
        context.stroke(); 

    }

    function getMousePos(canv, evt) {
        var rect = canv.getBoundingClientRect();
        return {
          x: evt.clientX - rect.left,
          y: evt.clientY - rect.top
        };
    }

    init();
});
Max
  • 2,082
  • 19
  • 25
  • An explanation for the -1 would be nice, if I'm not doing something correctly. – Max Jun 18 '13 at 03:57
  • It's because he thinks he is a funny guy :D – inf3rno Jun 18 '13 at 03:59
  • 1
    Awesome. Last thing we need is one of those :P – Max Jun 18 '13 at 03:59
  • Thanks for the solution. It's working. But I am still not sure even though I was starting at the same x,y coordinates. why I had those strange animations? – webdev Jun 18 '13 at 04:02
  • 2
    @PawanChopra Because when you press redraw 2 seconds apart, it will call the settimeout at the same time, and now you have two drawlineslowly running at the same time- but they are two seconds apart, which means they are not on the same index of xarray and yarray. This causes lineto to sometimes get called from the two different drawlineslowly, meaning it draws a line from (for example) (xarray[i],yarray[i]) to (xarray[i-40],yarray[i-40]) – Max Jun 18 '13 at 04:08