I am trying to create a drawing area with canvas. I am having trouble with making the lines look smooth when drawing curves and I also have changing line thickness in my algorithm which looks bad as well because the size jumps to much as well and you can see where the size changed. I did find this link on stackoverflow but this was for a native iPhone app and I can't figure it out.
Here is my current JS code. and Here is it running 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;
}
Thank you all in advance.
This is what it looks like right now if you draw.
... and this is what I would love to achieve:
I made something like this a while ago and turned it into a jquery plugin.
have a look over here, if it's what you're after I'll post a more detailed answer and dig out the simplified jquery version from my archives:
http://jsfiddle.net/95tft/
EDIT
OK, sorry I couldn't do this yesterday:
Originally the code above was forked from Mr Doob's 'harmony' sketcher over here:
http://mrdoob.com/projects/harmony/#ribbon
(which I think is the best solution).
But I kinda broke it down and remade it for my own purposes on another project. I've hacked my own plugin a bit to make it a bit easier still over here:
http://jsfiddle.net/dh3bj/
The only thing you might want to change is to change it to work on mousedown/mouseup which should be easy also have a look at the settings at the bottom of the plugin, you should be able to get the effect you want by playing with the brush size, colour, alpha (rgba) etc.
Hope that helps
Have a look at this code:
http://jsfiddle.net/aMmVQ/
What I'm doing is starting a new list of points on mouseDown, then for each mousemove I add a point to the list. Once I get enough points (6 or so) I start drawing quadratic curves, with the control point of the curve being the average of the current point and the next point.
drawPoints
is the bit that works this magic:
function drawPoints(ctx, points) {
// draw a basic circle instead
if (points.length < 6) {
var b = points[0];
ctx.beginPath(), ctx.arc(b.x, b.y, 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()
}
Why don't you use croquis.js?
It has neat brush implementation like photoshop :)
And here is Demo which is using croquis.js.
Seems that you need to use some brushes in your canvas. It's hard to say what kind of brush exactly you need but there is many JS libraries that has already implement brush technology.
For example did you look at this libraries?
- Processing.js
- HTML5-Canvas-Brush-Sketch (demo and article how it works)
Laso in web you can find many brushes implemented in Mr. Doob Harmony project. For example stringy or Harmony-Brushes project on github.
Suggest the rendering be done with a chain of bezier curves which surround the curve which is thus filled. (ie end with ctx.fill)
Still lots of work to do but hope this helps.
Adapted a nice demo app for bezier curves
added it to a fork of your fiddle
http://jsfiddle.net/d3zFU/1/
Code is
/*
* 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 - e.x;
dy = point[p].y - e.y;
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 += e.x - dPoint.x;
point[drag].y += e.y - 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");
}
})();
For those interested in a click version of the code provided by @Alex, I've rewritten his script here:
http://jsbin.com/aqoqad/3/