aboutsummaryrefslogtreecommitdiff
path: root/static/js/tegaki/tegaki.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/js/tegaki/tegaki.js')
-rw-r--r--static/js/tegaki/tegaki.js1947
1 files changed, 1947 insertions, 0 deletions
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