aboutsummaryrefslogtreecommitdiff
path: root/static/js/tegaki
diff options
context:
space:
mode:
Diffstat (limited to 'static/js/tegaki')
-rw-r--r--static/js/tegaki/tegaki.css187
-rw-r--r--static/js/tegaki/tegaki.js1947
2 files changed, 2134 insertions, 0 deletions
diff --git a/static/js/tegaki/tegaki.css b/static/js/tegaki/tegaki.css
new file mode 100644
index 0000000..d2e3591
--- /dev/null
+++ b/static/js/tegaki/tegaki.css
@@ -0,0 +1,187 @@
+@font-face {
+ font-family: 'tegaki';
+ src: url('data:application/octet-stream;base64,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') format('woff');
+ font-weight: normal;
+ font-style: normal;
+}
+
+.tegaki-icon:before {
+ font-size: 10px;
+ width: 10px;
+ font-family: 'tegaki';
+ font-style: normal;
+ font-weight: normal;
+ speak: none;
+ display: inline-block;
+ text-align: center;
+ font-variant: normal;
+ text-transform: none;
+ line-height: 1em;
+}
+
+.tegaki-cancel:before { content: '\e800'; } /* '' */
+.tegaki-plus:before { content: '\e801'; } /* '' */
+.tegaki-minus:before { content: '\e802'; } /* '' */
+.tegaki-eye:before { content: '\e803'; } /* '' */
+.tegaki-down-open:before { content: '\e804'; } /* '' */
+.tegaki-up-open:before { content: '\e805'; } /* '' */
+.tegaki-level-down:before { content: '\e806'; } /* '' */
+
+#tegaki {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: #a3b1bf;
+ color: #000;
+ font-family: arial, sans-serif;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ overflow: auto;
+ z-index: 9999;
+ image-rendering: optimizeSpeed;
+ image-rendering: -webkit-optimize-contrast;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+}
+
+#tegaki-debug {
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+#tegaki-debug canvas {
+ width: 75px;
+ height: 75px;
+ display: block;
+ border: 1px solid black;
+}
+
+.tegaki-backdrop {
+ overflow: hidden;
+}
+
+.tegaki-hidden {
+ display: none !important;
+}
+
+.tegaki-strike {
+ text-decoration: line-through;
+}
+
+#tegaki-cnt {
+ left: 50%;
+ top: 50%;
+ position: absolute;
+}
+
+#tegaki-cnt.tegaki-overflow-x {
+ left: 10px;
+ margin-left: 0 !important;
+}
+
+#tegaki-cnt.tegaki-overflow-y {
+ top: 10px;
+ margin-top: 0 !important;
+}
+
+.tegaki-tb-btn {
+ margin-left: 10px;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.tegaki-tb-btn:hover {
+ color: #007FFF;
+}
+
+.tegaki-tb-btn:focus {
+ color: #007FFF;
+ outline: none;
+}
+
+#tegaki-menu-bar {
+ font-size: 12px;
+ white-space: nowrap;
+ position: absolute;
+ right: 0;
+}
+
+#tegaki-canvas {
+ -moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
+ background: #FFFAFA;
+}
+
+#tegaki-layers {
+ display: inline-block;
+ font-size: 0;
+}
+
+#tegaki-finish-btn {
+ font-weight: bold;
+}
+
+.tegaki-ctrlgrp {
+ margin-bottom: 5px;
+}
+
+.tegaki-label {
+ font-size: 10px;
+}
+
+.tegaki-label:after {
+ content: ' ' attr(data-value);
+}
+
+#tegaki-ghost-layer,
+.tegaki-layer {
+ position: absolute;
+ left: 0;
+}
+
+#tegaki-ctrl {
+ position: absolute;
+ display: inline-block;
+ width: 80px;
+ padding-left: 5px;
+ font-size: 14px;
+}
+
+#tegaki-color {
+ padding: 0;
+ border: 0;
+ display: block;
+ width: 25px;
+ height: 25px;
+ cursor: pointer;
+}
+
+#tegaki-layer-grp span {
+ font-size: 12px;
+ margin-right: 3px;
+ cursor: pointer;
+}
+
+#tegaki-layer-grp span:hover {
+ color: #007FFF;
+}
+
+#tegaki-color::-moz-focus-inner {
+ border: none;
+ padding: 0;
+}
+
+#tegaki-alpha,
+#tegaki-size {
+ width: 90%;
+ margin: auto;
+}
+
+#tegaki-ctrl select {
+ font-size: 11px;
+ width: 100%;
+}
diff --git a/static/js/tegaki/tegaki.js b/static/js/tegaki/tegaki.js
new file mode 100644
index 0000000..3d38d00
--- /dev/null
+++ b/static/js/tegaki/tegaki.js
@@ -0,0 +1,1947 @@
+var TegakiBrush = {
+ brushFn: function(x, y) {
+ var i, ctx, dest, data, len, kernel;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ ctx = Tegaki.ghostCtx;
+ dest = ctx.getImageData(x, y, this.brushSize, this.brushSize);
+ data = dest.data;
+ kernel = this.kernel;
+ len = kernel.length;
+
+ i = 0;
+ while (i < len) {
+ data[i] = this.rgb[0]; ++i;
+ data[i] = this.rgb[1]; ++i;
+ data[i] = this.rgb[2]; ++i;
+ data[i] += kernel[i] * (1.0 - data[i] / 255); ++i;
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ commit: function() {
+ Tegaki.activeCtx.drawImage(Tegaki.ghostCanvas, 0, 0);
+ Tegaki.ghostCtx.clearRect(0, 0,
+ Tegaki.ghostCanvas.width, Tegaki.ghostCanvas.height
+ );
+ },
+
+ draw: function(posX, posY, pt) {
+ var offset, mx, my, fromX, fromY, dx, dy, err, derr, step, stepAcc;
+
+ offset = this.center;
+ step = this.stepSize;
+ stepAcc = this.stepAcc;
+
+ if (pt === true) {
+ this.stepAcc = 0;
+ this.posX = posX;
+ this.posY = posY;
+ this.brushFn(posX - offset, posY - offset);
+ return;
+ }
+
+ fromX = this.posX;
+ fromY = this.posY;
+
+ if (fromX < posX) { dx = posX - fromX; mx = 1; }
+ else { dx = fromX - posX; mx = -1; }
+ if (fromY < posY) { dy = posY - fromY; my = 1; }
+ else { dy = fromY - posY; my = -1; }
+
+ err = (dx > dy ? dx : -dy) / 2;
+
+ dx = -dx;
+
+ while (true) {
+ ++stepAcc;
+ if (stepAcc > step) {
+ this.brushFn(fromX - offset, fromY - offset);
+ stepAcc = 0;
+ }
+ if (fromX === posX && fromY === posY) {
+ break;
+ }
+ derr = err;
+ if (derr > dx) { err -= dy; fromX += mx; }
+ if (derr < dy) { err -= dx; fromY += my; }
+ }
+
+ this.stepAcc = stepAcc;
+ this.posX = posX;
+ this.posY = posY;
+ },
+
+ generateBrush: function() {
+ var i, size, r, brush, ctx, dest, data, len, sqd, sqlen, hs, col, row,
+ ecol, erow, a;
+
+ size = this.size * 2;
+ r = size / 2;
+
+ brush = T$.el('canvas');
+ brush.width = brush.height = size;
+ ctx = brush.getContext('2d');
+ dest = ctx.getImageData(0, 0, size, size);
+ data = dest.data;
+ len = size * size * 4;
+ sqlen = Math.sqrt(r * r);
+ hs = Math.round(r);
+ col = row = -hs;
+
+ i = 0;
+ while (i < len) {
+ if (col >= hs) {
+ col = -hs;
+ ++row;
+ continue;
+ }
+
+ ecol = col;
+ erow = row;
+
+ if (ecol < 0) { ecol = -ecol; }
+ if (erow < 0) { erow = -erow; }
+
+ sqd = Math.sqrt(ecol * ecol + erow * erow);
+
+ if (sqd > sqlen) {
+ a = 0;
+ }
+ else {
+ a = sqd / sqlen;
+ a = (Math.exp(1 - 1 / a) / a);
+ a = 255 - ((0 | (a * 100 + 0.5)) / 100) * 255;
+ }
+
+ if (this.alphaDamp) {
+ a *= this.alpha * this.alphaDamp;
+ }
+ else {
+ a *= this.alpha;
+ }
+
+ data[i + 3] = a;
+
+ i += 4;
+
+ ++col;
+ }
+
+ ctx.putImageData(dest, 0, 0);
+
+ this.center = r;
+ this.brushSize = size;
+ this.brush = brush;
+ this.kernel = data;
+ },
+
+ setSize: function(size, noBrush) {
+ this.size = size;
+ if (!noBrush) this.generateBrush();
+ this.stepSize = Math.floor(this.size * this.step);
+ },
+
+ setAlpha: function(alpha, noBrush) {
+ this.alpha = alpha;
+ if (!noBrush) this.generateBrush();
+ },
+
+ setColor: function(color, noBrush) {
+ this.rgb = Tegaki.hexToRgb(color);
+ if (!noBrush) this.generateBrush();
+ },
+
+ set: function() {
+ this.setAlpha(this.alpha, true);
+ this.setSize(this.size, true);
+ this.setColor(Tegaki.toolColor, true);
+ this.generateBrush();
+ }
+};
+
+var TegakiPen = {
+ init: function() {
+ this.size = 4;
+ this.alpha = 0.5;
+ this.step = 0.1;
+ this.stepAcc = 0;
+ },
+
+ draw: TegakiBrush.draw,
+
+ commit: TegakiBrush.commit,
+
+ brushFn: TegakiBrush.brushFn,
+
+ generateBrush: function() {
+ var size, r, brush, ctx;
+
+ size = this.size;
+ r = size / 2;
+
+ brush = T$.el('canvas');
+ brush.width = brush.height = size;
+ ctx = brush.getContext('2d');
+ ctx.globalAlpha = this.alpha;
+ ctx.beginPath();
+ ctx.arc(r, r, r, 0, Tegaki.TWOPI, false);
+ ctx.fillStyle = '#000000';
+ ctx.fill();
+ ctx.closePath();
+
+ this.center = r;
+ this.brushSize = size;
+ this.brush = brush;
+ this.kernel = ctx.getImageData(0, 0, this.brushSize, this.brushSize).data;
+ },
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiPipette = {
+ size: 1,
+ alpha: 1,
+ noCursor: true,
+
+ draw: function(posX, posY) {
+ var c, ctx;
+
+ if (true) {
+ ctx = Tegaki.flatten().getContext('2d');
+ }
+ else {
+ ctx = Tegaki.activeCtx;
+ }
+
+ c = Tegaki.getColorAt(ctx, posX, posY);
+
+ Tegaki.setToolColor(c);
+ Tegaki.updateUI('color');
+ }
+};
+
+var TegakiAirbrush = {
+ init: function() {
+ this.size = 32;
+ this.alpha = 0.5;
+ this.alphaDamp = 0.2;
+ this.step = 0.25;
+ this.stepAcc = 0;
+ },
+
+ draw: TegakiBrush.draw,
+
+ commit: TegakiBrush.commit,
+
+ brushFn: TegakiBrush.brushFn,
+
+ generateBrush: TegakiBrush.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiPencil = {
+ init: function() {
+ this.size = 1;
+ this.alpha = 1.0;
+ this.step = 0.25;
+ this.stepAcc = 0;
+ },
+
+ draw: TegakiBrush.draw,
+
+ commit: TegakiBrush.commit,
+
+ brushFn: function(x, y) {
+ var i, ctx, dest, data, len, kernel, a;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ ctx = Tegaki.ghostCtx;
+ dest = ctx.getImageData(x, y, this.brushSize, this.brushSize);
+ data = dest.data;
+ kernel = this.kernel;
+ len = kernel.length;
+
+ a = this.alpha * 255;
+
+ i = 0;
+ while (i < len) {
+ data[i] = this.rgb[0]; ++i;
+ data[i] = this.rgb[1]; ++i;
+ data[i] = this.rgb[2]; ++i;
+ if (kernel[i] > 0) {
+ data[i] = a;
+ }
+ ++i;
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ generateBrush: TegakiPen.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiEraser = {
+ init: function() {
+ this.size = 8;
+ this.alpha = 1.0;
+ this.step = 0.25;
+ this.stepAcc = 0;
+ },
+
+ draw: TegakiBrush.draw,
+
+ brushFn: function(x, y) {
+ var i, ctx, dest, data, len, kernel;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ ctx = Tegaki.activeCtx;
+ dest = ctx.getImageData(x, y, this.brushSize, this.brushSize);
+ data = dest.data;
+ kernel = this.kernel;
+ len = kernel.length;
+
+ for (i = 3; i < len; i += 4) {
+ if (kernel[i] > 0) {
+ data[i] = 0;
+ }
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ generateBrush: TegakiPen.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiDodge = {
+ init: function() {
+ this.size = 24;
+ this.alpha = 0.25;
+ this.alphaDamp = 0.05;
+ this.step = 0.25;
+ this.stepAcc = 0;
+ },
+
+ brushFn: function(x, y) {
+ var i, a, aa, ctx, dest, data, len, kernel;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ ctx = Tegaki.activeCtx;
+ dest = ctx.getImageData(x, y, this.brushSize, this.brushSize);
+ data = dest.data;
+ kernel = this.kernel;
+ len = kernel.length;
+
+ i = 0;
+ while (i < len) {
+ aa = kernel[i + 3] * 0.3;
+ a = 1 + kernel[i + 3] / 255;
+ data[i] = data[i] * a + aa; ++i;
+ data[i] = data[i] * a + aa; ++i;
+ data[i] = data[i] * a + aa; ++i;
+ ++i;
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ draw: TegakiBrush.draw,
+
+ generateBrush: TegakiBrush.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiBurn = {
+ init: TegakiDodge.init,
+
+ brushFn: function(x, y) {
+ var i, a, ctx, dest, data, len, kernel;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ ctx = Tegaki.activeCtx;
+ dest = ctx.getImageData(x, y, this.brushSize, this.brushSize);
+ data = dest.data;
+ kernel = this.kernel;
+ len = kernel.length;
+
+ i = 0;
+ while (i < len) {
+ a = 1 - kernel[i + 3] / 255;
+ data[i] = data[i] * a; ++i;
+ data[i] = data[i] * a; ++i;
+ data[i] = data[i] * a; ++i;
+ ++i;
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ draw: TegakiBrush.draw,
+
+ generateBrush: TegakiDodge.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiBlur = {
+ init: TegakiDodge.init,
+
+ brushFn: function(x, y) {
+ var i, j, ctx, src, size, srcData, dest, destData, lim, kernel,
+ sx, sy, r, g, b, a, aa, acc, kx, ky;
+
+ x = 0 | x;
+ y = 0 | y;
+
+ size = this.brushSize;
+ ctx = Tegaki.activeCtx;
+ src = ctx.getImageData(x, y, size, size);
+ srcData = src.data;
+ dest = ctx.createImageData(size, size);
+ destData = dest.data;
+ kernel = this.kernel;
+ lim = size - 1;
+
+ for (sx = 0; sx < size; ++sx) {
+ for (sy = 0; sy < size; ++sy) {
+ r = g = b = a = acc = 0;
+ i = (sy * size + sx) * 4;
+ if (kernel[(sy * size + sx) * 4 + 3] === 0
+ || sx === 0 || sy === 0 || sx === lim || sy === lim) {
+ destData[i] = srcData[i]; ++i;
+ destData[i] = srcData[i]; ++i;
+ destData[i] = srcData[i]; ++i;
+ destData[i] = srcData[i];
+ continue;
+ }
+ for (kx = -1; kx < 2; ++kx) {
+ for (ky = -1; ky < 2; ++ky) {
+ j = ((sy - ky) * size + (sx - kx)) * 4;
+ aa = srcData[j + 3];
+ acc += aa;
+ r += srcData[j] * aa; ++j;
+ g += srcData[j] * aa; ++j;
+ b += srcData[j] * aa; ++j;
+ a += srcData[j];
+ }
+ }
+ destData[i] = r / acc; ++i;
+ destData[i] = g / acc; ++i;
+ destData[i] = b / acc; ++i;
+ destData[i] = a / 9;
+ }
+ }
+
+ ctx.putImageData(dest, x, y);
+ },
+
+ draw: TegakiBrush.draw,
+
+ generateBrush: TegakiDodge.generateBrush,
+
+ setSize: TegakiBrush.setSize,
+
+ setAlpha: TegakiBrush.setAlpha,
+
+ setColor: TegakiBrush.setColor,
+
+ set: TegakiBrush.set
+};
+
+var TegakiHistory = {
+ maxSize: 10,
+
+ undoStack: [],
+ redoStack: [],
+
+ pendingAction: null,
+
+ clear: function() {
+ this.undoStack = [];
+ this.redoStack = [];
+ this.pendingAction = null;
+ },
+
+ push: function(action) {
+ this.undoStack.push(action);
+
+ if (this.undoStack.length > this.maxSize) {
+ this.undoStack.shift();
+ }
+
+ if (this.redoStack.length > 0) {
+ this.redoStack = [];
+ }
+ },
+
+ undo: function() {
+ var action;
+
+ if (!this.undoStack.length) {
+ return;
+ }
+
+ action = this.undoStack.pop();
+ action.undo();
+
+ this.redoStack.push(action);
+ },
+
+ redo: function() {
+ var action;
+
+ if (!this.redoStack.length) {
+ return;
+ }
+
+ action = this.redoStack.pop();
+ action.redo();
+
+ this.undoStack.push(action);
+ }
+};
+
+var TegakiHistoryActions = {
+ Draw: function(layerId) {
+ this.canvasBefore = null;
+ this.canvasAfter = null;
+ this.layerId = layerId;
+ },
+
+ DestroyLayers: function(indices, layers) {
+ this.indices = indices;
+ this.layers = layers;
+ this.canvasBefore = null;
+ this.canvasAfter = null;
+ this.layerId = null;
+ },
+
+ AddLayer: function(layerId) {
+ this.layerId = layerId;
+ },
+
+ MoveLayer: function(layerId, up) {
+ this.layerId = layerId;
+ this.up = up;
+ }
+};
+
+TegakiHistoryActions.Draw.prototype.addCanvasState = function(canvas, type) {
+ if (type) {
+ this.canvasAfter = T$.copyCanvas(canvas);
+ }
+ else {
+ this.canvasBefore = T$.copyCanvas(canvas);
+ }
+};
+
+TegakiHistoryActions.Draw.prototype.exec = function(type) {
+ var i, layer;
+
+ for (i in Tegaki.layers) {
+ layer = Tegaki.layers[i];
+
+ if (layer.id === this.layerId) {
+ layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
+ layer.ctx.drawImage(type ? this.canvasAfter: this.canvasBefore, 0, 0);
+ }
+ }
+};
+
+TegakiHistoryActions.Draw.prototype.undo = function() {
+ this.exec(0);
+};
+
+TegakiHistoryActions.Draw.prototype.redo = function() {
+ this.exec(1);
+};
+
+TegakiHistoryActions.DestroyLayers.prototype.undo = function() {
+ var i, ii, len, layers, idx, layer, frag;
+
+ layers = new Array(len);
+
+ for (i = 0; (idx = this.indices[i]) !== undefined; ++i) {
+ layers[idx] = this.layers[i];
+ }
+
+ i = ii = 0;
+ len = Tegaki.layers.length + this.layers.length;
+ frag = T$.frag();
+
+ while (i < len) {
+ if (!layers[i]) {
+ layer = layers[i] = Tegaki.layers[ii];
+ Tegaki.layersCnt.removeChild(layer.canvas);
+ ++ii;
+ }
+
+ if (this.layerId && layer.id === this.layerId) {
+ layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
+ layer.ctx.drawImage(this.canvasBefore, 0, 0);
+ }
+
+ frag.appendChild(layers[i].canvas);
+
+ ++i;
+ }
+
+ Tegaki.layersCnt.insertBefore(frag, Tegaki.canvas.nextElementSibling);
+
+ Tegaki.layers = layers;
+
+ Tegaki.setActiveLayer();
+
+ Tegaki.rebuildLayerCtrl();
+};
+
+TegakiHistoryActions.DestroyLayers.prototype.redo = function() {
+ var i, layer, ids = [];
+
+ for (i = 0; layer = this.layers[i]; ++i) {
+ ids.push(layer.id);
+ }
+
+ if (this.layerId) {
+ ids.push(this.layerId);
+ Tegaki.mergeLayers(ids);
+ }
+ else {
+ Tegaki.deleteLayers(ids);
+ }
+};
+
+TegakiHistoryActions.MoveLayer.prototype.undo = function() {
+ Tegaki.setActiveLayer(this.layerId);
+ Tegaki.moveLayer(this.layerId, !this.up);
+};
+
+TegakiHistoryActions.MoveLayer.prototype.redo = function() {
+ Tegaki.setActiveLayer(this.layerId);
+ Tegaki.moveLayer(this.layerId, this.up);
+};
+
+TegakiHistoryActions.AddLayer.prototype.undo = function() {
+ Tegaki.deleteLayers([this.layerId]);
+ Tegaki.layerIndex--;
+};
+
+TegakiHistoryActions.AddLayer.prototype.redo = function() {
+ Tegaki.addLayer();
+ Tegaki.setActiveLayer();
+};
+
+var T$ = {
+ docEl: document.documentElement,
+
+ id: function(id) {
+ return document.getElementById(id);
+ },
+
+ cls: function(klass, root) {
+ return (root || document).getElementsByClassName(klass);
+ },
+
+ on: function(o, e, h) {
+ o.addEventListener(e, h, false);
+ },
+
+ off: function(o, e, h) {
+ o.removeEventListener(e, h, false);
+ },
+
+ el: function(name) {
+ return document.createElement(name);
+ },
+
+ frag: function() {
+ return document.createDocumentFragment();
+ },
+
+ extend: function(destination, source) {
+ for (var key in source) {
+ destination[key] = source[key];
+ }
+ },
+
+ selectedOptions: function(el) {
+ var i, opt, sel;
+
+ if (el.selectedOptions) {
+ return el.selectedOptions;
+ }
+
+ sel = [];
+
+ for (i = 0; opt = el.options[i]; ++i) {
+ if (opt.selected) {
+ sel.push(opt);
+ }
+ }
+
+ return sel;
+ },
+
+ copyCanvas: function(source) {
+ var canvas = T$.el('canvas');
+ canvas.width = source.width;
+ canvas.height = source.height;
+ canvas.getContext('2d').drawImage(source, 0, 0);
+
+ return canvas;
+ }
+};
+
+var TegakiStrings = {
+ // Messages
+ badDimensions: 'Invalid dimensions.',
+ promptWidth: 'Canvas width in pixels',
+ promptHeight: 'Canvas height in pixels',
+ confirmDelLayers: 'Delete selected layers?',
+ errorMergeOneLayer: 'You need to select at least 2 layers.',
+ confirmMergeLayers: 'Merge selected layers?',
+ errorLoadImage: 'Could not load the image.',
+ noActiveLayer: 'No active layer.',
+ hiddenActiveLayer: 'The active layer is not visible.',
+ confirmCancel: 'Are you sure? Your work will be lost.',
+ confirmChangeCanvas: 'Changing the canvas will clear all layers and history.',
+
+ // UI
+ color: 'Color',
+ size: 'Size',
+ alpha: 'Opacity',
+ layers: 'Layers',
+ addLayer: 'Add layer',
+ delLayers: 'Delete layers',
+ mergeLayers: 'Merge layers',
+ showHideLayer: 'Toggle visibility',
+ moveLayerUp: 'Move up',
+ moveLayerDown: 'Move down',
+ tool: 'Tool',
+ changeCanvas: 'Change canvas',
+ blank: 'Blank',
+ newCanvas: 'New',
+ undo: 'Undo',
+ redo: 'Redo',
+ close: 'Close',
+ finish: 'Finish',
+
+ // Tools
+ pen: 'Pen',
+ pencil: 'Pencil',
+ airbrush: 'Airbrush',
+ pipette: 'Pipette',
+ dodge: 'Dodge',
+ burn: 'Burn',
+ blur: 'Blur',
+ eraser: 'Eraser'
+};
+
+var Tegaki = {
+ VERSION: '0.0.1',
+
+ bg: null,
+ cnt: null,
+ canvas: null,
+ ctx: null,
+ layers: [],
+ layersCnt: null,
+ ghostCanvas: null,
+ ghostCtx: null,
+ activeCtx: null,
+ activeLayer: null,
+ layerIndex: null,
+
+ isPainting: false,
+ isErasing: false,
+ isColorPicking: false,
+
+ offsetX: 0,
+ offsetY: 0,
+
+ TWOPI: 2 * Math.PI,
+
+ tools: {
+ pencil: TegakiPencil,
+ pen: TegakiPen,
+ airbrush: TegakiAirbrush,
+ pipette: TegakiPipette,
+ dodge: TegakiDodge,
+ burn: TegakiBurn,
+ blur: TegakiBlur,
+ eraser: TegakiEraser
+ },
+
+ tool: null,
+ toolColor: '#000000',
+
+ bgColor: '#ffffff',
+ maxSize: 32,
+ maxLayers: 25,
+ baseWidth: null,
+ baseHeight: null,
+
+ onDoneCb: null,
+ onCancelCb: null,
+
+ open: function(opts) {
+ var bg, cnt, el, el2, tool, lbl, btn, ctrl, canvas, grp, self = Tegaki;
+
+ if (self.bg) {
+ self.resume();
+ return;
+ }
+
+ if (opts.bgColor) {
+ self.bgColor = opts.bgColor;
+ }
+
+ self.onDoneCb = opts.onDone;
+ self.onCancelCb = opts.onCancel;
+
+ cnt = T$.el('div');
+ cnt.id = 'tegaki-cnt';
+
+ canvas = T$.el('canvas');
+ canvas.id = 'tegaki-canvas';
+ canvas.width = self.baseWidth = opts.width;
+ canvas.height = self.baseHeight = opts.height;
+
+ el = T$.el('div');
+ el.id = 'tegaki-layers';
+ el.appendChild(canvas);
+ self.layersCnt = el;
+
+ cnt.appendChild(el);
+
+ ctrl = T$.el('div');
+ ctrl.id = 'tegaki-ctrl';
+
+ // Colorpicker
+ grp = T$.el('div');
+ grp.className = 'tegaki-ctrlgrp';
+ el = T$.el('input');
+ el.id = 'tegaki-color';
+ el.value = self.toolColor;
+ try {
+ el.type = 'color';
+ } catch(e) {
+ el.type = 'text';
+ }
+ lbl = T$.el('div');
+ lbl.className = 'tegaki-label';
+ lbl.textContent = TegakiStrings.color;
+ grp.appendChild(lbl);
+ T$.on(el, 'change', self.onColorChange);
+ grp.appendChild(el);
+ ctrl.appendChild(grp);
+
+ // Size control
+ grp = T$.el('div');
+ grp.className = 'tegaki-ctrlgrp';
+ el = T$.el('input');
+ el.id = 'tegaki-size';
+ el.min = 1;
+ el.max = self.maxSize;
+ el.type = 'range';
+ lbl = T$.el('div');
+ lbl.className = 'tegaki-label';
+ lbl.textContent = TegakiStrings.size;
+ grp.appendChild(lbl);
+ T$.on(el, 'change', self.onSizeChange);
+ grp.appendChild(el);
+ ctrl.appendChild(grp);
+
+ // Alpha control
+ grp = T$.el('div');
+ grp.className = 'tegaki-ctrlgrp';
+ el = T$.el('input');
+ el.id = 'tegaki-alpha';
+ el.min = 0;
+ el.max = 1;
+ el.step = 0.01;
+ el.type = 'range';
+ lbl = T$.el('div');
+ lbl.className = 'tegaki-label';
+ lbl.textContent = TegakiStrings.alpha;
+ grp.appendChild(lbl);
+ T$.on(el, 'change', self.onAlphaChange);
+ grp.appendChild(el);
+ ctrl.appendChild(grp);
+
+ // Layer control
+ grp = T$.el('div');
+ grp.className = 'tegaki-ctrlgrp';
+ grp.id = 'tegaki-layer-grp';
+ el = T$.el('select');
+ el.id = 'tegaki-layer';
+ el.multiple = true;
+ el.size = 3;
+ lbl = T$.el('div');
+ lbl.className = 'tegaki-label';
+ lbl.textContent = TegakiStrings.layers;
+ grp.appendChild(lbl);
+ T$.on(el, 'change', self.onLayerChange);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.title = TegakiStrings.addLayer;
+ el.className = 'tegaki-icon tegaki-plus';
+ T$.on(el, 'click', self.onLayerAdd);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.title = TegakiStrings.delLayers;
+ el.className = 'tegaki-icon tegaki-minus';
+ T$.on(el, 'click', self.onLayerDelete);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.id = 'tegaki-layer-visibility';
+ el.title = TegakiStrings.showHideLayer;
+ el.className = 'tegaki-icon tegaki-eye';
+ T$.on(el, 'click', self.onLayerVisibilityChange);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.id = 'tegaki-layer-merge';
+ el.title = TegakiStrings.mergeLayers;
+ el.className = 'tegaki-icon tegaki-level-down';
+ T$.on(el, 'click', self.onMergeLayers);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.id = 'tegaki-layer-up';
+ el.title = TegakiStrings.moveLayerUp;
+ el.setAttribute('data-up', '1');
+ el.className = 'tegaki-icon tegaki-up-open';
+ T$.on(el, 'click', self.onMoveLayer);
+ grp.appendChild(el);
+ el = T$.el('span');
+ el.id = 'tegaki-layer-down';
+ el.title = TegakiStrings.moveLayerDown;
+ el.className = 'tegaki-icon tegaki-down-open';
+ T$.on(el, 'click', self.onMoveLayer);
+ grp.appendChild(el);
+ ctrl.appendChild(grp);
+
+ // Tool selector
+ grp = T$.el('div');
+ grp.className = 'tegaki-ctrlgrp';
+ el = T$.el('select');
+ el.id = 'tegaki-tool';
+ for (tool in Tegaki.tools) {
+ el2 = T$.el('option');
+ el2.value = tool;
+ el2.textContent = TegakiStrings[tool];
+ el.appendChild(el2);
+ }
+ lbl = T$.el('div');
+ lbl.className = 'tegaki-label';
+ lbl.textContent = TegakiStrings.tool;
+ grp.appendChild(lbl);
+ T$.on(el, 'change', self.onToolChange);
+ grp.appendChild(el);
+ ctrl.appendChild(grp);
+
+ cnt.appendChild(ctrl);
+
+ el = T$.el('div');
+ el.id = 'tegaki-menu-bar';
+
+ if (opts.canvasOptions) {
+ btn = T$.el('select');
+ btn.id = 'tegaki-canvas-select';
+ btn.title = TegakiStrings.changeCanvas;
+ btn.innerHTML = '<option value="0">' + TegakiStrings.blank + '</option>';
+ opts.canvasOptions(btn);
+ T$.on(btn, 'change', Tegaki.onCanvasSelected);
+ T$.on(btn, 'focus', Tegaki.onCanvasSelectFocused);
+ el.appendChild(btn);
+ }
+
+ btn = T$.el('span');
+ btn.className = 'tegaki-tb-btn';
+ btn.textContent = TegakiStrings.newCanvas;
+ T$.on(btn, 'click', Tegaki.onNewClick);
+ el.appendChild(btn);
+
+ btn = T$.el('span');
+ btn.className = 'tegaki-tb-btn';
+ btn.textContent = TegakiStrings.undo;
+ T$.on(btn, 'click', Tegaki.onUndoClick);
+ el.appendChild(btn);
+
+ btn = T$.el('span');
+ btn.className = 'tegaki-tb-btn';
+ btn.textContent = TegakiStrings.redo;
+ T$.on(btn, 'click', Tegaki.onRedoClick);
+ el.appendChild(btn);
+
+ btn = T$.el('span');
+ btn.className = 'tegaki-tb-btn';
+ btn.textContent = TegakiStrings.close;
+ T$.on(btn, 'click', Tegaki.onCancelClick);
+ el.appendChild(btn);
+
+ btn = T$.el('span');
+ btn.id = 'tegaki-finish-btn';
+ btn.className = 'tegaki-tb-btn';
+ btn.textContent = TegakiStrings.finish;
+ T$.on(btn, 'click', Tegaki.onDoneClick);
+ el.appendChild(btn);
+
+ cnt.appendChild(el);
+
+ bg = T$.el('div');
+ bg.id = 'tegaki';
+ self.bg = bg;
+ bg.appendChild(cnt);
+ document.body.appendChild(bg);
+ document.body.classList.add('tegaki-backdrop');
+
+ el = T$.el('canvas');
+ el.id = 'tegaki-ghost-layer';
+ el.width = canvas.width;
+ el.height = canvas.height;
+ self.ghostCanvas = el;
+ self.ghostCtx = el.getContext('2d');
+
+ self.cnt = cnt;
+ self.centerCnt();
+
+ self.canvas = canvas;
+
+ self.ctx = canvas.getContext('2d');
+ self.ctx.fillStyle = self.bgColor;
+ self.ctx.fillRect(0, 0, opts.width, opts.height);
+
+ self.addLayer();
+
+ self.setActiveLayer();
+
+ self.initTools();
+
+ self.setTool('pencil');
+
+ self.updateUI();
+
+ self.updateCursor();
+ self.updatePosOffset();
+
+ T$.on(self.bg, 'mousemove', self.onMouseMove);
+ T$.on(self.bg, 'mousedown', self.onMouseDown);
+ T$.on(self.layersCnt, 'contextmenu', self.onDummy);
+
+ T$.on(document, 'mouseup', self.onMouseUp);
+ T$.on(window, 'resize', self.updatePosOffset);
+ T$.on(window, 'scroll', self.updatePosOffset);
+ },
+
+ initTools: function() {
+ var tool;
+
+ for (tool in Tegaki.tools) {
+ (tool = Tegaki.tools[tool]) && tool.init && tool.init();
+ }
+ },
+
+ hexToRgb: function(hex) {
+ var c = hex.match(/^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i);
+
+ if (c) {
+ return [
+ parseInt(c[1], 16),
+ parseInt(c[2], 16),
+ parseInt(c[3], 16)
+ ];
+ }
+
+ return null;
+ },
+
+ centerCnt: function() {
+ var aabb, cnt;
+
+ cnt = Tegaki.cnt;
+ aabb = cnt.getBoundingClientRect();
+
+ if (aabb.width > T$.docEl.clientWidth || aabb.height > T$.docEl.clientHeight) {
+ if (aabb.width > T$.docEl.clientWidth) {
+ cnt.classList.add('tegaki-overflow-x');
+ }
+ if (aabb.height > T$.docEl.clientHeight) {
+ cnt.classList.add('tegaki-overflow-y');
+ }
+ }
+ else {
+ cnt.classList.remove('tegaki-overflow-x');
+ cnt.classList.remove('tegaki-overflow-y');
+ }
+
+ cnt.style.marginTop = -Math.round(aabb.height / 2) + 'px';
+ cnt.style.marginLeft = -Math.round(aabb.width / 2) + 'px';
+ },
+
+ getCursorPos: function(e, axis) {
+ if (axis === 0) {
+ return e.clientX + window.pageXOffset + Tegaki.bg.scrollLeft - Tegaki.offsetX;
+ }
+ else {
+ return e.clientY + window.pageYOffset + Tegaki.bg.scrollTop - Tegaki.offsetY;
+ }
+ },
+
+ resume: function() {
+ Tegaki.bg.classList.remove('tegaki-hidden');
+ document.body.classList.add('tegaki-backdrop');
+
+ Tegaki.centerCnt();
+ Tegaki.updatePosOffset();
+
+ T$.on(document, 'mouseup', Tegaki.onMouseUp);
+ T$.on(window, 'resize', Tegaki.updatePosOffset);
+ T$.on(window, 'scroll', Tegaki.updatePosOffset);
+ },
+
+ hide: function() {
+ Tegaki.bg.classList.add('tegaki-hidden');
+ document.body.classList.remove('tegaki-backdrop');
+
+ T$.off(document, 'mouseup', Tegaki.onMouseUp);
+ T$.off(window, 'resize', Tegaki.updatePosOffset);
+ T$.off(window, 'scroll', Tegaki.updatePosOffset);
+ },
+
+ destroy: function() {
+ T$.off(Tegaki.bg, 'mousemove', Tegaki.onMouseMove);
+ T$.off(Tegaki.bg, 'mousedown', Tegaki.onMouseDown);
+ T$.off(Tegaki.layersCnt, 'contextmenu', Tegaki.onDummy);
+
+ T$.off(document, 'mouseup', Tegaki.onMouseUp);
+ T$.off(window, 'resize', Tegaki.updatePosOffset);
+ T$.off(window, 'scroll', Tegaki.updatePosOffset);
+
+ Tegaki.bg.parentNode.removeChild(Tegaki.bg);
+
+ TegakiHistory.clear();
+
+ document.body.classList.remove('tegaki-backdrop');
+
+ Tegaki.bg = null;
+ Tegaki.cnt = null;
+ Tegaki.canvas = null;
+ Tegaki.ctx = null;
+ Tegaki.layers = [];
+ Tegaki.layerIndex = 0;
+ Tegaki.activeCtx = null;
+ },
+
+ flatten: function() {
+ var i, layer, canvas, ctx;
+
+ canvas = T$.el('canvas');
+ canvas.width = Tegaki.canvas.width;
+ canvas.height = Tegaki.canvas.height;
+
+ ctx = canvas.getContext('2d');
+
+ ctx.drawImage(Tegaki.canvas, 0, 0);
+
+ for (i = 0; layer = Tegaki.layers[i]; ++i) {
+ if (layer.canvas.classList.contains('tegaki-hidden')) {
+ continue;
+ }
+ ctx.drawImage(layer.canvas, 0, 0);
+ }
+
+ return canvas;
+ },
+
+ updateUI: function(type) {
+ var i, ary, el, tool = Tegaki.tool;
+
+ ary = type ? [type] : ['size', 'alpha', 'color'];
+
+ for (i = 0; type = ary[i]; ++i) {
+ el = T$.id('tegaki-' + type);
+ el.value = type === 'color' ? Tegaki.toolColor : tool[type];
+
+ if (el.type === 'range') {
+ el.previousElementSibling.setAttribute('data-value', tool[type]);
+ }
+ }
+ },
+
+ rebuildLayerCtrl: function() {
+ var i, layer, sel, opt;
+
+ sel = T$.id('tegaki-layer');
+
+ sel.textContent = '';
+
+ for (i = Tegaki.layers.length - 1; layer = Tegaki.layers[i]; i--) {
+ opt = T$.el('option');
+ opt.value = layer.id;
+ opt.textContent = layer.name;
+ sel.appendChild(opt);
+ }
+ },
+
+ getColorAt: function(ctx, posX, posY) {
+ var rgba = ctx.getImageData(posX, posY, 1, 1).data;
+
+ return '#'
+ + ('0' + rgba[0].toString(16)).slice(-2)
+ + ('0' + rgba[1].toString(16)).slice(-2)
+ + ('0' + rgba[2].toString(16)).slice(-2);
+ },
+
+ renderCircle: function(r) {
+ var i, canvas, ctx, d, e, x, y, dx, dy, idata, data, c, color;
+
+ e = 1 - r;
+ dx = 0;
+ dy = -2 * r;
+ x = 0;
+ y = r;
+ d = 33;
+ c = 16;
+
+ canvas = T$.el('canvas');
+ canvas.width = canvas.height = d;
+ ctx = canvas.getContext('2d');
+ idata = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
+ data = idata.data;
+
+ color = 255;
+
+ data[(c + (c + r) * d) * 4 + 3] = color;
+ data[(c + (c - r) * d) * 4 + 3] = color;
+ data[(c + r + c * d) * 4 + 3] = color;
+ data[(c - r + c * d) * 4 + 3] = color;
+
+ while (x < y) {
+ if (e >= 0) {
+ y--;
+ dy += 2;
+ e += dy;
+ }
+
+ ++x;
+ dx += 2;
+ e += dx;
+
+ data[(c + x + (c + y) * d) * 4 + 3] = color;
+ data[(c - x + (c + y) * d) * 4 + 3] = color;
+ data[(c + x + (c - y) * d) * 4 + 3] = color;
+ data[(c - x + (c - y) * d) * 4 + 3] = color;
+ data[(c + y + (c + x) * d) * 4 + 3] = color;
+ data[(c - y + (c + x) * d) * 4 + 3] = color;
+ data[(c + y + (c - x) * d) * 4 + 3] = color;
+ data[(c - y + (c - x) * d) * 4 + 3] = color;
+ }
+
+ if (r > 0) {
+ for (i = 0; i < 3; ++i) {
+ data[(c + c * d) * 4 + i] = 127;
+ }
+ data[(c + c * d) * 4 + i] = color;
+ }
+
+ ctx.putImageData(idata, 0, 0);
+
+ return canvas;
+ },
+
+ setToolSize: function(size) {
+ Tegaki.tool.setSize && Tegaki.tool.setSize(size);
+ Tegaki.updateCursor();
+ },
+
+ setToolAlpha: function(alpha) {
+ Tegaki.tool.setAlpha && Tegaki.tool.setAlpha(alpha);
+ },
+
+ setToolColor: function(color) {
+ Tegaki.toolColor = color;
+ Tegaki.tool.setColor && Tegaki.tool.setColor(color);
+ Tegaki.updateCursor();
+ },
+
+ setTool: function(tool) {
+ tool = Tegaki.tools[tool];
+ Tegaki.tool = tool;
+ tool.set && tool.set();
+ },
+
+ debugDumpPixelData: function(canvas) {
+ var i, idata, data, len, out, el;
+
+ idata = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height);
+ data = idata.data;
+ len = data.length;
+
+ out = '';
+
+ for (i = 0; i < len; i += 4) {
+ out += data[i] + ' ' + data[i+1] + ' ' + data[i+2] + ' ' + data[i+3] + '%0a';
+ }
+
+ el = document.createElement('a');
+ el.href = 'data:,' + out;
+ el.download = 'dump.txt';
+ document.body.appendChild(el);
+ el.click();
+ document.body.removeChild(el);
+ },
+
+ debugDrawColors: function(sat) {
+ var i, ctx, grad, a;
+
+ Tegaki.resizeCanvas(360, 360);
+
+ ctx = Tegaki.activeCtx;
+ a = ctx.globalAlpha;
+ ctx.globalAlpha = 1;
+
+ ctx.fillStyle = '#000000';
+ ctx.fillRect(0, 0, 360, 360);
+
+ for (i = 0; i < 360; ++i) {
+ if (sat) {
+ grad = ctx.createLinearGradient(0, 0, 10, 360);
+ grad.addColorStop(0, 'hsl(' + i + ', 0%, ' + '50%)');
+ grad.addColorStop(1, 'hsl(' + i + ', 100%, ' + '50%)');
+ ctx.strokeStyle = grad;
+ }
+ else {
+ ctx.strokeStyle = 'hsl(' + i + ', 100%, ' + '50%)';
+ }
+ ctx.beginPath();
+ ctx.moveTo(i, 0);
+ ctx.lineTo(i, 360);
+ ctx.stroke();
+ ctx.closePath();
+ }
+
+ if (!sat) {
+ grad = ctx.createLinearGradient(0, 0, 10, 360);
+ grad.addColorStop(0, '#000000');
+ grad.addColorStop(1, 'rgba(0,0,0,0)');
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, 360, 360);
+ }
+
+ ctx.globalAlpha = a;
+ },
+
+ onNewClick: function() {
+ var width, height, tmp;
+
+ width = prompt(TegakiStrings.promptWidth, Tegaki.canvas.width);
+ if (!width) { return; }
+
+ height = prompt(TegakiStrings.promptHeight, Tegaki.canvas.height);
+ if (!height) { return; }
+
+ width = +width;
+ height = +height;
+
+ if (width < 1 || height < 1) {
+ alert(TegakiStrings.badDimensions);
+ return;
+ }
+
+ tmp = {};
+ Tegaki.copyContextState(Tegaki.activeCtx, tmp);
+ Tegaki.resizeCanvas(width, height);
+ Tegaki.copyContextState(tmp, Tegaki.activeCtx);
+
+ TegakiHistory.clear();
+ Tegaki.centerCnt();
+ Tegaki.updatePosOffset();
+ },
+
+ onUndoClick: function() {
+ TegakiHistory.undo();
+ },
+
+ onRedoClick: function() {
+ TegakiHistory.redo();
+ },
+
+ onDoneClick: function() {
+ Tegaki.hide();
+ Tegaki.onDoneCb();
+ },
+
+ onCancelClick: function() {
+ if (!confirm(TegakiStrings.confirmCancel)) {
+ return;
+ }
+
+ Tegaki.destroy();
+ Tegaki.onCancelCb();
+ },
+
+ onColorChange: function() {
+ Tegaki.setToolColor(this.value);
+ },
+
+ onSizeChange: function() {
+ this.previousElementSibling.setAttribute('data-value', this.value);
+ Tegaki.setToolSize(+this.value);
+ },
+
+ onAlphaChange: function() {
+ this.previousElementSibling.setAttribute('data-value', this.value);
+ Tegaki.setToolAlpha(+this.value);
+ },
+
+ onLayerChange: function() {
+ var selectedOptions = T$.selectedOptions(this);
+
+ if (selectedOptions.length > 1) {
+ Tegaki.activeLayer = null;
+ }
+ else {
+ Tegaki.setActiveLayer(+this.value);
+ }
+ },
+
+ onLayerAdd: function() {
+ TegakiHistory.push(Tegaki.addLayer());
+ Tegaki.setActiveLayer();
+ },
+
+ onLayerDelete: function() {
+ var i, ary, sel, opt, selectedOptions, action;
+
+ sel = T$.id('tegaki-layer');
+
+ selectedOptions = T$.selectedOptions(sel);
+
+ if (Tegaki.layers.length === selectedOptions.length) {
+ return;
+ }
+
+ if (!confirm(TegakiStrings.confirmDelLayers)) {
+ return;
+ }
+
+ if (selectedOptions.length > 1) {
+ ary = [];
+
+ for (i = 0; opt = selectedOptions[i]; ++i) {
+ ary.push(+opt.value);
+ }
+ }
+ else {
+ ary = [+sel.value];
+ }
+
+ action = Tegaki.deleteLayers(ary);
+
+ TegakiHistory.push(action);
+ },
+
+ onLayerVisibilityChange: function() {
+ var i, ary, sel, opt, flag, selectedOptions;
+
+ sel = T$.id('tegaki-layer');
+
+ selectedOptions = T$.selectedOptions(sel);
+
+ if (selectedOptions.length > 1) {
+ ary = [];
+
+ for (i = 0; opt = selectedOptions[i]; ++i) {
+ ary.push(+opt.value);
+ }
+ }
+ else {
+ ary = [+sel.value];
+ }
+
+ flag = !Tegaki.getLayerById(ary[0]).visible;
+
+ Tegaki.setLayerVisibility(ary, flag);
+ },
+
+ onMergeLayers: function() {
+ var i, ary, sel, opt, selectedOptions, action;
+
+ sel = T$.id('tegaki-layer');
+
+ selectedOptions = T$.selectedOptions(sel);
+
+ if (selectedOptions.length > 1) {
+ ary = [];
+
+ for (i = 0; opt = selectedOptions[i]; ++i) {
+ ary.push(+opt.value);
+ }
+ }
+ else {
+ ary = [+sel.value];
+ }
+
+ if (ary.length < 2) {
+ alert(TegakiStrings.errorMergeOneLayer);
+ return;
+ }
+
+ if (!confirm(TegakiStrings.confirmMergeLayers)) {
+ return;
+ }
+
+ action = Tegaki.mergeLayers(ary);
+
+ TegakiHistory.push(action);
+ },
+
+ onMoveLayer: function(e) {
+ var id, action, sel;
+
+ sel = T$.id('tegaki-layer');
+
+ id = +sel.options[sel.selectedIndex].value;
+
+ if (action = Tegaki.moveLayer(id, e.target.hasAttribute('data-up'))) {
+ TegakiHistory.push(action);
+ }
+ },
+
+ onToolChange: function() {
+ Tegaki.setTool(this.value);
+ Tegaki.updateUI();
+ Tegaki.updateCursor();
+ },
+
+ onCanvasSelected: function() {
+ var img;
+
+ if (!confirm(TegakiStrings.confirmChangeCanvas)) {
+ this.selectedIndex = +this.getAttribute('data-current');
+ return;
+ }
+
+ if (this.value === '0') {
+ Tegaki.ctx.fillStyle = Tegaki.bgColor;
+ Tegaki.ctx.fillRect(0, 0, Tegaki.baseWidth, Tegaki.baseHeight);
+ }
+ else {
+ img = T$.el('img');
+ img.onload = Tegaki.onImageLoaded;
+ img.onerror = Tegaki.onImageError;
+ this.disabled = true;
+ img.src = this.value;
+ }
+ },
+
+ onImageLoaded: function() {
+ var el, tmp = {};
+
+ el = T$.id('tegaki-canvas-select');
+ el.setAttribute('data-current', el.selectedIndex);
+ el.disabled = false;
+
+ Tegaki.copyContextState(Tegaki.activeCtx, tmp);
+ Tegaki.resizeCanvas(this.naturalWidth, this.naturalHeight);
+ Tegaki.activeCtx.drawImage(this, 0, 0);
+ Tegaki.copyContextState(tmp, Tegaki.activeCtx);
+
+ TegakiHistory.clear();
+ Tegaki.centerCnt();
+ Tegaki.updatePosOffset();
+ },
+
+ onImageError: function() {
+ var el;
+
+ el = T$.id('tegaki-canvas-select');
+ el.selectedIndex = +el.getAttribute('data-current');
+ el.disabled = false;
+
+ alert(TegakiStrings.errorLoadImage);
+ },
+
+ resizeCanvas: function(width, height) {
+ var i, layer;
+
+ Tegaki.canvas.width = width;
+ Tegaki.canvas.height = height;
+ Tegaki.ghostCanvas.width = width;
+ Tegaki.ghostCanvas.height = height;
+
+ Tegaki.ctx.fillStyle = Tegaki.bgColor;
+ Tegaki.ctx.fillRect(0, 0, width, height);
+
+ for (i = 0; layer = Tegaki.layers[i]; ++i) {
+ Tegaki.layersCnt.removeChild(layer.canvas);
+ }
+
+ Tegaki.activeCtx = null;
+ Tegaki.layers = [];
+ Tegaki.layerIndex = 0;
+ T$.id('tegaki-layer').textContent = '';
+
+ Tegaki.addLayer();
+ Tegaki.setActiveLayer();
+ },
+
+ getLayerIndex: function(id) {
+ var i, layer, layers = Tegaki.layers;
+
+ for (i = 0; layer = layers[i]; ++i) {
+ if (layer.id === id) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ getLayerById: function(id) {
+ return Tegaki.layers[Tegaki.getLayerIndex(id)];
+ },
+
+ addLayer: function() {
+ var id, cnt, opt, canvas, layer, nodes, last;
+
+ canvas = T$.el('canvas');
+ canvas.className = 'tegaki-layer';
+ canvas.width = Tegaki.canvas.width;
+ canvas.height = Tegaki.canvas.height;
+
+ id = ++Tegaki.layerIndex;
+
+ layer = {
+ id: id,
+ name: 'Layer ' + id,
+ canvas: canvas,
+ ctx: canvas.getContext('2d'),
+ visible: true,
+ empty: true,
+ opacity: 1.0
+ };
+
+ Tegaki.layers.push(layer);
+
+ cnt = T$.id('tegaki-layer');
+ opt = T$.el('option');
+ opt.value = layer.id;
+ opt.textContent = layer.name;
+ cnt.insertBefore(opt, cnt.firstElementChild);
+
+ nodes = T$.cls('tegaki-layer', Tegaki.layersCnt);
+
+ if (nodes.length) {
+ last = nodes[nodes.length - 1];
+ }
+ else {
+ last = Tegaki.canvas;
+ }
+
+ Tegaki.layersCnt.insertBefore(canvas, last.nextElementSibling);
+
+ return new TegakiHistoryActions.AddLayer(id);
+ },
+
+ deleteLayers: function(ids) {
+ var i, id, len, sel, idx, indices, layers;
+
+ sel = T$.id('tegaki-layer');
+
+ indices = [];
+ layers = [];
+
+ for (i = 0, len = ids.length; i < len; ++i) {
+ id = ids[i];
+ idx = Tegaki.getLayerIndex(id);
+ sel.removeChild(sel.options[Tegaki.layers.length - 1 - idx]);
+ Tegaki.layersCnt.removeChild(Tegaki.layers[idx].canvas);
+
+ indices.push(idx);
+ layers.push(Tegaki.layers[idx]);
+
+ Tegaki.layers.splice(idx, 1);
+ }
+
+ Tegaki.setActiveLayer();
+
+ return new TegakiHistoryActions.DestroyLayers(indices, layers);
+ },
+
+ mergeLayers: function(ids) {
+ var i, id, sel, idx, canvasBefore, destId, dest, action;
+
+ sel = T$.id('tegaki-layer');
+
+ destId = ids.pop();
+ idx = Tegaki.getLayerIndex(destId);
+ dest = Tegaki.layers[idx].ctx;
+
+ canvasBefore = T$.copyCanvas(Tegaki.layers[idx].canvas);
+
+ for (i = ids.length - 1; i >= 0; i--) {
+ id = ids[i];
+ idx = Tegaki.getLayerIndex(id);
+ dest.drawImage(Tegaki.layers[idx].canvas, 0, 0);
+ }
+
+ action = Tegaki.deleteLayers(ids);
+ action.layerId = destId;
+ action.canvasBefore = canvasBefore;
+ action.canvasAfter = T$.copyCanvas(dest.canvas);
+
+ Tegaki.setActiveLayer(destId);
+
+ return action;
+ },
+
+ moveLayer: function(id, up) {
+ var idx, sel, opt, canvas, tmp, tmpId;
+
+ sel = T$.id('tegaki-layer');
+ idx = Tegaki.getLayerIndex(id);
+
+ canvas = Tegaki.layers[idx].canvas;
+ opt = sel.options[Tegaki.layers.length - 1 - idx];
+
+ if (up) {
+ if (!Tegaki.ghostCanvas.nextElementSibling) { return false; }
+ canvas.parentNode.insertBefore(canvas,
+ Tegaki.ghostCanvas.nextElementSibling.nextElementSibling
+ );
+ opt.parentNode.insertBefore(opt, opt.previousElementSibling);
+ tmpId = idx + 1;
+ }
+ else {
+ if (canvas.previousElementSibling.id === 'tegaki-canvas') { return false; }
+ canvas.parentNode.insertBefore(canvas, canvas.previousElementSibling);
+ opt.parentNode.insertBefore(opt, opt.nextElementSibling.nextElementSibling);
+ tmpId = idx - 1;
+ }
+
+ Tegaki.updateGhostLayerPos();
+
+ tmp = Tegaki.layers[tmpId];
+ Tegaki.layers[tmpId] = Tegaki.layers[idx];
+ Tegaki.layers[idx] = tmp;
+
+ Tegaki.activeLayer = tmpId;
+
+ return new TegakiHistoryActions.MoveLayer(id, up);
+ },
+
+ setLayerVisibility: function(ids, flag) {
+ var i, len, sel, idx, layer, optIdx;
+
+ sel = T$.id('tegaki-layer');
+ optIdx = Tegaki.layers.length - 1;
+
+ for (i = 0, len = ids.length; i < len; ++i) {
+ idx = Tegaki.getLayerIndex(ids[i]);
+ layer = Tegaki.layers[idx];
+ layer.visible = flag;
+
+ if (flag) {
+ sel.options[optIdx - idx].classList.remove('tegaki-strike');
+ layer.canvas.classList.remove('tegaki-hidden');
+ }
+ else {
+ sel.options[optIdx - idx].classList.add('tegaki-strike');
+ layer.canvas.classList.add('tegaki-hidden');
+ }
+ }
+ },
+
+ setActiveLayer: function(id) {
+ var ctx, idx;
+
+ idx = id ? Tegaki.getLayerIndex(id) : Tegaki.layers.length - 1;
+
+ if (idx < 0 || idx > Tegaki.maxLayers) {
+ return;
+ }
+
+ ctx = Tegaki.layers[idx].ctx;
+
+ if (Tegaki.activeCtx) {
+ Tegaki.copyContextState(Tegaki.activeCtx, ctx);
+ }
+
+ Tegaki.activeCtx = ctx;
+ Tegaki.activeLayer = idx;
+ T$.id('tegaki-layer').selectedIndex = Tegaki.layers.length - idx - 1;
+
+ Tegaki.updateGhostLayerPos();
+ },
+
+ updateGhostLayerPos: function() {
+ Tegaki.layersCnt.insertBefore(
+ Tegaki.ghostCanvas,
+ Tegaki.activeCtx.canvas.nextElementSibling
+ );
+ },
+
+ copyContextState: function(src, dest) {
+ var i, p, props = [
+ 'lineCap', 'lineJoin', 'strokeStyle', 'fillStyle', 'globalAlpha',
+ 'lineWidth', 'globalCompositeOperation'
+ ];
+
+ for (i = 0; p = props[i]; ++i) {
+ dest[p] = src[p];
+ }
+ },
+
+ updateCursor: function() {
+ var radius;
+
+ radius = 0 | (Tegaki.tool.size / 2);
+
+ if (Tegaki.tool.noCursor || radius < 1) {
+ Tegaki.layersCnt.style.cursor = 'default';
+ return;
+ }
+
+ Tegaki.layersCnt.style.cursor = 'url("'
+ + Tegaki.renderCircle(radius).toDataURL('image/png')
+ + '") 16 16, default';
+ },
+
+ updatePosOffset: function() {
+ var aabb = Tegaki.canvas.getBoundingClientRect();
+ Tegaki.offsetX = aabb.left + window.pageXOffset + Tegaki.cnt.scrollLeft;
+ Tegaki.offsetY = aabb.top + window.pageYOffset + Tegaki.cnt.scrollTop;
+ },
+
+ onMouseMove: function(e) {
+ if (Tegaki.isPainting) {
+ Tegaki.tool.draw(Tegaki.getCursorPos(e, 0), Tegaki.getCursorPos(e, 1));
+ }
+ else if (Tegaki.isColorPicking) {
+ TegakiPipette.draw(Tegaki.getCursorPos(e, 0), Tegaki.getCursorPos(e, 1));
+ }
+ },
+
+ onMouseDown: function(e) {
+ if (e.target.parentNode === Tegaki.layersCnt) {
+ if (Tegaki.activeLayer === null) {
+ alert(TegakiStrings.noActiveLayer);
+ return;
+ }
+ if (!Tegaki.layers[Tegaki.activeLayer].visible) {
+ alert(TegakiStrings.hiddenActiveLayer);
+ return;
+ }
+ }
+ else if (e.target !== Tegaki.bg) {
+ return;
+ }
+
+ if (e.which === 3 || e.altKey) {
+ Tegaki.isColorPicking = true;
+ TegakiPipette.draw(Tegaki.getCursorPos(e, 0), Tegaki.getCursorPos(e, 1));
+ }
+ else {
+ Tegaki.isPainting = true;
+ TegakiHistory.pendingAction = new TegakiHistoryActions.Draw(
+ Tegaki.layers[Tegaki.activeLayer].id
+ );
+ TegakiHistory.pendingAction.addCanvasState(Tegaki.activeCtx.canvas, 0);
+ Tegaki.tool.draw(Tegaki.getCursorPos(e, 0), Tegaki.getCursorPos(e, 1), true);
+ }
+ },
+
+ onMouseUp: function(e) {
+ if (Tegaki.isPainting) {
+ Tegaki.tool.commit && Tegaki.tool.commit();
+ TegakiHistory.pendingAction.addCanvasState(Tegaki.activeCtx.canvas, 1);
+ TegakiHistory.push(TegakiHistory.pendingAction);
+ Tegaki.isPainting = false;
+ }
+ else if (Tegaki.isColorPicking) {
+ e.preventDefault();
+ Tegaki.isColorPicking = false;
+ }
+ },
+
+ onDummy: function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+};
+
+// Bienvenido a Internet
+function topen() {
+ console.log(document.forms["imgform"].dataset.w);
+ console.log(document.forms["imgform"].dataset.h);
+ Tegaki.open({
+ onDone: function() {
+ document.getElementById('status').innerHTML = 'Subiendo...';
+ document.getElementById('buttons').style.display = 'none';
+ document.getElementById('links').style.display = 'none';
+ document.getElementById('filebase').value = Tegaki.flatten().toDataURL('image/png');
+ document.forms["imgform"].submit();
+ },
+ onCancel: function() { history.back(-1); },
+ width: document.forms["imgform"].dataset.w,
+ height: document.forms["imgform"].dataset.h
+ });
+}
+window.onload = function() {
+ document.getElementById('topen').addEventListener('click', topen);
+ topen();
+}; \ No newline at end of file