I have an HTML5 canvas that allows user to draw various shapes and a brush feature allowing the user to freehand. I am using the command pattern for these implementations. The problem I am facing is the "undo" functionality. It works perfectly fine for all the other commands however when it comes to the "brush" there seems to be an issue with it.
The way the brush works is it stores points with each drag of the mouse, once a new point is added the entire array of points are redrawn on the screen. Only when the user releases the mouse does the drawing stop. You can probably see the problem immediately, the older the points the more they get redrawn on the screen. This causes the line to look much darker in color than it actually is.
My solution is to only connect the last point n-1 with point n-2 however that literally only redraws the last 2 points. I do not understand fully how the canvas is working and why this method does not work YET redrawing overtop points seems to work...
Here is some code outlining the key parts.
BrushStrat.prototype.mousemove=function(event){
if(this.command!=null){
//add this point to the list
this.command.addPoint({x:event.canvasX, y:event.canvasY});
//redraw all points
this.command.draw(this.paint.context);
}
}
BrushCommand.prototype.draw=function(context){
if(this.points.length==0){
return;
}
context.beginPath();
context.strokeStyle = this.strokeStyle;
context.lineWidth=this.lineWidth;
//start from the first point
context.moveTo(this.points[0].x,this.points[0].y);
//redraw all subsequent points
for(var i=1;i<this.points.length;i++){
context.lineTo(this.points[i].x, this.points[i].y);
}
context.stroke();
}
Freehand brush strokes using the Command Pattern
You made a good choice in implementing the “command pattern” to track your user’s freestyle brush strokes!
Every drawing between mouseDown and mouseUp is treated as a "drag group".
Every "drag group" is added to a master array (CommandStack[]).
Then you can easily UNDO the last drawing by simply removing the last group on CommandStack[].
This is what happens during a drag cycle by the user:
MouseDown:
Set the starting X,Y for this set of dragged lines.
Create a new array of points dedicated to this set of drag lines (newPoints[])
MouseMove:
Add each mouse position point to newPoints[].
MouseUp:
The drag is over--Stop adding points to newPoints[].
Store both the starting X,Y and newPoints[] to the CommandStack[] array.
Then you can simply and efficiently UNDO strokes:
Remove the last newPoints[] from CommandStack[] like this: CommandStack.pop()
Redraw all the remaining strokes in CommandStack[].
The drawing is visually the same as before the user's last stroke (quick+efficient)!
You can remove more lines by doing more pops off the CommandStack[].
You can also easily implement REDO by saving the newPoints[] that were popped off.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/nUbzS/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<!--[if lt IE 9]><script type="text/javascript" src="../excanvas.js"></script><![endif]-->
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var lastX;
var lastY;
var strokeColor="red";
var strokeWidth=2;
var canMouseX;
var canMouseY;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isMouseDown=false;
// command pattern -- undo
var commandStack=new Array();
var newStart;
var newPoints=new Array();
function handleMouseDown(e){
canMouseX=parseInt(e.clientX-offsetX);
canMouseY=parseInt(e.clientY-offsetY);
$("#downlog").html("Down: "+ canMouseX + " / " + canMouseY);
// Put your mousedown stuff here
lastX=canMouseX;
lastY=canMouseY;
isMouseDown=true;
// command pattern stuff
newStart={x:canMouseX,y:canMouseY};
newPoints=new Array();
}
function handleMouseUp(e){
canMouseX=parseInt(e.clientX-offsetX);
canMouseY=parseInt(e.clientY-offsetY);
$("#uplog").html("Up: "+ canMouseX + " / " + canMouseY);
// Put your mouseup stuff here
isMouseDown=false;
// command pattern stuff
commandStack.push({moveTo:newStart,points:newPoints});
}
function handleMouseOut(e){
canMouseX=parseInt(e.clientX-offsetX);
canMouseY=parseInt(e.clientY-offsetY);
$("#outlog").html("Out: "+ canMouseX + " / " + canMouseY);
// Put your mouseOut stuff here
isMouseDown=false;
}
function handleMouseMove(e){
canMouseX=parseInt(e.clientX-offsetX);
canMouseY=parseInt(e.clientY-offsetY);
$("#movelog").html("Move: "+ canMouseX + " / " + canMouseY);
// Put your mousemove stuff here
if(isMouseDown){
ctx.beginPath();
ctx.moveTo(lastX,lastY);
ctx.lineTo(canMouseX,canMouseY);
ctx.stroke();
lastX=canMouseX;
lastY=canMouseY;
// command pattern stuff
newPoints.push({x:canMouseX,y:canMouseY});
}
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
$("#undo").click(function(e){ undoLast(); });
function undoLast(){
commandStack.pop();
redrawAll();
}
function redrawAll(){
// prep for commandStack redraws
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save()
ctx.strokeStyle="blue";
ctx.beginPath();
// loop through the commandStack and draw all nodes
for(var s=0;s<commandStack.length;s++){
// move to the starting point of this node
var start=commandStack[s].moveTo;
ctx.moveTo(start.x,start.y);
// draw each line segment in this node
var pts=commandStack[s].points;
for(var p=0;p<pts.length;p++){
ctx.lineTo(pts[p].x,pts[p].y);
} // end for(p)
} // end for(s)
// actually draw the lines
ctx.stroke();
ctx.restore();
}
}); // end $(function(){});
</script>
</head>
<body>
<p id="downlog">Down</p>
<p id="movelog">Move</p>
<p id="uplog">Up</p>
<p id="outlog">Out</p>
<canvas id="canvas" width=300 height=300></canvas>
<button id="undo">Undo</button>
</body>
</html>
Clear the canvas, or atleast the drawing area, before redrawing each array point.
Edit
Yeah sorry, I assumed you had a game loop. However, it's still a valid option: use two canvases for the drawing area. One for the "current" shape/squiggle being drawn (which you clear before drawing each point) and another persistent layer which has all the completed shapes/squiggles.
So to recap, when a user clicks and drags this shape is drawn to the current layer. When the user releases the mouse, the image is now "locked it" and transferred to the persistent layer.
Hope that makes sense.
Related
Please notice that I have no background on mathematics or computer graphics.
I would like to know the best way to programmaticaly manipulate an hand-drawn line, if it is even possible.
The draw action must be done in a html page. (may be irrelevant)
methods I tough off:
Draw a line into a canvas (hand-drawn line with up and downs) -> convert to bitmap -> somewhow intepret line on bitmap and manipulate its form (is this possible?)
Instead of interpret from bitmap, at the drawing moment have a kind of button to toggle capture on/off and after capture, generate some kind of mathematical function wich I am able to manipulate and from it generate the new bitmap
Yes, you can.
It's not difficult, but there are lots of small coding aspects to learn.
If you’re in “drawing” mode, you would collect mousepoints that the user clicks and make a line from all those points.
If you’re in “editing” mode, you would let the user drag one of those collected points to a new coordinate and make a line from all the edited points.
Here’s starting code for you to look and learn from plus a Fiddle: http://jsfiddle.net/m1erickson/J5PrN/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth=3;
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var isDown=false;
var startX;
var startY;
var points=[];
var selected=-1;
var mode="draw";
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
for(var i=1;i<points.length;i++){
ctx.lineTo(points[i].x,points[i].y);
}
ctx.stroke();
if(mode=="edit"){
for(var i=0;i<points.length;i++){
ctx.beginPath();
ctx.arc(points[i].x,points[i].y,10,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
}
}
function handleMouseDown(e){
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
if(mode=="draw"){
points.push({x:startX,y:startY});
draw();
}else if(mode=="edit"){
for(var i=0;i<points.length;i++){
var pt=points[i];
var dx=startX-pt.x;
var dy=startY-pt.y;
if(dx*dx+dy*dy<100){
selected=i;
return;
}
}
}
}
function handleMouseUp(e){
selected=-1;
}
function handleMouseOut(e){
selected=-1;
}
function handleMouseMove(e){
if(selected<0){return;}
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
if(selected<0){return;}
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
var pt=points[selected];
points[selected]={x:pt.x+dx,y:pt.y+dy};
draw();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
$("#draw").click(function(){
mode="draw";
draw();
$instructions.text("Click points to draw a line");
});
$("#edit").click(function(){
mode="edit";
draw();
$instructions.text("Drag points to move the line");
});
$instructions=$("#instructions");
}); // end $(function(){});
</script>
</head>
<body>
<button id="draw">Add to Line</button>
<button id="edit">Change Line</button><br>
<p id="instructions">Click points to draw a line</p>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I can't speak to HTML, but in most applications I have seen (such as this one), hand drawn lines are broken up into small straight segments. This is because the sensing system (touch or mouse) gives your application a (somewhat) continuous stream of points; it does not give the actual line. The individual segments are then used to do whatever the goal of the application is.
In the case of line drawing, as the line is drawn, the application takes the points and smooths them (cubic spline, least squares polynomial fit, b-spline, etc.) then draws the smoothed lines onto the screen in the color and style (pen, pencil, chalk, etc.). This gives the user immediate feedback about where their hand is moving, etc.
In the case of gestural control, some overlay line may be drawn to give the user feedback, but the segments are processed differently to determine the gesture (this can be very complex).
Having the lines cached as a series of gestures gives you options for undo/redo. You can also store the drawing as a series of gestures instead of a fixed bitmap.
Was this helpful?
I'm very new to coding. My project is to zoom in to an canvas image based on a 100px square. My selector square is moving around the image successfully with the old squares deleted as I move around the grid.
Here's my code:
HTML
<!doctype HTML>
<html lang="en">
<head>
<script src="draw.js"></script>
</head>
<body>
<section id="main">
<canvas id="canvas" width="400" height="600" style='border:3px solid red'>
</canvas>
</section>
</body>
JAVASCRIPT
function doFirst(){
var x = document.getElementById('canvas')
canvas=x.getContext('2d');
canvas.strokeStyle = "blue";
var pic= new Image();
pic.src = "tut.jpg";
pic.addEventListener ("load",function(){canvas.drawImage(pic,0,0)},false);
}
function select(e){
var xPos=e.clientX;
var yPos=e.clientY;
var locationX = Math.floor(xPos/100)*100
var locationY = Math.floor(yPos/100)*100
var pic= new Image();
pic.src = "tut.jpg";
canvas.drawImage(pic,0,0);
canvas.strokeRect(locationX,locationY,100,100);
}
function zoom (e) {
var locationX = Math.floor(xPos/100)*100
var locationY = Math.floor(yPos/100)*100
canvas.translate(-locationX,-locationY);
canvas.scale(5,5);
}
window.addEventListener ("load", doFirst, false);
window.addEventListener ("mousemove", select, false);
window.addEventListener("mousedown",zoom,false);
The zoom function doesn't kick in at all. And I'm not sure how to move on from there next function - a simple colouring app -= rather than going back to the select function once it has - order functions is still a bit of a mystery. Excuse any naivety as I've probably made some huge clangers in this code.
All help much appreciated,
Nick
To answer your question regarding zoom():
Variables in JavaScript operate under function scope. That is, any variables declared in a function remain visible only to whatever's inside that function.
When you attempt to use xPos and yPos in zoom(), you will get undefined values for them because xPos and yPos are only declared inside select().
xPos and yPos need to be declared and calculated in zoom() as well.
Of course! Thank you.
The zoom function seems a little harder to perfect than I thought: The scale seems to throw the offset out.
Also my animated square refreshed differently on Chrome and IE. How strange. Smooth on IE but flashes on Chrome.
Is there a good place to read up about creating a sequence of function events or do I just put one function inside the other?
Really appreciate your help,
Nick
I'm new to the canvas tag and am playing around with some animation. Basically, I'm trying to setup a "ground" section composed of multiple images (similar to an 8bit side scroller game like Mario Brothers). The ground will be composed of multiple images, which I've built a constructor function to load these and tile them across the bottom.
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
img=new Image();
img.onload=function(){context.drawImage(img,ImgX,ImgY,ImgW,ImgH);};
img.src='images/'+ImageName;};
This seems to work out just fine. I've then setup the rest of the animation, including a basic setup for Key Left/Right events, like so:
window.onload=function(){
var canvas=document.getElementById('canvas'),
context=canvas.getContext('2d'),
Grounds=[],
ImgX=-150; // INITIAL STARTING X FOR FIRST GROUND TILE
// INSERT GROUND ELEMENTS
for(var i=0,l=8; i<l; i++){
var ImgX+=150;
Grounds[i]=new Ground(context,"ground.png",ImgX,650,150,150);
};
// ASSIGN LEFT/RIGHT KEYS
window.addEventListener('keyup',function(event){
switch(event.keyCode){
case 37:
for(var i=0,l=Grounds.length; i<l; i++){
Grounds[i].x+=10;
};
break;
case 39:break;
};
});
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
}());
};
I know exactly what my problem is, but don't know how to solve it. The animation loop is clearing the canvas every frame, but not redrawing the updated position (if any) when the user presses the left arrow key.
I'm missing the redraw part here and I'm not exactly sure how to handle this or if I'm approaching this entirely wrong. Any help is very appreciated! Thanks!
First of all you're incrementing the property x of the ground tiles but that property is not even used anywhere in your code. Modify your code so that the onload event of those image objects draws the image according to their own x property so changes to it will actually affect what is drawn. Also add the image object as a property of the Ground object so you can access it later on from outside.
Your approach is really not so good but if you want to do it without going back to 0 do it as follows:
function Ground(context,ImageName,ImgX,ImgY,ImgW,ImgH){
this.width=ImgW;
this.height=ImgH;
this.x=ImgX;
this.y=ImgY;
var self = this; // Add local reference to this Ground instance
this.img=new Image(); // img is now a property too
this.img.onload=function(){context.drawImage(this, self.x, self.y,self.width,self.height);};
this.img.src='images/'+ImageName;};
Ok so now you can change the property x of the ground tiles and call the draw function of it again (which is the onload event).
Grounds[i].x+=10;
Grounds[i].img.dispatchEvent(new Event("load"));
Please note that you should really make the updates of all the values first and then all the draw calls separately.
Can you not just add a draw method? You usually so something like this:
init -> update -> clear, redraw -> update -> clear, redraw -> ...
// ANIMATION LOOP
(function drawFrame(){
window.mozRequestAnimationFrame(drawFrame,canvas);
context.clearRect(0, 0, canvas.width, canvas.height);
contect.drawImage(...);
}());
I have a question...
I'm trying to figure out how to touch draw shapes on an HTML5 canvas. I've been searching everywhere and so far it's been impossible to find any descent tutorial on matter. Can somebody please help me out? I know how to "draw" shapes on the canvas (with code), but how do you draw/paint with (touch) your finger for mobile apps?
Here's my code so far...
Javascript:
// draws a Square to the x and y coordinates of the mouse event inside
// the specified element using the specified context
function drawSquare(mouseEvent, sigCanvas, context) {
var position = getPosition(mouseEvent, sigCanvas);
context.strokeStyle = "color";
context.strokeRect(x,y,width,height);
}
// draws a square from the last coordiantes in the path to the finishing
// coordinates and unbind any event handlers which need to be preceded
// by the mouse down event
function finishDrawing(mouseEvent, sigCanvas, context) {
// draw the line to the finishing coordinates
drawSquare(mouseEvent, sigCanvas, context);
context.closePath();
// unbind any events which could draw
$(sigCanvas).unbind("mousemove")
.unbind("mouseup")
.unbind("mouseout");
}
HTML5:
<div id="squareButton">
<p><button onclick="drawSquare();">Square</button></p>
</div>
Thanks a lot,
Wardenclyffe
Not really a tutorial, but MDN has a solution here https://developer.mozilla.org/en/DOM/Touch_events#Drawing_as_the_touches_move
additionally you might want to look into the touch events ( also available on the same link above )
here is an excerpt from the link I provided
function startup() {
var el = document.getElementsByTagName("canvas")[0];
el.addEventListener("touchstart", handleStart, false);
el.addEventListener("touchend", handleEnd, false);
el.addEventListener("touchcancel", handleCancel, false);
el.addEventListener("touchleave", handleEnd, false);
el.addEventListener("touchmove", handleMove, false);
}
I have a slight problem here I try to solve. As I start to do animation with HTML5 and Canvas I want to have a constant animation flow and also be able to capture mouse movement without interrupting the animation flow. This right now seems like a problem. Ill bring
some code from my test code here.
function mainInit()
{
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
ballArray.push(new Ball(30,30,2,4,15,"#EEEEEE"));
setInterval(drawScene,20);
}
function drawScene()
{
// main drawScene function
clear(); // clear canvas
// draw animated objects
for(var i = 0; i < ballArray.length; i++)
{
ballArray[i].draw();
}
Event_MouseMove();
}
var messageMousePos = '';
function Event_MouseMove()
{
canvas.addEventListener('mousemove', function(evt)
{
var mousePos = getMousePos(canvas, evt);
messageMousePos = "Mouse position: " + mousePos.x + "," + mousePos.y;
context.font = '10pt Calibri';
context.fillStyle = 'black';
context.fillText(messageMousePos , 5, 15);
}, false);
}
The problem is that when I move the eventListner for mouse movement overrides the draw interval and makes it go much slower. How/where should I put the code for the mouse event so it do not interrupt this draw interval, but still draw the mouse events according to the interval?
At a glance, it looks like the code will try to add an event listener every frame...While JS will dump duplicate handlers, it will slow your code down. It's unclear whether you are trying to only capture mouse movement every interval, or constantly, because your code is kinda trying to do both. Here's the best of both worlds solution:
Call addEventListener once outside the loop, and have the function it calls save the mouse position in messageMousePos. Then, within the drawScene function, put your font/fillstyle/filltext code if you really do only want the text outputting every 20ms. This might look choppy compared to how smoothly the text would change if you were constantly rendering the mouse position text.
Here is an example of constantly capturing and displaying the mouse position, as you might actually want to do.