Skip to content

Instantly share code, notes, and snippets.

@wkpark
Last active July 24, 2019 06:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wkpark/5223791 to your computer and use it in GitHub Desktop.
Save wkpark/5223791 to your computer and use it in GitHub Desktop.
Handwriting canvas
//
// 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