Implementing smooth sketching and drawing on the <canvas> element

I am trying to create a drawing area with a canvas. I am having problems with the fact that the lines look smooth when drawing curves, and I also change the line thickness in my algorithm, which also looks bad, because the size also accumulates, and you can see where the size changed. I found this link https://stackoverflow.com/a/16626944/2326321 , but it was for a native iPhone app, and I can't figure it out.

Here is my current JS code. and here it works on jsFiddle

var xStart, xEnd, yStart, yEnd, paint, ctx; $(document).ready(function (){ ctx = $('canvas')[0].getContext("2d"); ctx.strokeStyle = '#000'; ctx.lineJoin="round"; ctx.lineCap="round"; ctx.lineWidth = 1; $('canvas').bind('mousedown mousemove mouseup mouseleave touchstart touchmove touchend', function(e){ var orig = e.originalEvent; if(e.type == 'mousedown'){ e.preventDefault(); e.stopPropagation(); xStart = e.clientX - $(this).offset().left; yStart = e.clientY - $(this).offset().top; xEnd = xStart; yEnd = yStart; paint = true; draw(e.type); }else if(e.type == 'mousemove'){ if(paint==true){ xEnd = e.clientX - $(this).offset().left; yEnd = e.clientY - $(this).offset().top; lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/5; if(lineThickness > 10){ lineThickness = 10; } ctx.lineWidth = lineThickness; draw(e.type); } }else if(e.type == 'mouseup'){ paint = false; }else if(e.type == 'mouseleave'){ paint = false; }else if(e.type == 'touchstart'){ if(orig.touches.length == 1){ e.preventDefault(); e.stopPropagation(); xStart = orig.changedTouches[0].pageX - $(this).offset().left; yStart = orig.changedTouches[0].pageY - $(this).offset().top; xEnd = xStart; yEnd = yStart; paint = true; draw(e.type); } }else if(e.type == 'touchmove'){ if(orig.touches.length == 1){ if(paint==true){ xEnd = orig.changedTouches[0].pageX - $(this).offset().left; yEnd = orig.changedTouches[0].pageY - $(this).offset().top; lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/6; if(lineThickness > 10){ lineThickness = 10; } ctx.lineWidth = lineThickness; draw(e.type); } } }else if(e.type == 'touchend'){ paint = false; } }); }); function draw(event){ if(event == 'mousedown'){ ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.stroke(); }else if(event == 'mousemove'){ ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.stroke(); }else if(event == 'touchstart'){ ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.stroke(); }else if(event == 'touchmove'){ ctx.beginPath(); ctx.moveTo(xStart, yStart); ctx.lineTo(xEnd, yEnd); ctx.stroke(); } xStart = xEnd; yStart = yEnd; } 

Thanks to everyone in advance.

Here's what it looks like now if you draw. current (jagged) implementation

... and this is what I would like to achieve:

smooth brushstrokes

+27
javascript html5 canvas drawing paint
May 12 '12 at 20:45
source share
6 answers

I did something like this a while ago and turned it into a jquery plugin. look here if this happens after I post a more detailed answer and dig a simplified version of jquery from my archives:

http://jsfiddle.net/95tft/

EDIT

OK, sorry, I could not do this yesterday:

Initially, the above code was forked by Mr. Doub "harmony": http://mrdoob.com/projects/harmony/#ribbon

(what I consider the best solution). But somehow I broke it and redid it for another purpose in another project. I hacked my own plugin a bit to make it even easier:

http://jsfiddle.net/dh3bj/

The only thing you might want to change is to change it to work with mousedown / mouseup, which should be easy to also look at the settings at the bottom of the plugin, you can get the desired effect of the game with brush size, color, alpha (rgba) etc.

Hope that helps

+20
May 19 '12 at 2:34 am
source share

Take a look at this code:

http://jsfiddle.net/aMmVQ/

What I do is start a new list of points on mouseDown, then for each mousemove I add a point to the list. As soon as I get enough points (6 or so), I start to draw quadratic curves, and the control point of the curve is the average of the current point and the next point.

drawPoints is the bit that works with this magic:

 function drawPoints(ctx, points) { // draw a basic circle instead if (points.length < 6) { var b = points[0]; ctx.beginPath(), ctx.arc(bx, by, ctx.lineWidth / 2, 0, Math.PI * 2, !0), ctx.closePath(), ctx.fill(); return } ctx.beginPath(), ctx.moveTo(points[0].x, points[0].y); // draw a bunch of quadratics, using the average of two points as the control point for (i = 1; i < points.length - 2; i++) { var c = (points[i].x + points[i + 1].x) / 2, d = (points[i].y + points[i + 1].y) / 2; ctx.quadraticCurveTo(points[i].x, points[i].y, c, d) } ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y), ctx.stroke() } 
+8
May 12 '12 at 23:01
source share

Why don't you use croquis.js ?

He has a neat brush implementation, for example, Photoshop :)

And here is the demo using croquis.js.

brush-preview

+5
Jan 15 '14 at 7:09
source share

It seems you need to use some brushes on your canvas. It's hard to say which brush you need, but there are many JS libraries that already use brush technology.

For example, have you looked at these libraries?

Laso on the Internet you can find many brushes implemented in the project Mr. Doob Harmony . For example stringy or Harmony-Brushes github project.

+2
May 16 '12 at 9:39
source share

Suggest rendering a chain of bezier curves that surround a curve filled this way. (i.e. ending ctx.fill) Still a lot of work, but hope this helps.

Adapted a good demo application for Bezier curves

added it to the fork of your violin http://jsfiddle.net/d3zFU/1/

The code

 /* * Canvas curves example * * By Craig Buckler, http://twitter.com/craigbuckler * of OptimalWorks.net http://optimalworks.net/ * for SitePoint.com http://sitepoint.com/ * * Refer to: * http://blogs.sitepoint.com/html5-canvas-draw-quadratic-curves/ * http://blogs.sitepoint.com/html5-canvas-draw-bezier-curves/ * * This code can be used without restriction. */ 

(function () {

 var canvas, ctx, code, point, style, drag = null, dPoint; // define initial points function Init(quadratic) { point = { p1: { x:100, y:250 }, p2: { x:400, y:250 } }; if (quadratic) { point.cp1 = { x: 250, y: 100 }; } else { point.cp1 = { x: 150, y: 100 }; point.cp2 = { x: 350, y: 100 }; } // default styles style = { curve: { width: 6, color: "#333" }, cpline: { width: 1, color: "#C00" }, point: { radius: 10, width: 2, color: "#900", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI } } // line style defaults ctx.lineCap = "round"; ctx.lineJoin = "round"; // event handlers canvas.onmousedown = DragStart; canvas.onmousemove = Dragging; canvas.onmouseup = canvas.onmouseout = DragEnd; DrawCanvas(); } // draw canvas function DrawCanvas() { ctx.clearRect(0, 0, canvas.width, canvas.height); // control lines ctx.lineWidth = style.cpline.width; ctx.strokeStyle = style.cpline.color; ctx.fillStyle = style.cpline.color; ctx.beginPath(); ctx.moveTo(point.p1.x, point.p1.y); ctx.lineTo(point.cp1.x, point.cp1.y); if (point.cp2) { ctx.moveTo(point.p2.x, point.p2.y); ctx.lineTo(point.cp2.x, point.cp2.y); } else { ctx.lineTo(point.p2.x, point.p2.y); } ctx.stroke(); // curve ctx.lineWidth = 1 ; //style.curve.width; ctx.strokeStyle = style.curve.color; ctx.beginPath(); ctx.moveTo(point.p1.x, point.p1.y); if (point.cp2) { ctx.bezierCurveTo(point.cp1.x, point.cp1.y, point.cp2.x, point.cp2.y, point.p2.x, point.p2.y); ctx.bezierCurveTo(point.cp2.x, point.cp2.y+12, point.cp1.x, point.cp1.y+12, point.p1.x, point.p1.y); } else { ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y); } //ctx.stroke(); ctx.fill(); // control points for (var p in point) { ctx.lineWidth = style.point.width; ctx.strokeStyle = style.point.color; ctx.fillStyle = style.point.fill; ctx.beginPath(); ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true); ctx.fill(); ctx.stroke(); } ShowCode(); } // show canvas code function ShowCode() { if (code) { code.firstChild.nodeValue = "canvas = document.getElementById(\"canvas\");\n"+ "ctx = canvas.getContext(\"2d\")\n"+ "ctx.lineWidth = " + style.curve.width + ";\nctx.strokeStyle = \"" + style.curve.color + "\";\nctx.beginPath();\n" + "ctx.moveTo(" + point.p1.x + ", " + point.p1.y +");\n" + (point.cp2 ? "ctx.bezierCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.cp2.x+", "+point.cp2.y+", "+point.p2.x+", "+point.p2.y+");" : "ctx.quadraticCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.p2.x+", "+point.p2.y+");" ) + "\nctx.stroke();" ; } } // start dragging function DragStart(e) { e = MousePos(e); var dx, dy; for (var p in point) { dx = point[p].x - ex; dy = point[p].y - ey; if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) { drag = p; dPoint = e; canvas.style.cursor = "move"; return; } } } // dragging function Dragging(e) { if (drag) { e = MousePos(e); point[drag].x += ex - dPoint.x; point[drag].y += ey - dPoint.y; dPoint = e; DrawCanvas(); } } // end dragging function DragEnd(e) { drag = null; canvas.style.cursor = "default"; DrawCanvas(); } // event parser function MousePos(event) { event = (event ? event : window.event); return { x: event.pageX - canvas.offsetLeft, y: event.pageY - canvas.offsetTop } } // start canvas = document.getElementById("canvas"); code = document.getElementById("code"); if (canvas.getContext) { ctx = canvas.getContext("2d"); Init(canvas.className == "quadratic"); } 

}) ();

+2
May 21 '12 at 17:42
source share

For anyone interested in clicking the code version provided by @Alex, I rewrote its script here:

http://jsbin.com/aqoqad/3/

+2
Apr 13 '13 at
source share



All Articles