scratch

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 83  →  ?path2? @ 84
/bower_components/drawingboard.js/dist/drawingboard.css
@@ -0,0 +1,88 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; }
 
.drawing-board-utils-hidden, .drawing-board-controls-hidden { display: none !important; }
 
.drawing-board { position: relative; display: block; }
 
.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; }
 
.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; }
 
.drawing-board-canvas { cursor: crosshair; z-index: 20; }
 
.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; }
 
.drawing-board-control > button, .drawing-board-control-colors-rainbows, .drawing-board-control-size .drawing-board-control-inner, .drawing-board-control-size-dropdown { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; overflow: hidden; border: none; background-color: #eee; padding: 2px 4px; border: 1px solid #ccc; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); height: 28px; }
 
.drawing-board-control > button { cursor: pointer; min-width: 28px; line-height: 14px; }
.drawing-board-control > button:hover, .drawing-board-control > button:focus { background-color: #ddd; }
.drawing-board-control > button:active, .drawing-board-control > button.active { box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); background-color: #ddd; }
.drawing-board-control > button[disabled] { color: gray; }
.drawing-board-control > button[disabled]:hover, .drawing-board-control > button[disabled]:focus, .drawing-board-control > button[disabled]:active, .drawing-board-control > button[disabled].active { background-color: #eee; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); cursor: default; }
 
.drawing-board-controls { margin: 0 auto; text-align: center; font-size: 0; display: table; border-spacing: 9.33333px 0; position: relative; min-height: 28px; }
.drawing-board-controls[data-align="left"] { margin: 0; left: -9.33333px; }
.drawing-board-controls[data-align="right"] { margin: 0 0 0 auto; right: -9.33333px; }
.drawing-board-canvas-wrapper + .drawing-board-controls, .drawing-board-controls + .drawing-board-canvas-wrapper { margin-top: 5px; }
 
.drawing-board-controls-hidden { height: 0; min-height: 0; padding: 0; margin: 0; border: 0; }
 
.drawing-board-control { display: table-cell; border-collapse: separate; vertical-align: middle; font-size: 16px; height: 100%; }
 
.drawing-board-control-inner { position: relative; height: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
 
.drawing-board-control > button { margin: 0; vertical-align: middle; }
 
.drawing-board-control-colors { font-size: 0; line-height: 0; }
 
.drawing-board-control-colors-current { border: 1px solid #ccc; cursor: pointer; display: inline-block; width: 26px; height: 26px; }
 
.drawing-board-control-colors-rainbows { display: inline-block; margin-left: 5px; position: absolute; left: 0; top: 33px; margin-left: 0; z-index: 100; width: 250px; height: auto; padding: 4px; }
 
.drawing-board-control-colors-rainbow { height: 18px; }
 
.drawing-board-control-colors-picker:first-child { margin-right: 5px; }
 
.drawing-board-control-colors-picker { display: inline-block; width: 18px; height: 18px; cursor: pointer; }
 
.drawing-board-control-colors-picker[data-color="rgba(255, 255, 255, 1)"] { width: 16px; height: 17px; border: 1px solid #ccc; border-bottom: none; }
 
.drawing-board-control-colors-picker:hover { width: 16px; height: 16px; border: 1px solid #555; }
 
.drawing-board-control-drawingmode > button { margin-right: 2px; }
.drawing-board-control-drawingmode > button:last-child { margin-right: 0; }
 
.drawing-board-control-drawingmode-pencil-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe9JREFUeNpiZAACVlFRBhYREQZcQPnbNwa3N28YlL5+ZfgLFfvPwGD9m4FhIgsDHuAO0gTUDNKIBvyBmqt/MTDMY8Gl0f31azD7L6oUIxCnAzWmAPHBfwwM01AMUAV6JfPQIVwOYgVqqPnFyOjz6///O38YGKpAgmAD1OXlGdTk5PD5hgeouZudj8/uy9evP/78/dsFFPsJNiAoKIiBABAHap4oLi9v8fTNm48//v7NBwbgWZgkE7rqt8DY+A8JZRBW+cfIuEDT0NDlzadP3z98/doPFDuCrB7TAGFhBqCNIGwM9OcKUzs7+xdv3355+f79VqDYAiTDwZgJh7ONgYpnOvn4GL949erT7UePdgL5JVCD4fgBLBBxaX74+PG789evnwby0/8jKXgExIeB+CG6Af///1e9Ki9vFSAkZPzoyZPPJy9evA9MB77/sWiEARZkzV+/fvXYtGnTpG3btj28EBT0BqjZ5D8OjXCwPksUhA1Wpggf/PHjx/9169Y9EBERaUlgZmaIAcrLE4rk5sIqBqDmlefnRPzfWGX5EaSZm5ubgRloADGA5QZ3RgK7gESY4PMNn9ZtObPpzZvfU4DiYkiB/RcHG+S7fyxAMH/lFU2GOZd2bLx18/cEUMoD4j9I+DcS/RtJHGTYf4AAAwAxaOMYHjxKFwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-pencil-button:before { content: ""; display: block; width: 0; height: 100%; }
 
.drawing-board-control-drawingmode-eraser-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkpJREFUeNp0kk+IElEcx39vFBc9+OfQRTAwzFt4CaYOKStj6MoeculStzoIQSB4kCVckmDx4iGCXWYJIqjoVOzO1l4qT1F7WfBWHvxzDPyTB3XUmXn93suRybUffHmP997n9/cRsFgwGARJkiAcDsPlwgEIeEZQAhCRAkgAlOD6SQP4rgMFDWVnYCAQgFgsBqFQCBwOByzZNQOotPHx1RNCCCipu6bfb+zSnslkeOQVILPrBkAirbws9btdTEWAzZPXpfepOzaeGMBXwe/3w3+MwTc3Dl+UeghTiskbBvR6Pbh18mZHB0jjmxvCKhIfR37s3r+Sevf8ca/T4TBF2HTSODuDxP7uNjrZFFbBk8lEzOVyspa4ykGYw2zfbTb/7ilvok1YhlVVFfP5vDydTkHXdXDdlhZOOnPY4/HA0YPtp3h6LFjh8XgsFgoFGTPgsKm1zDr8ajTQh8Fh5eGjZzjGI8yjKlgjF4tFGdd/YKYmRja24hw+zu3sYe2HiH3hYzQjl8tleTQanWtou93G6Qngdrth6+1+9h6hTULJZ/PeziJXKhV5OByeg1ut1gJOp9NZTdNOcQ419ot+ggp1qoLdBFmqVmNpm3A8Huewy+Wq1RH8QH9zmBlJJpMRdCIqiiIPBgN+2MCGsW/r8/kgGo1m0fmpzWarseayHlmNeL1eFiWC0cRqtSr3+/3FpSiKHMZtjU1glbFyfKgLTqfzEka9OJvNeDnzz1JnCaFmqOl8ZdJY1SiDOXCiXKg1NtG5DIt0y6ov3dE/AgwAENFWYYLj4mYAAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-eraser-button:before { content: ""; display: block; width: 0; height: 100%; }
 
.drawing-board-control-drawingmode-filler-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnNJREFUeNp0k0trE1EUx89MJpNJooYYXBgDNtCKdRPwlbqoCKUtaNVNA0Uo7UbMxoVPEARTXEi+QWfnwn6DEAlEkrSLttTGRiULEQlJ8yChmbzI++E50yTUJA78uMy953/u/557LmOz2WDEZ2m1WrckSRJSqdR2tVrdHQyYebwHtVoNuFHqTqczhQnWKaBYLDoKhcIuzgHDMKBSqeD20qd+LNdsNocSoFhRr9ctpVLJigl4xIIJQizLAmG4cAPa7bYcy9Iug5TL5UYikbD6/X7Rbre/IUcYe3WUW5ZsnQQzW9LpNOPz+UQc5aBM5mgdh7vI9FCCAesW2tnr9YqZTAby+bw8f3AQRP6853n+Ph5hemSCntjj8YjZbFYWx2IxeS2RSEMwuA87O79eqdXquVolK+GxnP0EPbHb7RZJSGABIR6PA11zJHKIR2MhHA5DIPDj7eH3j95KpfK60Wg8Yntil8slkqgnpioLghacTidoNDpEC3q9HnheCc3s1jZeLcW943pirPw/4lKpBkqlDubnl/riycnLsLy88EKj0fhzuRyZv8RFo1E6wpBYkiqy7Z54YmIcVlYeyOKC4mYwJ0nHRaQuM5vNT6hB/iceG7sIq6sPnwmC4MerDkby40AOCCoiddie1Wp92W7zQ2KTyQSLizNP8T0EsPLBbxEDnCj0GkM2qIEwyZRCobizsfH5A1ZXFhuN52F29vpz3HkL574mk8lj24Y5wsHkvjjoX0BOIWc5jruHzbK2ufmzEwpFO3jnDhQv4JoROYdoERVyGjEgZ8iBDlF3FzXo4go6utZ9lftY4N/dXisjR0i1G0ublv8KMAA0ZoUlicxrhwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-drawingmode-filler-button:before { content: ""; display: block; width: 0; height: 100%; }
 
.drawing-board-control-navigation > button { font-family: Helvetica, Arial, sans-serif; font-size: 14px; font-weight: bold; margin-right: 2px; }
.drawing-board-control-navigation > button:last-child { margin-right: 0; }
 
.drawing-board-control-size[data-drawing-board-type="range"] .drawing-board-control-inner { width: 75px; }
.drawing-board-control-size[data-drawing-board-type="dropdown"] .drawing-board-control-inner { overflow: visible; }
 
.drawing-board-control-size-range-input { position: relative; width: 100%; z-index: 100; margin: 0; padding: 0; border: 0; }
 
.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span, .drawing-board-control-size-dropdown span { display: block; background: #333; opacity: .8; }
 
.drawing-board-control-size-range-current { display: inline-block; opacity: .15; position: absolute; pointer-events: none; left: 50%; top: 50%; z-index: 50; }
 
.drawing-board-control-size-dropdown-current { display: block; height: 100%; width: 40px; overflow: hidden; position: relative; }
.drawing-board-control-size-dropdown-current span { position: absolute; left: 50%; top: 50%; }
 
.drawing-board-control-size-dropdown { position: absolute; left: -6px; top: 33px; height: auto; list-style-type: none; margin: 0; padding: 0; z-index: 100; }
.drawing-board-control-size-dropdown li { display: block; padding: 4px; margin: 3px 0; min-height: 16px; }
.drawing-board-control-size-dropdown li:hover { background: #ccc; }
.drawing-board-control-size-dropdown span { margin: 0 auto; }
 
.drawing-board-control-download-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNqMkr9PU1EUx7/vR1tQ3yu10hAmTawtBSYSy2YccFOcnDQm/gewOLnj5GYMg4sdXFxkMca4OBAwdUBe/ZkIGFp+9tHXvh/3/fTcAm01aLzJybnn3nM+95tzrnDl6Tb+sibuTmWUWj3C6/Juk+LySUmyvt0FCKKA02ryOCy6LBiu15ngMbZ5DDCNBqIw6gKM+n4nECUJru3glKry8CpjQaHVYmC2rVH82DIMMMdGGARdwJ+SPNdFS9chx+MXDNMp/NzagWNatk/nQU/hiYAoih6FYTBCBs9zUXMCbAhx2OYOv351lPOJ3EwH4LteL6Dcp/Rfu3FrstDyIizt+agpaYxNDU0M9gl4v7Ck+TYrCYLQqZHUyTtdQBiutPSGUflczSXHs5lVKwZdSOBMvwztxVvN0RtzsiyXBFHsAvL5PBSnCpXV2getILFiE2SjspYbuZzPiDSZ2vOXmlvX5yQqTmMfg9ZXqtls1wnT09OHEyAq0aFLg/gSXsSWq9wWk+p9PrCoYTwcijdLOfE7UsEufN9HGIYnT4EnTGIXe1KqtNNIvuNnGamxfi7SgQD/nIJCTbzOPQ/SQh1pud7T4M6W/8qFIw/5WAr5m7Ozsw9UVc069Fls2yJzSC5/lnc9RhaHZVnfSqUnEgXP2oBqtYqBgYG2+mKxmOVADnAcB4yxHgD1RzehKKns/LyV4gUHBweQy+UyRkdH6UKJ6fQDFxcXoWkaXJeRuTgUGCdLQJ9bx72lGZimGWs2m+083oN+2iiFQiGxvLy8RrDzudyltgrG3N8U2G8CrPz4sGYYRqJSqWR4H/jNWbJhUjAWi8XG8R/L87yPpGCVttVfAgwAVpZR+8tZC08AAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; }
.drawing-board-control-download-button:before { content: ""; display: block; width: 0; height: 100%; }
/bower_components/drawingboard.js/dist/drawingboard.js
@@ -0,0 +1,1374 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
(function() {
'use strict';
 
/**
* SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
*
* options are: {
* * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
* * `maxLength` : the maximum number of items in history
* * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
* }
*
*/
var SimpleUndo = function(options) {
var settings = options ? options : {};
var defaultOptions = {
provider: function() {
throw new Error("No provider!");
},
maxLength: 30,
onUpdate: function() {}
};
this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
this.initialItem = null;
this.clear();
};
 
function truncate (stack, limit) {
while (stack.length > limit) {
stack.shift();
}
}
 
SimpleUndo.prototype.initialize = function(initialItem) {
this.stack[0] = initialItem;
this.initialItem = initialItem;
};
 
 
SimpleUndo.prototype.clear = function() {
this.stack = [this.initialItem];
this.position = 0;
this.onUpdate();
};
 
SimpleUndo.prototype.save = function() {
this.provider(function(current) {
truncate(this.stack, this.maxLength);
this.position = Math.min(this.position,this.stack.length - 1);
this.stack = this.stack.slice(0, this.position + 1);
this.stack.push(current);
this.position++;
this.onUpdate();
}.bind(this));
};
 
SimpleUndo.prototype.undo = function(callback) {
if (this.canUndo()) {
var item = this.stack[--this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
 
SimpleUndo.prototype.redo = function(callback) {
if (this.canRedo()) {
var item = this.stack[++this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
 
SimpleUndo.prototype.canUndo = function() {
return this.position > 0;
};
 
SimpleUndo.prototype.canRedo = function() {
return this.position < this.count();
};
 
SimpleUndo.prototype.count = function() {
return this.stack.length - 1; // -1 because of initial item
};
 
 
 
 
 
//exports
// node module
if (typeof module != 'undefined') {
module.exports = SimpleUndo;
}
 
// browser global
if (typeof window != 'undefined') {
window.SimpleUndo = SimpleUndo;
}
 
})();
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
 
 
DrawingBoard.Utils = {};
 
/*!
* Tim (lite)
* github.com/premasagar/tim
*//*
A tiny, secure JavaScript micro-templating script.
*/
DrawingBoard.Utils.tpl = (function(){
"use strict";
 
var start = "{{",
end = "}}",
path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
undef;
 
return function(template, data){
// Merge data into the template string
return template.replace(pattern, function(tag, token){
var path = token.split("."),
len = path.length,
lookup = data,
i = 0;
 
for (; i < len; i++){
lookup = lookup[path[i]];
 
// Property not found
if (lookup === undef){
throw "tim: '" + path[i] + "' not found in " + tag;
}
 
// Return the required value
if (i === len - 1){
return lookup;
}
}
});
};
}());
 
/**
* https://github.com/jeromeetienne/microevent.js
* MicroEvent - to make any js object an event emitter (server or browser)
*
* - pure javascript - server compatible, browser compatible
* - dont rely on the browser doms
* - super simple - you get it immediatly, no mistery, no magic involved
*
* - create a MicroEventDebug with goodies to debug
* - make it safer to use
*/
DrawingBoard.Utils.MicroEvent = function(){};
 
DrawingBoard.Utils.MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
 
//I know.
DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
withPadding = !!withPadding || true;
withMargin = !!withMargin || false;
var width = 0,
props;
if (direction == "width") {
props = ['border-left-width', 'border-right-width'];
if (withPadding) props.push('padding-left', 'padding-right');
if (withMargin) props.push('margin-left', 'margin-right');
} else {
props = ['border-top-width', 'border-bottom-width'];
if (withPadding) props.push('padding-top', 'padding-bottom');
if (withMargin) props.push('margin-top', 'margin-bottom');
}
for (var i = props.length - 1; i >= 0; i--)
width += parseInt($el.css(props[i]).replace('px', ''), 10);
return width;
};
 
DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
};
 
DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
};
 
DrawingBoard.Utils.isColor = function(string) {
if (!string || !string.length) return false;
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
};
 
/**
* Packs an RGB color into a single integer.
*/
DrawingBoard.Utils.RGBToInt = function(r, g, b) {
var c = 0;
c |= (r & 255) << 16;
c |= (g & 255) << 8;
c |= (b & 255);
return c;
};
 
/**
* Returns informations on the pixel located at (x,y).
*/
DrawingBoard.Utils.pixelAt = function(image, x, y) {
var i = (y * image.width + x) * 4;
var c = DrawingBoard.Utils.RGBToInt(
image.data[i],
image.data[i + 1],
image.data[i + 2]
);
 
return [
i, // INDEX
x, // X
y, // Y
c // COLOR
];
};
 
/**
* Compares two colors with the given tolerance (between 0 and 255).
*/
DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
if (tolerance === 0) {
return (a === b);
}
 
var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
ga = (a >> 8) & 255, gb = (b >> 8) & 255,
ba = a & 255, bb = b & 255;
 
return (Math.abs(ra - rb) <= tolerance)
&& (Math.abs(ga - gb) <= tolerance)
&& (Math.abs(ba - bb) <= tolerance);
};
 
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
}());
 
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
 
/**
* pass the id of the html element to put the drawing board into
* and some options : {
* controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
* instead of simple strings, you can pass an object to define a control opts
* ie ['Color', { Navigation: { reset: false }}]
* controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
* background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
* color: pencil color ("#000000" by default)
* size: pencil size (3 by default)
* webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
* droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it,
* errorMessage: html string to put in the board's element on browsers that don't support canvas.
* stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default
* }
*/
DrawingBoard.Board = function(id, opts) {
this.opts = this.mergeOptions(opts);
 
this.ev = new DrawingBoard.Utils.MicroEvent();
 
this.id = id;
this.$el = $(document.getElementById(id));
if (!this.$el.length)
return false;
 
var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
else tpl = '<div class="drawing-board-controls"></div>' + tpl;
 
this.$el.addClass('drawing-board').append(tpl);
this.dom = {
$canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
$canvas: this.$el.find('.drawing-board-canvas'),
$cursor: this.$el.find('.drawing-board-cursor'),
$controls: this.$el.find('.drawing-board-controls')
};
 
$.each(['left', 'right', 'center'], $.proxy(function(n, val) {
if (this.opts.controlsPosition.indexOf(val) > -1) {
this.dom.$controls.attr('data-align', val);
return false;
}
}, this));
 
this.canvas = this.dom.$canvas.get(0);
this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
this.color = this.opts.color;
 
if (!this.ctx) {
if (this.opts.errorMessage)
this.$el.html(this.opts.errorMessage);
return false;
}
 
this.storage = this._getStorage();
 
this.initHistory();
//init default board values before controls are added (mostly pencil color and size)
this.reset({ webStorage: false, history: false, background: false });
//init controls (they will need the default board values to work like pencil color and size)
this.initControls();
//set board's size after the controls div is added
this.resize();
//reset the board to take all resized space
this.reset({ webStorage: false, history: false, background: true });
this.restoreWebStorage();
this.initDropEvents();
this.initDrawEvents();
};
 
 
 
DrawingBoard.Board.defaultOpts = {
controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
controlsPosition: "top left",
color: "#000000",
size: 1,
background: "#fff",
eraserColor: "background",
fillTolerance: 100,
fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
webStorage: 'session',
droppable: false,
enlargeYourContainer: false,
errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true
};
 
 
 
DrawingBoard.Board.prototype = {
 
mergeOptions: function(opts) {
opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
if (!opts.background && opts.eraserColor === "background")
opts.eraserColor = "transparent";
return opts;
},
 
/**
* Canvas reset/resize methods: put back the canvas to its default values
*
* depending on options, can set color, size, background back to default values
* and store the reseted canvas in webstorage and history queue
*
* resize values depend on the `enlargeYourContainer` option
*/
 
reset: function(opts) {
opts = $.extend({
color: this.opts.color,
size: this.opts.size,
webStorage: true,
history: true,
background: false
}, opts);
 
this.setMode('pencil');
 
if (opts.background) {
this.resetBackground(this.opts.background, $.proxy(function() {
if (opts.history) this.saveHistory();
}, this));
}
 
if (opts.color) this.setColor(opts.color);
if (opts.size) this.ctx.lineWidth = opts.size;
 
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
// this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
 
if (opts.webStorage) this.saveWebStorage();
 
// if opts.background we already dealt with the history
if (opts.history && !opts.background) this.saveHistory();
 
this.blankCanvas = this.getImg();
 
this.ev.trigger('board:reset', opts);
},
 
resetBackground: function(background, callback) {
background = background || this.opts.background;
 
var bgIsColor = DrawingBoard.Utils.isColor(background);
var prevMode = this.getMode();
this.setMode('pencil');
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
if (bgIsColor) {
this.ctx.fillStyle = background;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.history.initialize(this.getImg());
if (callback) callback();
} else if (background)
this.setImg(background, {
callback: $.proxy(function() {
this.history.initialize(this.getImg());
if (callback) callback();
}, this)
});
this.setMode(prevMode);
},
 
resize: function() {
this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
 
var canvasWidth, canvasHeight;
var widths = [
this.$el.width(),
DrawingBoard.Utils.boxBorderWidth(this.$el),
DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
];
var heights = [
this.$el.height(),
DrawingBoard.Utils.boxBorderHeight(this.$el),
this.dom.$controls.height(),
DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
];
var that = this;
var sum = function(values, multiplier) { //make the sum of all array values
multiplier = multiplier || 1;
var res = values[0];
for (var i = 1; i < values.length; i++) {
res = res + (values[i]*multiplier);
}
return res;
};
var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
 
if (this.opts.enlargeYourContainer) {
canvasWidth = this.$el.width();
canvasHeight = this.$el.height();
 
this.$el.width( sum(widths) );
this.$el.height( sum(heights) );
} else {
canvasWidth = sub(widths);
canvasHeight = sub(heights);
}
 
this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
 
this.dom.$canvas.css('width', canvasWidth + 'px');
this.dom.$canvas.css('height', canvasHeight + 'px');
 
this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;
},
 
 
 
/**
* Controls:
* the drawing board can has various UI elements to control it.
* one control is represented by a class in the namespace DrawingBoard.Control
* it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
*
*/
 
initControls: function() {
this.controls = [];
if (!this.opts.controls.length || !DrawingBoard.Control) return false;
for (var i = 0; i < this.opts.controls.length; i++) {
var c = null;
if (typeof this.opts.controls[i] == "string")
c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
else if (typeof this.opts.controls[i] == "object") {
for (var controlName in this.opts.controls[i]) break;
c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
}
if (c) {
this.addControl(c);
}
}
},
 
//add a new control or an existing one at the position you want in the UI
//to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }"
//the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
addControl: function(control, optsOrPos, pos) {
if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
return false;
 
var opts = typeof optsOrPos == "object" ? optsOrPos : {};
pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
 
if (typeof control == "string")
control = new window['DrawingBoard']['Control'][control](this, opts);
 
if (pos)
this.dom.$controls.children().eq(pos).before(control.$el);
else
this.dom.$controls.append(control.$el);
 
if (!this.controls)
this.controls = [];
this.controls.push(control);
this.dom.$controls.removeClass('drawing-board-controls-hidden');
},
 
 
 
/**
* History methods: undo and redo drawed lines
*/
 
initHistory: function() {
this.history = new SimpleUndo({
maxLength: 30,
provider: $.proxy(function(done) {
done(this.getImg());
}, this),
onUpdate: $.proxy(function() {
this.ev.trigger('historyNavigation');
}, this)
});
},
 
saveHistory: function() {
this.history.save();
},
 
restoreHistory: function(image) {
this.setImg(image, {
callback: $.proxy(function() {
this.saveWebStorage();
}, this)
});
},
 
goBackInHistory: function() {
this.history.undo($.proxy(this.restoreHistory, this));
},
 
goForthInHistory: function() {
this.history.redo($.proxy(this.restoreHistory, this));
},
 
/**
* Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
*/
 
setImg: function(src, opts) {
opts = $.extend({
stretch: this.opts.stretchImg,
callback: null
}, opts);
 
var ctx = this.ctx;
var img = new Image();
var oldGCO = ctx.globalCompositeOperation;
img.onload = function() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
 
if (opts.stretch) {
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
} else {
ctx.drawImage(img, 0, 0);
}
 
ctx.globalCompositeOperation = oldGCO;
 
if (opts.callback) {
opts.callback();
}
};
img.src = src;
},
 
getImg: function() {
return this.canvas.toDataURL("image/png");
},
 
downloadImg: function() {
var img = this.getImg();
img = img.replace("image/png", "image/octet-stream");
window.location.href = img;
},
 
 
 
/**
* WebStorage handling : save and restore to local or session storage
*/
 
saveWebStorage: function() {
if (window[this.storage]) {
window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
}
},
 
restoreWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
}
},
 
clearWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
window[this.storage].removeItem('drawing-board-' + this.id);
this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
}
},
 
_getStorage: function() {
if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
return this.opts.webStorage + 'Storage';
},
 
 
 
/**
* Drop an image on the canvas to draw on it
*/
 
initDropEvents: function() {
if (!this.opts.droppable)
return false;
 
this.dom.$canvas.on('dragover dragenter drop', function(e) {
e.stopPropagation();
e.preventDefault();
});
 
this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
},
 
_onCanvasDrop: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var files = e.dataTransfer.files;
if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
return false;
var fr = new FileReader();
fr.readAsDataURL(files[0]);
fr.onload = $.proxy(function(ev) {
this.setImg(ev.target.result, {
callback: $.proxy(function() {
this.saveHistory();
}, this)
});
this.ev.trigger('board:imageDropped', ev.target.result);
this.ev.trigger('board:userAction');
}, this);
},
 
 
 
/**
* set and get current drawing mode
*
* possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
*/
 
setMode: function(newMode, silent) {
silent = silent || false;
newMode = newMode || 'pencil';
 
this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
 
if (this.opts.eraserColor === "transparent")
this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
else {
if (newMode === "eraser") {
if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
this.ctx.strokeStyle = this.opts.background;
else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
this.ctx.strokeStyle = this.opts.eraserColor;
} else if (!this.mode || this.mode === "eraser") {
this.ctx.strokeStyle = this.color;
}
 
if (newMode === "filler")
this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
}
this.mode = newMode;
if (!silent)
this.ev.trigger('board:mode', this.mode);
},
 
getMode: function() {
return this.mode || "pencil";
},
 
setColor: function(color) {
var that = this;
color = color || this.color;
if (!DrawingBoard.Utils.isColor(color))
return false;
this.color = color;
if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
var setStrokeStyle = function(mode) {
if (mode !== "eraser")
that.strokeStyle = that.color;
that.ev.unbind('board:mode', setStrokeStyle);
};
this.ev.bind('board:mode', setStrokeStyle);
} else
this.ctx.strokeStyle = this.color;
},
 
/**
* Fills an area with the current stroke color.
*/
fill: function(e) {
if (this.getImg() === this.blankCanvas) {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.ctx.fillStyle = this.color;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
return;
}
 
var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
 
// constants identifying pixels components
var INDEX = 0, X = 1, Y = 2, COLOR = 3;
 
// target color components
var stroke = this.ctx.strokeStyle;
var r = parseInt(stroke.substr(1, 2), 16);
var g = parseInt(stroke.substr(3, 2), 16);
var b = parseInt(stroke.substr(5, 2), 16);
 
// starting point
var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10));
var startColor = start[COLOR];
var tolerance = this.opts.fillTolerance;
var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38
 
// no need to continue if starting and target colors are the same
if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance))
return;
 
// pixels to evaluate
var queue = [start];
 
// loop vars
var pixel, x, y;
var maxX = img.width - 1;
var maxY = img.height - 1;
 
function updatePixelColor(pixel) {
img.data[pixel[INDEX]] = r;
img.data[pixel[INDEX] + 1] = g;
img.data[pixel[INDEX] + 2] = b;
}
 
while ((pixel = queue.pop())) {
if (useHack)
updatePixelColor(pixel);
 
if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) {
if (!useHack)
updatePixelColor(pixel);
if (pixel[X] > 0) // west
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
if (pixel[X] < maxX) // east
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
if (pixel[Y] > 0) // north
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
if (pixel[Y] < maxY) // south
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
}
}
 
this.ctx.putImageData(img, 0, 0);
},
 
 
/**
* Drawing handling, with mouse or touch
*/
 
initDrawEvents: function() {
this.isDrawing = false;
this.isMouseHovering = false;
this.coords = {};
this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
 
this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
this._onInputStart(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
this._onInputMove(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mousemove', $.proxy(function(e) {
 
}, this));
 
this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
this._onInputStop(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mouseover', $.proxy(function(e) {
this._onMouseOver(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mouseout', $.proxy(function(e) {
this._onMouseOut(e, this._getInputCoords(e) );
 
}, this));
 
$('body').on('mouseup touchend', $.proxy(function(e) {
this.isDrawing = false;
}, this));
 
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
},
 
draw: function() {
//if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
//todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
//we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) });
this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
this.dom.$cursor.removeClass('drawing-board-utils-hidden');
} else {
this.dom.$cursor.addClass('drawing-board-utils-hidden');
}
 
if (this.isDrawing) {
var currentMid = this._getMidInputCoords(this.coords.current);
this.ctx.beginPath();
this.ctx.moveTo(currentMid.x, currentMid.y);
this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
this.ctx.stroke();
 
this.coords.old = this.coords.current;
this.coords.oldMid = currentMid;
}
 
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
},
 
_onInputStart: function(e, coords) {
this.coords.current = this.coords.old = coords;
this.coords.oldMid = this._getMidInputCoords(coords);
this.isDrawing = true;
 
if (!window.requestAnimationFrame) this.draw();
 
this.ev.trigger('board:startDrawing', {e: e, coords: coords});
e.stopPropagation();
e.preventDefault();
},
 
_onInputMove: function(e, coords) {
this.coords.current = coords;
this.ev.trigger('board:drawing', {e: e, coords: coords});
 
if (!window.requestAnimationFrame) this.draw();
 
e.stopPropagation();
e.preventDefault();
},
 
_onInputStop: function(e, coords) {
if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
this.isDrawing = false;
 
this.saveWebStorage();
this.saveHistory();
 
this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
this.ev.trigger('board:userAction');
e.stopPropagation();
e.preventDefault();
}
},
 
_onMouseOver: function(e, coords) {
this.isMouseHovering = true;
this.coords.old = this._getInputCoords(e);
this.coords.oldMid = this._getMidInputCoords(this.coords.old);
 
this.ev.trigger('board:mouseOver', {e: e, coords: coords});
},
 
_onMouseOut: function(e, coords) {
this.isMouseHovering = false;
 
this.ev.trigger('board:mouseOut', {e: e, coords: coords});
},
 
_getInputCoords: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var
rect = this.canvas.getBoundingClientRect(),
width = this.dom.$canvas.width(),
height = this.dom.$canvas.height()
;
var x, y;
if (e.touches && e.touches.length == 1) {
x = e.touches[0].pageX;
y = e.touches[0].pageY;
} else {
x = e.pageX;
y = e.pageY;
}
x = x - this.dom.$canvas.offset().left;
y = y - this.dom.$canvas.offset().top;
x *= (width / rect.width);
y *= (height / rect.height);
return {
x: x,
y: y
};
},
 
_getMidInputCoords: function(coords) {
return {
x: this.coords.old.x + coords.x>>1,
y: this.coords.old.y + coords.y>>1
};
}
};
 
DrawingBoard.Control = function(drawingBoard, opts) {
this.board = drawingBoard;
this.opts = $.extend({}, this.defaults, opts);
 
this.$el = $(document.createElement('div')).addClass('drawing-board-control');
if (this.name)
this.$el.addClass('drawing-board-control-' + this.name);
 
this.board.ev.bind('board:reset', $.proxy(this.onBoardReset, this));
 
this.initialize.apply(this, arguments);
return this;
};
 
DrawingBoard.Control.prototype = {
 
name: '',
 
defaults: {},
 
initialize: function() {
 
},
 
addToBoard: function() {
this.board.addControl(this);
},
 
onBoardReset: function(opts) {
 
}
 
};
 
//extend directly taken from backbone.js
DrawingBoard.Control.extend = function(protoProps, staticProps) {
var parent = this;
var child;
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
child = function(){ return parent.apply(this, arguments); };
}
$.extend(child, parent, staticProps);
var Surrogate = function(){ this.constructor = child; };
Surrogate.prototype = parent.prototype;
child.prototype = new Surrogate();
if (protoProps) $.extend(child.prototype, protoProps);
child.__super__ = parent.prototype;
return child;
};
DrawingBoard.Control.Color = DrawingBoard.Control.extend({
name: 'colors',
 
initialize: function() {
this.initTemplate();
 
var that = this;
this.$el.on('click', '.drawing-board-control-colors-picker', function(e) {
var color = $(this).attr('data-color');
that.board.setColor(color);
that.$el.find('.drawing-board-control-colors-current')
.css('background-color', color)
.attr('data-color', color);
 
that.board.ev.trigger('color:changed', color);
that.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
 
e.preventDefault();
});
 
this.$el.on('click', '.drawing-board-control-colors-current', function(e) {
that.$el.find('.drawing-board-control-colors-rainbows').toggleClass('drawing-board-utils-hidden');
e.preventDefault();
});
 
$('body').on('click', function(e) {
var $target = $(e.target);
var $relatedButton = $target.hasClass('drawing-board-control-colors-current') ? $target : $target.closest('.drawing-board-control-colors-current');
var $myButton = that.$el.find('.drawing-board-control-colors-current');
var $popup = that.$el.find('.drawing-board-control-colors-rainbows');
if ( (!$relatedButton.length || $relatedButton.get(0) !== $myButton.get(0)) && !$popup.hasClass('drawing-board-utils-hidden') )
$popup.addClass('drawing-board-utils-hidden');
});
},
 
initTemplate: function() {
var tpl = '<div class="drawing-board-control-inner">' +
'<div class="drawing-board-control-colors-current" style="background-color: {{color}}" data-color="{{color}}"></div>' +
'<div class="drawing-board-control-colors-rainbows">{{rainbows}}</div>' +
'</div>';
var oneColorTpl = '<div class="drawing-board-control-colors-picker" data-color="{{color}}" style="background-color: {{color}}"></div>';
var rainbows = '';
$.each([0.75, 0.5, 0.25], $.proxy(function(key, val) {
var i = 0;
var additionalColor = null;
rainbows += '<div class="drawing-board-control-colors-rainbow">';
if (val == 0.25) additionalColor = this._rgba(0, 0, 0, 1);
if (val == 0.5) additionalColor = this._rgba(150, 150, 150, 1);
if (val == 0.75) additionalColor = this._rgba(255, 255, 255, 1);
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: additionalColor.toString() });
while (i <= 330) {
rainbows += DrawingBoard.Utils.tpl(oneColorTpl, {color: this._hsl2Rgba(this._hsl(i-60, 1, val)).toString() });
i+=30;
}
rainbows += '</div>';
}, this));
 
this.$el.append( $( DrawingBoard.Utils.tpl(tpl, {color: this.board.color, rainbows: rainbows }) ) );
this.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden');
},
 
onBoardReset: function(opts) {
this.board.setColor(this.$el.find('.drawing-board-control-colors-current').attr('data-color'));
},
 
_rgba: function(r, g, b, a) {
return { r: r, g: g, b: b, a: a, toString: function() { return "rgba(" + r +", " + g + ", " + b + ", " + a + ")"; } };
},
 
_hsl: function(h, s, l) {
return { h: h, s: s, l: l, toString: function() { return "hsl(" + h +", " + s*100 + "%, " + l*100 + "%)"; } };
},
 
_hex2Rgba: function(hex) {
var num = parseInt(hex.substring(1), 16);
return this._rgba(num >> 16, num >> 8 & 255, num & 255, 1);
},
 
//conversion function (modified a bit) taken from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
_hsl2Rgba: function(hsl) {
var h = hsl.h/360, s = hsl.s, l = hsl.l, r, g, b;
function hue2rgb(p, q, t) {
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
if (s === 0) {
r = g = b = l; // achromatic
} else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = Math.floor( (hue2rgb(p, q, h + 1/3)) * 255);
g = Math.floor( (hue2rgb(p, q, h)) * 255);
b = Math.floor( (hue2rgb(p, q, h - 1/3)) * 255);
}
return this._rgba(r, g, b, 1);
}
});
DrawingBoard.Control.DrawingMode = DrawingBoard.Control.extend({
 
name: 'drawingmode',
 
defaults: {
pencil: true,
eraser: true,
filler: true
},
 
initialize: function() {
 
this.prevMode = this.board.getMode();
 
$.each(["pencil", "eraser", "filler"], $.proxy(function(k, value) {
if (this.opts[value]) {
this.$el.append('<button class="drawing-board-control-drawingmode-' + value + '-button" data-mode="' + value + '"></button>');
}
}, this));
 
this.$el.on('click', 'button[data-mode]', $.proxy(function(e) {
var value = $(e.currentTarget).attr('data-mode');
var mode = this.board.getMode();
if (mode !== value) this.prevMode = mode;
var newMode = mode === value ? this.prevMode : value;
this.board.setMode( newMode );
e.preventDefault();
}, this));
 
this.board.ev.bind('board:mode', $.proxy(function(mode) {
this.toggleButtons(mode);
}, this));
 
this.toggleButtons( this.board.getMode() );
},
 
toggleButtons: function(mode) {
this.$el.find('button[data-mode]').each(function(k, item) {
var $item = $(item);
$item.toggleClass('active', mode === $item.attr('data-mode'));
});
}
 
});
 
DrawingBoard.Control.Navigation = DrawingBoard.Control.extend({
 
name: 'navigation',
 
defaults: {
back: true,
forward: true,
reset: true
},
 
initialize: function() {
var el = '';
if (this.opts.back) el += '<button class="drawing-board-control-navigation-back">&larr;</button>';
if (this.opts.forward) el += '<button class="drawing-board-control-navigation-forward">&rarr;</button>';
if (this.opts.reset) el += '<button class="drawing-board-control-navigation-reset">&times;</button>';
this.$el.append(el);
 
if (this.opts.back) {
var $back = this.$el.find('.drawing-board-control-navigation-back');
this.board.ev.bind('historyNavigation', $.proxy(this.updateBack, this, $back));
this.$el.on('click', '.drawing-board-control-navigation-back', $.proxy(function(e) {
this.board.goBackInHistory();
e.preventDefault();
}, this));
 
this.updateBack($back);
}
 
if (this.opts.forward) {
var $forward = this.$el.find('.drawing-board-control-navigation-forward');
this.board.ev.bind('historyNavigation', $.proxy(this.updateForward, this, $forward));
this.$el.on('click', '.drawing-board-control-navigation-forward', $.proxy(function(e) {
this.board.goForthInHistory();
e.preventDefault();
}, this));
 
this.updateForward($forward);
}
 
if (this.opts.reset) {
this.$el.on('click', '.drawing-board-control-navigation-reset', $.proxy(function(e) {
this.board.reset({ background: true });
e.preventDefault();
}, this));
}
},
 
updateBack: function($back) {
if (this.board.history.canUndo()) {
$back.removeAttr('disabled');
} else {
$back.attr('disabled', 'disabled');
}
},
 
updateForward: function($forward) {
if (this.board.history.canRedo()) {
$forward.removeAttr('disabled');
} else {
$forward.attr('disabled', 'disabled');
}
}
});
DrawingBoard.Control.Size = DrawingBoard.Control.extend({
 
name: 'size',
 
defaults: {
type: "auto",
dropdownValues: [1, 3, 6, 10, 20, 30, 40, 50],
min: 1,
max: 50
},
 
types: ['dropdown', 'range'],
 
initialize: function() {
if (this.opts.type == "auto")
this.opts.type = this._iHasRangeInput() ? 'range' : 'dropdown';
var tpl = $.inArray(this.opts.type, this.types) > -1 ? this['_' + this.opts.type + 'Template']() : false;
if (!tpl) return false;
 
this.val = this.board.opts.size;
 
this.$el.append( $( tpl ) );
this.$el.attr('data-drawing-board-type', this.opts.type);
this.updateView();
 
var that = this;
 
if (this.opts.type == "range") {
this.$el.on('change', '.drawing-board-control-size-range-input', function(e) {
that.val = $(this).val();
that.updateView();
 
that.board.ev.trigger('size:changed', that.val);
 
e.preventDefault();
});
}
 
if (this.opts.type == "dropdown") {
this.$el.on('click', '.drawing-board-control-size-dropdown-current', $.proxy(function(e) {
this.$el.find('.drawing-board-control-size-dropdown').toggleClass('drawing-board-utils-hidden');
}, this));
 
this.$el.on('click', '[data-size]', function(e) {
that.val = parseInt($(this).attr('data-size'), 0);
that.updateView();
 
that.board.ev.trigger('size:changed', that.val);
 
e.preventDefault();
});
}
},
 
_rangeTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<input type="range" min="{{min}}" max="{{max}}" value="{{size}}" step="1" class="drawing-board-control-size-range-input">' +
'<span class="drawing-board-control-size-range-current"></span>' +
'</div>';
return DrawingBoard.Utils.tpl(tpl, {
min: this.opts.min,
max: this.opts.max,
size: this.board.opts.size
});
},
 
_dropdownTemplate: function() {
var tpl = '<div class="drawing-board-control-inner" title="{{size}}">' +
'<div class="drawing-board-control-size-dropdown-current"><span></span></div>' +
'<ul class="drawing-board-control-size-dropdown">';
$.each(this.opts.dropdownValues, function(i, size) {
tpl += DrawingBoard.Utils.tpl(
'<li data-size="{{size}}"><span style="width: {{size}}px; height: {{size}}px; border-radius: {{size}}px;"></span></li>',
{ size: size }
);
});
tpl += '</ul></div>';
return tpl;
},
 
onBoardReset: function(opts) {
this.updateView();
},
 
updateView: function() {
var val = this.val;
this.board.ctx.lineWidth = val;
 
this.$el.find('.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span').css({
width: val + 'px',
height: val + 'px',
borderRadius: val + 'px',
marginLeft: -1*val/2 + 'px',
marginTop: -1*val/2 + 'px'
});
 
this.$el.find('.drawing-board-control-inner').attr('title', val);
 
if (this.opts.type == 'dropdown') {
var closest = null;
$.each(this.opts.dropdownValues, function(i, size) {
if (closest === null || Math.abs(size - val) < Math.abs(closest - val))
closest = size;
});
this.$el.find('.drawing-board-control-size-dropdown').addClass('drawing-board-utils-hidden');
}
},
 
_iHasRangeInput: function() {
var inputElem = document.createElement('input'),
smile = ':)',
docElement = document.documentElement,
inputElemType = 'range',
available;
inputElem.setAttribute('type', inputElemType);
available = inputElem.type !== 'text';
inputElem.value = smile;
inputElem.style.cssText = 'position:absolute;visibility:hidden;';
if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) {
docElement.appendChild(inputElem);
defaultView = document.defaultView;
available = defaultView.getComputedStyle &&
defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' &&
(inputElem.offsetHeight !== 0);
docElement.removeChild(inputElem);
}
return !!available;
}
});
DrawingBoard.Control.Download = DrawingBoard.Control.extend({
 
name: 'download',
 
initialize: function() {
this.$el.append('<button class="drawing-board-control-download-button"></button>');
this.$el.on('click', '.drawing-board-control-download-button', $.proxy(function(e) {
this.board.downloadImg();
e.preventDefault();
}, this));
}
 
});
/bower_components/drawingboard.js/dist/drawingboard.min.css
@@ -0,0 +1,5 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
 
.drawing-board,.drawing-board *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.drawing-board-controls-hidden,.drawing-board-utils-hidden{display:none!important}.drawing-board{position:relative;display:block}.drawing-board-canvas-wrapper{position:relative;margin:0;border:1px solid #ddd}.drawing-board-canvas{position:absolute;top:0;left:0;width:auto;cursor:crosshair;z-index:20}.drawing-board-cursor{position:absolute;top:0;left:0;pointer-events:none;border-radius:50%;background:#ccc;background:rgba(0,0,0,.2);z-index:30}.drawing-board-control-colors-rainbows,.drawing-board-control-size .drawing-board-control-inner,.drawing-board-control-size-dropdown,.drawing-board-control>button{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;overflow:hidden;background-color:#eee;padding:2px 4px;border:1px solid #ccc;box-shadow:0 1px 3px -2px #121212,inset 0 2px 5px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 3px -2px #121212,inset 0 2px 5px 0 rgba(255,255,255,.3);height:28px}.drawing-board-control>button{cursor:pointer;min-width:28px;line-height:14px}.drawing-board-control>button:focus,.drawing-board-control>button:hover{background-color:#ddd}.drawing-board-control>button.active,.drawing-board-control>button:active{box-shadow:inset 0 1px 2px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 2px 0 rgba(0,0,0,.2);background-color:#ddd}.drawing-board-control>button[disabled]{color:gray}.drawing-board-control>button[disabled].active,.drawing-board-control>button[disabled]:active,.drawing-board-control>button[disabled]:focus,.drawing-board-control>button[disabled]:hover{background-color:#eee;box-shadow:0 1px 3px -2px #121212,inset 0 2px 5px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 3px -2px #121212,inset 0 2px 5px 0 rgba(255,255,255,.3);cursor:default}.drawing-board-controls{margin:0 auto;text-align:center;font-size:0;display:table;border-spacing:9.33px 0;position:relative;min-height:28px}.drawing-board-controls[data-align=left]{margin:0;left:-9.33px}.drawing-board-controls[data-align=right]{margin:0 0 0 auto;right:-9.33px}.drawing-board-canvas-wrapper+.drawing-board-controls,.drawing-board-controls+.drawing-board-canvas-wrapper{margin-top:5px}.drawing-board-controls-hidden{height:0;min-height:0;padding:0;margin:0;border:0}.drawing-board-control{display:table-cell;border-collapse:separate;vertical-align:middle;font-size:16px;height:100%}.drawing-board-control-inner{position:relative;height:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.drawing-board-control>button{margin:0;vertical-align:middle}.drawing-board-control-colors{font-size:0;line-height:0}.drawing-board-control-colors-current{border:1px solid #ccc;cursor:pointer;display:inline-block;width:26px;height:26px}.drawing-board-control-colors-rainbows{display:inline-block;position:absolute;left:0;top:33px;margin-left:0;z-index:100;width:250px;height:auto;padding:4px}.drawing-board-control-colors-rainbow{height:18px}.drawing-board-control-colors-picker:first-child{margin-right:5px}.drawing-board-control-colors-picker{display:inline-block;width:18px;height:18px;cursor:pointer}.drawing-board-control-colors-picker[data-color="rgba(255, 255, 255, 1)"]{width:16px;height:17px;border:1px solid #ccc;border-bottom:none}.drawing-board-control-colors-picker:hover{width:16px;height:16px;border:1px solid #555}.drawing-board-control-drawingmode>button{margin-right:2px}.drawing-board-control-drawingmode>button:last-child{margin-right:0}.drawing-board-control-drawingmode-pencil-button{overflow:hidden;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe9JREFUeNpiZAACVlFRBhYREQZcQPnbNwa3N28YlL5+ZfgLFfvPwGD9m4FhIgsDHuAO0gTUDNKIBvyBmqt/MTDMY8Gl0f31azD7L6oUIxCnAzWmAPHBfwwM01AMUAV6JfPQIVwOYgVqqPnFyOjz6///O38YGKpAgmAD1OXlGdTk5PD5hgeouZudj8/uy9evP/78/dsFFPsJNiAoKIiBABAHap4oLi9v8fTNm48//v7NBwbgWZgkE7rqt8DY+A8JZRBW+cfIuEDT0NDlzadP3z98/doPFDuCrB7TAGFhBqCNIGwM9OcKUzs7+xdv3355+f79VqDYAiTDwZgJh7ONgYpnOvn4GL949erT7UePdgL5JVCD4fgBLBBxaX74+PG789evnwby0/8jKXgExIeB+CG6Af///1e9Ki9vFSAkZPzoyZPPJy9evA9MB77/sWiEARZkzV+/fvXYtGnTpG3btj28EBT0BqjZ5D8OjXCwPksUhA1Wpggf/PHjx/9169Y9EBERaUlgZmaIAcrLE4rk5sIqBqDmlefnRPzfWGX5EaSZm5ubgRloADGA5QZ3RgK7gESY4PMNn9ZtObPpzZvfU4DiYkiB/RcHG+S7fyxAMH/lFU2GOZd2bLx18/cEUMoD4j9I+DcS/RtJHGTYf4AAAwAxaOMYHjxKFwAAAABJRU5ErkJggg==);background-position:50% 50%;background-repeat:no-repeat}.drawing-board-control-drawingmode-pencil-button:before{content:"";display:block;width:0;height:100%}.drawing-board-control-drawingmode-eraser-button{overflow:hidden;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkpJREFUeNp0kk+IElEcx39vFBc9+OfQRTAwzFt4CaYOKStj6MoeculStzoIQSB4kCVckmDx4iGCXWYJIqjoVOzO1l4qT1F7WfBWHvxzDPyTB3XUmXn93suRybUffHmP997n9/cRsFgwGARJkiAcDsPlwgEIeEZQAhCRAkgAlOD6SQP4rgMFDWVnYCAQgFgsBqFQCBwOByzZNQOotPHx1RNCCCipu6bfb+zSnslkeOQVILPrBkAirbws9btdTEWAzZPXpfepOzaeGMBXwe/3w3+MwTc3Dl+UeghTiskbBvR6Pbh18mZHB0jjmxvCKhIfR37s3r+Sevf8ca/T4TBF2HTSODuDxP7uNjrZFFbBk8lEzOVyspa4ykGYw2zfbTb/7ilvok1YhlVVFfP5vDydTkHXdXDdlhZOOnPY4/HA0YPtp3h6LFjh8XgsFgoFGTPgsKm1zDr8ajTQh8Fh5eGjZzjGI8yjKlgjF4tFGdd/YKYmRja24hw+zu3sYe2HiH3hYzQjl8tleTQanWtou93G6Qngdrth6+1+9h6hTULJZ/PeziJXKhV5OByeg1ut1gJOp9NZTdNOcQ419ot+ggp1qoLdBFmqVmNpm3A8Huewy+Wq1RH8QH9zmBlJJpMRdCIqiiIPBgN+2MCGsW/r8/kgGo1m0fmpzWarseayHlmNeL1eFiWC0cRqtSr3+/3FpSiKHMZtjU1glbFyfKgLTqfzEka9OJvNeDnzz1JnCaFmqOl8ZdJY1SiDOXCiXKg1NtG5DIt0y6ov3dE/AgwAENFWYYLj4mYAAAAASUVORK5CYII=);background-position:50% 50%;background-repeat:no-repeat}.drawing-board-control-drawingmode-eraser-button:before{content:"";display:block;width:0;height:100%}.drawing-board-control-drawingmode-filler-button{overflow:hidden;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnNJREFUeNp0k0trE1EUx89MJpNJooYYXBgDNtCKdRPwlbqoCKUtaNVNA0Uo7UbMxoVPEARTXEi+QWfnwn6DEAlEkrSLttTGRiULEQlJ8yChmbzI++E50yTUJA78uMy953/u/557LmOz2WDEZ2m1WrckSRJSqdR2tVrdHQyYebwHtVoNuFHqTqczhQnWKaBYLDoKhcIuzgHDMKBSqeD20qd+LNdsNocSoFhRr9ctpVLJigl4xIIJQizLAmG4cAPa7bYcy9Iug5TL5UYikbD6/X7Rbre/IUcYe3WUW5ZsnQQzW9LpNOPz+UQc5aBM5mgdh7vI9FCCAesW2tnr9YqZTAby+bw8f3AQRP6853n+Ph5hemSCntjj8YjZbFYWx2IxeS2RSEMwuA87O79eqdXquVolK+GxnP0EPbHb7RZJSGABIR6PA11zJHKIR2MhHA5DIPDj7eH3j95KpfK60Wg8Yntil8slkqgnpioLghacTidoNDpEC3q9HnheCc3s1jZeLcW943pirPw/4lKpBkqlDubnl/riycnLsLy88EKj0fhzuRyZv8RFo1E6wpBYkiqy7Z54YmIcVlYeyOKC4mYwJ0nHRaQuM5vNT6hB/iceG7sIq6sPnwmC4MerDkby40AOCCoiddie1Wp92W7zQ2KTyQSLizNP8T0EsPLBbxEDnCj0GkM2qIEwyZRCobizsfH5A1ZXFhuN52F29vpz3HkL574mk8lj24Y5wsHkvjjoX0BOIWc5jruHzbK2ufmzEwpFO3jnDhQv4JoROYdoERVyGjEgZ8iBDlF3FzXo4go6utZ9lftY4N/dXisjR0i1G0ublv8KMAA0ZoUlicxrhwAAAABJRU5ErkJggg==);background-position:50% 50%;background-repeat:no-repeat}.drawing-board-control-drawingmode-filler-button:before{content:"";display:block;width:0;height:100%}.drawing-board-control-navigation>button{font-family:Helvetica,Arial,sans-serif;font-size:14px;font-weight:700;margin-right:2px}.drawing-board-control-navigation>button:last-child{margin-right:0}.drawing-board-control-size[data-drawing-board-type=range] .drawing-board-control-inner{width:75px}.drawing-board-control-size[data-drawing-board-type=dropdown] .drawing-board-control-inner{overflow:visible}.drawing-board-control-size-range-input{position:relative;width:100%;z-index:100;margin:0;padding:0;border:0}.drawing-board-control-size-dropdown span,.drawing-board-control-size-dropdown-current span,.drawing-board-control-size-range-current{display:block;background:#333;opacity:.8}.drawing-board-control-size-range-current{display:inline-block;opacity:.15;position:absolute;pointer-events:none;left:50%;top:50%;z-index:50}.drawing-board-control-size-dropdown-current{display:block;height:100%;width:40px;overflow:hidden;position:relative}.drawing-board-control-size-dropdown-current span{position:absolute;left:50%;top:50%}.drawing-board-control-size-dropdown{position:absolute;left:-6px;top:33px;height:auto;list-style-type:none;margin:0;padding:0;z-index:100}.drawing-board-control-size-dropdown li{display:block;padding:4px;margin:3px 0;min-height:16px}.drawing-board-control-size-dropdown li:hover{background:#ccc}.drawing-board-control-size-dropdown span{margin:0 auto}.drawing-board-control-download-button{overflow:hidden;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNqMkr9PU1EUx7/vR1tQ3yu10hAmTawtBSYSy2YccFOcnDQm/gewOLnj5GYMg4sdXFxkMca4OBAwdUBe/ZkIGFp+9tHXvh/3/fTcAm01aLzJybnn3nM+95tzrnDl6Tb+sibuTmWUWj3C6/Juk+LySUmyvt0FCKKA02ryOCy6LBiu15ngMbZ5DDCNBqIw6gKM+n4nECUJru3glKry8CpjQaHVYmC2rVH82DIMMMdGGARdwJ+SPNdFS9chx+MXDNMp/NzagWNatk/nQU/hiYAoih6FYTBCBs9zUXMCbAhx2OYOv351lPOJ3EwH4LteL6Dcp/Rfu3FrstDyIizt+agpaYxNDU0M9gl4v7Ck+TYrCYLQqZHUyTtdQBiutPSGUflczSXHs5lVKwZdSOBMvwztxVvN0RtzsiyXBFHsAvL5PBSnCpXV2getILFiE2SjspYbuZzPiDSZ2vOXmlvX5yQqTmMfg9ZXqtls1wnT09OHEyAq0aFLg/gSXsSWq9wWk+p9PrCoYTwcijdLOfE7UsEufN9HGIYnT4EnTGIXe1KqtNNIvuNnGamxfi7SgQD/nIJCTbzOPQ/SQh1pud7T4M6W/8qFIw/5WAr5m7Ozsw9UVc069Fls2yJzSC5/lnc9RhaHZVnfSqUnEgXP2oBqtYqBgYG2+mKxmOVADnAcB4yxHgD1RzehKKns/LyV4gUHBweQy+UyRkdH6UKJ6fQDFxcXoWkaXJeRuTgUGCdLQJ9bx72lGZimGWs2m+083oN+2iiFQiGxvLy8RrDzudyltgrG3N8U2G8CrPz4sGYYRqJSqWR4H/jNWbJhUjAWi8XG8R/L87yPpGCVttVfAgwAVpZR+8tZC08AAAAASUVORK5CYII=);background-position:50% 50%;background-repeat:no-repeat}.drawing-board-control-download-button:before{content:"";display:block;width:0;height:100%}
/bower_components/drawingboard.js/dist/drawingboard.min.js
@@ -0,0 +1,4 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
!function(){"use strict";function a(a,b){for(;a.length>b;)a.shift()}var b=function(a){var b=a?a:{},c={provider:function(){throw new Error("No provider!")},maxLength:30,onUpdate:function(){}};this.provider="undefined"!=typeof b.provider?b.provider:c.provider,this.maxLength="undefined"!=typeof b.maxLength?b.maxLength:c.maxLength,this.onUpdate="undefined"!=typeof b.onUpdate?b.onUpdate:c.onUpdate,this.initialItem=null,this.clear()};b.prototype.initialize=function(a){this.stack[0]=a,this.initialItem=a},b.prototype.clear=function(){this.stack=[this.initialItem],this.position=0,this.onUpdate()},b.prototype.save=function(){this.provider(function(b){a(this.stack,this.maxLength),this.position=Math.min(this.position,this.stack.length-1),this.stack=this.stack.slice(0,this.position+1),this.stack.push(b),this.position++,this.onUpdate()}.bind(this))},b.prototype.undo=function(a){if(this.canUndo()){var b=this.stack[--this.position];this.onUpdate(),a&&a(b)}},b.prototype.redo=function(a){if(this.canRedo()){var b=this.stack[++this.position];this.onUpdate(),a&&a(b)}},b.prototype.canUndo=function(){return this.position>0},b.prototype.canRedo=function(){return this.position<this.count()},b.prototype.count=function(){return this.stack.length-1},"undefined"!=typeof module&&(module.exports=b),"undefined"!=typeof window&&(window.SimpleUndo=b)}(),window.DrawingBoard="undefined"!=typeof DrawingBoard?DrawingBoard:{},DrawingBoard.Utils={},DrawingBoard.Utils.tpl=function(){"use strict";var a,b="{{",c="}}",d="[a-z0-9_][\\.a-z0-9_]*",e=new RegExp(b+"\\s*("+d+")\\s*"+c,"gi");return function(b,c){return b.replace(e,function(b,d){for(var e=d.split("."),f=e.length,g=c,h=0;f>h;h++){if(g=g[e[h]],g===a)throw"tim: '"+e[h]+"' not found in "+b;if(h===f-1)return g}})}}(),DrawingBoard.Utils.MicroEvent=function(){},DrawingBoard.Utils.MicroEvent.prototype={bind:function(a,b){this._events=this._events||{},this._events[a]=this._events[a]||[],this._events[a].push(b)},unbind:function(a,b){this._events=this._events||{},a in this._events!=!1&&this._events[a].splice(this._events[a].indexOf(b),1)},trigger:function(a){if(this._events=this._events||{},a in this._events!=!1)for(var b=0;b<this._events[a].length;b++)this._events[a][b].apply(this,Array.prototype.slice.call(arguments,1))}},DrawingBoard.Utils._boxBorderSize=function(a,b,c,d){b=!!b||!0,c=!!c||!1;var e,f=0;"width"==d?(e=["border-left-width","border-right-width"],b&&e.push("padding-left","padding-right"),c&&e.push("margin-left","margin-right")):(e=["border-top-width","border-bottom-width"],b&&e.push("padding-top","padding-bottom"),c&&e.push("margin-top","margin-bottom"));for(var g=e.length-1;g>=0;g--)f+=parseInt(a.css(e[g]).replace("px",""),10);return f},DrawingBoard.Utils.boxBorderWidth=function(a,b,c){return DrawingBoard.Utils._boxBorderSize(a,b,c,"width")},DrawingBoard.Utils.boxBorderHeight=function(a,b,c){return DrawingBoard.Utils._boxBorderSize(a,b,c,"height")},DrawingBoard.Utils.isColor=function(a){return a&&a.length?/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a)||-1!==$.inArray(a.substring(0,3),["rgb","hsl"]):!1},DrawingBoard.Utils.RGBToInt=function(a,b,c){var d=0;return d|=(255&a)<<16,d|=(255&b)<<8,d|=255&c},DrawingBoard.Utils.pixelAt=function(a,b,c){var d=4*(c*a.width+b),e=DrawingBoard.Utils.RGBToInt(a.data[d],a.data[d+1],a.data[d+2]);return[d,b,c,e]},DrawingBoard.Utils.compareColors=function(a,b,c){if(0===c)return a===b;var d=a>>16&255,e=b>>16&255,f=a>>8&255,g=b>>8&255,h=255&a,i=255&b;return Math.abs(d-e)<=c&&Math.abs(f-g)<=c&&Math.abs(h-i)<=c},function(){for(var a=["ms","moz","webkit","o"],b=0;b<a.length&&!window.requestAnimationFrame;++b)window.requestAnimationFrame=window[a[b]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[a[b]+"CancelAnimationFrame"]||window[a[b]+"CancelRequestAnimationFrame"]}(),window.DrawingBoard="undefined"!=typeof DrawingBoard?DrawingBoard:{},DrawingBoard.Board=function(a,b){if(this.opts=this.mergeOptions(b),this.ev=new DrawingBoard.Utils.MicroEvent,this.id=a,this.$el=$(document.getElementById(a)),!this.$el.length)return!1;var c='<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';return this.opts.controlsPosition.indexOf("bottom")>-1?c+='<div class="drawing-board-controls"></div>':c='<div class="drawing-board-controls"></div>'+c,this.$el.addClass("drawing-board").append(c),this.dom={$canvasWrapper:this.$el.find(".drawing-board-canvas-wrapper"),$canvas:this.$el.find(".drawing-board-canvas"),$cursor:this.$el.find(".drawing-board-cursor"),$controls:this.$el.find(".drawing-board-controls")},$.each(["left","right","center"],$.proxy(function(a,b){return this.opts.controlsPosition.indexOf(b)>-1?(this.dom.$controls.attr("data-align",b),!1):void 0},this)),this.canvas=this.dom.$canvas.get(0),this.ctx=this.canvas&&this.canvas.getContext&&this.canvas.getContext("2d")?this.canvas.getContext("2d"):null,this.color=this.opts.color,this.ctx?(this.storage=this._getStorage(),this.initHistory(),this.reset({webStorage:!1,history:!1,background:!1}),this.initControls(),this.resize(),this.reset({webStorage:!1,history:!1,background:!0}),this.restoreWebStorage(),this.initDropEvents(),void this.initDrawEvents()):(this.opts.errorMessage&&this.$el.html(this.opts.errorMessage),!1)},DrawingBoard.Board.defaultOpts={controls:["Color","DrawingMode","Size","Navigation"],controlsPosition:"top left",color:"#000000",size:1,background:"#fff",eraserColor:"background",fillTolerance:100,fillHack:!0,webStorage:"session",droppable:!1,enlargeYourContainer:!1,errorMessage:'<p>It seems you use an obsolete browser. <a href="http://browsehappy.com/" target="_blank">Update it</a> to start drawing.</p>',stretchImg:!1},DrawingBoard.Board.prototype={mergeOptions:function(a){return a=$.extend({},DrawingBoard.Board.defaultOpts,a),a.background||"background"!==a.eraserColor||(a.eraserColor="transparent"),a},reset:function(a){a=$.extend({color:this.opts.color,size:this.opts.size,webStorage:!0,history:!0,background:!1},a),this.setMode("pencil"),a.background&&this.resetBackground(this.opts.background,$.proxy(function(){a.history&&this.saveHistory()},this)),a.color&&this.setColor(a.color),a.size&&(this.ctx.lineWidth=a.size),this.ctx.lineCap="round",this.ctx.lineJoin="round",a.webStorage&&this.saveWebStorage(),a.history&&!a.background&&this.saveHistory(),this.blankCanvas=this.getImg(),this.ev.trigger("board:reset",a)},resetBackground:function(a,b){a=a||this.opts.background;var c=DrawingBoard.Utils.isColor(a),d=this.getMode();this.setMode("pencil"),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),c?(this.ctx.fillStyle=a,this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.history.initialize(this.getImg()),b&&b()):a&&this.setImg(a,{callback:$.proxy(function(){this.history.initialize(this.getImg()),b&&b()},this)}),this.setMode(d)},resize:function(){this.dom.$controls.toggleClass("drawing-board-controls-hidden",!this.controls||!this.controls.length);var a,b,c=[this.$el.width(),DrawingBoard.Utils.boxBorderWidth(this.$el),DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper,!0,!0)],d=[this.$el.height(),DrawingBoard.Utils.boxBorderHeight(this.$el),this.dom.$controls.height(),DrawingBoard.Utils.boxBorderHeight(this.dom.$controls,!1,!0),DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper,!0,!0)],e=function(a,b){b=b||1;for(var c=a[0],d=1;d<a.length;d++)c+=a[d]*b;return c},f=function(a){return e(a,-1)};this.opts.enlargeYourContainer?(a=this.$el.width(),b=this.$el.height(),this.$el.width(e(c)),this.$el.height(e(d))):(a=f(c),b=f(d)),this.dom.$canvasWrapper.css("width",a+"px"),this.dom.$canvasWrapper.css("height",b+"px"),this.dom.$canvas.css("width",a+"px"),this.dom.$canvas.css("height",b+"px"),this.canvas.width=a,this.canvas.height=b},initControls:function(){if(this.controls=[],!this.opts.controls.length||!DrawingBoard.Control)return!1;for(var a=0;a<this.opts.controls.length;a++){var b=null;if("string"==typeof this.opts.controls[a])b=new window.DrawingBoard.Control[this.opts.controls[a]](this);else if("object"==typeof this.opts.controls[a]){for(var c in this.opts.controls[a])break;b=new window.DrawingBoard.Control[c](this,this.opts.controls[a][c])}b&&this.addControl(b)}},addControl:function(a,b,c){if("string"!=typeof a&&("object"!=typeof a||!a instanceof DrawingBoard.Control))return!1;var d="object"==typeof b?b:{};c=c?1*c:"number"==typeof b?b:null,"string"==typeof a&&(a=new window.DrawingBoard.Control[a](this,d)),c?this.dom.$controls.children().eq(c).before(a.$el):this.dom.$controls.append(a.$el),this.controls||(this.controls=[]),this.controls.push(a),this.dom.$controls.removeClass("drawing-board-controls-hidden")},initHistory:function(){this.history=new SimpleUndo({maxLength:30,provider:$.proxy(function(a){a(this.getImg())},this),onUpdate:$.proxy(function(){this.ev.trigger("historyNavigation")},this)})},saveHistory:function(){this.history.save()},restoreHistory:function(a){this.setImg(a,{callback:$.proxy(function(){this.saveWebStorage()},this)})},goBackInHistory:function(){this.history.undo($.proxy(this.restoreHistory,this))},goForthInHistory:function(){this.history.redo($.proxy(this.restoreHistory,this))},setImg:function(a,b){b=$.extend({stretch:this.opts.stretchImg,callback:null},b);var c=this.ctx,d=new Image,e=c.globalCompositeOperation;d.onload=function(){c.globalCompositeOperation="source-over",c.clearRect(0,0,c.canvas.width,c.canvas.height),b.stretch?c.drawImage(d,0,0,c.canvas.width,c.canvas.height):c.drawImage(d,0,0),c.globalCompositeOperation=e,b.callback&&b.callback()},d.src=a},getImg:function(){return this.canvas.toDataURL("image/png")},downloadImg:function(){var a=this.getImg();a=a.replace("image/png","image/octet-stream"),window.location.href=a},saveWebStorage:function(){window[this.storage]&&(window[this.storage].setItem("drawing-board-"+this.id,this.getImg()),this.ev.trigger("board:save"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1),this.getImg()))},restoreWebStorage:function(){window[this.storage]&&null!==window[this.storage].getItem("drawing-board-"+this.id)&&(this.setImg(window[this.storage].getItem("drawing-board-"+this.id)),this.ev.trigger("board:restore"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1),window[this.storage].getItem("drawing-board-"+this.id)))},clearWebStorage:function(){window[this.storage]&&null!==window[this.storage].getItem("drawing-board-"+this.id)&&(window[this.storage].removeItem("drawing-board-"+this.id),this.ev.trigger("board:clear"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1)))},_getStorage:function(){return!this.opts.webStorage||"session"!==this.opts.webStorage&&"local"!==this.opts.webStorage?!1:this.opts.webStorage+"Storage"},initDropEvents:function(){return this.opts.droppable?(this.dom.$canvas.on("dragover dragenter drop",function(a){a.stopPropagation(),a.preventDefault()}),void this.dom.$canvas.on("drop",$.proxy(this._onCanvasDrop,this))):!1},_onCanvasDrop:function(a){a=a.originalEvent?a.originalEvent:a;var b=a.dataTransfer.files;if(!b||!b.length||-1==b[0].type.indexOf("image")||!window.FileReader)return!1;var c=new FileReader;c.readAsDataURL(b[0]),c.onload=$.proxy(function(a){this.setImg(a.target.result,{callback:$.proxy(function(){this.saveHistory()},this)}),this.ev.trigger("board:imageDropped",a.target.result),this.ev.trigger("board:userAction")},this)},setMode:function(a,b){b=b||!1,a=a||"pencil",this.ev.unbind("board:startDrawing",$.proxy(this.fill,this)),"transparent"===this.opts.eraserColor?this.ctx.globalCompositeOperation="eraser"===a?"destination-out":"source-over":("eraser"===a?"background"===this.opts.eraserColor&&DrawingBoard.Utils.isColor(this.opts.background)?this.ctx.strokeStyle=this.opts.background:DrawingBoard.Utils.isColor(this.opts.eraserColor)&&(this.ctx.strokeStyle=this.opts.eraserColor):this.mode&&"eraser"!==this.mode||(this.ctx.strokeStyle=this.color),"filler"===a&&this.ev.bind("board:startDrawing",$.proxy(this.fill,this))),this.mode=a,b||this.ev.trigger("board:mode",this.mode)},getMode:function(){return this.mode||"pencil"},setColor:function(a){var b=this;if(a=a||this.color,!DrawingBoard.Utils.isColor(a))return!1;if(this.color=a,"transparent"!==this.opts.eraserColor&&"eraser"===this.mode){var c=function(a){"eraser"!==a&&(b.strokeStyle=b.color),b.ev.unbind("board:mode",c)};this.ev.bind("board:mode",c)}else this.ctx.strokeStyle=this.color},fill:function(a){function b(a){c.data[a[d]]=i,c.data[a[d]+1]=j,c.data[a[d]+2]=k}if(this.getImg()===this.blankCanvas)return this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.ctx.fillStyle=this.color,void this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);var c=this.ctx.getImageData(0,0,this.canvas.width,this.canvas.height),d=0,e=1,f=2,g=3,h=this.ctx.strokeStyle,i=parseInt(h.substr(1,2),16),j=parseInt(h.substr(3,2),16),k=parseInt(h.substr(5,2),16),l=DrawingBoard.Utils.pixelAt(c,parseInt(a.coords.x,10),parseInt(a.coords.y,10)),m=l[g],n=this.opts.fillTolerance,o=this.opts.fillHack;if(!DrawingBoard.Utils.compareColors(m,DrawingBoard.Utils.RGBToInt(i,j,k),n)){for(var p,q=[l],r=c.width-1,s=c.height-1;p=q.pop();)o&&b(p),DrawingBoard.Utils.compareColors(p[g],m,n)&&(o||b(p),p[e]>0&&q.push(DrawingBoard.Utils.pixelAt(c,p[e]-1,p[f])),p[e]<r&&q.push(DrawingBoard.Utils.pixelAt(c,p[e]+1,p[f])),p[f]>0&&q.push(DrawingBoard.Utils.pixelAt(c,p[e],p[f]-1)),p[f]<s&&q.push(DrawingBoard.Utils.pixelAt(c,p[e],p[f]+1)));this.ctx.putImageData(c,0,0)}},initDrawEvents:function(){this.isDrawing=!1,this.isMouseHovering=!1,this.coords={},this.coords.old=this.coords.current=this.coords.oldMid={x:0,y:0},this.dom.$canvas.on("mousedown touchstart",$.proxy(function(a){this._onInputStart(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mousemove touchmove",$.proxy(function(a){this._onInputMove(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mousemove",$.proxy(function(){},this)),this.dom.$canvas.on("mouseup touchend",$.proxy(function(a){this._onInputStop(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mouseover",$.proxy(function(a){this._onMouseOver(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mouseout",$.proxy(function(a){this._onMouseOut(a,this._getInputCoords(a))},this)),$("body").on("mouseup touchend",$.proxy(function(){this.isDrawing=!1},this)),window.requestAnimationFrame&&requestAnimationFrame($.proxy(this.draw,this))},draw:function(){if(window.requestAnimationFrame&&this.ctx.lineWidth>10&&this.isMouseHovering){this.dom.$cursor.css({width:this.ctx.lineWidth+"px",height:this.ctx.lineWidth+"px"});var a=DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)",{x:this.coords.current.x-this.ctx.lineWidth/2,y:this.coords.current.y-this.ctx.lineWidth/2});this.dom.$cursor.css({transform:a,"-webkit-transform":a,"-ms-transform":a}),this.dom.$cursor.removeClass("drawing-board-utils-hidden")}else this.dom.$cursor.addClass("drawing-board-utils-hidden");if(this.isDrawing){var b=this._getMidInputCoords(this.coords.current);this.ctx.beginPath(),this.ctx.moveTo(b.x,b.y),this.ctx.quadraticCurveTo(this.coords.old.x,this.coords.old.y,this.coords.oldMid.x,this.coords.oldMid.y),this.ctx.stroke(),this.coords.old=this.coords.current,this.coords.oldMid=b}window.requestAnimationFrame&&requestAnimationFrame($.proxy(function(){this.draw()},this))},_onInputStart:function(a,b){this.coords.current=this.coords.old=b,this.coords.oldMid=this._getMidInputCoords(b),this.isDrawing=!0,window.requestAnimationFrame||this.draw(),this.ev.trigger("board:startDrawing",{e:a,coords:b}),a.stopPropagation(),a.preventDefault()},_onInputMove:function(a,b){this.coords.current=b,this.ev.trigger("board:drawing",{e:a,coords:b}),window.requestAnimationFrame||this.draw(),a.stopPropagation(),a.preventDefault()},_onInputStop:function(a,b){!this.isDrawing||a.touches&&0!==a.touches.length||(this.isDrawing=!1,this.saveWebStorage(),this.saveHistory(),this.ev.trigger("board:stopDrawing",{e:a,coords:b}),this.ev.trigger("board:userAction"),a.stopPropagation(),a.preventDefault())},_onMouseOver:function(a,b){this.isMouseHovering=!0,this.coords.old=this._getInputCoords(a),this.coords.oldMid=this._getMidInputCoords(this.coords.old),this.ev.trigger("board:mouseOver",{e:a,coords:b})},_onMouseOut:function(a,b){this.isMouseHovering=!1,this.ev.trigger("board:mouseOut",{e:a,coords:b})},_getInputCoords:function(a){a=a.originalEvent?a.originalEvent:a;var b,c,d=this.canvas.getBoundingClientRect(),e=this.dom.$canvas.width(),f=this.dom.$canvas.height();return a.touches&&1==a.touches.length?(b=a.touches[0].pageX,c=a.touches[0].pageY):(b=a.pageX,c=a.pageY),b-=this.dom.$canvas.offset().left,c-=this.dom.$canvas.offset().top,b*=e/d.width,c*=f/d.height,{x:b,y:c}},_getMidInputCoords:function(a){return{x:this.coords.old.x+a.x>>1,y:this.coords.old.y+a.y>>1}}},DrawingBoard.Control=function(a,b){return this.board=a,this.opts=$.extend({},this.defaults,b),this.$el=$(document.createElement("div")).addClass("drawing-board-control"),this.name&&this.$el.addClass("drawing-board-control-"+this.name),this.board.ev.bind("board:reset",$.proxy(this.onBoardReset,this)),this.initialize.apply(this,arguments),this},DrawingBoard.Control.prototype={name:"",defaults:{},initialize:function(){},addToBoard:function(){this.board.addControl(this)},onBoardReset:function(){}},DrawingBoard.Control.extend=function(a,b){var c,d=this;c=a&&a.hasOwnProperty("constructor")?a.constructor:function(){return d.apply(this,arguments)},$.extend(c,d,b);var e=function(){this.constructor=c};return e.prototype=d.prototype,c.prototype=new e,a&&$.extend(c.prototype,a),c.__super__=d.prototype,c},DrawingBoard.Control.Color=DrawingBoard.Control.extend({name:"colors",initialize:function(){this.initTemplate();var a=this;this.$el.on("click",".drawing-board-control-colors-picker",function(b){var c=$(this).attr("data-color");a.board.setColor(c),a.$el.find(".drawing-board-control-colors-current").css("background-color",c).attr("data-color",c),a.board.ev.trigger("color:changed",c),a.$el.find(".drawing-board-control-colors-rainbows").addClass("drawing-board-utils-hidden"),b.preventDefault()}),this.$el.on("click",".drawing-board-control-colors-current",function(b){a.$el.find(".drawing-board-control-colors-rainbows").toggleClass("drawing-board-utils-hidden"),b.preventDefault()}),$("body").on("click",function(b){var c=$(b.target),d=c.hasClass("drawing-board-control-colors-current")?c:c.closest(".drawing-board-control-colors-current"),e=a.$el.find(".drawing-board-control-colors-current"),f=a.$el.find(".drawing-board-control-colors-rainbows");d.length&&d.get(0)===e.get(0)||f.hasClass("drawing-board-utils-hidden")||f.addClass("drawing-board-utils-hidden")})},initTemplate:function(){var a='<div class="drawing-board-control-inner"><div class="drawing-board-control-colors-current" style="background-color: {{color}}" data-color="{{color}}"></div><div class="drawing-board-control-colors-rainbows">{{rainbows}}</div></div>',b='<div class="drawing-board-control-colors-picker" data-color="{{color}}" style="background-color: {{color}}"></div>',c="";$.each([.75,.5,.25],$.proxy(function(a,d){var e=0,f=null;for(c+='<div class="drawing-board-control-colors-rainbow">',.25==d&&(f=this._rgba(0,0,0,1)),.5==d&&(f=this._rgba(150,150,150,1)),.75==d&&(f=this._rgba(255,255,255,1)),c+=DrawingBoard.Utils.tpl(b,{color:f.toString()});330>=e;)c+=DrawingBoard.Utils.tpl(b,{color:this._hsl2Rgba(this._hsl(e-60,1,d)).toString()}),e+=30;c+="</div>"},this)),this.$el.append($(DrawingBoard.Utils.tpl(a,{color:this.board.color,rainbows:c}))),this.$el.find(".drawing-board-control-colors-rainbows").addClass("drawing-board-utils-hidden")},onBoardReset:function(){this.board.setColor(this.$el.find(".drawing-board-control-colors-current").attr("data-color"))},_rgba:function(a,b,c,d){return{r:a,g:b,b:c,a:d,toString:function(){return"rgba("+a+", "+b+", "+c+", "+d+")"}}},_hsl:function(a,b,c){return{h:a,s:b,l:c,toString:function(){return"hsl("+a+", "+100*b+"%, "+100*c+"%)"}}},_hex2Rgba:function(a){var b=parseInt(a.substring(1),16);return this._rgba(b>>16,b>>8&255,255&b,1)},_hsl2Rgba:function(a){function b(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}var c,d,e,f=a.h/360,g=a.s,h=a.l;if(0===g)c=d=e=h;else{var i=.5>h?h*(1+g):h+g-h*g,j=2*h-i;c=Math.floor(255*b(j,i,f+1/3)),d=Math.floor(255*b(j,i,f)),e=Math.floor(255*b(j,i,f-1/3))}return this._rgba(c,d,e,1)}}),DrawingBoard.Control.DrawingMode=DrawingBoard.Control.extend({name:"drawingmode",defaults:{pencil:!0,eraser:!0,filler:!0},initialize:function(){this.prevMode=this.board.getMode(),$.each(["pencil","eraser","filler"],$.proxy(function(a,b){this.opts[b]&&this.$el.append('<button class="drawing-board-control-drawingmode-'+b+'-button" data-mode="'+b+'"></button>')},this)),this.$el.on("click","button[data-mode]",$.proxy(function(a){var b=$(a.currentTarget).attr("data-mode"),c=this.board.getMode();c!==b&&(this.prevMode=c);var d=c===b?this.prevMode:b;this.board.setMode(d),a.preventDefault()},this)),this.board.ev.bind("board:mode",$.proxy(function(a){this.toggleButtons(a)},this)),this.toggleButtons(this.board.getMode())},toggleButtons:function(a){this.$el.find("button[data-mode]").each(function(b,c){var d=$(c);d.toggleClass("active",a===d.attr("data-mode"))})}}),DrawingBoard.Control.Navigation=DrawingBoard.Control.extend({name:"navigation",defaults:{back:!0,forward:!0,reset:!0},initialize:function(){var a="";if(this.opts.back&&(a+='<button class="drawing-board-control-navigation-back">&larr;</button>'),this.opts.forward&&(a+='<button class="drawing-board-control-navigation-forward">&rarr;</button>'),this.opts.reset&&(a+='<button class="drawing-board-control-navigation-reset">&times;</button>'),this.$el.append(a),this.opts.back){var b=this.$el.find(".drawing-board-control-navigation-back");this.board.ev.bind("historyNavigation",$.proxy(this.updateBack,this,b)),this.$el.on("click",".drawing-board-control-navigation-back",$.proxy(function(a){this.board.goBackInHistory(),a.preventDefault()},this)),this.updateBack(b)}if(this.opts.forward){var c=this.$el.find(".drawing-board-control-navigation-forward");this.board.ev.bind("historyNavigation",$.proxy(this.updateForward,this,c)),this.$el.on("click",".drawing-board-control-navigation-forward",$.proxy(function(a){this.board.goForthInHistory(),a.preventDefault()},this)),this.updateForward(c)}this.opts.reset&&this.$el.on("click",".drawing-board-control-navigation-reset",$.proxy(function(a){this.board.reset({background:!0}),a.preventDefault()},this))},updateBack:function(a){this.board.history.canUndo()?a.removeAttr("disabled"):a.attr("disabled","disabled")},updateForward:function(a){this.board.history.canRedo()?a.removeAttr("disabled"):a.attr("disabled","disabled")}}),DrawingBoard.Control.Size=DrawingBoard.Control.extend({name:"size",defaults:{type:"auto",dropdownValues:[1,3,6,10,20,30,40,50],min:1,max:50},types:["dropdown","range"],initialize:function(){"auto"==this.opts.type&&(this.opts.type=this._iHasRangeInput()?"range":"dropdown");var a=$.inArray(this.opts.type,this.types)>-1?this["_"+this.opts.type+"Template"]():!1;if(!a)return!1;this.val=this.board.opts.size,this.$el.append($(a)),this.$el.attr("data-drawing-board-type",this.opts.type),this.updateView();var b=this;"range"==this.opts.type&&this.$el.on("change",".drawing-board-control-size-range-input",function(a){b.val=$(this).val(),b.updateView(),b.board.ev.trigger("size:changed",b.val),a.preventDefault()}),"dropdown"==this.opts.type&&(this.$el.on("click",".drawing-board-control-size-dropdown-current",$.proxy(function(){this.$el.find(".drawing-board-control-size-dropdown").toggleClass("drawing-board-utils-hidden")},this)),this.$el.on("click","[data-size]",function(a){b.val=parseInt($(this).attr("data-size"),0),b.updateView(),b.board.ev.trigger("size:changed",b.val),a.preventDefault()}))},_rangeTemplate:function(){var a='<div class="drawing-board-control-inner" title="{{size}}"><input type="range" min="{{min}}" max="{{max}}" value="{{size}}" step="1" class="drawing-board-control-size-range-input"><span class="drawing-board-control-size-range-current"></span></div>';return DrawingBoard.Utils.tpl(a,{min:this.opts.min,max:this.opts.max,size:this.board.opts.size})},_dropdownTemplate:function(){var a='<div class="drawing-board-control-inner" title="{{size}}"><div class="drawing-board-control-size-dropdown-current"><span></span></div><ul class="drawing-board-control-size-dropdown">';return $.each(this.opts.dropdownValues,function(b,c){a+=DrawingBoard.Utils.tpl('<li data-size="{{size}}"><span style="width: {{size}}px; height: {{size}}px; border-radius: {{size}}px;"></span></li>',{size:c})}),a+="</ul></div>"},onBoardReset:function(){this.updateView()},updateView:function(){var a=this.val;if(this.board.ctx.lineWidth=a,this.$el.find(".drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span").css({width:a+"px",height:a+"px",borderRadius:a+"px",marginLeft:-1*a/2+"px",marginTop:-1*a/2+"px"}),this.$el.find(".drawing-board-control-inner").attr("title",a),"dropdown"==this.opts.type){var b=null;$.each(this.opts.dropdownValues,function(c,d){(null===b||Math.abs(d-a)<Math.abs(b-a))&&(b=d)}),this.$el.find(".drawing-board-control-size-dropdown").addClass("drawing-board-utils-hidden")}},_iHasRangeInput:function(){var a,b=document.createElement("input"),c=":)",d=document.documentElement,e="range";return b.setAttribute("type",e),a="text"!==b.type,b.value=c,b.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&void 0!==b.style.WebkitAppearance&&(d.appendChild(b),defaultView=document.defaultView,a=defaultView.getComputedStyle&&"textfield"!==defaultView.getComputedStyle(b,null).WebkitAppearance&&0!==b.offsetHeight,d.removeChild(b)),!!a}}),DrawingBoard.Control.Download=DrawingBoard.Control.extend({name:"download",initialize:function(){this.$el.append('<button class="drawing-board-control-download-button"></button>'),this.$el.on("click",".drawing-board-control-download-button",$.proxy(function(a){this.board.downloadImg(),a.preventDefault()},this))}});
/bower_components/drawingboard.js/dist/drawingboard.nocontrol.css
@@ -0,0 +1,16 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; }
 
.drawing-board-utils-hidden { display: none !important; }
 
.drawing-board { position: relative; display: block; }
 
.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; }
 
.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; }
 
.drawing-board-canvas { cursor: crosshair; z-index: 20; }
 
.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; }
/bower_components/drawingboard.js/dist/drawingboard.nocontrol.js
@@ -0,0 +1,971 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
(function() {
'use strict';
 
/**
* SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything.
*
* options are: {
* * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback
* * `maxLength` : the maximum number of items in history
* * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear`
* }
*
*/
var SimpleUndo = function(options) {
var settings = options ? options : {};
var defaultOptions = {
provider: function() {
throw new Error("No provider!");
},
maxLength: 30,
onUpdate: function() {}
};
this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider;
this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength;
this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate;
this.initialItem = null;
this.clear();
};
 
function truncate (stack, limit) {
while (stack.length > limit) {
stack.shift();
}
}
 
SimpleUndo.prototype.initialize = function(initialItem) {
this.stack[0] = initialItem;
this.initialItem = initialItem;
};
 
 
SimpleUndo.prototype.clear = function() {
this.stack = [this.initialItem];
this.position = 0;
this.onUpdate();
};
 
SimpleUndo.prototype.save = function() {
this.provider(function(current) {
truncate(this.stack, this.maxLength);
this.position = Math.min(this.position,this.stack.length - 1);
this.stack = this.stack.slice(0, this.position + 1);
this.stack.push(current);
this.position++;
this.onUpdate();
}.bind(this));
};
 
SimpleUndo.prototype.undo = function(callback) {
if (this.canUndo()) {
var item = this.stack[--this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
 
SimpleUndo.prototype.redo = function(callback) {
if (this.canRedo()) {
var item = this.stack[++this.position];
this.onUpdate();
if (callback) {
callback(item);
}
}
};
 
SimpleUndo.prototype.canUndo = function() {
return this.position > 0;
};
 
SimpleUndo.prototype.canRedo = function() {
return this.position < this.count();
};
 
SimpleUndo.prototype.count = function() {
return this.stack.length - 1; // -1 because of initial item
};
 
 
 
 
 
//exports
// node module
if (typeof module != 'undefined') {
module.exports = SimpleUndo;
}
 
// browser global
if (typeof window != 'undefined') {
window.SimpleUndo = SimpleUndo;
}
 
})();
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
 
 
DrawingBoard.Utils = {};
 
/*!
* Tim (lite)
* github.com/premasagar/tim
*//*
A tiny, secure JavaScript micro-templating script.
*/
DrawingBoard.Utils.tpl = (function(){
"use strict";
 
var start = "{{",
end = "}}",
path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name
pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"),
undef;
 
return function(template, data){
// Merge data into the template string
return template.replace(pattern, function(tag, token){
var path = token.split("."),
len = path.length,
lookup = data,
i = 0;
 
for (; i < len; i++){
lookup = lookup[path[i]];
 
// Property not found
if (lookup === undef){
throw "tim: '" + path[i] + "' not found in " + tag;
}
 
// Return the required value
if (i === len - 1){
return lookup;
}
}
});
};
}());
 
/**
* https://github.com/jeromeetienne/microevent.js
* MicroEvent - to make any js object an event emitter (server or browser)
*
* - pure javascript - server compatible, browser compatible
* - dont rely on the browser doms
* - super simple - you get it immediatly, no mistery, no magic involved
*
* - create a MicroEventDebug with goodies to debug
* - make it safer to use
*/
DrawingBoard.Utils.MicroEvent = function(){};
 
DrawingBoard.Utils.MicroEvent.prototype = {
bind : function(event, fct){
this._events = this._events || {};
this._events[event] = this._events[event] || [];
this._events[event].push(fct);
},
unbind : function(event, fct){
this._events = this._events || {};
if( event in this._events === false ) return;
this._events[event].splice(this._events[event].indexOf(fct), 1);
},
trigger : function(event /* , args... */){
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
 
//I know.
DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) {
withPadding = !!withPadding || true;
withMargin = !!withMargin || false;
var width = 0,
props;
if (direction == "width") {
props = ['border-left-width', 'border-right-width'];
if (withPadding) props.push('padding-left', 'padding-right');
if (withMargin) props.push('margin-left', 'margin-right');
} else {
props = ['border-top-width', 'border-bottom-width'];
if (withPadding) props.push('padding-top', 'padding-bottom');
if (withMargin) props.push('margin-top', 'margin-bottom');
}
for (var i = props.length - 1; i >= 0; i--)
width += parseInt($el.css(props[i]).replace('px', ''), 10);
return width;
};
 
DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width');
};
 
DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) {
return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height');
};
 
DrawingBoard.Utils.isColor = function(string) {
if (!string || !string.length) return false;
return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1;
};
 
/**
* Packs an RGB color into a single integer.
*/
DrawingBoard.Utils.RGBToInt = function(r, g, b) {
var c = 0;
c |= (r & 255) << 16;
c |= (g & 255) << 8;
c |= (b & 255);
return c;
};
 
/**
* Returns informations on the pixel located at (x,y).
*/
DrawingBoard.Utils.pixelAt = function(image, x, y) {
var i = (y * image.width + x) * 4;
var c = DrawingBoard.Utils.RGBToInt(
image.data[i],
image.data[i + 1],
image.data[i + 2]
);
 
return [
i, // INDEX
x, // X
y, // Y
c // COLOR
];
};
 
/**
* Compares two colors with the given tolerance (between 0 and 255).
*/
DrawingBoard.Utils.compareColors = function(a, b, tolerance) {
if (tolerance === 0) {
return (a === b);
}
 
var ra = (a >> 16) & 255, rb = (b >> 16) & 255,
ga = (a >> 8) & 255, gb = (b >> 8) & 255,
ba = a & 255, bb = b & 255;
 
return (Math.abs(ra - rb) <= tolerance)
&& (Math.abs(ga - gb) <= tolerance)
&& (Math.abs(ba - bb) <= tolerance);
};
 
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
}());
 
window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {};
 
/**
* pass the id of the html element to put the drawing board into
* and some options : {
* controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default
* instead of simple strings, you can pass an object to define a control opts
* ie ['Color', { Navigation: { reset: false }}]
* controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center"
* background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default
* color: pencil color ("#000000" by default)
* size: pencil size (3 by default)
* webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back
* droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it,
* errorMessage: html string to put in the board's element on browsers that don't support canvas.
* stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default
* }
*/
DrawingBoard.Board = function(id, opts) {
this.opts = this.mergeOptions(opts);
 
this.ev = new DrawingBoard.Utils.MicroEvent();
 
this.id = id;
this.$el = $(document.getElementById(id));
if (!this.$el.length)
return false;
 
var tpl = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
else tpl = '<div class="drawing-board-controls"></div>' + tpl;
 
this.$el.addClass('drawing-board').append(tpl);
this.dom = {
$canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
$canvas: this.$el.find('.drawing-board-canvas'),
$cursor: this.$el.find('.drawing-board-cursor'),
$controls: this.$el.find('.drawing-board-controls')
};
 
$.each(['left', 'right', 'center'], $.proxy(function(n, val) {
if (this.opts.controlsPosition.indexOf(val) > -1) {
this.dom.$controls.attr('data-align', val);
return false;
}
}, this));
 
this.canvas = this.dom.$canvas.get(0);
this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
this.color = this.opts.color;
 
if (!this.ctx) {
if (this.opts.errorMessage)
this.$el.html(this.opts.errorMessage);
return false;
}
 
this.storage = this._getStorage();
 
this.initHistory();
//init default board values before controls are added (mostly pencil color and size)
this.reset({ webStorage: false, history: false, background: false });
//init controls (they will need the default board values to work like pencil color and size)
this.initControls();
//set board's size after the controls div is added
this.resize();
//reset the board to take all resized space
this.reset({ webStorage: false, history: false, background: true });
this.restoreWebStorage();
this.initDropEvents();
this.initDrawEvents();
};
 
 
 
DrawingBoard.Board.defaultOpts = {
controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
controlsPosition: "top left",
color: "#000000",
size: 1,
background: "#fff",
eraserColor: "background",
fillTolerance: 100,
fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
webStorage: 'session',
droppable: false,
enlargeYourContainer: false,
errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true
};
 
 
 
DrawingBoard.Board.prototype = {
 
mergeOptions: function(opts) {
opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts);
if (!opts.background && opts.eraserColor === "background")
opts.eraserColor = "transparent";
return opts;
},
 
/**
* Canvas reset/resize methods: put back the canvas to its default values
*
* depending on options, can set color, size, background back to default values
* and store the reseted canvas in webstorage and history queue
*
* resize values depend on the `enlargeYourContainer` option
*/
 
reset: function(opts) {
opts = $.extend({
color: this.opts.color,
size: this.opts.size,
webStorage: true,
history: true,
background: false
}, opts);
 
this.setMode('pencil');
 
if (opts.background) {
this.resetBackground(this.opts.background, $.proxy(function() {
if (opts.history) this.saveHistory();
}, this));
}
 
if (opts.color) this.setColor(opts.color);
if (opts.size) this.ctx.lineWidth = opts.size;
 
this.ctx.lineCap = "round";
this.ctx.lineJoin = "round";
// this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width);
 
if (opts.webStorage) this.saveWebStorage();
 
// if opts.background we already dealt with the history
if (opts.history && !opts.background) this.saveHistory();
 
this.blankCanvas = this.getImg();
 
this.ev.trigger('board:reset', opts);
},
 
resetBackground: function(background, callback) {
background = background || this.opts.background;
 
var bgIsColor = DrawingBoard.Utils.isColor(background);
var prevMode = this.getMode();
this.setMode('pencil');
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
if (bgIsColor) {
this.ctx.fillStyle = background;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.history.initialize(this.getImg());
if (callback) callback();
} else if (background)
this.setImg(background, {
callback: $.proxy(function() {
this.history.initialize(this.getImg());
if (callback) callback();
}, this)
});
this.setMode(prevMode);
},
 
resize: function() {
this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length));
 
var canvasWidth, canvasHeight;
var widths = [
this.$el.width(),
DrawingBoard.Utils.boxBorderWidth(this.$el),
DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true)
];
var heights = [
this.$el.height(),
DrawingBoard.Utils.boxBorderHeight(this.$el),
this.dom.$controls.height(),
DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true),
DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true)
];
var that = this;
var sum = function(values, multiplier) { //make the sum of all array values
multiplier = multiplier || 1;
var res = values[0];
for (var i = 1; i < values.length; i++) {
res = res + (values[i]*multiplier);
}
return res;
};
var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one
 
if (this.opts.enlargeYourContainer) {
canvasWidth = this.$el.width();
canvasHeight = this.$el.height();
 
this.$el.width( sum(widths) );
this.$el.height( sum(heights) );
} else {
canvasWidth = sub(widths);
canvasHeight = sub(heights);
}
 
this.dom.$canvasWrapper.css('width', canvasWidth + 'px');
this.dom.$canvasWrapper.css('height', canvasHeight + 'px');
 
this.dom.$canvas.css('width', canvasWidth + 'px');
this.dom.$canvas.css('height', canvasHeight + 'px');
 
this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;
},
 
 
 
/**
* Controls:
* the drawing board can has various UI elements to control it.
* one control is represented by a class in the namespace DrawingBoard.Control
* it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization.
*
*/
 
initControls: function() {
this.controls = [];
if (!this.opts.controls.length || !DrawingBoard.Control) return false;
for (var i = 0; i < this.opts.controls.length; i++) {
var c = null;
if (typeof this.opts.controls[i] == "string")
c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this);
else if (typeof this.opts.controls[i] == "object") {
for (var controlName in this.opts.controls[i]) break;
c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]);
}
if (c) {
this.addControl(c);
}
}
},
 
//add a new control or an existing one at the position you want in the UI
//to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }"
//the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at
addControl: function(control, optsOrPos, pos) {
if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control))
return false;
 
var opts = typeof optsOrPos == "object" ? optsOrPos : {};
pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null);
 
if (typeof control == "string")
control = new window['DrawingBoard']['Control'][control](this, opts);
 
if (pos)
this.dom.$controls.children().eq(pos).before(control.$el);
else
this.dom.$controls.append(control.$el);
 
if (!this.controls)
this.controls = [];
this.controls.push(control);
this.dom.$controls.removeClass('drawing-board-controls-hidden');
},
 
 
 
/**
* History methods: undo and redo drawed lines
*/
 
initHistory: function() {
this.history = new SimpleUndo({
maxLength: 30,
provider: $.proxy(function(done) {
done(this.getImg());
}, this),
onUpdate: $.proxy(function() {
this.ev.trigger('historyNavigation');
}, this)
});
},
 
saveHistory: function() {
this.history.save();
},
 
restoreHistory: function(image) {
this.setImg(image, {
callback: $.proxy(function() {
this.saveWebStorage();
}, this)
});
},
 
goBackInHistory: function() {
this.history.undo($.proxy(this.restoreHistory, this));
},
 
goForthInHistory: function() {
this.history.redo($.proxy(this.restoreHistory, this));
},
 
/**
* Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download
*/
 
setImg: function(src, opts) {
opts = $.extend({
stretch: this.opts.stretchImg,
callback: null
}, opts);
 
var ctx = this.ctx;
var img = new Image();
var oldGCO = ctx.globalCompositeOperation;
img.onload = function() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
 
if (opts.stretch) {
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
} else {
ctx.drawImage(img, 0, 0);
}
 
ctx.globalCompositeOperation = oldGCO;
 
if (opts.callback) {
opts.callback();
}
};
img.src = src;
},
 
getImg: function() {
return this.canvas.toDataURL("image/png");
},
 
downloadImg: function() {
var img = this.getImg();
img = img.replace("image/png", "image/octet-stream");
window.location.href = img;
},
 
 
 
/**
* WebStorage handling : save and restore to local or session storage
*/
 
saveWebStorage: function() {
if (window[this.storage]) {
window[this.storage].setItem('drawing-board-' + this.id, this.getImg());
this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg());
}
},
 
restoreWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
this.setImg(window[this.storage].getItem('drawing-board-' + this.id));
this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id));
}
},
 
clearWebStorage: function() {
if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) {
window[this.storage].removeItem('drawing-board-' + this.id);
this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1));
}
},
 
_getStorage: function() {
if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false;
return this.opts.webStorage + 'Storage';
},
 
 
 
/**
* Drop an image on the canvas to draw on it
*/
 
initDropEvents: function() {
if (!this.opts.droppable)
return false;
 
this.dom.$canvas.on('dragover dragenter drop', function(e) {
e.stopPropagation();
e.preventDefault();
});
 
this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this));
},
 
_onCanvasDrop: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var files = e.dataTransfer.files;
if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader)
return false;
var fr = new FileReader();
fr.readAsDataURL(files[0]);
fr.onload = $.proxy(function(ev) {
this.setImg(ev.target.result, {
callback: $.proxy(function() {
this.saveHistory();
}, this)
});
this.ev.trigger('board:imageDropped', ev.target.result);
this.ev.trigger('board:userAction');
}, this);
},
 
 
 
/**
* set and get current drawing mode
*
* possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can)
*/
 
setMode: function(newMode, silent) {
silent = silent || false;
newMode = newMode || 'pencil';
 
this.ev.unbind('board:startDrawing', $.proxy(this.fill, this));
 
if (this.opts.eraserColor === "transparent")
this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over";
else {
if (newMode === "eraser") {
if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background))
this.ctx.strokeStyle = this.opts.background;
else if (DrawingBoard.Utils.isColor(this.opts.eraserColor))
this.ctx.strokeStyle = this.opts.eraserColor;
} else if (!this.mode || this.mode === "eraser") {
this.ctx.strokeStyle = this.color;
}
 
if (newMode === "filler")
this.ev.bind('board:startDrawing', $.proxy(this.fill, this));
}
this.mode = newMode;
if (!silent)
this.ev.trigger('board:mode', this.mode);
},
 
getMode: function() {
return this.mode || "pencil";
},
 
setColor: function(color) {
var that = this;
color = color || this.color;
if (!DrawingBoard.Utils.isColor(color))
return false;
this.color = color;
if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") {
var setStrokeStyle = function(mode) {
if (mode !== "eraser")
that.strokeStyle = that.color;
that.ev.unbind('board:mode', setStrokeStyle);
};
this.ev.bind('board:mode', setStrokeStyle);
} else
this.ctx.strokeStyle = this.color;
},
 
/**
* Fills an area with the current stroke color.
*/
fill: function(e) {
if (this.getImg() === this.blankCanvas) {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.ctx.fillStyle = this.color;
this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
return;
}
 
var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
 
// constants identifying pixels components
var INDEX = 0, X = 1, Y = 2, COLOR = 3;
 
// target color components
var stroke = this.ctx.strokeStyle;
var r = parseInt(stroke.substr(1, 2), 16);
var g = parseInt(stroke.substr(3, 2), 16);
var b = parseInt(stroke.substr(5, 2), 16);
 
// starting point
var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10));
var startColor = start[COLOR];
var tolerance = this.opts.fillTolerance;
var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38
 
// no need to continue if starting and target colors are the same
if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance))
return;
 
// pixels to evaluate
var queue = [start];
 
// loop vars
var pixel, x, y;
var maxX = img.width - 1;
var maxY = img.height - 1;
 
function updatePixelColor(pixel) {
img.data[pixel[INDEX]] = r;
img.data[pixel[INDEX] + 1] = g;
img.data[pixel[INDEX] + 2] = b;
}
 
while ((pixel = queue.pop())) {
if (useHack)
updatePixelColor(pixel);
 
if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) {
if (!useHack)
updatePixelColor(pixel);
if (pixel[X] > 0) // west
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y]));
if (pixel[X] < maxX) // east
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y]));
if (pixel[Y] > 0) // north
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1));
if (pixel[Y] < maxY) // south
queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1));
}
}
 
this.ctx.putImageData(img, 0, 0);
},
 
 
/**
* Drawing handling, with mouse or touch
*/
 
initDrawEvents: function() {
this.isDrawing = false;
this.isMouseHovering = false;
this.coords = {};
this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 };
 
this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) {
this._onInputStart(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) {
this._onInputMove(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mousemove', $.proxy(function(e) {
 
}, this));
 
this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) {
this._onInputStop(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mouseover', $.proxy(function(e) {
this._onMouseOver(e, this._getInputCoords(e) );
}, this));
 
this.dom.$canvas.on('mouseout', $.proxy(function(e) {
this._onMouseOut(e, this._getInputCoords(e) );
 
}, this));
 
$('body').on('mouseup touchend', $.proxy(function(e) {
this.isDrawing = false;
}, this));
 
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) );
},
 
draw: function() {
//if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil
//todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported
//we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad.
if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) {
this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' });
var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) });
this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform });
this.dom.$cursor.removeClass('drawing-board-utils-hidden');
} else {
this.dom.$cursor.addClass('drawing-board-utils-hidden');
}
 
if (this.isDrawing) {
var currentMid = this._getMidInputCoords(this.coords.current);
this.ctx.beginPath();
this.ctx.moveTo(currentMid.x, currentMid.y);
this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y);
this.ctx.stroke();
 
this.coords.old = this.coords.current;
this.coords.oldMid = currentMid;
}
 
if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) );
},
 
_onInputStart: function(e, coords) {
this.coords.current = this.coords.old = coords;
this.coords.oldMid = this._getMidInputCoords(coords);
this.isDrawing = true;
 
if (!window.requestAnimationFrame) this.draw();
 
this.ev.trigger('board:startDrawing', {e: e, coords: coords});
e.stopPropagation();
e.preventDefault();
},
 
_onInputMove: function(e, coords) {
this.coords.current = coords;
this.ev.trigger('board:drawing', {e: e, coords: coords});
 
if (!window.requestAnimationFrame) this.draw();
 
e.stopPropagation();
e.preventDefault();
},
 
_onInputStop: function(e, coords) {
if (this.isDrawing && (!e.touches || e.touches.length === 0)) {
this.isDrawing = false;
 
this.saveWebStorage();
this.saveHistory();
 
this.ev.trigger('board:stopDrawing', {e: e, coords: coords});
this.ev.trigger('board:userAction');
e.stopPropagation();
e.preventDefault();
}
},
 
_onMouseOver: function(e, coords) {
this.isMouseHovering = true;
this.coords.old = this._getInputCoords(e);
this.coords.oldMid = this._getMidInputCoords(this.coords.old);
 
this.ev.trigger('board:mouseOver', {e: e, coords: coords});
},
 
_onMouseOut: function(e, coords) {
this.isMouseHovering = false;
 
this.ev.trigger('board:mouseOut', {e: e, coords: coords});
},
 
_getInputCoords: function(e) {
e = e.originalEvent ? e.originalEvent : e;
var
rect = this.canvas.getBoundingClientRect(),
width = this.dom.$canvas.width(),
height = this.dom.$canvas.height()
;
var x, y;
if (e.touches && e.touches.length == 1) {
x = e.touches[0].pageX;
y = e.touches[0].pageY;
} else {
x = e.pageX;
y = e.pageY;
}
x = x - this.dom.$canvas.offset().left;
y = y - this.dom.$canvas.offset().top;
x *= (width / rect.width);
y *= (height / rect.height);
return {
x: x,
y: y
};
},
 
_getMidInputCoords: function(coords) {
return {
x: this.coords.old.x + coords.x>>1,
y: this.coords.old.y + coords.y>>1
};
}
};
/bower_components/drawingboard.js/dist/drawingboard.nocontrol.min.css
@@ -0,0 +1,5 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
 
.drawing-board,.drawing-board *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.drawing-board-utils-hidden{display:none!important}.drawing-board{position:relative;display:block}.drawing-board-canvas-wrapper{position:relative;margin:0;border:1px solid #ddd}.drawing-board-canvas{position:absolute;top:0;left:0;width:auto;cursor:crosshair;z-index:20}.drawing-board-cursor{position:absolute;top:0;left:0;pointer-events:none;border-radius:50%;background:#ccc;background:rgba(0,0,0,.2);z-index:30}
/bower_components/drawingboard.js/dist/drawingboard.nocontrol.min.js
@@ -0,0 +1,4 @@
/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js
* Copyright (c) 2015 Emmanuel Pelletier
* Licensed MIT */
!function(){"use strict";function a(a,b){for(;a.length>b;)a.shift()}var b=function(a){var b=a?a:{},c={provider:function(){throw new Error("No provider!")},maxLength:30,onUpdate:function(){}};this.provider="undefined"!=typeof b.provider?b.provider:c.provider,this.maxLength="undefined"!=typeof b.maxLength?b.maxLength:c.maxLength,this.onUpdate="undefined"!=typeof b.onUpdate?b.onUpdate:c.onUpdate,this.initialItem=null,this.clear()};b.prototype.initialize=function(a){this.stack[0]=a,this.initialItem=a},b.prototype.clear=function(){this.stack=[this.initialItem],this.position=0,this.onUpdate()},b.prototype.save=function(){this.provider(function(b){a(this.stack,this.maxLength),this.position=Math.min(this.position,this.stack.length-1),this.stack=this.stack.slice(0,this.position+1),this.stack.push(b),this.position++,this.onUpdate()}.bind(this))},b.prototype.undo=function(a){if(this.canUndo()){var b=this.stack[--this.position];this.onUpdate(),a&&a(b)}},b.prototype.redo=function(a){if(this.canRedo()){var b=this.stack[++this.position];this.onUpdate(),a&&a(b)}},b.prototype.canUndo=function(){return this.position>0},b.prototype.canRedo=function(){return this.position<this.count()},b.prototype.count=function(){return this.stack.length-1},"undefined"!=typeof module&&(module.exports=b),"undefined"!=typeof window&&(window.SimpleUndo=b)}(),window.DrawingBoard="undefined"!=typeof DrawingBoard?DrawingBoard:{},DrawingBoard.Utils={},DrawingBoard.Utils.tpl=function(){"use strict";var a,b="{{",c="}}",d="[a-z0-9_][\\.a-z0-9_]*",e=new RegExp(b+"\\s*("+d+")\\s*"+c,"gi");return function(b,c){return b.replace(e,function(b,d){for(var e=d.split("."),f=e.length,g=c,h=0;f>h;h++){if(g=g[e[h]],g===a)throw"tim: '"+e[h]+"' not found in "+b;if(h===f-1)return g}})}}(),DrawingBoard.Utils.MicroEvent=function(){},DrawingBoard.Utils.MicroEvent.prototype={bind:function(a,b){this._events=this._events||{},this._events[a]=this._events[a]||[],this._events[a].push(b)},unbind:function(a,b){this._events=this._events||{},a in this._events!=!1&&this._events[a].splice(this._events[a].indexOf(b),1)},trigger:function(a){if(this._events=this._events||{},a in this._events!=!1)for(var b=0;b<this._events[a].length;b++)this._events[a][b].apply(this,Array.prototype.slice.call(arguments,1))}},DrawingBoard.Utils._boxBorderSize=function(a,b,c,d){b=!!b||!0,c=!!c||!1;var e,f=0;"width"==d?(e=["border-left-width","border-right-width"],b&&e.push("padding-left","padding-right"),c&&e.push("margin-left","margin-right")):(e=["border-top-width","border-bottom-width"],b&&e.push("padding-top","padding-bottom"),c&&e.push("margin-top","margin-bottom"));for(var g=e.length-1;g>=0;g--)f+=parseInt(a.css(e[g]).replace("px",""),10);return f},DrawingBoard.Utils.boxBorderWidth=function(a,b,c){return DrawingBoard.Utils._boxBorderSize(a,b,c,"width")},DrawingBoard.Utils.boxBorderHeight=function(a,b,c){return DrawingBoard.Utils._boxBorderSize(a,b,c,"height")},DrawingBoard.Utils.isColor=function(a){return a&&a.length?/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a)||-1!==$.inArray(a.substring(0,3),["rgb","hsl"]):!1},DrawingBoard.Utils.RGBToInt=function(a,b,c){var d=0;return d|=(255&a)<<16,d|=(255&b)<<8,d|=255&c},DrawingBoard.Utils.pixelAt=function(a,b,c){var d=4*(c*a.width+b),e=DrawingBoard.Utils.RGBToInt(a.data[d],a.data[d+1],a.data[d+2]);return[d,b,c,e]},DrawingBoard.Utils.compareColors=function(a,b,c){if(0===c)return a===b;var d=a>>16&255,e=b>>16&255,f=a>>8&255,g=b>>8&255,h=255&a,i=255&b;return Math.abs(d-e)<=c&&Math.abs(f-g)<=c&&Math.abs(h-i)<=c},function(){for(var a=["ms","moz","webkit","o"],b=0;b<a.length&&!window.requestAnimationFrame;++b)window.requestAnimationFrame=window[a[b]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[a[b]+"CancelAnimationFrame"]||window[a[b]+"CancelRequestAnimationFrame"]}(),window.DrawingBoard="undefined"!=typeof DrawingBoard?DrawingBoard:{},DrawingBoard.Board=function(a,b){if(this.opts=this.mergeOptions(b),this.ev=new DrawingBoard.Utils.MicroEvent,this.id=a,this.$el=$(document.getElementById(a)),!this.$el.length)return!1;var c='<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';return this.opts.controlsPosition.indexOf("bottom")>-1?c+='<div class="drawing-board-controls"></div>':c='<div class="drawing-board-controls"></div>'+c,this.$el.addClass("drawing-board").append(c),this.dom={$canvasWrapper:this.$el.find(".drawing-board-canvas-wrapper"),$canvas:this.$el.find(".drawing-board-canvas"),$cursor:this.$el.find(".drawing-board-cursor"),$controls:this.$el.find(".drawing-board-controls")},$.each(["left","right","center"],$.proxy(function(a,b){return this.opts.controlsPosition.indexOf(b)>-1?(this.dom.$controls.attr("data-align",b),!1):void 0},this)),this.canvas=this.dom.$canvas.get(0),this.ctx=this.canvas&&this.canvas.getContext&&this.canvas.getContext("2d")?this.canvas.getContext("2d"):null,this.color=this.opts.color,this.ctx?(this.storage=this._getStorage(),this.initHistory(),this.reset({webStorage:!1,history:!1,background:!1}),this.initControls(),this.resize(),this.reset({webStorage:!1,history:!1,background:!0}),this.restoreWebStorage(),this.initDropEvents(),void this.initDrawEvents()):(this.opts.errorMessage&&this.$el.html(this.opts.errorMessage),!1)},DrawingBoard.Board.defaultOpts={controls:["Color","DrawingMode","Size","Navigation"],controlsPosition:"top left",color:"#000000",size:1,background:"#fff",eraserColor:"background",fillTolerance:100,fillHack:!0,webStorage:"session",droppable:!1,enlargeYourContainer:!1,errorMessage:'<p>It seems you use an obsolete browser. <a href="http://browsehappy.com/" target="_blank">Update it</a> to start drawing.</p>',stretchImg:!1},DrawingBoard.Board.prototype={mergeOptions:function(a){return a=$.extend({},DrawingBoard.Board.defaultOpts,a),a.background||"background"!==a.eraserColor||(a.eraserColor="transparent"),a},reset:function(a){a=$.extend({color:this.opts.color,size:this.opts.size,webStorage:!0,history:!0,background:!1},a),this.setMode("pencil"),a.background&&this.resetBackground(this.opts.background,$.proxy(function(){a.history&&this.saveHistory()},this)),a.color&&this.setColor(a.color),a.size&&(this.ctx.lineWidth=a.size),this.ctx.lineCap="round",this.ctx.lineJoin="round",a.webStorage&&this.saveWebStorage(),a.history&&!a.background&&this.saveHistory(),this.blankCanvas=this.getImg(),this.ev.trigger("board:reset",a)},resetBackground:function(a,b){a=a||this.opts.background;var c=DrawingBoard.Utils.isColor(a),d=this.getMode();this.setMode("pencil"),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),c?(this.ctx.fillStyle=a,this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.history.initialize(this.getImg()),b&&b()):a&&this.setImg(a,{callback:$.proxy(function(){this.history.initialize(this.getImg()),b&&b()},this)}),this.setMode(d)},resize:function(){this.dom.$controls.toggleClass("drawing-board-controls-hidden",!this.controls||!this.controls.length);var a,b,c=[this.$el.width(),DrawingBoard.Utils.boxBorderWidth(this.$el),DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper,!0,!0)],d=[this.$el.height(),DrawingBoard.Utils.boxBorderHeight(this.$el),this.dom.$controls.height(),DrawingBoard.Utils.boxBorderHeight(this.dom.$controls,!1,!0),DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper,!0,!0)],e=function(a,b){b=b||1;for(var c=a[0],d=1;d<a.length;d++)c+=a[d]*b;return c},f=function(a){return e(a,-1)};this.opts.enlargeYourContainer?(a=this.$el.width(),b=this.$el.height(),this.$el.width(e(c)),this.$el.height(e(d))):(a=f(c),b=f(d)),this.dom.$canvasWrapper.css("width",a+"px"),this.dom.$canvasWrapper.css("height",b+"px"),this.dom.$canvas.css("width",a+"px"),this.dom.$canvas.css("height",b+"px"),this.canvas.width=a,this.canvas.height=b},initControls:function(){if(this.controls=[],!this.opts.controls.length||!DrawingBoard.Control)return!1;for(var a=0;a<this.opts.controls.length;a++){var b=null;if("string"==typeof this.opts.controls[a])b=new window.DrawingBoard.Control[this.opts.controls[a]](this);else if("object"==typeof this.opts.controls[a]){for(var c in this.opts.controls[a])break;b=new window.DrawingBoard.Control[c](this,this.opts.controls[a][c])}b&&this.addControl(b)}},addControl:function(a,b,c){if("string"!=typeof a&&("object"!=typeof a||!a instanceof DrawingBoard.Control))return!1;var d="object"==typeof b?b:{};c=c?1*c:"number"==typeof b?b:null,"string"==typeof a&&(a=new window.DrawingBoard.Control[a](this,d)),c?this.dom.$controls.children().eq(c).before(a.$el):this.dom.$controls.append(a.$el),this.controls||(this.controls=[]),this.controls.push(a),this.dom.$controls.removeClass("drawing-board-controls-hidden")},initHistory:function(){this.history=new SimpleUndo({maxLength:30,provider:$.proxy(function(a){a(this.getImg())},this),onUpdate:$.proxy(function(){this.ev.trigger("historyNavigation")},this)})},saveHistory:function(){this.history.save()},restoreHistory:function(a){this.setImg(a,{callback:$.proxy(function(){this.saveWebStorage()},this)})},goBackInHistory:function(){this.history.undo($.proxy(this.restoreHistory,this))},goForthInHistory:function(){this.history.redo($.proxy(this.restoreHistory,this))},setImg:function(a,b){b=$.extend({stretch:this.opts.stretchImg,callback:null},b);var c=this.ctx,d=new Image,e=c.globalCompositeOperation;d.onload=function(){c.globalCompositeOperation="source-over",c.clearRect(0,0,c.canvas.width,c.canvas.height),b.stretch?c.drawImage(d,0,0,c.canvas.width,c.canvas.height):c.drawImage(d,0,0),c.globalCompositeOperation=e,b.callback&&b.callback()},d.src=a},getImg:function(){return this.canvas.toDataURL("image/png")},downloadImg:function(){var a=this.getImg();a=a.replace("image/png","image/octet-stream"),window.location.href=a},saveWebStorage:function(){window[this.storage]&&(window[this.storage].setItem("drawing-board-"+this.id,this.getImg()),this.ev.trigger("board:save"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1),this.getImg()))},restoreWebStorage:function(){window[this.storage]&&null!==window[this.storage].getItem("drawing-board-"+this.id)&&(this.setImg(window[this.storage].getItem("drawing-board-"+this.id)),this.ev.trigger("board:restore"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1),window[this.storage].getItem("drawing-board-"+this.id)))},clearWebStorage:function(){window[this.storage]&&null!==window[this.storage].getItem("drawing-board-"+this.id)&&(window[this.storage].removeItem("drawing-board-"+this.id),this.ev.trigger("board:clear"+this.storage.charAt(0).toUpperCase()+this.storage.slice(1)))},_getStorage:function(){return!this.opts.webStorage||"session"!==this.opts.webStorage&&"local"!==this.opts.webStorage?!1:this.opts.webStorage+"Storage"},initDropEvents:function(){return this.opts.droppable?(this.dom.$canvas.on("dragover dragenter drop",function(a){a.stopPropagation(),a.preventDefault()}),void this.dom.$canvas.on("drop",$.proxy(this._onCanvasDrop,this))):!1},_onCanvasDrop:function(a){a=a.originalEvent?a.originalEvent:a;var b=a.dataTransfer.files;if(!b||!b.length||-1==b[0].type.indexOf("image")||!window.FileReader)return!1;var c=new FileReader;c.readAsDataURL(b[0]),c.onload=$.proxy(function(a){this.setImg(a.target.result,{callback:$.proxy(function(){this.saveHistory()},this)}),this.ev.trigger("board:imageDropped",a.target.result),this.ev.trigger("board:userAction")},this)},setMode:function(a,b){b=b||!1,a=a||"pencil",this.ev.unbind("board:startDrawing",$.proxy(this.fill,this)),"transparent"===this.opts.eraserColor?this.ctx.globalCompositeOperation="eraser"===a?"destination-out":"source-over":("eraser"===a?"background"===this.opts.eraserColor&&DrawingBoard.Utils.isColor(this.opts.background)?this.ctx.strokeStyle=this.opts.background:DrawingBoard.Utils.isColor(this.opts.eraserColor)&&(this.ctx.strokeStyle=this.opts.eraserColor):this.mode&&"eraser"!==this.mode||(this.ctx.strokeStyle=this.color),"filler"===a&&this.ev.bind("board:startDrawing",$.proxy(this.fill,this))),this.mode=a,b||this.ev.trigger("board:mode",this.mode)},getMode:function(){return this.mode||"pencil"},setColor:function(a){var b=this;if(a=a||this.color,!DrawingBoard.Utils.isColor(a))return!1;if(this.color=a,"transparent"!==this.opts.eraserColor&&"eraser"===this.mode){var c=function(a){"eraser"!==a&&(b.strokeStyle=b.color),b.ev.unbind("board:mode",c)};this.ev.bind("board:mode",c)}else this.ctx.strokeStyle=this.color},fill:function(a){function b(a){c.data[a[d]]=i,c.data[a[d]+1]=j,c.data[a[d]+2]=k}if(this.getImg()===this.blankCanvas)return this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.ctx.fillStyle=this.color,void this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height);var c=this.ctx.getImageData(0,0,this.canvas.width,this.canvas.height),d=0,e=1,f=2,g=3,h=this.ctx.strokeStyle,i=parseInt(h.substr(1,2),16),j=parseInt(h.substr(3,2),16),k=parseInt(h.substr(5,2),16),l=DrawingBoard.Utils.pixelAt(c,parseInt(a.coords.x,10),parseInt(a.coords.y,10)),m=l[g],n=this.opts.fillTolerance,o=this.opts.fillHack;if(!DrawingBoard.Utils.compareColors(m,DrawingBoard.Utils.RGBToInt(i,j,k),n)){for(var p,q=[l],r=c.width-1,s=c.height-1;p=q.pop();)o&&b(p),DrawingBoard.Utils.compareColors(p[g],m,n)&&(o||b(p),p[e]>0&&q.push(DrawingBoard.Utils.pixelAt(c,p[e]-1,p[f])),p[e]<r&&q.push(DrawingBoard.Utils.pixelAt(c,p[e]+1,p[f])),p[f]>0&&q.push(DrawingBoard.Utils.pixelAt(c,p[e],p[f]-1)),p[f]<s&&q.push(DrawingBoard.Utils.pixelAt(c,p[e],p[f]+1)));this.ctx.putImageData(c,0,0)}},initDrawEvents:function(){this.isDrawing=!1,this.isMouseHovering=!1,this.coords={},this.coords.old=this.coords.current=this.coords.oldMid={x:0,y:0},this.dom.$canvas.on("mousedown touchstart",$.proxy(function(a){this._onInputStart(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mousemove touchmove",$.proxy(function(a){this._onInputMove(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mousemove",$.proxy(function(){},this)),this.dom.$canvas.on("mouseup touchend",$.proxy(function(a){this._onInputStop(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mouseover",$.proxy(function(a){this._onMouseOver(a,this._getInputCoords(a))},this)),this.dom.$canvas.on("mouseout",$.proxy(function(a){this._onMouseOut(a,this._getInputCoords(a))},this)),$("body").on("mouseup touchend",$.proxy(function(){this.isDrawing=!1},this)),window.requestAnimationFrame&&requestAnimationFrame($.proxy(this.draw,this))},draw:function(){if(window.requestAnimationFrame&&this.ctx.lineWidth>10&&this.isMouseHovering){this.dom.$cursor.css({width:this.ctx.lineWidth+"px",height:this.ctx.lineWidth+"px"});var a=DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)",{x:this.coords.current.x-this.ctx.lineWidth/2,y:this.coords.current.y-this.ctx.lineWidth/2});this.dom.$cursor.css({transform:a,"-webkit-transform":a,"-ms-transform":a}),this.dom.$cursor.removeClass("drawing-board-utils-hidden")}else this.dom.$cursor.addClass("drawing-board-utils-hidden");if(this.isDrawing){var b=this._getMidInputCoords(this.coords.current);this.ctx.beginPath(),this.ctx.moveTo(b.x,b.y),this.ctx.quadraticCurveTo(this.coords.old.x,this.coords.old.y,this.coords.oldMid.x,this.coords.oldMid.y),this.ctx.stroke(),this.coords.old=this.coords.current,this.coords.oldMid=b}window.requestAnimationFrame&&requestAnimationFrame($.proxy(function(){this.draw()},this))},_onInputStart:function(a,b){this.coords.current=this.coords.old=b,this.coords.oldMid=this._getMidInputCoords(b),this.isDrawing=!0,window.requestAnimationFrame||this.draw(),this.ev.trigger("board:startDrawing",{e:a,coords:b}),a.stopPropagation(),a.preventDefault()},_onInputMove:function(a,b){this.coords.current=b,this.ev.trigger("board:drawing",{e:a,coords:b}),window.requestAnimationFrame||this.draw(),a.stopPropagation(),a.preventDefault()},_onInputStop:function(a,b){!this.isDrawing||a.touches&&0!==a.touches.length||(this.isDrawing=!1,this.saveWebStorage(),this.saveHistory(),this.ev.trigger("board:stopDrawing",{e:a,coords:b}),this.ev.trigger("board:userAction"),a.stopPropagation(),a.preventDefault())},_onMouseOver:function(a,b){this.isMouseHovering=!0,this.coords.old=this._getInputCoords(a),this.coords.oldMid=this._getMidInputCoords(this.coords.old),this.ev.trigger("board:mouseOver",{e:a,coords:b})},_onMouseOut:function(a,b){this.isMouseHovering=!1,this.ev.trigger("board:mouseOut",{e:a,coords:b})},_getInputCoords:function(a){a=a.originalEvent?a.originalEvent:a;var b,c,d=this.canvas.getBoundingClientRect(),e=this.dom.$canvas.width(),f=this.dom.$canvas.height();return a.touches&&1==a.touches.length?(b=a.touches[0].pageX,c=a.touches[0].pageY):(b=a.pageX,c=a.pageY),b-=this.dom.$canvas.offset().left,c-=this.dom.$canvas.offset().top,b*=e/d.width,c*=f/d.height,{x:b,y:c}},_getMidInputCoords:function(a){return{x:this.coords.old.x+a.x>>1,y:this.coords.old.y+a.y>>1}}};