Created
May 8, 2012 07:51
-
-
Save upsuper/2633348 to your computer and use it in GitHub Desktop.
HTML5 Canvas demo with lines smoothed using Bézier curve
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> | |
<title></title> | |
<style type="text/css"> | |
canvas { position: absolute; top: 0; left: 0; } | |
#c { background: #ccc; } | |
#ps { position: absolute; top: 0; right: 0; } | |
#cl { position: absolute; bottom: 0; right: 0; } | |
</style> | |
</head> | |
<body> | |
<canvas id="c" width="300" height="300"></canvas> | |
<canvas id="p" width="300" height="300"></canvas> | |
<div id="ps"></div> | |
<button id="cl">Clear</button> | |
<script type="text/javascript"> | |
var $c = document.getElementById('c'); | |
var $p = document.getElementById('p'); | |
var $ps = document.getElementById('ps'); | |
var $cl = document.getElementById('cl'); | |
$cl.addEventListener('click', function () { | |
var ctx = $c.getContext('2d'); | |
ctx.clearRect(0, 0, 800, 600); | |
}, false); | |
var path = []; | |
var painting = false; | |
function redrawPath() { | |
var ctx = $p.getContext('2d'); | |
ctx.clearRect(0, 0, 800, 600); | |
if (!path.length) | |
return; | |
ctx.beginPath(); | |
ctx.moveTo(path[0].x, path[0].y); | |
for (var i = 1; i < path.length; ++i) | |
ctx.lineTo(path[i].x, path[i].y); | |
ctx.stroke(); | |
} | |
function centrosymmetry(p0, p) { | |
return {x: p0.x * 2 - p.x, y: p0.y * 2 - p.y}; | |
} | |
function averagePoint(p1, p2) { | |
return {x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2}; | |
} | |
function getp(e) { | |
if (e.touches) | |
return {x: e.touches[0].clientX, y: e.touches[0].clientY}; | |
else | |
return {x: e.clientX, y: e.clientY}; | |
} | |
function startDrawing(e) { | |
e.preventDefault(); | |
var curp = getp(e); | |
//var ctx = $c.getContext('2d'); | |
//ctx.clearRect(0, 0, 800, 600); | |
painting = true; | |
path.push(curp); | |
redrawPath(); | |
} | |
function drawing(e) { | |
e.preventDefault(); | |
if (!painting) | |
return; | |
var curp = getp(e); | |
//$ps.innerHTML += curp.x + ',' + curp.y + ' '; | |
path.push(curp); | |
redrawPath(); | |
} | |
function smooth1() { | |
if (path.length <= 2) | |
return; | |
var ctx = $c.getContext('2d'); | |
ctx.strokeStyle = "blue"; | |
ctx.beginPath(); | |
ctx.moveTo(path[0].x, path[0].y); | |
var a = path[0], | |
o = path[1], | |
b = centrosymmetry(o, path[2]), | |
c = averagePoint(averagePoint(averagePoint(a, b), o), o); | |
ctx.quadraticCurveTo(c.x, c.y, o.x, o.y); | |
for (var i = 1; i + 2 < path.length; ++i) { | |
d = centrosymmetry(o, c); | |
a = o; | |
o = path[i + 1]; | |
b = centrosymmetry(o, path[i + 2]); | |
c = averagePoint(averagePoint(averagePoint(a, b), o), o); | |
ctx.bezierCurveTo(d.x, d.y, c.x, c.y, o.x, o.y); | |
} | |
b = path[path.length - 1]; | |
o = path[path.length - 2]; | |
c = centrosymmetry(o, c); | |
ctx.quadraticCurveTo(c.x, c.y, b.x, b.y); | |
ctx.stroke(); | |
} | |
function Vector(p0, p1) { | |
this.x = p0.x; | |
this.y = p0.y; | |
var x_ = this.x_ = p1.x - p0.x; | |
var y_ = this.y_ = p1.y - p0.y; | |
this.length = Math.sqrt(x_ * x_ + y_ * y_); | |
} | |
Vector.prototype.mul = function (k) { | |
this.x_ *= k; | |
this.y_ *= k; | |
this.length *= k; | |
return this; | |
}; | |
Vector.prototype.end = function () { | |
return {x: this.x + this.x_, y: this.y + this.y_}; | |
}; | |
Vector.prototype.copy = function () { | |
return new Vector(this, this.end()); | |
}; | |
function smoothPoint(a, b, o) { | |
var va = new Vector(o, a), | |
vb = new Vector(o, b); | |
var minLen = Math.min(va.length, vb.length); | |
va.mul(minLen / va.length); | |
vb.mul(minLen / vb.length); | |
return averagePoint(va.end(), vb.end()); | |
} | |
function smooth2() { | |
if (path.length <= 2) | |
return; | |
var ctx = $c.getContext('2d'); | |
ctx.beginPath(); | |
ctx.moveTo(path[0].x, path[0].y); | |
var a = path[0], | |
o = path[1], | |
b = centrosymmetry(o, path[2]), | |
c = smoothPoint(a, b, o); | |
ctx.quadraticCurveTo(c.x, c.y, o.x, o.y); | |
for (var i = 1; i + 2 < path.length; ++i) { | |
d = centrosymmetry(o, c); | |
a = o; | |
o = path[i + 1]; | |
b = centrosymmetry(o, path[i + 2]); | |
c = smoothPoint(a, b, o); | |
ctx.bezierCurveTo(d.x, d.y, c.x, c.y, o.x, o.y); | |
} | |
b = path[path.length - 1]; | |
o = path[path.length - 2]; | |
c = centrosymmetry(o, c); | |
ctx.quadraticCurveTo(c.x, c.y, b.x, b.y); | |
ctx.stroke(); | |
} | |
function smooth3() { | |
if (path.length <= 1) | |
return; | |
var ctx = $c.getContext('2d'); | |
ctx.strokeStyle = "black"; | |
ctx.beginPath(); | |
ctx.moveTo(path[0].x, path[0].y); | |
if (path.length == 2) { | |
ctx.lineTo(path[1].x, path[1].y); | |
} | |
else { | |
var a = path[0], | |
o = path[1], | |
b = centrosymmetry(o, path[2]), | |
v = new Vector(o, averagePoint(a, b)); | |
var la = (new Vector(o, a)).length, | |
lb = (new Vector(o, b)).length, | |
ls = la + lb; | |
var c = v.copy().mul(la / ls).end(); | |
var cs = [], ds = []; | |
cs.push(c); | |
ctx.quadraticCurveTo(c.x, c.y, o.x, o.y); | |
for (var i = 1; i + 2 < path.length; ++i) { | |
v = new Vector(o, centrosymmetry(o, averagePoint(a, b))); | |
d = v.copy().mul(lb / ls).end(); | |
a = o; | |
o = path[i + 1]; | |
b = centrosymmetry(o, path[i + 2]); | |
v = new Vector(o, averagePoint(a, b)); | |
la = lb; | |
lb = (new Vector(o, b)).length; | |
ls = la + lb; | |
c = v.copy().mul(la / ls).end(); | |
ds.push(d); cs.push(c); | |
ctx.bezierCurveTo(d.x, d.y, c.x, c.y, o.x, o.y); | |
} | |
v = new Vector(o, centrosymmetry(o, averagePoint(a, b))); | |
b = path[path.length - 1]; | |
o = path[path.length - 2]; | |
c = v.copy().mul(lb / ls).end(); | |
cs.push(c); | |
ctx.quadraticCurveTo(c.x, c.y, b.x, b.y); | |
} | |
ctx.stroke(); | |
/* | |
ctx.save(); | |
ctx.fillStyle = "red"; | |
ctx.strokeStyle = "red"; | |
for (var i = 0; i < path.length; ++i) | |
ctx.fillRect(path[i].x - 1, path[i].y - 1, 3, 3); | |
ctx.fillStyle = "yellow"; | |
ctx.strokeStyle = "yellow"; | |
for (var i = 1; i < cs.length - 1; ++i) | |
ctx.fillRect(cs[i].x - 1, cs[i].y - 1, 3, 3); | |
ctx.fillStyle = "orange"; | |
ctx.strokeStyle = "orange"; | |
for (var i = 0; i < ds.length; ++i) | |
ctx.fillRect(ds[i].x - 1, ds[i].y - 1, 3, 3); | |
ctx.restore(); | |
*/ | |
} | |
function endDrawing(e) { | |
e.preventDefault(); | |
if (!painting) | |
return; | |
if (!e.touches) | |
path.push(getp(e)); | |
if (!path.length) | |
return; | |
painting = false; | |
//$ps.innerHTML = ''; | |
//for (var i = 0; i < path.length; ++i) | |
// $ps.innerHTML += path[i].x + ',' + path[i].y + '<br>'; | |
//smooth1(); | |
smooth3(); | |
// cleaning | |
path = []; | |
redrawPath(); | |
} | |
$p.addEventListener('mousedown', startDrawing); | |
$p.addEventListener('mousemove', drawing); | |
$p.addEventListener('mouseup', endDrawing); | |
$p.addEventListener('touchstart', startDrawing); | |
$p.addEventListener('touchmove', drawing); | |
$p.addEventListener('touchend', endDrawing); | |
//$p.addEventListener('touchleave', endDrawing); | |
//$p.addEventListener('touchcancel', endDrawing); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There are 3 smoothing algorithms, and the final curves of any of them always pass through all points sampled.
smooth1 is the basic algorithm, and smooth2 is worse than smooth1. smooth3 is the best one.