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?
Related
I have two similar examples but one using SVG other using CANVAS:
WIth html5 canvas: http://jsbin.com/yepigu/5
With SVG: http://jsbin.com/yumova
I need to know which render is faster. What I need to use in my case?
Is there some online tool for that or I need to make some timer into code ?
To perf test the 2 alternatives, run each alternative while recording starting & ending times.
BTW, I reworked your canvas stroked line to work without shadowing which should speed that up:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var pts = [{x:22,y:59.45},{x:136,y:66},{x:170,y:99},{x:171,y:114},{x:183,y:125},{x:218,y:144},{x:218,y:165},{x:226,y:193},{x:254,y:195},{x:283,y:195},{x:292,y:202},{x:325,y:213},{x:341,y:134},{x:397,y:245},{x:417,y:548}];
ctx.lineCap='round';
ctx.lineJoin='round';
ctx.lineWidth=25;
ctx.strokeStyle='red';
drawPolyline(pts);
ctx.lineWidth=22;
ctx.strokeStyle='pink';
drawPolyline(pts);
ctx.lineWidth=2;
ctx.strokeStyle='blue';
drawPolyline(pts);
function drawPolyline(pts){
ctx.beginPath();
ctx.moveTo(pts[0].x,pts[0].y);
for(var i=1;i<pts.length;i++){
ctx.lineTo(pts[i].x,pts[i].y);
}
ctx.stroke();
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=600 height=600></canvas>
Iam searching for a solution, in html5-canvas how to clear the scale function?
If I scaled a shape, for next shape it should not scale or scale less
thank you
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
<script>
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
// first draw
ctx.strokeRect(5,5,25,15);
//scale
ctx.scale(2,2);
ctx.strokeRect(5,5,25,15);
// how to clear scale this ? It should draw like first
ctx.scale(-1,-1); // this is not working
ctx.strokeRect(5,5,25,15);
</script>
</body>
</html>
Since you scaled by 2, you can un-scale by 1/2
// first draw
ctx.strokeRect(5,5,25,15);
//scale
ctx.scale(2,2);
ctx.strokeRect(5,5,25,15);
// un-scale
ctx.scale(.5,.5); // unscale by 1/2
ctx.strokeRect(5,5,25,15);
Another example, if you had scaled by 3 you would unscale by 1/3
// scale
ctx.scale(3,3);
// unscale
ctx.scale(1/3,1/3);
I think the most convenient solution for your problem is to save the context and then restore it after you are done drawing the stuff you want to be scaled.
// Draw the first rect with size 20x20
ctx.strokeRect(50,75,20,20);
// Save context
ctx.save();
// Double the scale
ctx.scale(2, 2);
// Draw the second rect with the same size
ctx.strokeRect(65,35,20,20);
// Restore context
ctx.restore();
// Draw the last rect, still with size 20x20
ctx.strokeRect(220,75,20,20);
Example: http://jsfiddle.net/8tXVL/
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 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.
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);
}