From 95dfe14528663923ca2a88ec928f1d8d9df2402b Mon Sep 17 00:00:00 2001 From: bai Date: Fri, 29 Mar 2019 02:14:43 +0000 Subject: Init --- static/js/tegaki/tegaki.css | 187 +++++ static/js/tegaki/tegaki.js | 1947 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2134 insertions(+) create mode 100644 static/js/tegaki/tegaki.css create mode 100644 static/js/tegaki/tegaki.js (limited to 'static/js/tegaki') 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 = ''; + 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 -- cgit v1.2.1-18-gbd029