Last active
July 24, 2019 06:26
-
-
Save wkpark/5223791 to your computer and use it in GitHub Desktop.
Handwriting canvas
This file contains hidden or 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
// | |
// Author: Won-Kyu Park wkpark at gmail.com (2013/02/07) | |
// | |
// License: GPL | |
// | |
// See also http://www.nogginbox.co.uk/blog/canvas-and-multi-touch | |
// by Richard Garside - www.nogginbox.co.uk (2010) | |
// | |
// ShortStrawJS corner test added (2013/02/20) | |
// http://www.lab4games.net/zz85/blog/2010/01/21/geeknotes-shortstrawjs-fast-and-simple-corner-detection/ | |
function hwrCanvas() | |
{ | |
this.lastPoints = Array(); | |
this.strokes = []; | |
this.stroke = [[], []]; | |
} | |
hwrCanvas.prototype.init = function(id) | |
{ | |
// Setup event handlers | |
var ctx; | |
var canvas = document.getElementById(id); | |
this.canvas = canvas; | |
this.feedback = null; | |
this.postAction = 'hwr.php'; | |
this.useShadow = false; | |
this.highquality = false; | |
this.strokeTimeout = 300; | |
this.canvasTimeout = 500; | |
this.timer = true; | |
this.saveImage = null; | |
// for training mode XXX (not fully implemented) | |
var self = this; | |
var control = document.getElementById('hwr-control'); | |
var train = document.getElementById('hwr-control-train'); | |
if (train) | |
train.onclick = function(e) { self.train(); }; | |
var reset = document.getElementById('hwr-control-reset'); | |
if (reset) { | |
this.timer = null; | |
reset.onclick = function(e) { self.reset(); }; | |
} | |
if (canvas.getContext) { | |
ctx = canvas.getContext('2d'); | |
this.ctx = ctx; | |
this.ctx.lineCap = "round"; | |
this.ctx.strokeStyle = "#4d90fe"; // "rgb(51, 102, 255)"; | |
this.ctx.lineWidth = 3; | |
this.min = { x:1000, y:1000 }; | |
this.max = { x:0, y:0 }; | |
this.canvas.onmousedown = function(e) { self.startDraw(e); } | |
this.canvas.onmouseup = function(e) { self.stopDraw(e); } | |
this.canvas.ontouchstart = function(e) { self.startDraw(e); } | |
this.canvas.ontouchend = function(e) { self.stopDraw(e); } | |
} | |
} | |
// Get the coordinates for a mouse or touch event | |
hwrCanvas.prototype.getCoords = function(e) | |
{ | |
var self = this; | |
if (e.offsetX) { // chrome, IE mouse event | |
return { x: e.offsetX, y: e.offsetY }; | |
} /* else if (e.layerX) { // firefox mouse | |
alert('b'); | |
return { x: e.layerX, y: e.layerY }; | |
} */ else { | |
// chrome touch-screen | |
return { x: e.pageX - self.canvas.offsetLeft, y: e.pageY - self.canvas.offsetTop }; | |
} | |
} | |
hwrCanvas.prototype.startDraw = function(e) | |
{ | |
var self = this; | |
// clear XXX | |
this.clear(); | |
this.ctx.beginPath(); | |
this.saveImage = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); | |
if (this.timer) | |
clearTimeout(this.timer); | |
if (e.touches) { | |
// Touch event | |
// multi touch supported but not used | |
for (var i = 0; i < e.touches.length; i++) { | |
this.lastPoints[i] = self.getCoords(e.touches[i]); | |
} | |
this.canvas.ontouchmove = function(e) { self.drawMouse(e); }; | |
} else { | |
// Mouse event | |
this.lastPoints[0] = this.getCoords(e); | |
this.canvas.onmousemove = function(e) { self.drawMouse(e); }; | |
this.moveTo(this.lastPoints[0]); | |
} | |
this.stroke[0].push(this.lastPoints[0].x); | |
this.stroke[1].push(this.lastPoints[0].y); | |
this.log("start\n"); | |
return false; | |
} | |
// Called whenever cursor position changes after drawing has started | |
hwrCanvas.prototype.stopDraw = function(e) | |
{ | |
this.canvas.onmousemove = null; | |
this.canvas.ontouchmove = null; | |
// restore old saved image to cleanup | |
this.ctx.putImageData(this.saveImage, 0, 0); | |
// redraw | |
this.ctx.save(); | |
this.ctx.strokeStyle = "#4d90fe"; //rgb(51, 102, 255)"; | |
this.ctx.fillStyle = "#4d90fe"; //rgb(51, 102, 255)"; | |
this.ctx.lineWidth = 4; | |
if (this.useShadow) { | |
this.ctx.shadowColor = "rgba(0, 0, 0, 0.3)"; | |
this.ctx.shadowBlur = 4; | |
this.ctx.shadowOffsetX = 1; | |
this.ctx.shadowOffsetY = 1; | |
} | |
if (this.stroke[0].length == 1) { | |
this.ctx.lineWidth = 3; | |
this.ctx.arc(this.lastPoints[0].x, this.lastPoints[0].y, 2, 0, 2 * Math.PI, true); | |
this.ctx.fill(); | |
this.stroke[0].push(this.lastPoints[0].x); | |
this.stroke[1].push(this.lastPoints[0].y); | |
} | |
this.ctx.stroke(); | |
this.ctx.restore(); | |
this.ctx.closePath(); | |
e.preventDefault(); | |
// new stroke | |
if (this.stroke[0].length > 1) | |
this.strokes.push(this.stroke); | |
this.stroke = [[], []]; | |
var self = this; | |
if (this.timer) { | |
clearTimeout(this.timer); | |
if (this.hwr_mode != 'timer') { | |
self.postStroke(); | |
} else { | |
this.timer = setTimeout(function() { self.postStroke(); }, self.strokeTimeout); | |
} | |
} | |
} | |
hwrCanvas.prototype.drawMouse = function(e) | |
{ | |
e.preventDefault(); | |
var self = this; | |
var p; | |
if (e.touches) { | |
// Touch Enabled | |
for (var i = 0; i < e.touches.length; i++) { | |
var p = self.getCoords(e.touches[i]); | |
this.drawLine(this.lastPoints[i], p); | |
this.lastPoints[i] = p; | |
} | |
p = self.getCoords(e.touches[0]); | |
} else { | |
// Not touch enabled | |
var p = this.getCoords(e); | |
this.lineTo(p); | |
this.lastPoints[0] = p; | |
} | |
//if ((p.x == lastPoints[0].x) && (p.y == lastPoints[0].y)) return; | |
this.stroke[0].push(this.lastPoints[0].x); | |
this.stroke[1].push(this.lastPoints[0].y); | |
this.minmax(this.lastPoints[0]); | |
// restore saved image to cleanup before draw | |
if (this.highquality) | |
this.ctx.putImageData(this.saveImage, 0, 0); | |
this.ctx.stroke(); | |
return true; | |
} | |
hwrCanvas.prototype.reset = function(e) | |
{ | |
//this.clear(); | |
this.strokes = []; | |
this.stroke = [[], []]; | |
this.min = { x:1000, y:1000 }; | |
this.max = { x:0, y:0 }; | |
} | |
hwrCanvas.prototype.train = function(e) | |
{ | |
var ch = document.getElementById('hwr-control-char'); | |
if (ch) { | |
var str; | |
if (ch.value) { | |
str = ch.value; | |
} else if (ch.firstChild && ch.firstChild.nodeType == 3) { | |
str = ch.firstChild.nodeValue; | |
} else { | |
str = null; | |
} | |
this.postStroke(str); | |
} | |
} | |
hwrCanvas.prototype.postStroke = function(ch) | |
{ | |
// XXX too small stroke ? | |
if (this.strokes.length == 0 && this.stroke[0].length < 2) { | |
this.stroke = [[], []]; | |
this.clear(); | |
return; | |
} | |
var self = this; | |
if (this.timer) { | |
clearTimeout(this.timer); | |
this.timer = setTimeout(function() { self.reset(); }, self.canvasTimeout); | |
} | |
//e.preventDefault(); | |
var r = this.getStrokes(); | |
// for corner testing | |
if (typeof shortStraw != 'undefined') { | |
for (var j = 0; j < this.strokes.length; j++) { | |
var s = []; | |
for (var k = 0; k < this.strokes[j][0].length; k++) { | |
var x = this.strokes[j][0][k]; | |
var y = this.strokes[j][1][k]; | |
s.push({x:x, y:y}); | |
} | |
this.analyze(s); | |
} | |
} | |
var xmlhttp = new XMLHttpRequest(); | |
xmlhttp.open('POST', this.postAction, true); | |
xmlhttp.onreadystatechange = function() { | |
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { | |
if (self.feedback) { | |
self.feedback(self, xmlhttp.responseText); | |
} | |
} | |
}; | |
if (ch != null) | |
r = ch + "\n" + r; | |
xmlhttp.send(r); | |
} | |
hwrCanvas.prototype.analyze = function(stroke) { | |
// Use short straw algorithm | |
var ss = new shortStraw(); | |
var corners = ss.corners(stroke); | |
// Lets draw shortStrawPoints in RED. | |
this.ctx.save(); | |
this.ctx.strokeStyle = "red"; | |
this.ctx.lineWidth = 2; | |
this.ctx.beginPath(); | |
this.ctx.moveTo(corners[0].x,corners[0].y); | |
for (var i in corners) { | |
this.ctx.lineTo(corners[i].x,corners[i].y); | |
} | |
this.ctx.stroke(); | |
this.ctx.restore(); | |
// Let's display the corner points | |
//$('#d2').val(JSON.stringify(corners)); | |
} | |
hwrCanvas.prototype.clear = function() | |
{ | |
this.ctx.save(); | |
// Use the identity matrix while clearing the canvas | |
this.ctx.setTransform(1, 0, 0, 1, 0, 0); | |
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
// Restore the transform | |
this.ctx.restore(); | |
// clear region to speed up | |
//this.ctx.clearRect(this.min.x - 20, this.min.y - 20, this.max.x + 20, this.max.y + 20); | |
} | |
/* simple log function XXX */ | |
hwrCanvas.prototype.log = function(str) | |
{ | |
if (this.debug) { | |
var log = document.getElementById('log'); | |
log.innerHTML = str; | |
} | |
} | |
hwrCanvas.prototype.minmax = function(p) | |
{ | |
if (p.x > this.max.x) | |
this.max.x = p.x; | |
if (p.x < this.min.x) | |
this.min.x = p.x; | |
if (p.y > this.max.y) | |
this.max.y = p.y; | |
if (p.y < this.min.y) | |
this.min.y = p.y; | |
} | |
/* for mouse to speed up */ | |
hwrCanvas.prototype.moveTo = function(p) | |
{ | |
this.ctx.moveTo(p.x, p.y); | |
} | |
hwrCanvas.prototype.lineTo = function(p) | |
{ | |
this.ctx.lineTo(p.x, p.y); | |
} | |
/* for multi-touch */ | |
hwrCanvas.prototype.drawLine = function(s, e) | |
{ | |
this.ctx.moveTo(s.x, s.y); | |
this.ctx.lineTo(e.x, e.y); | |
} | |
hwrCanvas.prototype.getStrokes = function() | |
{ | |
/* | |
var s = ":" + this.strokes.length + "\n"; | |
for (var j = 0; j < this.strokes.length; j++) { | |
var stroke = this.strokes[j]; | |
var points = []; | |
for (var i = 0; i < stroke[0].length; i++) { | |
points.push('(' + stroke[0][i] + ' ' + stroke[1][i] + ')'); | |
} | |
s+= stroke[0].length + ' ' + points.join(' ') + "\n"; | |
} | |
return s; | |
*/ | |
var s = []; | |
for (var j = 0; j < this.strokes.length; j++) { | |
var stroke = this.strokes[j]; | |
s.push('[' + stroke[0].join(',') + '],[' + stroke[1].join(',') + ']'); | |
} | |
return '[[' + s.join('],[') + ']]'; | |
} | |
/* end of hwrCanvas */ | |
/* | |
* vim:et:sts=4:sw=4: | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment