/*! tegaki.js, MIT License */'use strict';var TegakiStrings = { // Messages badDimensions: __('Invalid dimensions.'), promptWidth: __('Canvas width in pixels'), promptHeight: __('Canvas height in pixels'), confirmDelLayers: __('Delete selected layers?'), confirmMergeLayers: __('Merge selected layers?'), tooManyLayers: __('Layer limit reached.'), 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: __('Are you sure? Changing the canvas will clear all layers and history and disable replay recording.'), // Controls color: __('Color'), size: __('Size'), alpha: __('Opacity'), flow: __('Flow'), zoom: __('Zoom'), layers: __('Layers'), switchPalette: __('Switch color palette'), paletteSlotReplace: __('Right click to replace with the current color'), // Layers layer: __('Layer'), addLayer: __('Add layer'), delLayers: __('Delete layers'), mergeLayers: __('Merge layers'), moveLayerUp: __('Move up'), moveLayerDown: __('Move down'), toggleVisibility: __('Toggle visibility'), // Menu bar newCanvas: __('New'), open: __('Open'), save: __('Save'), saveAs: __('Save As'), export: __('Export'), undo: __('Undo'), redo: __('Redo'), close: __('Close'), finish: __('Finish'), // Tool modes tip: __('Tip'), pressure: __('Pressure'), preserveAlpha: __('Preserve Alpha'), // Tools pen: __('Pen'), pencil: __('Pencil'), airbrush: __('Airbrush'), pipette: __('Pipette'), blur: __('Blur'), eraser: __('Eraser'), bucket: __('Bucket'), tone: __('Tone'), // Replay gapless: __('Gapless'), play: __('Play'), pause: __('Pause'), rewind: __('Rewind'), slower: __('Slower'), faster: __('Faster'), recordingEnabled: __('Recording replay'), errorLoadReplay: __('Could not load the replay: '), loadingReplay: __('Loading replay…'), }; class TegakiTool { constructor() { this.id = 0; this.name = null; this.keybind = null; this.useFlow = false; this.useSizeDynamics = false; this.useAlphaDynamics = false; this.useFlowDynamics = false; this.usePreserveAlpha = false; this.step = 0.0; this.size = 1; this.alpha = 1.0; this.flow = 1.0; this.useSize = true; this.useAlpha = true; this.useFlow = true; this.noCursor = false; this.color = '#000000'; this.rgb = [0, 0, 0]; this.brushSize = 0; this.brushAlpha = 0.0; this.brushFlow = 0.0; this.stepSize = 0.0; this.center = 0.0; this.sizeDynamicsEnabled = false; this.alphaDynamicsEnabled = false; this.flowDynamicsEnabled = false; this.preserveAlphaEnabled = false; this.tip = -1; this.tipList = null; this.stepAcc = 0; this.shapeCache = null; this.kernel = null; } brushFn(x, y, offsetX, offsetY) {} start(posX, posY) {} commit() {} draw(posX, posY) {} usesDynamics() { return this.useSizeDynamics || this.useAlphaDynamics || this.useFlowDynamics; } enabledDynamics() { return this.sizeDynamicsEnabled || this.alphaDynamicsEnabled || this.flowDynamicsEnabled; } setSize(size) { this.size = size; } setAlpha(alpha) { this.alpha = alpha; this.brushAlpha = alpha; } setFlow(flow) { this.flow = flow; this.brushFlow = this.easeFlow(flow); } easeFlow(flow) { return flow; } setColor(hex) { this.rgb = $T.hexToRgb(hex); } setSizeDynamics(flag) { if (!this.useSizeDynamics) { return; } if (!flag) { this.setSize(this.size); } this.sizeDynamicsEnabled = flag; } setAlphaDynamics(flag) { if (!this.useAlphaDynamics) { return; } if (!flag) { this.setAlpha(this.alpha); } this.alphaDynamicsEnabled = flag; } setFlowDynamics(flag) { if (!this.useFlowDynamics) { return; } if (!flag) { this.setFlow(this.flow); } this.flowDynamicsEnabled = flag; } setPreserveAlpha(flag) { this.preserveAlphaEnabled = flag; } set() { this.setAlpha(this.alpha); this.setFlow(this.flow); this.setSize(this.size); this.setColor(Tegaki.toolColor); Tegaki.onToolChanged(this); } } class TegakiBrush extends TegakiTool { constructor() { super(); } generateShape(size) {} brushFn(x, y, offsetX, offsetY) { var aData, gData, bData, aWidth, canvasWidth, canvasHeight, kernel, xx, yy, ix, iy, pa, ka, a, sa, kr, kg, kb, r, g, b, pr, pg, pb, px, ba, brushSize, brushAlpha, brushFlow, preserveAlpha; preserveAlpha = this.preserveAlphaEnabled; kernel = this.kernel; brushAlpha = this.brushAlpha; brushFlow = this.brushFlow; brushSize = this.brushSize; aData = Tegaki.activeLayer.imageData.data; gData = Tegaki.ghostBuffer.data; bData = Tegaki.blendBuffer.data; canvasWidth = Tegaki.baseWidth; canvasHeight = Tegaki.baseHeight; aWidth = canvasWidth; kr = this.rgb[0]; kg = this.rgb[1]; kb = this.rgb[2]; for (yy = 0; yy < brushSize; ++yy) { iy = y + yy + offsetY; if (iy < 0 || iy >= canvasHeight) { continue; } for (xx = 0; xx < brushSize; ++xx) { ix = x + xx + offsetX; if (ix < 0 || ix >= canvasWidth) { continue; } ka = kernel[(yy * brushSize + xx) * 4 + 3] / 255; if (ka <= 0.0) { continue; } px = (iy * canvasWidth + ix) * 4; sa = bData[px + 3] / 255; sa = sa + ka * brushFlow * (brushAlpha - sa); ba = Math.ceil(sa * 255); if (ba > bData[px + 3]) { if (bData[px] === 0) { gData[px] = aData[px]; gData[px + 1] = aData[px + 1]; gData[px + 2] = aData[px + 2]; gData[px + 3] = aData[px + 3]; } bData[px] = 1; bData[px + 3] = ba; pr = gData[px]; pg = gData[px + 1]; pb = gData[px + 2]; pa = gData[px + 3] / 255; a = pa + sa - pa * sa; r = ((kr * sa) + (pr * pa) * (1 - sa)) / a; g = ((kg * sa) + (pg * pa) * (1 - sa)) / a; b = ((kb * sa) + (pb * pa) * (1 - sa)) / a; aData[px] = (kr > pr) ? Math.ceil(r) : Math.floor(r); aData[px + 1] = (kg > pg) ? Math.ceil(g) : Math.floor(g); aData[px + 2] = (kb > pb) ? Math.ceil(b) : Math.floor(b); if (!preserveAlpha) { aData[px + 3] = Math.ceil(a * 255); } } } } } generateShapeCache(force) { var i, shape; if (!this.shapeCache) { this.shapeCache = new Array(Tegaki.maxSize); } if (this.shapeCache[0] && !force) { return; } for (i = 0; i < Tegaki.maxSize; ++i) { shape = this.generateShape(i + 1); this.shapeCache[i] = shape; this.setShape(shape); } } updateDynamics(t) { var pressure, shape, val; pressure = TegakiPressure.lerp(t); if (this.sizeDynamicsEnabled) { val = Math.ceil(pressure * this.size); if (val === 0) { return false; } shape = this.shapeCache[val - 1]; this.setShape(shape); } if (this.alphaDynamicsEnabled) { val = this.alpha * pressure; if (val <= 0) { return false; } this.brushAlpha = val; } if (this.flowDynamicsEnabled) { val = this.flow * pressure; if (val <= 0) { return false; } this.brushFlow = this.easeFlow(val); } return true; } start(posX, posY) { var sampleX, sampleY; this.stepAcc = 0; this.posX = posX; this.posY = posY; if (this.enabledDynamics()) { if (!this.updateDynamics(1.0)) { return; } } sampleX = posX - this.center; sampleY = posY - this.center; this.readImageData(sampleX, sampleY, this.brushSize, this.brushSize); this.brushFn(0, 0, sampleX, sampleY); this.writeImageData(sampleX, sampleY, this.brushSize, this.brushSize); } commit() { Tegaki.clearBuffers(); } draw(posX, posY) { var mx, my, fromX, fromY, sampleX, sampleY, dx, dy, err, derr, stepAcc, lastX, lastY, distBase, shape, center, brushSize, t, tainted, w, h; stepAcc = this.stepAcc; fromX = this.posX; fromY = this.posY; if (fromX < posX) { dx = posX - fromX; sampleX = fromX; mx = 1; } else { dx = fromX - posX; sampleX = posX; mx = -1; } if (fromY < posY) { dy = posY - fromY; sampleY = fromY; my = 1; } else { dy = fromY - posY; sampleY = posY; my = -1; } if (this.enabledDynamics()) { distBase = Math.sqrt((posX - fromX) * (posX - fromX) + (posY - fromY) * (posY - fromY)); } if (this.sizeDynamicsEnabled) { shape = this.shapeCache[this.size - 1]; center = shape.center; brushSize = shape.brushSize; } else { center = this.center; brushSize = this.brushSize; } sampleX -= center; sampleY -= center; w = dx + brushSize; h = dy + brushSize; this.readImageData(sampleX, sampleY, w, h); err = (dx > dy ? dx : (dy !== 0 ? -dy : 0)) / 2; if (dx !== 0) { dx = -dx; } tainted = false; lastX = fromX; lastY = fromY; while (true) { stepAcc += Math.max(Math.abs(lastX - fromX), Math.abs(lastY - fromY)); lastX = fromX; lastY = fromY; if (stepAcc >= this.stepSize) { if (this.enabledDynamics()) { if (distBase > 0) { t = 1.0 - (Math.sqrt((posX - fromX) * (posX - fromX) + (posY - fromY) * (posY - fromY)) / distBase); } else { t = 0.0; } if (this.updateDynamics(t)) { this.brushFn(fromX - this.center - sampleX, fromY - this.center - sampleY, sampleX, sampleY); tainted = true; } } else { this.brushFn(fromX - this.center - sampleX, fromY - this.center - sampleY, sampleX, sampleY); tainted = true; } 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; if (tainted) { this.writeImageData(sampleX, sampleY, w, h); } } writeImageData(x, y, w, h) { Tegaki.activeLayer.ctx.putImageData(Tegaki.activeLayer.imageData, 0, 0, x, y, w, h); } readImageData(x, y, w, h) {} setShape(shape) { this.center = shape.center; this.stepSize = shape.stepSize; this.brushSize = shape.brushSize; this.kernel = shape.kernel; } setSize(size) { this.size = size; if (this.sizeDynamicsEnabled) { this.generateShapeCache(); } else { this.setShape(this.generateShape(size)); } } setSizeDynamics(flag) { if (!this.useSizeDynamics) { return; } if (this.sizeDynamicsEnabled === flag) { return; } if (flag) { this.generateShapeCache(); } else { this.setShape(this.generateShape(this.size)); } this.sizeDynamicsEnabled = flag; } setTip(tipId) { this.tipId = tipId; if (this.sizeDynamicsEnabled) { this.generateShapeCache(true); } else { this.setShape(this.generateShape(this.size)); } } } class TegakiPencil extends TegakiBrush { constructor() { super(); this.id = 1; this.name = 'pencil'; this.keybind = 'b'; this.step = 0.01; this.useFlow = false; this.size = 1; this.alpha = 1.0; this.useSizeDynamics = true; this.useAlphaDynamics = true; this.usePreserveAlpha = true; } generateShape(size) { var e, x, y, imageData, data, c, color, r, rr; r = 0 | ((size) / 2); rr = 0 | ((size + 1) % 2); imageData = new ImageData(size, size); data = new Uint32Array(imageData.data.buffer); color = 0xFF000000; x = r; y = 0; e = 1 - r; c = r; while (x >= y) { data[c + x - rr + (c + y - rr) * size] = color; data[c + y - rr + (c + x - rr) * size] = color; data[c - y + (c + x - rr) * size] = color; data[c - x + (c + y - rr) * size] = color; data[c - y + (c - x) * size] = color; data[c - x + (c - y) * size] = color; data[c + y - rr + (c - x) * size] = color; data[c + x - rr + (c - y) * size] = color; ++y; if (e <= 0) { e += 2 * y + 1; } else { x--; e += 2 * (y - x) + 1; } } if (r > 0) { Tegaki.tools.bucket.fill(imageData, r, r, this.rgb, 1.0); } return { center: r, stepSize: Math.ceil(size * this.step), brushSize: size, kernel: imageData.data, }; } } class TegakiAirbrush extends TegakiBrush { constructor() { super(); this.id = 3; this.name = 'airbrush'; this.keybind = 'a'; this.step = 0.1; this.size = 32; this.alpha = 1.0; this.useSizeDynamics = true; this.useAlphaDynamics = true; this.useFlowDynamics = true; this.usePreserveAlpha = true; } easeFlow(flow) { return 1 - Math.sqrt(1 - flow); } generateShape(size) { var i, r, data, len, sqd, sqlen, hs, col, row, ecol, erow, a; r = size; size = size * 2; data = new ImageData(size, size).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 if (sqd === 0) { a = 255; } else { a = (sqd / sqlen) + 0.1; if (a > 1.0) { a = 1.0; } a = (1 - (Math.exp(1 - 1 / a) / a)) * 255; } data[i + 3] = a; i += 4; ++col; } return { center: r, stepSize: Math.ceil(size * this.step), brushSize: size, kernel: data, }; } } class TegakiPen extends TegakiBrush { constructor() { super(); this.id = 2; this.name = 'pen'; this.keybind = 'p'; this.step = 0.05; this.size = 8; this.alpha = 1.0; this.flow = 1.0; this.useSizeDynamics = true; this.useAlphaDynamics = true; this.useFlowDynamics = true; this.usePreserveAlpha = true; } easeFlow(flow) { return 1 - Math.sqrt(1 - Math.pow(flow, 3)); } generateShape(size) { var e, x, y, imageData, data, c, color, r, rr, f, ff, bSize, bData, i, ii, xx, yy, center, brushSize; center = Math.floor(size / 2) + 1; brushSize = size + 2; f = 4; ff = f * f; bSize = brushSize * f; r = Math.floor(bSize / 2); rr = Math.floor((bSize + 1) % 2); imageData = new ImageData(bSize, bSize); bData = new Uint32Array(imageData.data.buffer); color = 0x55000000; x = r; y = 0; e = 1 - r; c = r; while (x >= y) { bData[c + x - rr + (c + y - rr) * bSize] = color; bData[c + y - rr + (c + x - rr) * bSize] = color; bData[c - y + (c + x - rr) * bSize] = color; bData[c - x + (c + y - rr) * bSize] = color; bData[c - y + (c - x) * bSize] = color; bData[c - x + (c - y) * bSize] = color; bData[c + y - rr + (c - x) * bSize] = color; bData[c + x - rr + (c - y) * bSize] = color; ++y; if (e <= 0) { e += 2 * y + 1; } else { x--; e += 2 * (y - x) + 1; } } color = 0xFF000000; x = r - 3; y = 0; e = 1 - r; c = r; while (x >= y) { bData[c + x - rr + (c + y - rr) * bSize] = color; bData[c + y - rr + (c + x - rr) * bSize] = color; bData[c - y + (c + x - rr) * bSize] = color; bData[c - x + (c + y - rr) * bSize] = color; bData[c - y + (c - x) * bSize] = color; bData[c - x + (c - y) * bSize] = color; bData[c + y - rr + (c - x) * bSize] = color; bData[c + x - rr + (c - y) * bSize] = color; ++y; if (e <= 0) { e += 2 * y + 1; } else { x--; e += 2 * (y - x) + 1; } } if (r > 0) { Tegaki.tools.bucket.fill(imageData, r, r, this.rgb, 1.0); } bData = imageData.data; data = new ImageData(brushSize, brushSize).data; for (x = 0; x < brushSize; ++x) { for (y = 0; y < brushSize; ++y) { i = (y * brushSize + x) * 4 + 3; color = 0; for (xx = 0; xx < f; ++xx) { for (yy = 0; yy < f; ++yy) { ii = ((yy + y * f) * bSize + (xx + x * f)) * 4 + 3; color += bData[ii]; } } data[i] = color / ff; } } return { center: center, stepSize: Math.ceil(size * this.step), brushSize: brushSize, kernel: data, }; } } class TegakiBucket extends TegakiTool { constructor() { super(); this.id = 4; this.name = 'bucket'; this.keybind = 'g'; this.step = 100.0; this.useSize = false; this.useFlow = false; this.noCursor = true; } fill(imageData, x, y, color, alpha) { var r, g, b, px, tr, tg, tb, ta, q, pxMap, yy, xx, yn, ys, yyy, yyn, yys, xd, data, w, h; w = imageData.width; h = imageData.height; r = color[0]; g = color[1]; b = color[2]; px = (y * w + x) * 4; data = imageData.data; tr = data[px]; tg = data[px + 1]; tb = data[px + 2]; ta = data[px + 3]; pxMap = new Uint8Array(w * h * 4); q = []; q[0] = x; q[1] = y; while (q.length) { yy = q.pop(); xx = q.pop(); yn = (yy - 1); ys = (yy + 1); yyy = yy * w; yyn = yn * w; yys = ys * w; xd = xx; while (xd >= 0) { px = (yyy + xd) * 4; if (!this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { break; } this.blendPixel(data, px, r, g, b, alpha); pxMap[px] = 1; if (yn >= 0) { px = (yyn + xd) * 4; if (this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { q.push(xd); q.push(yn); } } if (ys < h) { px = (yys + xd) * 4; if (this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { q.push(xd); q.push(ys); } } xd--; } xd = xx + 1; while (xd < w) { px = (yyy + xd) * 4; if (!this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { break; } this.blendPixel(data, px, r, g, b, alpha); pxMap[px] = 1; if (yn >= 0) { px = (yyn + xd) * 4; if (this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { q.push(xd); q.push(yn); } } if (ys < h) { px = (yys + xd) * 4; if (this.testPixel(data, px, pxMap, tr, tg, tb, ta)) { q.push(xd); q.push(ys); } } ++xd; } } } brushFn(x, y) { if (x < 0 || y < 0 || x >= Tegaki.baseWidth || y >= Tegaki.baseHeight) { return; } this.fill(Tegaki.activeLayer.imageData, x, y, this.rgb, this.alpha); // TODO: write back only the tainted rect Tegaki.activeLayer.ctx.putImageData(Tegaki.activeLayer.imageData, 0, 0); } blendPixel(data, px, r, g, b, a) { var sr, sg, sb, sa, dr, dg, db, da; sr = data[px]; sg = data[px + 1]; sb = data[px + 2]; sa = data[px + 3] / 255; da = sa + a - sa * a; dr = ((r * a) + (sr * sa) * (1 - a)) / da; dg = ((g * a) + (sg * sa) * (1 - a)) / da; db = ((b * a) + (sb * sa) * (1 - a)) / da; data[px] = (r > sr) ? Math.ceil(dr) : Math.floor(dr); data[px + 1] = (g > sg) ? Math.ceil(dg) : Math.floor(dg); data[px + 2] = (b > sb) ? Math.ceil(db) : Math.floor(db); data[px + 3] = Math.ceil(da * 255); } testPixel(data, px, pxMap, tr, tg, tb, ta) { return !pxMap[px] && (data[px] == tr && data[++px] == tg && data[++px] == tb && data[++px] == ta) ; } start(x, y) { this.brushFn(x, y); } draw(x, y) { this.brushFn(x, y); } setSize(size) {} } class TegakiTone extends TegakiPencil { constructor() { super(); this.id = 5; this.name = 'tone'; this.keybind = 't'; this.step = 0.01; this.useFlow = false; this.size = 8; this.alpha = 0.5; this.useSizeDynamics = true; this.useAlphaDynamics = true; this.usePreserveAlpha = true; this.matrix = [ [0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1 ,9], [15, 7, 13, 5] ]; this.mapCache = null; this.mapWidth = 0; this.mapHeight = 0; } start(x, y) { if (this.mapWidth !== Tegaki.baseWidth || this.mapHeight !== Tegaki.baseHeight) { this.generateMapCache(true); } super.start(x, y); } brushFn(x, y, offsetX, offsetY) { var data, kernel, brushSize, map, idx, preserveAlpha, px, mx, mapWidth, xx, yy, ix, iy, canvasWidth, canvasHeight; data = Tegaki.activeLayer.imageData.data; canvasWidth = Tegaki.baseWidth; canvasHeight = Tegaki.baseHeight; kernel = this.kernel; brushSize = this.brushSize; mapWidth = this.mapWidth; preserveAlpha = this.preserveAlphaEnabled; idx = Math.round(this.brushAlpha * 16) - 1; if (idx < 0) { return; } map = this.mapCache[idx]; for (yy = 0; yy < brushSize; ++yy) { iy = y + yy + offsetY; if (iy < 0 || iy >= canvasHeight) { continue; } for (xx = 0; xx < brushSize; ++xx) { ix = x + xx + offsetX; if (ix < 0 || ix >= canvasWidth) { continue; } if (kernel[(yy * brushSize + xx) * 4 + 3] === 0) { continue; } mx = iy * canvasWidth + ix; px = mx * 4; if (map[mx] === 0) { data[px] = this.rgb[0]; data[px + 1] = this.rgb[1]; data[px + 2] = this.rgb[2]; if (!preserveAlpha) { data[px + 3] = 255; } } } } } generateMap(w, h, idx) { var data, x, y; data = new Uint8Array(w * h); for (y = 0; y < h; ++y) { for (x = 0; x < w; ++x) { if (idx < this.matrix[y % 4][x % 4]) { data[w * y + x] = 1; } } } return data; } generateMapCache(force) { var i, cacheSize; cacheSize = this.matrix.length * this.matrix[0].length; if (!this.mapCache) { this.mapCache = new Array(cacheSize); } if (!force && this.mapCache[0] && this.mapWidth === Tegaki.baseWidth && this.mapHeight === Tegaki.baseHeight) { return; } this.mapWidth = Tegaki.baseWidth; this.mapHeight = Tegaki.baseHeight; for (i = 0; i < cacheSize; ++i) { this.mapCache[i] = this.generateMap(this.mapWidth, this.mapHeight, i); } } setAlpha(alpha) { super.setAlpha(alpha); this.generateMapCache(); } } class TegakiPipette extends TegakiTool { constructor() { super(); this.id = 6; this.name = 'pipette'; this.keybind = 'i'; this.step = 100.0; this.useSize = false; this.useAlpha = false; this.useFlow = false; this.noCursor = true; } start(posX, posY) { this.draw(posX, posY); } draw(posX, posY) { var c, ctx; if (true) { ctx = Tegaki.flatten().getContext('2d'); } else { ctx = Tegaki.activeLayer.ctx; } c = $T.getColorAt(ctx, posX, posY); Tegaki.setToolColor(c); } set() { Tegaki.onToolChanged(this); } commit() {} setSize() {} setAlpha() {} } class TegakiBlur extends TegakiBrush { constructor() { super(); this.id = 7; this.name = 'blur'; this.step = 0.25; this.useFlow = false; this.size = 32; this.alpha = 0.5; this.useAlphaDynamics = true; this.usePreserveAlpha = false; } writeImageData(x, y, w, h) { var xx, yy, ix, iy, px, canvasWidth, aData, bData; aData = Tegaki.activeLayer.imageData.data; bData = Tegaki.blendBuffer.data; canvasWidth = Tegaki.baseWidth; for (xx = 0; xx < w; ++xx) { ix = x + xx; for (yy = 0; yy < h; ++yy) { iy = y + yy; px = (iy * canvasWidth + ix) * 4; aData[px] = bData[px]; aData[px + 1] = bData[px + 1]; aData[px + 2] = bData[px + 2]; aData[px + 3] = bData[px + 3]; } } super.writeImageData(x, y, w, h); } readImageData(x, y, w, h) { var xx, yy, ix, iy, px, canvasWidth, aData, bData; aData = Tegaki.activeLayer.imageData.data; bData = Tegaki.blendBuffer.data; canvasWidth = Tegaki.baseWidth; for (xx = 0; xx < w; ++xx) { ix = x + xx; for (yy = 0; yy < h; ++yy) { iy = y + yy; px = (iy * canvasWidth + ix) * 4; bData[px] = aData[px]; bData[px + 1] = aData[px + 1]; bData[px + 2] = aData[px + 2]; bData[px + 3] = aData[px + 3]; } } } brushFn(x, y, offsetX, offsetY) { var i, j, size, aData, bData, limX, limY, kernel, alpha, alpha0, ix, iy, canvasWidth, canvasHeight, sx, sy, r, g, b, a, kx, ky, px, pa, acc, aa; alpha0 = this.brushAlpha; alpha = alpha0 * alpha0 * alpha0; if (alpha <= 0.0) { return; } size = this.brushSize; kernel = this.kernel; aData = Tegaki.activeLayer.imageData.data; bData = Tegaki.blendBuffer.data; canvasWidth = Tegaki.baseWidth; canvasHeight = Tegaki.baseHeight; limX = canvasWidth - 1; limY = canvasHeight - 1; for (sx = 0; sx < size; ++sx) { ix = x + sx + offsetX; if (ix < 0 || ix >= canvasWidth) { continue; } for (sy = 0; sy < size; ++sy) { iy = y + sy + offsetY; if (iy < 0 || iy >= canvasHeight) { continue; } i = (sy * size + sx) * 4; px = (iy * canvasWidth + ix) * 4; if (kernel[i + 3] === 0 || ix <= 0 || iy <= 0 || ix >= limX || iy >= limY) { continue; } r = g = b = a = acc = 0; for (kx = -1; kx < 2; ++kx) { for (ky = -1; ky < 2; ++ky) { j = ((iy - ky) * canvasWidth + (ix - kx)) * 4; pa = aData[j + 3]; if (kx === 0 && ky === 0) { aa = pa * alpha0; acc += alpha0; } else { aa = pa * alpha; acc += alpha; } r = r + aData[j] * aa; ++j; g = g + aData[j] * aa; ++j; b = b + aData[j] * aa; a = a + aa; } } a = a / acc; if (a <= 0.0) { continue; } bData[px] = Math.round((r / acc) / a); bData[px + 1] = Math.round((g / acc) / a); bData[px + 2] = Math.round((b / acc) / a); bData[px + 3] = Math.round(a); } } } } TegakiBlur.prototype.generateShape = TegakiPencil.prototype.generateShape; class TegakiEraser extends TegakiBrush { constructor() { super(); this.id = 8; this.name = 'eraser'; this.keybind = 'e'; this.step = 0.1; this.size = 8; this.alpha = 1.0; this.useFlow = false; this.useSizeDynamics = true; this.useAlphaDynamics = true; this.usePreserveAlpha = false; this.tipId = 0; this.tipList = [ 'pencil', 'pen', 'airbrush' ]; } brushFn(x, y, offsetX, offsetY) { var aData, bData, gData, kernel, canvasWidth, canvasHeight, ka, ba, px, xx, yy, ix, iy, brushSize, brushAlpha; brushAlpha = this.brushAlpha; brushSize = this.brushSize; kernel = this.kernel; aData = Tegaki.activeLayer.imageData.data; gData = Tegaki.ghostBuffer.data; bData = Tegaki.blendBuffer.data; canvasWidth = Tegaki.baseWidth; canvasHeight = Tegaki.baseHeight; for (yy = 0; yy < brushSize; ++yy) { iy = y + yy + offsetY; if (iy < 0 || iy >= canvasHeight) { continue; } for (xx = 0; xx < brushSize; ++xx) { ix = x + xx + offsetX; if (ix < 0 || ix >= canvasWidth) { continue; } ka = kernel[(yy * brushSize + xx) * 4 + 3] / 255; px = (iy * canvasWidth + ix) * 4 + 3; if (gData[px] === 0) { gData[px] = aData[px]; } ba = bData[px] / 255; ba = ba + ka * (brushAlpha - ba); bData[px] = Math.floor(ba * 255); aData[px] = Math.floor(gData[px] * (1 - ba)); } } } generateShape(size) { if (this.tipId === 0) { return this.generateShapePencil(size); } else if (this.tipId === 1) { return this.generateShapePen(size); } else { return this.generateShapeAirbrush(size); } } } TegakiEraser.prototype.generateShapePencil = TegakiPencil.prototype.generateShape; TegakiEraser.prototype.generateShapePen = TegakiPen.prototype.generateShape; TegakiEraser.prototype.generateShapeAirbrush = TegakiAirbrush.prototype.generateShape; 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(); }, copyImageData(imageData) { return new ImageData( new Uint8ClampedArray(imageData.data), imageData.width ); }, copyCanvas: function(source, clone) { var canvas; if (!clone) { canvas = $T.el('canvas'); canvas.width = source.width; canvas.height = source.height; } else { canvas = source.cloneNode(false); } canvas.getContext('2d').drawImage(source, 0, 0); return canvas; }, clearCtx: function(ctx) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); }, 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; }, RgbToHex: function(r, g, b) { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); }, 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); }, generateFilename: function() { return 'tegaki_' + (new Date()).toISOString().split('.')[0].replace(/[^0-9]/g, '_'); }, sortAscCb: function(a, b) { if (a > b) { return 1; } if (a < b) { return -1; } return 0; }, sortDescCb: function(a, b) { if (a > b) { return -1; } if (a < b) { return 1; } return 0; }, msToHms: function(ms) { var h, m, s, ary; s = 0 | (ms / 1000); h = 0 | (s / 3600); m = 0 | ((s - h * 3600) / 60); s = s - h * 3600 - m * 60; ary = []; if (h) { ary.push(h < 10 ? ('0' + h) : h); } if (m) { ary.push(m < 10 ? ('0' + m) : m); } else { ary.push('00'); } if (s) { ary.push(s < 10 ? ('0' + s) : s); } else { ary.push('00'); } return ary.join(':'); }, calcThumbSize(w, h, maxSide) { var r; if (w > maxSide) { r = maxSide / w; w = maxSide; h = h * r; } if (h > maxSide) { r = maxSide / h; h = maxSide; w = w * r; } return [Math.ceil(w), Math.ceil(h)]; } }; class TegakiBinReader { constructor(buf) { this.pos = 0; this.view = new DataView(buf); this.buf = buf; } readInt8() { var data = this.view.getInt8(this.pos); this.pos += 1; return data; } readUint8() { var data = this.view.getUint8(this.pos); this.pos += 1; return data; } readInt16() { var data = this.view.getInt16(this.pos); this.pos += 2; return data; } readUint16() { var data = this.view.getUint16(this.pos); this.pos += 2; return data; } readUint32() { var data = this.view.getUint32(this.pos); this.pos += 4; return data; } readFloat32() { var data = this.view.getFloat32(this.pos); this.pos += 4; return data; } } class TegakiBinWriter { constructor(buf) { this.pos = 0; this.view = new DataView(buf); this.buf = buf; } writeInt8(val) { this.view.setInt8(this.pos, val); this.pos += 1; } writeUint8(val) { this.view.setUint8(this.pos, val); this.pos += 1; } writeInt16(val) { this.view.setInt16(this.pos, val); this.pos += 2; } writeUint16(val) { this.view.setUint16(this.pos, val); this.pos += 2; } writeUint32(val) { this.view.setUint32(this.pos, val); this.pos += 4; } writeFloat32(val) { this.view.setFloat32(this.pos, val); this.pos += 4; } } var TegakiCursor = { size: 0, radius: 0, points: null, cursorCtx: null, offsetX: 0, offsetY: 0, lastX: 0, lastY: 0, lastSize: 0, init: function(w, h) { var el; el = $T.el('canvas'); el.id = 'tegaki-cursor-layer'; [ el.width, el.height ] = TegakiCursor.getMaxCanvasSize() Tegaki.canvasCnt.appendChild(el); this.offsetX = el.offsetLeft; this.offsetY = el.offsetTop; this.cursorCtx = el.getContext('2d'); }, getCanvas: function() { if (this.cursorCtx) { return this.cursorCtx.canvas; } else { return null; } }, getMaxCanvasSize: function() { let w = Tegaki.canvasCnt.offsetWidth; let h = Tegaki.canvasCnt.offsetHeight; let [ sbwh, scbv ] = Tegaki.getScrollbarSizes(); return [ w - sbwh, h - scbv ]; }, updateCanvasSize: function() { let canvas = this.cursorCtx.canvas; let [w, h] = this.getMaxCanvasSize(); if (w !== canvas.width || h !== canvas.height) { canvas.width = w; canvas.height = h; this.offsetX = canvas.offsetLeft; this.offsetY = canvas.offsetTop; } }, render: function(rawX, rawY) { var x, y, i, destImg, destData; x = rawX - this.offsetX - this.radius; y = rawY - this.offsetY - this.radius; this.clear(); this.lastX = x; this.lastY = y; this.lastSize = this.size; destImg = this.cursorCtx.createImageData(this.size, this.size); destData = new Uint32Array(destImg.data.buffer); for (i = 0; i < this.points.length; ++i) { destData[this.points[i]] = 0xFFFFFF7F; } this.cursorCtx.putImageData(destImg, x, y); }, clear: function() { this.cursorCtx.clearRect(this.lastX, this.lastY, this.lastSize, this.lastSize); }, clearAll: function() { var canvas = this.cursorCtx.canvas; this.cursorCtx.clearRect(0, 0, canvas.width, canvas.height); }, destroy() { this.size = 0; this.radius = 0; this.offsetX = 0; this.offsetY = 0; this.lastX = 0; this.lastY = 0; this.lastSize = 0; this.points = null; this.cursorCtx = null; }, generate: function(size) { var e, x, y, c, r, rr, points; size = 0 | (size * Tegaki.zoomFactor); if (size < 2) { return false; } r = 0 | ((size) / 2); rr = 0 | ((size + 1) % 2); points = []; x = r; y = 0; e = 1 - r; c = r; while (x >= y) { points.push(c + x - rr + (c + y - rr) * size); points.push(c + y - rr + (c + x - rr) * size); points.push(c - y + (c + x - rr) * size); points.push(c - x + (c + y - rr) * size); points.push(c - y + (c - x) * size); points.push(c - x + (c - y) * size); points.push(c + y - rr + (c - x) * size); points.push(c + x - rr + (c - y) * size); ++y; if (e <= 0) { e += 2 * y + 1; } else { x--; e += 2 * (y - x) + 1; } } this.size = size; this.radius = r; this.points = points; return true; } }; var TegakiHistory = { maxSize: 50, undoStack: [], redoStack: [], pendingAction: null, clear: function() { this.undoStack = []; this.redoStack = []; this.pendingAction = null; this.onChange(0); }, push: function(action) { if (!action) { return; } if (action.coalesce) { if (this.undoStack[this.undoStack.length - 1] instanceof action.constructor) { if (this.undoStack[this.undoStack.length - 1].coalesce(action)) { return; } } } this.undoStack.push(action); if (this.undoStack.length > this.maxSize) { this.undoStack.shift(); } if (this.redoStack.length > 0) { this.redoStack = []; } this.onChange(0); }, undo: function() { var action; if (!this.undoStack.length) { return; } action = this.undoStack.pop(); action.undo(); this.redoStack.push(action); this.onChange(-1); }, redo: function() { var action; if (!this.redoStack.length) { return; } action = this.redoStack.pop(); action.redo(); this.undoStack.push(action); this.onChange(1); }, onChange: function(type) { Tegaki.onHistoryChange(this.undoStack.length, this.redoStack.length, type); }, sortPosLayerCallback: function(a, b) { if (a[0] > b[0]) { return 1; } if (a[0] < b[0]) { return -1; } return 0; } }; var TegakiHistoryActions = { Dummy: function() { this.undo = function() {}; this.redo = function() {}; }, Draw: function(layerId) { this.coalesce = false; this.imageDataBefore = null; this.imageDataAfter = null; this.layerId = layerId; }, DeleteLayers: function(layerPosMap, params) { var item; this.coalesce = false; this.layerPosMap = []; for (item of layerPosMap.sort(TegakiHistory.sortPosLayerCallback)) { item[1] = TegakiLayers.cloneLayer(item[1]); this.layerPosMap.push(item); } this.tgtLayerId = null; this.aLayerIdBefore = null; this.aLayerIdAfter = null; this.imageDataBefore = null; this.imageDataAfter = null; this.mergeDown = false; if (params) { for (let k in params) { this[k] = params[k]; } } }, AddLayer: function(params, aLayerIdBefore, aLayerIdAfter) { this.coalesce = false; this.layer = params; this.layerId = params.id; this.aLayerIdBefore = aLayerIdBefore; this.aLayerIdAfter = aLayerIdAfter; }, MoveLayers: function(layers, belowPos, activeLayerId) { this.coalesce = false; this.layers = layers; this.belowPos = belowPos; this.aLayerId = activeLayerId; }, SetLayersAlpha: function(layerAlphas, newAlpha) { this.layerAlphas = layerAlphas; this.newAlpha = newAlpha; }, SetLayerName: function(id, oldName, newName) { this.layerId = id; this.oldName = oldName; this.newName = newName; } }; // --- TegakiHistoryActions.Draw.prototype.addCanvasState = function(imageData, type) { if (type) { this.imageDataAfter = $T.copyImageData(imageData); } else { this.imageDataBefore = $T.copyImageData(imageData); } }; TegakiHistoryActions.Draw.prototype.exec = function(type) { var layer = TegakiLayers.getLayerById(this.layerId); if (type) { layer.ctx.putImageData(this.imageDataAfter, 0, 0); TegakiLayers.syncLayerImageData(layer, this.imageDataAfter); } else { layer.ctx.putImageData(this.imageDataBefore, 0, 0); TegakiLayers.syncLayerImageData(layer, this.imageDataBefore); } TegakiUI.updateLayerPreview(layer); TegakiLayers.setActiveLayer(this.layerId); }; TegakiHistoryActions.Draw.prototype.undo = function() { this.exec(0); }; TegakiHistoryActions.Draw.prototype.redo = function() { this.exec(1); }; TegakiHistoryActions.DeleteLayers.prototype.undo = function() { var i, lim, refLayer, layer, pos, refId; for (i = 0, lim = this.layerPosMap.length; i < lim; ++i) { [pos, layer] = this.layerPosMap[i]; layer = TegakiLayers.cloneLayer(layer); refLayer = Tegaki.layers[pos]; if (refLayer) { if (refId = TegakiLayers.getLayerBelowId(refLayer.id)) { refId = refId.id; } TegakiUI.updateLayersGridAdd(layer, refId); TegakiUI.updateLayerPreview(layer); Tegaki.layersCnt.insertBefore(layer.canvas, refLayer.canvas); Tegaki.layers.splice(pos, 0, layer); } else { if (!Tegaki.layers[0]) { refLayer = Tegaki.layersCnt.children[0]; } else { refLayer = Tegaki.layers[Tegaki.layers.length - 1].canvas; } TegakiUI.updateLayersGridAdd(layer, TegakiLayers.getTopLayerId()); TegakiUI.updateLayerPreview(layer); Tegaki.layersCnt.insertBefore(layer.canvas, refLayer.nextElementSibling); Tegaki.layers.push(layer); } } if (this.tgtLayerId) { layer = TegakiLayers.getLayerById(this.tgtLayerId); layer.ctx.putImageData(this.imageDataBefore, 0, 0); TegakiLayers.syncLayerImageData(layer, this.imageDataBefore); TegakiLayers.setLayerAlpha(layer, this.tgtLayerAlpha); TegakiUI.updateLayerPreview(layer); } TegakiLayers.setActiveLayer(this.aLayerIdBefore); }; TegakiHistoryActions.DeleteLayers.prototype.redo = function() { var layer, ids = []; for (layer of this.layerPosMap) { ids.unshift(layer[1].id); } if (this.tgtLayerId) { if (!this.mergeDown) { ids.unshift(this.tgtLayerId); } TegakiLayers.mergeLayers(new Set(ids)); } else { TegakiLayers.deleteLayers(ids); } TegakiLayers.setActiveLayer(this.aLayerIdAfter); }; TegakiHistoryActions.MoveLayers.prototype.undo = function() { var i, layer, stack, ref, posMap, len; stack = new Array(Tegaki.layers.length); posMap = {}; for (layer of this.layers) { posMap[layer[1].id] = layer[0]; } for (i = 0, len = Tegaki.layers.length; i < len; ++i) { layer = Tegaki.layers[i]; if (posMap[layer.id] !== undefined) { Tegaki.layers.splice(i, 1); Tegaki.layers.splice(posMap[layer.id], 0, layer); } } TegakiUI.updayeLayersGridOrder(); ref = Tegaki.layersCnt.children[0]; for (i = Tegaki.layers.length - 1; i >= 0; i--) { layer = Tegaki.layers[i]; Tegaki.layersCnt.insertBefore(layer.canvas, ref.nextElementSibling); } TegakiLayers.setActiveLayer(this.aLayerId); }; TegakiHistoryActions.MoveLayers.prototype.redo = function() { var layer, layers = new Set(); for (layer of this.layers.slice().reverse()) { layers.add(layer[1].id); } TegakiLayers.setActiveLayer(this.aLayerId); TegakiLayers.moveLayers(layers, this.belowPos); }; TegakiHistoryActions.AddLayer.prototype.undo = function() { TegakiLayers.deleteLayers([this.layer.id]); TegakiLayers.setActiveLayer(this.aLayerIdBefore); Tegaki.layerCounter--; }; TegakiHistoryActions.AddLayer.prototype.redo = function() { TegakiLayers.setActiveLayer(this.aLayerIdBefore); TegakiLayers.addLayer(this.layer); TegakiLayers.setActiveLayer(this.aLayerIdAfter); }; TegakiHistoryActions.SetLayersAlpha.prototype.undo = function() { var id, layerAlpha, layer; for (layerAlpha of this.layerAlphas) { [id, layerAlpha] = layerAlpha; if (layer = TegakiLayers.getLayerById(id)) { TegakiLayers.setLayerAlpha(layer, layerAlpha); } } TegakiUI.updateLayerAlphaOpt(); }; TegakiHistoryActions.SetLayersAlpha.prototype.redo = function() { var id, layerAlpha, layer; for (layerAlpha of this.layerAlphas) { [id, layerAlpha] = layerAlpha; if (layer = TegakiLayers.getLayerById(id)) { TegakiLayers.setLayerAlpha(layer, this.newAlpha); } } TegakiUI.updateLayerAlphaOpt(); }; TegakiHistoryActions.SetLayersAlpha.prototype.coalesce = function(action) { var i; if (this.layerAlphas.length !== action.layerAlphas.length) { return false; } for (i = 0; i < this.layerAlphas.length; ++i) { if (this.layerAlphas[i][0] !== action.layerAlphas[i][0]) { return false; } } this.newAlpha = action.newAlpha; return true; }; TegakiHistoryActions.SetLayerName.prototype.exec = function(type) { var layer = TegakiLayers.getLayerById(this.layerId); if (layer) { layer.name = type ? this.newName : this.oldName; TegakiUI.updateLayerName(layer); } }; TegakiHistoryActions.SetLayerName.prototype.undo = function() { this.exec(0); }; TegakiHistoryActions.SetLayerName.prototype.redo = function() { this.exec(1); }; var TegakiKeybinds = { keyMap: {}, captionMap: {}, clear: function() { this.keyMap = {}; this.captionMap = {}; }, bind: function(keys, klass, fn, id, caption) { this.keyMap[keys] = [klass, fn]; if (id) { this.captionMap[id] = caption; } }, getCaption(id) { return this.captionMap[id]; }, resolve: function(e) { var fn, mods, keys, el; el = e.target; if (el.nodeName == 'INPUT' && (el.type === 'text' || el.type === 'number')) { return; } mods = []; if (e.ctrlKey) { mods.push('ctrl'); } if (e.shiftKey) { mods.push('shift'); } keys = e.key.toLowerCase(); if (mods[0]) { keys = mods.join('+') + '+' + keys; } fn = TegakiKeybinds.keyMap[keys]; if (fn && !e.altKey && !e.metaKey) { e.preventDefault(); e.stopPropagation(); fn[0][fn[1]](); } }, }; var TegakiLayers = { cloneLayer: function(layer) { var newLayer = Object.assign({}, layer); newLayer.canvas = $T.copyCanvas(layer.canvas, true); newLayer.ctx = newLayer.canvas.getContext('2d'); newLayer.imageData = $T.copyImageData(layer.imageData); return newLayer; }, getCanvasById: function(id) { return $T.id('tegaki-canvas-' + id); }, getActiveLayer: function() { return Tegaki.activeLayer; }, getLayerPosById: function(id) { var i, layers = Tegaki.layers; for (i = 0; i < layers.length; ++i) { if (layers[i].id === id) { return i; } } return -1; }, getTopFencedLayerId: function() { var i, id, layer, layers = Tegaki.layers; for (i = layers.length - 1; i >= 0; i--) { if (TegakiLayers.selectedLayersHas(layers[i].id)) { break; } } for (i = i - 1; i >= 0; i--) { if (!TegakiLayers.selectedLayersHas(layers[i].id)) { break; } } if (layer = layers[i]) { id = layer.id; } else { id = 0; } return id; }, getSelectedEdgeLayerPos: function(top) { var i, layers = Tegaki.layers; if (top) { for (i = Tegaki.layers.length - 1; i >= 0; i--) { if (TegakiLayers.selectedLayersHas(layers[i].id)) { break; } } } else { for (i = 0; i < layers.length; ++i) { if (TegakiLayers.selectedLayersHas(layers[i].id)) { break; } } if (i >= layers.length) { i = -1; } } return i; }, getTopLayer: function() { return Tegaki.layers[Tegaki.layers.length - 1]; }, getTopLayerId: function() { var layer = TegakiLayers.getTopLayer(); if (layer) { return layer.id; } else { return 0; } }, getLayerBelowId: function(belowId) { var idx; idx = TegakiLayers.getLayerPosById(belowId); if (idx < 1) { return null; } return Tegaki.layers[idx - 1]; }, getLayerById: function(id) { return Tegaki.layers[TegakiLayers.getLayerPosById(id)]; }, isSameLayerOrder: function(a, b) { var i, al; if (a.length !== b.length) { return false; } for (i = 0; al = a[i]; ++i) { if (al.id !== b[i].id) { return false; } } return true; }, addLayer: function(baseLayer = {}) { var id, canvas, k, params, layer, afterNode, afterPos, aLayerIdBefore, ctx; if (Tegaki.activeLayer) { aLayerIdBefore = Tegaki.activeLayer.id; afterPos = TegakiLayers.getLayerPosById(Tegaki.activeLayer.id); afterNode = $T.cls('tegaki-layer', Tegaki.layersCnt)[afterPos]; } else { afterPos = -1; afterNode = null; } if (!afterNode) { afterNode = Tegaki.layersCnt.firstElementChild; } canvas = $T.el('canvas'); canvas.className = 'tegaki-layer'; canvas.width = Tegaki.baseWidth; canvas.height = Tegaki.baseHeight; id = ++Tegaki.layerCounter; canvas.id = 'tegaki-canvas-' + id; canvas.setAttribute('data-id', id); params = { name: TegakiStrings.layer + ' ' + id, visible: true, alpha: 1.0, }; ctx = canvas.getContext('2d'); layer = { id: id, canvas: canvas, ctx: ctx, imageData: ctx.getImageData(0, 0, canvas.width, canvas.height) }; for (k in params) { if (baseLayer[k] !== undefined) { params[k] = baseLayer[k]; } layer[k] = params[k]; } Tegaki.layers.splice(afterPos + 1, 0, layer); TegakiUI.updateLayersGridAdd(layer, aLayerIdBefore); Tegaki.layersCnt.insertBefore(canvas, afterNode.nextElementSibling); Tegaki.onLayerStackChanged(); return new TegakiHistoryActions.AddLayer(layer, aLayerIdBefore, id); }, deleteLayers: function(ids, extraParams) { var id, idx, layer, layers, delIndexes, params; params = { aLayerIdBefore: Tegaki.activeLayer ? Tegaki.activeLayer.id : -1, aLayerIdAfter: TegakiLayers.getTopFencedLayerId() }; layers = []; delIndexes = []; for (id of ids) { idx = TegakiLayers.getLayerPosById(id); layer = Tegaki.layers[idx]; layers.push([idx, layer]); Tegaki.layersCnt.removeChild(layer.canvas); delIndexes.push(idx); TegakiUI.updateLayersGridRemove(id); TegakiUI.deleteLayerPreviewCtx(layer); } delIndexes = delIndexes.sort($T.sortDescCb); for (idx of delIndexes) { Tegaki.layers.splice(idx, 1); } if (extraParams) { Object.assign(params, extraParams); } Tegaki.onLayerStackChanged(); return new TegakiHistoryActions.DeleteLayers(layers, params); }, mergeLayers: function(idSet) { var canvas, ctx, imageDataAfter, imageDataBefore, targetLayer, action, layer, layers, delIds, mergeDown; layers = []; for (layer of Tegaki.layers) { if (idSet.has(layer.id)) { layers.push(layer); } } if (layers.length < 1) { return; } if (layers.length === 1) { targetLayer = TegakiLayers.getLayerBelowId(layers[0].id); if (!targetLayer) { return; } layers.unshift(targetLayer); mergeDown = true; } else { targetLayer = layers[layers.length - 1]; mergeDown = false; } canvas = $T.el('canvas'); canvas.width = Tegaki.baseWidth; canvas.height = Tegaki.baseHeight; ctx = canvas.getContext('2d'); imageDataBefore = $T.copyImageData(targetLayer.imageData); delIds = []; for (layer of layers) { if (layer.id !== targetLayer.id) { delIds.push(layer.id); } ctx.globalAlpha = layer.alpha; ctx.drawImage(layer.canvas, 0, 0); } $T.clearCtx(targetLayer.ctx); targetLayer.ctx.drawImage(canvas, 0, 0); TegakiLayers.syncLayerImageData(targetLayer); imageDataAfter = $T.copyImageData(targetLayer.imageData); action = TegakiLayers.deleteLayers(delIds, { tgtLayerId: targetLayer.id, tgtLayerAlpha: targetLayer.alpha, aLayerIdAfter: targetLayer.id, imageDataBefore: imageDataBefore, imageDataAfter: imageDataAfter, mergeDown: mergeDown }); TegakiLayers.setLayerAlpha(targetLayer, 1.0); TegakiUI.updateLayerAlphaOpt(); TegakiUI.updateLayerPreview(targetLayer); Tegaki.onLayerStackChanged(); return action; }, moveLayers: function(idSet, belowPos) { var idx, layer, historyLayers, updLayers, movedLayers, tgtCanvas, updTgtPos; if (!idSet.size || !Tegaki.layers.length) { return; } if (belowPos >= Tegaki.layers.length) { tgtCanvas = TegakiLayers.getTopLayer().canvas.nextElementSibling; } else { layer = Tegaki.layers[belowPos]; tgtCanvas = layer.canvas; } historyLayers = []; updLayers = []; movedLayers = []; updTgtPos = belowPos; idx = 0; for (layer of Tegaki.layers) { if (idSet.has(layer.id)) { if (belowPos > 0 && idx <= belowPos) { updTgtPos--; } historyLayers.push([idx, layer]); movedLayers.push(layer); } else { updLayers.push(layer); } ++idx; } updLayers.splice(updTgtPos, 0, ...movedLayers); if (TegakiLayers.isSameLayerOrder(updLayers, Tegaki.layers)) { return; } Tegaki.layers = updLayers; for (layer of historyLayers) { Tegaki.layersCnt.insertBefore(layer[1].canvas, tgtCanvas); } TegakiUI.updayeLayersGridOrder(); Tegaki.onLayerStackChanged(); return new TegakiHistoryActions.MoveLayers( historyLayers, belowPos, Tegaki.activeLayer ? Tegaki.activeLayer.id : -1 ); }, setLayerVisibility: function(layer, flag) { layer.visible = flag; if (flag) { layer.canvas.classList.remove('tegaki-hidden'); } else { layer.canvas.classList.add('tegaki-hidden'); } Tegaki.onLayerStackChanged(); TegakiUI.updateLayersGridVisibility(layer.id, flag); }, setLayerAlpha: function(layer, alpha) { layer.alpha = alpha; layer.canvas.style.opacity = alpha; }, setActiveLayer: function(id) { var idx, layer; if (!id) { id = TegakiLayers.getTopLayerId(); if (!id) { Tegaki.activeLayer = null; return; } } idx = TegakiLayers.getLayerPosById(id); if (idx < 0) { return; } layer = Tegaki.layers[idx]; if (Tegaki.activeLayer) { Tegaki.copyContextState(Tegaki.activeLayer.ctx, layer.ctx); } Tegaki.activeLayer = layer; TegakiLayers.selectedLayersClear(); TegakiLayers.selectedLayersAdd(id); TegakiUI.updateLayersGridActive(id); TegakiUI.updateLayerAlphaOpt(); Tegaki.onLayerStackChanged(); }, syncLayerImageData(layer, imageData = null) { if (imageData) { layer.imageData = $T.copyImageData(imageData); } else { layer.imageData = layer.ctx.getImageData( 0, 0, Tegaki.baseWidth, Tegaki.baseHeight ); } }, selectedLayersHas: function(id) { return Tegaki.selectedLayers.has(+id); }, selectedLayersClear: function() { Tegaki.selectedLayers.clear(); TegakiUI.updateLayerAlphaOpt(); TegakiUI.updateLayersGridSelectedClear(); }, selectedLayersAdd: function(id) { Tegaki.selectedLayers.add(+id); TegakiUI.updateLayerAlphaOpt(); TegakiUI.updateLayersGridSelectedSet(id, true); }, selectedLayersRemove: function(id) { Tegaki.selectedLayers.delete(+id); TegakiUI.updateLayerAlphaOpt(); TegakiUI.updateLayersGridSelectedSet(id, false); }, selectedLayersToggle: function(id) { if (TegakiLayers.selectedLayersHas(id)) { TegakiLayers.selectedLayersRemove(id); } else { TegakiLayers.selectedLayersAdd(id); } } }; var Tegaki = { VERSION: '0.9.2', startTimeStamp: 0, bg: null, canvas: null, ctx: null, layers: [], layersCnt: null, canvasCnt: null, ghostBuffer: null, blendBuffer: null, ghostBuffer32: null, blendBuffer32: null, activeLayer: null, layerCounter: 0, selectedLayers: new Set(), activePointerId: 0, activePointerIsPen: false, isPainting: false, offsetX: 0, offsetY: 0, zoomLevel: 0, zoomFactor: 1.0, zoomFactorList: [0.5, 1.0, 2.0, 4.0, 8.0, 16.0], zoomBaseLevel: 1, hasCustomCanvas: false, TWOPI: 2 * Math.PI, toolList: [ TegakiPencil, TegakiPen, TegakiAirbrush, TegakiBucket, TegakiTone, TegakiPipette, TegakiBlur, TegakiEraser ], tools: {}, tool: null, colorPaletteId: 0, toolColor: '#000000', defaultTool: 'pencil', bgColor: '#ffffff', maxSize: 64, maxLayers: 25, baseWidth: 0, baseHeight: 0, replayRecorder: null, replayViewer: null, onDoneCb: null, onCancelCb: null, replayMode: false, saveReplay: false, open: function(opts = {}) { var self = Tegaki; if (self.bg) { if (self.replayMode !== (opts.replayMode ? true : false)) { self.destroy(); } else { self.resume(); return; } } self.startTimeStamp = Date.now(); if (opts.bgColor) { self.bgColor = opts.bgColor; } self.hasCustomCanvas = false; self.saveReplay = !!opts.saveReplay; self.replayMode = !!opts.replayMode; self.onDoneCb = opts.onDone; self.onCancelCb = opts.onCancel; self.baseWidth = opts.width || 0; self.baseHeight = opts.height || 0; self.createTools(); self.initKeybinds(); [self.bg, self.canvasCnt, self.layersCnt] = TegakiUI.buildUI(); document.body.appendChild(self.bg); document.body.classList.add('tegaki-backdrop'); if (!self.replayMode) { self.init(); self.setTool(self.defaultTool); if (self.saveReplay) { self.replayRecorder = new TegakiReplayRecorder(); self.replayRecorder.start(); } } else { TegakiUI.setReplayMode(true); self.replayViewer = new TegakiReplayViewer(); if (opts.replayURL) { self.loadReplayFromURL(opts.replayURL); } } }, init: function() { var self = Tegaki; self.createCanvas(); self.updateLayersCntSize(); self.createBuffers(); self.updatePosOffset(); self.resetLayers(); TegakiCursor.init(self.baseWidth, self.baseHeight); self.bindGlobalEvents(); TegakiUI.updateUndoRedo(0, 0); TegakiUI.updateZoomLevel(); }, initFromReplay: function() { var self, r; self = Tegaki; r = self.replayViewer; self.initToolsFromReplay(); self.baseWidth = r.canvasWidth; self.baseHeight = r.canvasHeight; self.bgColor = $T.RgbToHex(...r.bgColor); self.toolColor = $T.RgbToHex(...r.toolColor); }, initToolsFromReplay: function() { var self, r, name, tool, rTool, prop, props; self = Tegaki; r = self.replayViewer; for (name in self.tools) { tool = self.tools[name]; if (tool.id === r.toolId) { self.defaultTool = name; } rTool = r.toolMap[tool.id]; props = ['step', 'size', 'alpha', 'flow', 'tipId']; for (prop of props) { if (rTool[prop] !== undefined) { tool[prop] = rTool[prop]; } } props = [ 'sizeDynamicsEnabled', 'alphaDynamicsEnabled', 'flowDynamicsEnabled', 'usePreserveAlpha' ]; for (prop of props) { if (rTool[prop] !== undefined) { tool[prop] = !!rTool[prop]; } } } }, resetLayers: function() { var i, len; if (Tegaki.layers.length) { for (i = 0, len = Tegaki.layers.length; i < len; ++i) { Tegaki.layersCnt.removeChild(Tegaki.layers[i].canvas); } Tegaki.layers = []; Tegaki.layerCounter = 0; TegakiUI.updateLayersGridClear(); } TegakiLayers.addLayer(); TegakiLayers.setActiveLayer(0); }, createCanvas: function() { var canvas, self = Tegaki; canvas = $T.el('canvas'); canvas.id = 'tegaki-canvas'; canvas.width = self.baseWidth; canvas.height = self.baseHeight; self.canvas = canvas; self.ctx = canvas.getContext('2d'); self.ctx.fillStyle = self.bgColor; self.ctx.fillRect(0, 0, self.baseWidth, self.baseHeight); self.layersCnt.appendChild(canvas); }, createTools: function() { var klass, tool; for (klass of Tegaki.toolList) { tool = new klass(); Tegaki.tools[tool.name] = tool; } }, bindGlobalEvents: function() { var self = Tegaki; if (!self.replayMode) { let cursorNode = TegakiCursor.getCanvas(); $T.on(cursorNode, 'pointermove', self.onPointerMove); $T.on(cursorNode, 'pointerdown', self.onPointerDown); $T.on(cursorNode, 'pointerout', self.onPointerOut); $T.on(document, 'pointerup', self.onPointerUp); $T.on(document, 'pointercancel', self.onPointerUp); $T.on(document, 'keydown', TegakiKeybinds.resolve); $T.on(window, 'beforeunload', Tegaki.onTabClose); } else { $T.on(document, 'visibilitychange', Tegaki.onVisibilityChange); } $T.on(self.bg, 'contextmenu', self.onDummy); $T.on(window, 'resize', self.onWindowResized); $T.on(window, 'scroll', self.updatePosOffset); }, unBindGlobalEvents: function() { var self = Tegaki; if (!self.replayMode) { let cursorNode = TegakiCursor.getCanvas(); $T.off(cursorNode, 'pointermove', self.onPointerMove); $T.off(cursorNode, 'pointerdown', self.onPointerDown); $T.off(cursorNode, 'pointerout', self.onPointerOut); $T.off(document, 'pointerup', self.onPointerUp); $T.off(document, 'pointercancel', self.onPointerUp); $T.off(document, 'keydown', TegakiKeybinds.resolve); $T.off(window, 'beforeunload', Tegaki.onTabClose); } else { $T.off(document, 'visibilitychange', Tegaki.onVisibilityChange); } $T.off(self.bg, 'contextmenu', self.onDummy); $T.off(window, 'resize', self.onWindowResized); $T.off(window, 'scroll', self.updatePosOffset); }, createBuffers() { Tegaki.ghostBuffer = new ImageData(Tegaki.baseWidth, Tegaki.baseHeight); Tegaki.blendBuffer = new ImageData(Tegaki.baseWidth, Tegaki.baseHeight); Tegaki.ghostBuffer32 = new Uint32Array(Tegaki.ghostBuffer.data.buffer); Tegaki.blendBuffer32 = new Uint32Array(Tegaki.blendBuffer.data.buffer); }, clearBuffers() { Tegaki.ghostBuffer32.fill(0); Tegaki.blendBuffer32.fill(0); }, destroyBuffers() { Tegaki.ghostBuffer = null; Tegaki.blendBuffer = null; Tegaki.ghostBuffer32 = null; Tegaki.blendBuffer32 = null; }, disableSmoothing: function(ctx) { ctx.mozImageSmoothingEnabled = false; ctx.webkitImageSmoothingEnabled = false; ctx.msImageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false; }, updateLayersCntSize: function() { var style = Tegaki.layersCnt.style; style.width = Tegaki.baseWidth + 'px'; style.height = Tegaki.baseHeight + 'px'; }, onTabClose: function(e) { e.preventDefault(); e.returnValue = ''; }, onVisibilityChange: function(e) { if (!Tegaki.replayMode) { return; } if (document.visibilityState === 'visible') { if (Tegaki.replayViewer.autoPaused) { Tegaki.replayViewer.play(); } } else { if (Tegaki.replayViewer.playing) { Tegaki.replayViewer.autoPause(); } } }, onWindowResized: function() { Tegaki.updatePosOffset(); TegakiCursor.updateCanvasSize(); }, initKeybinds: function() { var cls, tool; if (Tegaki.replayMode) { return; } TegakiKeybinds.bind('ctrl+z', TegakiHistory, 'undo', 'undo', 'Ctrl+Z'); TegakiKeybinds.bind('ctrl+y', TegakiHistory, 'redo', 'redo', 'Ctrl+Y'); TegakiKeybinds.bind('+', Tegaki, 'setToolSizeUp', 'toolSize', 'Numpad +/-'); TegakiKeybinds.bind('-', Tegaki, 'setToolSizeDown'); for (tool in Tegaki.tools) { cls = Tegaki.tools[tool]; if (cls.keybind) { TegakiKeybinds.bind(cls.keybind, cls, 'set'); } } }, getPointerPos: function(e, axis) { if (axis === 0) { return 0 | (( e.clientX + window.pageXOffset + Tegaki.canvasCnt.scrollLeft - Tegaki.offsetX ) / Tegaki.zoomFactor); } else { return 0 | (( e.clientY + window.pageYOffset + Tegaki.canvasCnt.scrollTop - Tegaki.offsetY ) / Tegaki.zoomFactor); } }, resume: function() { if (Tegaki.saveReplay) { Tegaki.replayRecorder.start(); } Tegaki.bg.classList.remove('tegaki-hidden'); document.body.classList.add('tegaki-backdrop'); Tegaki.setZoom(0); Tegaki.updateLayersCntSize(); Tegaki.updatePosOffset(); TegakiCursor.updateCanvasSize(); Tegaki.bindGlobalEvents(); }, hide: function() { if (Tegaki.saveReplay) { Tegaki.replayRecorder.stop(); } Tegaki.bg.classList.add('tegaki-hidden'); document.body.classList.remove('tegaki-backdrop'); Tegaki.unBindGlobalEvents(); }, destroy: function() { Tegaki.unBindGlobalEvents(); TegakiKeybinds.clear(); TegakiHistory.clear(); Tegaki.bg.parentNode.removeChild(Tegaki.bg); document.body.classList.remove('tegaki-backdrop'); Tegaki.startTimeStamp = 0; Tegaki.bg = null; Tegaki.canvasCnt = null; Tegaki.layersCnt = null; Tegaki.canvas = null; Tegaki.ctx = null; Tegaki.layers = []; Tegaki.layerCounter = 0; Tegaki.zoomLevel = 0; Tegaki.zoomFactor = 1.0; Tegaki.activeLayer = null; Tegaki.tool = null; TegakiCursor.destroy(); Tegaki.replayRecorder = null; Tegaki.replayViewer = null; Tegaki.destroyBuffers(); }, flatten: function(ctx) { var i, layer, canvas, len; if (!ctx) { canvas = $T.el('canvas'); ctx = canvas.getContext('2d'); } else { canvas = ctx.canvas; } canvas.width = Tegaki.canvas.width; canvas.height = Tegaki.canvas.height; ctx.drawImage(Tegaki.canvas, 0, 0); for (i = 0, len = Tegaki.layers.length; i < len; ++i) { layer = Tegaki.layers[i]; if (!layer.visible) { continue; } ctx.globalAlpha = layer.alpha; ctx.drawImage(layer.canvas, 0, 0); } return canvas; }, onReplayLoaded: function() { TegakiUI.clearMsg(); Tegaki.initFromReplay(); Tegaki.init(); Tegaki.setTool(Tegaki.defaultTool); TegakiUI.updateReplayControls(); TegakiUI.updateReplayTime(true); TegakiUI.enableReplayControls(true); Tegaki.replayViewer.play(); }, onReplayGaplessClick: function() { Tegaki.replayViewer.toggleGapless(); TegakiUI.updateReplayGapless(); }, onReplayPlayPauseClick: function() { Tegaki.replayViewer.togglePlayPause(); }, onReplayRewindClick: function() { Tegaki.replayViewer.rewind(); }, onReplaySlowDownClick: function() { Tegaki.replayViewer.slowDown(); TegakiUI.updateReplaySpeed(); }, onReplaySpeedUpClick: function() { Tegaki.replayViewer.speedUp(); TegakiUI.updateReplaySpeed(); }, onReplayTimeChanged: function() { TegakiUI.updateReplayTime(); }, onReplayPlayPauseChanged: function() { TegakiUI.updateReplayPlayPause(); }, onReplayReset: function() { Tegaki.initFromReplay(); Tegaki.setTool(Tegaki.defaultTool); Tegaki.resizeCanvas(Tegaki.baseWidth, Tegaki.baseHeight); TegakiUI.updateReplayControls(); TegakiUI.updateReplayTime(); }, onMainColorClick: function(e) { var el; e.preventDefault(); el = $T.id('tegaki-colorpicker'); el.click(); }, onPaletteColorClick: function(e) { if (e.button === 2) { this.style.backgroundColor = Tegaki.toolColor; this.setAttribute('data-color', Tegaki.toolColor); } else if (e.button === 0) { Tegaki.setToolColor(this.getAttribute('data-color')); } }, onColorPicked: function(e) { $T.id('tegaki-color').style.backgroundColor = this.value; Tegaki.setToolColor(this.value); }, onSwitchPaletteClick: function(e) { var id; if (e.target.hasAttribute('data-prev')) { id = Tegaki.colorPaletteId - 1; } else { id = Tegaki.colorPaletteId + 1; } Tegaki.setColorPalette(id); }, setColorPalette: function(id) { if (id < 0 || id >= TegakiColorPalettes.length) { return; } Tegaki.colorPaletteId = id; TegakiUI.updateColorPalette(); }, setToolSizeUp: function() { Tegaki.setToolSize(Tegaki.tool.size + 1); }, setToolSizeDown: function() { Tegaki.setToolSize(Tegaki.tool.size - 1); }, setToolSize: function(size) { if (size > 0 && size <= Tegaki.maxSize) { Tegaki.tool.setSize(size); Tegaki.updateCursorStatus(); Tegaki.recordEvent(TegakiEventSetToolSize, performance.now(), size); TegakiUI.updateToolSize(); } }, setToolAlpha: function(alpha) { alpha = Math.fround(alpha); if (alpha >= 0.0 && alpha <= 1.0) { Tegaki.tool.setAlpha(alpha); Tegaki.recordEvent(TegakiEventSetToolAlpha, performance.now(), alpha); TegakiUI.updateToolAlpha(); } }, setToolFlow: function(flow) { flow = Math.fround(flow); if (flow >= 0.0 && flow <= 1.0) { Tegaki.tool.setFlow(flow); Tegaki.recordEvent(TegakiEventSetToolFlow, performance.now(), flow); TegakiUI.updateToolFlow(); } }, setToolColor: function(color) { Tegaki.toolColor = color; $T.id('tegaki-color').style.backgroundColor = color; $T.id('tegaki-colorpicker').value = color; Tegaki.tool.setColor(color); Tegaki.recordEvent(TegakiEventSetColor, performance.now(), Tegaki.tool.rgb); }, setToolColorRGB: function(r, g, b) { Tegaki.setToolColor($T.RgbToHex(r, g, b)); }, setTool: function(tool) { Tegaki.tools[tool].set(); }, setToolById: function(id) { var tool; for (tool in Tegaki.tools) { if (Tegaki.tools[tool].id === id) { Tegaki.setTool(tool); return; } } }, setZoom: function(level) { var idx; idx = level + Tegaki.zoomBaseLevel; if (idx >= Tegaki.zoomFactorList.length || idx < 0 || !Tegaki.canvas) { return; } Tegaki.zoomLevel = level; Tegaki.zoomFactor = Tegaki.zoomFactorList[idx]; TegakiUI.updateZoomLevel(); Tegaki.layersCnt.style.width = Math.ceil(Tegaki.baseWidth * Tegaki.zoomFactor) + 'px'; Tegaki.layersCnt.style.height = Math.ceil(Tegaki.baseHeight * Tegaki.zoomFactor) + 'px'; Tegaki.updateCursorStatus(); Tegaki.updatePosOffset(); }, onZoomChange: function() { if (this.hasAttribute('data-in')) { Tegaki.setZoom(Tegaki.zoomLevel + 1); } else { Tegaki.setZoom(Tegaki.zoomLevel - 1); } TegakiCursor.updateCanvasSize(); }, onNewClick: function() { var width, height, tmp, self = Tegaki; width = prompt(TegakiStrings.promptWidth, self.canvas.width); if (!width) { return; } height = prompt(TegakiStrings.promptHeight, self.canvas.height); if (!height) { return; } width = +width; height = +height; if (width < 1 || height < 1) { TegakiUI.printMsg(TegakiStrings.badDimensions); return; } tmp = {}; self.copyContextState(self.activeLayer.ctx, tmp); self.resizeCanvas(width, height); self.copyContextState(tmp, self.activeLayer.ctx); self.setZoom(0); TegakiHistory.clear(); TegakiUI.updateLayerPreviewSize(); self.startTimeStamp = Date.now(); if (self.saveReplay) { self.createTools(); self.setTool(self.defaultTool); self.replayRecorder = new TegakiReplayRecorder(); self.replayRecorder.start(); } }, onOpenClick: function() { var el, tainted; tainted = TegakiHistory.undoStack[0] || TegakiHistory.redoStack[0]; if (tainted || Tegaki.saveReplay) { if (!confirm(TegakiStrings.confirmChangeCanvas)) { return; } } el = $T.id('tegaki-filepicker'); el.click(); }, loadReplayFromFile: function() { Tegaki.replayViewer.debugLoadLocal(); }, loadReplayFromURL: function(url) { TegakiUI.printMsg(TegakiStrings.loadingReplay, 0); Tegaki.replayViewer.loadFromURL(url); }, onExportClick: function() { Tegaki.flatten().toBlob(function(b) { var el = $T.el('a'); el.className = 'tegaki-hidden'; el.download = $T.generateFilename() + '.png'; el.href = URL.createObjectURL(b); Tegaki.bg.appendChild(el); el.click(); Tegaki.bg.removeChild(el); }, 'image/png'); }, onUndoClick: function() { TegakiHistory.undo(); }, onRedoClick: function() { TegakiHistory.redo(); }, onHistoryChange: function(undoSize, redoSize, type = 0) { TegakiUI.updateUndoRedo(undoSize, redoSize); if (type === -1) { Tegaki.recordEvent(TegakiEventUndo, performance.now()); } else if (type === 1) { Tegaki.recordEvent(TegakiEventRedo, performance.now()); } }, onDoneClick: function() { Tegaki.hide(); Tegaki.onDoneCb(); }, onCancelClick: function() { if (!confirm(TegakiStrings.confirmCancel)) { return; } Tegaki.destroy(); Tegaki.onCancelCb(); }, onCloseViewerClick: function() { Tegaki.replayViewer.destroy(); Tegaki.destroy(); }, onToolSizeChange: function() { var val = +this.value; if (val < 1) { val = 1; } else if (val > Tegaki.maxSize) { val = Tegaki.maxSize; } Tegaki.setToolSize(val); }, onToolAlphaChange: function(e) { var val = +this.value; val = val / 100; if (val < 0.0) { val = 0.0; } else if (val > 1.0) { val = 1.0; } Tegaki.setToolAlpha(val); }, onToolFlowChange: function(e) { var val = +this.value; val = val / 100; if (val < 0.0) { val = 0.0; } else if (val > 1.0) { val = 1.0; } Tegaki.setToolFlow(val); }, onToolPressureSizeClick: function(e) { if (!Tegaki.tool.useSizeDynamics) { return; } Tegaki.setToolSizeDynamics(!Tegaki.tool.sizeDynamicsEnabled); }, setToolSizeDynamics: function(flag) { Tegaki.tool.setSizeDynamics(flag); TegakiUI.updateToolDynamics(); Tegaki.recordEvent(TegakiEventSetToolSizeDynamics, performance.now(), +flag); }, onToolPressureAlphaClick: function(e) { if (!Tegaki.tool.useAlphaDynamics) { return; } Tegaki.setToolAlphaDynamics(!Tegaki.tool.alphaDynamicsEnabled); }, setToolAlphaDynamics: function(flag) { Tegaki.tool.setAlphaDynamics(flag); TegakiUI.updateToolDynamics(); Tegaki.recordEvent(TegakiEventSetToolAlphaDynamics, performance.now(), +flag); }, onToolPressureFlowClick: function(e) { if (!Tegaki.tool.useFlowDynamics) { return; } Tegaki.setToolFlowDynamics(!Tegaki.tool.flowDynamicsEnabled); }, setToolFlowDynamics: function(flag) { Tegaki.tool.setFlowDynamics(flag); TegakiUI.updateToolDynamics(); Tegaki.recordEvent(TegakiEventSetToolFlowDynamics, performance.now(), +flag); }, onToolPreserveAlphaClick: function(e) { if (!Tegaki.tool.usePreserveAlpha) { return; } Tegaki.setToolPreserveAlpha(!Tegaki.tool.preserveAlphaEnabled); }, setToolPreserveAlpha: function(flag) { Tegaki.tool.setPreserveAlpha(flag); TegakiUI.updateToolPreserveAlpha(); Tegaki.recordEvent(TegakiEventPreserveAlpha, performance.now(), +flag); }, onToolTipClick: function(e) { var tipId = +e.target.getAttribute('data-id'); if (tipId !== Tegaki.tool.tipId) { Tegaki.setToolTip(tipId); } }, setToolTip: function(id) { Tegaki.tool.setTip(id); TegakiUI.updateToolShape(); Tegaki.recordEvent(TegakiEventSetToolTip, performance.now(), id); }, onLayerSelectorClick: function(e) { var id = +this.getAttribute('data-id'); if (!id || e.target.classList.contains('tegaki-ui-cb')) { return; } if (e.ctrlKey) { Tegaki.toggleSelectedLayer(id); } else { Tegaki.setActiveLayer(id); } }, toggleSelectedLayer: function(id) { TegakiLayers.selectedLayersToggle(id); Tegaki.recordEvent(TegakiEventToggleLayerSelection, performance.now(), id); }, setActiveLayer: function(id) { TegakiLayers.setActiveLayer(id); Tegaki.recordEvent(TegakiEventSetActiveLayer, performance.now(), id); }, onLayerAlphaDragStart: function(e) { TegakiUI.setupDragLabel(e, Tegaki.onLayerAlphaDragMove); }, onLayerAlphaDragMove: function(delta) { var val; if (!delta) { return; } val = Tegaki.activeLayer.alpha + delta / 100 ; if (val < 0.0) { val = 0.0; } else if (val > 1.0) { val = 1.0; } Tegaki.setSelectedLayersAlpha(val); }, onLayerAlphaChange: function() { var val = +this.value; val = val / 100; if (val < 0.0) { val = 0.0; } else if (val > 1.0) { val = 1.0; } Tegaki.setSelectedLayersAlpha(val); }, setSelectedLayersAlpha: function(alpha) { var layer, id, layerAlphas; alpha = Math.fround(alpha); if (alpha >= 0.0 && alpha <= 1.0 && Tegaki.selectedLayers.size > 0) { layerAlphas = []; for (id of Tegaki.selectedLayers) { if (layer = TegakiLayers.getLayerById(id)) { layerAlphas.push([layer.id, layer.alpha]); TegakiLayers.setLayerAlpha(layer, alpha); } } TegakiUI.updateLayerAlphaOpt(); TegakiHistory.push(new TegakiHistoryActions.SetLayersAlpha(layerAlphas, alpha)); Tegaki.recordEvent(TegakiEventSetSelectedLayersAlpha, performance.now(), alpha); } }, onLayerNameChangeClick: function(e) { var id, name, layer; id = +this.getAttribute('data-id'); layer = TegakiLayers.getLayerById(id); if (!layer) { return; } if (name = prompt(undefined, layer.name)) { Tegaki.setLayerName(id, name); } }, setLayerName: function(id, name) { var oldName, layer; name = name.trim().slice(0, 25); layer = TegakiLayers.getLayerById(id); if (!layer || !name || name === layer.name) { return; } oldName = layer.name; layer.name = name; TegakiUI.updateLayerName(layer); TegakiHistory.push(new TegakiHistoryActions.SetLayerName(id, oldName, name)); Tegaki.recordEvent(TegakiEventHistoryDummy, performance.now()); }, onLayerAddClick: function() { Tegaki.addLayer(); }, addLayer: function() { var action; if (Tegaki.layers.length >= Tegaki.maxLayers) { TegakiUI.printMsg(TegakiStrings.tooManyLayers); return; } TegakiHistory.push(action = TegakiLayers.addLayer()); TegakiLayers.setActiveLayer(action.aLayerIdAfter); Tegaki.recordEvent(TegakiEventAddLayer, performance.now()); }, onLayerDeleteClick: function() { Tegaki.deleteSelectedLayers(); }, deleteSelectedLayers: function() { var action, layerSet; layerSet = Tegaki.selectedLayers; if (layerSet.size === Tegaki.layers.length) { return; } if (!layerSet.size || Tegaki.layers.length < 2) { return; } TegakiHistory.push(action = TegakiLayers.deleteLayers(layerSet)); TegakiLayers.selectedLayersClear(); TegakiLayers.setActiveLayer(action.aLayerIdAfter); Tegaki.recordEvent(TegakiEventDeleteLayers, performance.now()); }, onLayerToggleVisibilityClick: function() { Tegaki.toggleLayerVisibility(+this.getAttribute('data-id')); }, toggleLayerVisibility: function(id) { var layer = TegakiLayers.getLayerById(id); TegakiLayers.setLayerVisibility(layer, !layer.visible); Tegaki.recordEvent(TegakiEventToggleLayerVisibility, performance.now(), id); }, onMergeLayersClick: function() { Tegaki.mergeSelectedLayers(); }, mergeSelectedLayers: function() { var action; if (Tegaki.selectedLayers.size) { if (action = TegakiLayers.mergeLayers(Tegaki.selectedLayers)) { TegakiHistory.push(action); TegakiLayers.setActiveLayer(action.aLayerIdAfter); Tegaki.recordEvent(TegakiEventMergeLayers, performance.now()); } } }, onMoveLayerClick: function(e) { var belowPos, up; if (!Tegaki.selectedLayers.size) { return; } up = e.target.hasAttribute('data-up'); belowPos = TegakiLayers.getSelectedEdgeLayerPos(up); if (belowPos < 0) { return; } if (up) { belowPos += 2; } else if (belowPos >= 1) { belowPos--; } Tegaki.moveSelectedLayers(belowPos); }, moveSelectedLayers: function(belowPos) { TegakiHistory.push(TegakiLayers.moveLayers(Tegaki.selectedLayers, belowPos)); Tegaki.recordEvent(TegakiEventMoveLayers, performance.now(), belowPos); }, onToolClick: function() { Tegaki.setTool(this.getAttribute('data-tool')); }, onToolChanged: function(tool) { var el; Tegaki.tool = tool; if (el = $T.cls('tegaki-tool-active')[0]) { el.classList.remove('tegaki-tool-active'); } $T.id('tegaki-tool-btn-' + tool.name).classList.add('tegaki-tool-active'); Tegaki.recordEvent(TegakiEventSetTool, performance.now(), Tegaki.tool.id); TegakiUI.onToolChanged(); Tegaki.updateCursorStatus(); }, onLayerStackChanged: function() {}, onOpenFileSelected: function() { var img; if (this.files && this.files[0]) { img = new Image(); img.onload = Tegaki.onOpenImageLoaded; img.onerror = Tegaki.onOpenImageError; img.src = URL.createObjectURL(this.files[0]); } }, onOpenImageLoaded: function() { var tmp = {}, self = Tegaki; self.hasCustomCanvas = true; self.copyContextState(self.activeLayer.ctx, tmp); self.resizeCanvas(this.naturalWidth, this.naturalHeight); self.activeLayer.ctx.drawImage(this, 0, 0); TegakiLayers.syncLayerImageData(self.activeLayer); self.copyContextState(tmp, self.activeLayer.ctx); self.setZoom(0); TegakiHistory.clear(); TegakiUI.updateLayerPreviewSize(true); self.startTimeStamp = Date.now(); if (self.saveReplay) { self.replayRecorder.stop(); self.replayRecorder = null; self.saveReplay = false; TegakiUI.setRecordingStatus(false); } }, onOpenImageError: function() { TegakiUI.printMsg(TegakiStrings.errorLoadImage); }, resizeCanvas: function(width, height) { Tegaki.baseWidth = width; Tegaki.baseHeight = height; Tegaki.createBuffers(); Tegaki.canvas.width = width; Tegaki.canvas.height = height; Tegaki.ctx.fillStyle = Tegaki.bgColor; Tegaki.ctx.fillRect(0, 0, width, height); Tegaki.activeLayer = null; Tegaki.resetLayers(); Tegaki.updateLayersCntSize(); Tegaki.updatePosOffset(); TegakiCursor.updateCanvasSize(); }, 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]; } }, updateCursorStatus: function() { if (!Tegaki.tool.noCursor && Tegaki.tool.size > 1) { Tegaki.cursor = TegakiCursor.generate(Tegaki.tool.size); } else { Tegaki.cursor = false; TegakiCursor.clear(); } }, updatePosOffset: function() { var aabb = Tegaki.canvas.getBoundingClientRect(); Tegaki.offsetX = aabb.left + window.pageXOffset + Tegaki.canvasCnt.scrollLeft + Tegaki.layersCnt.scrollLeft; Tegaki.offsetY = aabb.top + window.pageYOffset + Tegaki.canvasCnt.scrollTop + Tegaki.layersCnt.scrollTop; }, getScrollbarSizes: function() { return [ Tegaki.canvasCnt.offsetWidth - Tegaki.canvasCnt.clientWidth, Tegaki.canvasCnt.offsetHeight - Tegaki.canvasCnt.clientHeight ]; }, isScrollbarClick: function(e) { var [ sbwh, scbv ] = Tegaki.getScrollbarSizes(); if (sbwh > 0 && e.clientX >= Tegaki.canvasCnt.offsetLeft + Tegaki.canvasCnt.clientWidth && e.clientX <= Tegaki.canvasCnt.offsetLeft + Tegaki.canvasCnt.clientWidth + sbwh) { return true; } if (scbv > 0 && e.clientY >= Tegaki.canvasCnt.offsetTop + Tegaki.canvasCnt.clientHeight && e.clientY <= Tegaki.canvasCnt.offsetTop + Tegaki.canvasCnt.clientHeight + sbwh) { return true; } return false; }, onPointerMove: function(e) { var events, x, y, tool, ts, p; if (e.mozInputSource !== undefined) { // Firefox thing where mouse events fire for no reason when the pointer is a pen if (Tegaki.activePointerIsPen && e.pointerType === 'mouse') { return; } } else { // Webkit thing where a pointermove event is fired at pointerdown location after a pointerup if (Tegaki.activePointerId !== e.pointerId) { Tegaki.activePointerId = e.pointerId; return; } } if (Tegaki.isPainting) { tool = Tegaki.tool; if (Tegaki.activePointerIsPen && e.getCoalescedEvents) { events = e.getCoalescedEvents(); ts = e.timeStamp; for (e of events) { x = Tegaki.getPointerPos(e, 0); y = Tegaki.getPointerPos(e, 1); if (!tool.enabledDynamics()) { Tegaki.recordEvent(TegakiEventDrawNoP, ts, x, y); } else { p = TegakiPressure.toShort(e.pressure); TegakiPressure.push(p); Tegaki.recordEvent(TegakiEventDraw, ts, x, y, p); } tool.draw(x, y); } } else { x = Tegaki.getPointerPos(e, 0); y = Tegaki.getPointerPos(e, 1); p = TegakiPressure.toShort(e.pressure); Tegaki.recordEvent(TegakiEventDraw, e.timeStamp, x, y, p); TegakiPressure.push(p); tool.draw(x, y); } } else { x = Tegaki.getPointerPos(e, 0); y = Tegaki.getPointerPos(e, 1); } if (Tegaki.cursor) { TegakiCursor.render(e.clientX, e.clientY); } }, onPointerDown: function(e) { var x, y, tool, p; if (Tegaki.isScrollbarClick(e)) { return; } Tegaki.activePointerId = e.pointerId; Tegaki.activePointerIsPen = e.pointerType === 'pen'; if (Tegaki.activeLayer === null) { if (e.target.parentNode === Tegaki.layersCnt) { TegakiUI.printMsg(TegakiStrings.noActiveLayer); } return; } if (!TegakiLayers.getActiveLayer().visible) { if (e.target.parentNode === Tegaki.layersCnt) { TegakiUI.printMsg(TegakiStrings.hiddenActiveLayer); } return; } x = Tegaki.getPointerPos(e, 0); y = Tegaki.getPointerPos(e, 1); if (e.button === 2 || e.altKey) { e.preventDefault(); Tegaki.isPainting = false; Tegaki.tools.pipette.draw(x, y); } else if (e.button === 0) { e.preventDefault(); tool = Tegaki.tool; if (!tool.enabledDynamics()) { Tegaki.recordEvent(TegakiEventDrawStartNoP, e.timeStamp, x, y); } else { p = TegakiPressure.toShort(e.pressure); TegakiPressure.push(p); Tegaki.recordEvent(TegakiEventDrawStart, e.timeStamp, x, y, p); } Tegaki.isPainting = true; TegakiHistory.pendingAction = new TegakiHistoryActions.Draw( Tegaki.activeLayer.id ); TegakiHistory.pendingAction.addCanvasState(Tegaki.activeLayer.imageData, 0); tool.start(x, y); } if (Tegaki.cursor) { TegakiCursor.render(e.clientX, e.clientY); } }, onPointerUp: function(e) { Tegaki.activePointerId = e.pointerId; Tegaki.activePointerIsPen = false; if (Tegaki.isPainting) { Tegaki.recordEvent(TegakiEventDrawCommit, e.timeStamp); Tegaki.tool.commit(); TegakiUI.updateLayerPreview(Tegaki.activeLayer); TegakiHistory.pendingAction.addCanvasState(Tegaki.activeLayer.imageData, 1); TegakiHistory.push(TegakiHistory.pendingAction); Tegaki.isPainting = false; } }, onPointerOut: function(e) { TegakiCursor.clearAll(); }, onDummy: function(e) { e.preventDefault(); e.stopPropagation(); }, recordEvent(klass, ...args) { if (Tegaki.replayRecorder) { Tegaki.replayRecorder.push(new klass(...args)); } } }; var TegakiColorPalettes = [ [ '#ffffff', '#000000', '#888888', '#b47575', '#c096c0', '#fa9696', '#8080ff', '#ffb6ff', '#e7e58d', '#25c7c9', '#99cb7b', '#e7962d', '#f9ddcf', '#fcece2' ], [ '#000000', '#ffffff', '#7f7f7f', '#c3c3c3', '#880015', '#b97a57', '#ed1c24', '#ffaec9', '#ff7f27', '#ffc90e', '#fff200', '#efe4b0', '#22b14c', '#b5e61d', '#00a2e8', '#99d9ea', '#3f48cc', '#7092be', '#a349a4', '#c8bfe7' ], [ '#000000', '#ffffff', '#8a8a8a', '#cacaca', '#fcece2', '#f9ddcf', '#e0a899', '#a05b53', '#7a444a', '#960018', '#c41e3a', '#de4537', '#ff3300', '#ff9800', '#ffc107', '#ffd700', '#ffeb3b', '#ffffcc', '#f3e5ab', '#cddc39', '#8bc34a', '#4caf50', '#3e8948', '#355e3b', '#3eb489', '#f0f8ff', '#87ceeb', '#6699cc', '#007fff', '#2d68c4', '#364478', '#352c4a', '#9c27b0', '#da70d6', '#ff0090', '#fa8072', '#f19cbb', '#c78b95' ] ]; var TegakiPressure = { pressureNow: 0.0, pressureThen: 0.0, toShort: function(pressure) { return 0 | (pressure * 65535); }, get: function() { return this.pressureNow; }, lerp: function(t) { return this.pressureThen * (1.0 - t) + this.pressureNow * t; }, push: function(p) { this.pressureThen = this.pressureNow; this.pressureNow = p / 65535; }, set: function(p) { this.pressureThen = this.pressureNow = p / 65535; } }; class TegakiEvent_void { constructor() { this.size = 5; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); } static unpack(r) { return new this(r.readUint32()); } } class TegakiEvent_c { constructor() { this.size = 6; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeUint8(this.value); } static unpack(r) { return new this(r.readUint32(), r.readUint8()); } } // --- class TegakiEventPrelude extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() {} } class TegakiEventConclusion extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() {} } class TegakiEventHistoryDummy extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { TegakiHistory.push(new TegakiHistoryActions.Dummy()); } } class TegakiEventDrawStart { constructor(timeStamp, x, y, pressure) { this.timeStamp = timeStamp; this.x = x; this.y = y; this.pressure = pressure; this.type = TegakiEvents[this.constructor.name][0]; this.size = 11; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeInt16(this.x); w.writeInt16(this.y); w.writeUint16(this.pressure); } static unpack(r) { var timeStamp, x, y, pressure; timeStamp = r.readUint32(); x = r.readInt16(); y = r.readInt16(); pressure = r.readUint16(); return new TegakiEventDrawStart(timeStamp, x, y, pressure); } dispatch() { TegakiPressure.set(this.pressure); TegakiHistory.pendingAction = new TegakiHistoryActions.Draw( Tegaki.activeLayer.id ); TegakiHistory.pendingAction.addCanvasState(Tegaki.activeLayer.imageData, 0); Tegaki.tool.start(this.x, this.y); } } class TegakiEventDrawStartNoP { constructor(timeStamp, x, y) { this.timeStamp = timeStamp; this.x = x; this.y = y; this.type = TegakiEvents[this.constructor.name][0]; this.size = 9; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeInt16(this.x); w.writeInt16(this.y); } static unpack(r) { var timeStamp, x, y; timeStamp = r.readUint32(); x = r.readInt16(); y = r.readInt16(); return new TegakiEventDrawStartNoP(timeStamp, x, y); } dispatch() { TegakiPressure.set(0.5); TegakiHistory.pendingAction = new TegakiHistoryActions.Draw( Tegaki.activeLayer.id ); TegakiHistory.pendingAction.addCanvasState(Tegaki.activeLayer.imageData, 0); Tegaki.tool.start(this.x, this.y); } } class TegakiEventDraw { constructor(timeStamp, x, y, pressure) { this.timeStamp = timeStamp; this.x = x; this.y = y; this.pressure = pressure; this.type = TegakiEvents[this.constructor.name][0]; this.size = 11; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeInt16(this.x); w.writeInt16(this.y); w.writeUint16(this.pressure); } static unpack(r) { var timeStamp, x, y, pressure; timeStamp = r.readUint32(); x = r.readInt16(); y = r.readInt16(); pressure = r.readUint16(); return new TegakiEventDraw(timeStamp, x, y, pressure); } dispatch() { TegakiPressure.push(this.pressure); Tegaki.tool.draw(this.x, this.y); } } class TegakiEventDrawNoP { constructor(timeStamp, x, y) { this.timeStamp = timeStamp; this.x = x; this.y = y; this.type = TegakiEvents[this.constructor.name][0]; this.size = 9; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeInt16(this.x); w.writeInt16(this.y); } static unpack(r) { var timeStamp, x, y; timeStamp = r.readUint32(); x = r.readInt16(); y = r.readInt16(); return new TegakiEventDraw(timeStamp, x, y); } dispatch() { TegakiPressure.push(0.5); Tegaki.tool.draw(this.x, this.y); } } class TegakiEventDrawCommit extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { Tegaki.tool.commit(); TegakiUI.updateLayerPreview(Tegaki.activeLayer); TegakiHistory.pendingAction.addCanvasState(Tegaki.activeLayer.imageData, 1); TegakiHistory.push(TegakiHistory.pendingAction); Tegaki.isPainting = false; } } class TegakiEventUndo extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { TegakiHistory.undo(); } } class TegakiEventRedo extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { TegakiHistory.redo(); } } class TegakiEventSetColor { constructor(timeStamp, rgb) { this.timeStamp = timeStamp; [this.r, this.g, this.b] = rgb; this.type = TegakiEvents[this.constructor.name][0]; this.size = 8; this.coalesce = true; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeUint8(this.r); w.writeUint8(this.g); w.writeUint8(this.b); } static unpack(r) { var timeStamp, rgb; timeStamp = r.readUint32(); rgb = [r.readUint8(), r.readUint8(), r.readUint8()]; return new TegakiEventSetColor(timeStamp, rgb); } dispatch() { Tegaki.setToolColorRGB(this.r, this.g, this.b); } } class TegakiEventSetTool extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolById(this.value); } } class TegakiEventSetToolSize extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolSize(this.value); } } class TegakiEventSetToolAlpha { constructor(timeStamp, value) { this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; this.size = 9; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeFloat32(this.value); } static unpack(r) { return new this(r.readUint32(), r.readFloat32()); } dispatch() { Tegaki.setToolAlpha(this.value); } } class TegakiEventSetToolFlow { constructor(timeStamp, value) { this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; this.size = 9; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeFloat32(this.value); } static unpack(r) { return new this(r.readUint32(), r.readFloat32()); } dispatch() { Tegaki.setToolFlow(this.value); } } class TegakiEventPreserveAlpha extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolPreserveAlpha(!!this.value); } } class TegakiEventSetToolSizeDynamics extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolSizeDynamics(!!this.value); } } class TegakiEventSetToolAlphaDynamics extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolAlphaDynamics(!!this.value); } } class TegakiEventSetToolFlowDynamics extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolFlowDynamics(!!this.value); } } class TegakiEventSetToolTip extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; } dispatch() { Tegaki.setToolTip(this.value); } } class TegakiEventAddLayer extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { Tegaki.addLayer(); } } class TegakiEventDeleteLayers extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { Tegaki.deleteSelectedLayers(); } } class TegakiEventMoveLayers extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; } dispatch() { Tegaki.moveSelectedLayers(this.value); } } class TegakiEventMergeLayers extends TegakiEvent_void { constructor(timeStamp) { super(); this.timeStamp = timeStamp; this.type = TegakiEvents[this.constructor.name][0]; } static unpack(r) { return super.unpack(r); } // FF bug 1628719 dispatch() { Tegaki.mergeSelectedLayers(); } } class TegakiEventToggleLayerVisibility extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; } dispatch() { Tegaki.toggleLayerVisibility(this.value); } } class TegakiEventSetActiveLayer extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; } dispatch() { Tegaki.setActiveLayer(this.value); } } class TegakiEventToggleLayerSelection extends TegakiEvent_c { constructor(timeStamp, value) { super(); this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; } dispatch() { Tegaki.toggleSelectedLayer(this.value); } } class TegakiEventSetSelectedLayersAlpha { constructor(timeStamp, value) { this.timeStamp = timeStamp; this.value = value; this.type = TegakiEvents[this.constructor.name][0]; this.coalesce = true; this.size = 9; } pack(w) { w.writeUint8(this.type); w.writeUint32(this.timeStamp); w.writeFloat32(this.value); } static unpack(r) { return new this(r.readUint32(), r.readFloat32()); } dispatch() { Tegaki.setSelectedLayersAlpha(this.value); } } const TegakiEvents = Object.freeze({ TegakiEventPrelude: [0, TegakiEventPrelude], TegakiEventDrawStart: [1, TegakiEventDrawStart], TegakiEventDraw: [2, TegakiEventDraw], TegakiEventDrawCommit: [3, TegakiEventDrawCommit], TegakiEventUndo: [4, TegakiEventUndo], TegakiEventRedo: [5, TegakiEventRedo], TegakiEventSetColor: [6, TegakiEventSetColor], TegakiEventDrawStartNoP: [7, TegakiEventDrawStartNoP], TegakiEventDrawNoP: [8, TegakiEventDrawNoP], TegakiEventSetTool: [10, TegakiEventSetTool], TegakiEventSetToolSize: [11, TegakiEventSetToolSize], TegakiEventSetToolAlpha: [12, TegakiEventSetToolAlpha], TegakiEventSetToolSizeDynamics: [13, TegakiEventSetToolSizeDynamics], TegakiEventSetToolAlphaDynamics: [14, TegakiEventSetToolAlphaDynamics], TegakiEventSetToolTip: [15, TegakiEventSetToolTip], TegakiEventPreserveAlpha: [16, TegakiEventPreserveAlpha], TegakiEventSetToolFlowDynamics: [17, TegakiEventSetToolFlowDynamics], TegakiEventSetToolFlow: [18, TegakiEventSetToolFlow], TegakiEventAddLayer: [20, TegakiEventAddLayer], TegakiEventDeleteLayers: [21, TegakiEventDeleteLayers], TegakiEventMoveLayers: [22, TegakiEventMoveLayers], TegakiEventMergeLayers: [23, TegakiEventMergeLayers], TegakiEventToggleLayerVisibility: [24, TegakiEventToggleLayerVisibility], TegakiEventSetActiveLayer: [25, TegakiEventSetActiveLayer], TegakiEventToggleLayerSelection: [26, TegakiEventToggleLayerSelection], TegakiEventSetSelectedLayersAlpha: [27, TegakiEventSetSelectedLayersAlpha], TegakiEventHistoryDummy: [254, TegakiEventHistoryDummy], TegakiEventConclusion: [255, TegakiEventConclusion] }); class TegakiReplayRecorder { constructor() { this.formatVersion = 1; this.compressed = true; this.tegakiVersion = Tegaki.VERSION.split('.').map((v) => +v); this.canvasWidth = Tegaki.baseWidth; this.canvasHeight = Tegaki.baseHeight; this.bgColor = $T.hexToRgb(Tegaki.bgColor); this.toolColor = $T.hexToRgb(Tegaki.toolColor); this.toolId = Tegaki.tools[Tegaki.defaultTool].id; this.toolList = this.buildToolList(Tegaki.tools); this.startTimeStamp = 0; this.endTimeStamp = 0; this.recording = false; this.events = []; this.mimeType = 'application/octet-stream'; } buildToolList(tools) { var k, tool, toolMap; toolMap = []; for (k in tools) { tool = tools[k]; toolMap.push({ id: tool.id, size: tool.size, alpha: tool.alpha, flow: tool.flow, step: tool.step, sizeDynamicsEnabled: +tool.sizeDynamicsEnabled, alphaDynamicsEnabled: +tool.alphaDynamicsEnabled, flowDynamicsEnabled: +tool.flowDynamicsEnabled, usePreserveAlpha: +tool.usePreserveAlpha, tipId: tool.tipId }); } return toolMap; } start() { if (this.recording) { return; } if (this.endTimeStamp > 0) { this.events.pop(); this.endTimeStamp = 0; } if (this.startTimeStamp === 0) { this.events.push(new TegakiEventPrelude(performance.now())); this.startTimeStamp = Date.now(); } this.recording = true; } stop() { if (this.startTimeStamp === 0 || !this.recording) { return; } this.events.push(new TegakiEventConclusion(performance.now())); this.endTimeStamp = Date.now(); this.recording = false; } push(e) { if (this.recording) { if (e.coalesce && this.events[this.events.length - 1].type === e.type) { this.events[this.events.length - 1] = e; } else { this.events.push(e); } } } getEventStackSize() { var e, size; size = 4; for (e of this.events) { size += e.size; } return size; } getHeaderSize() { return 12; } getMetaSize() { return 21; } getToolSize() { return 19; } getToolListSize() { return 2 + (this.toolList.length * this.getToolSize()); } writeToolList(w) { var tool, field, fields; fields = [ ['id', 'Uint8'], ['size', 'Uint8'], ['alpha', 'Float32'], ['step', 'Float32'], ['sizeDynamicsEnabled', 'Uint8'], ['alphaDynamicsEnabled', 'Uint8'], ['usePreserveAlpha', 'Uint8'], ['tipId', 'Int8'], ['flow', 'Float32'], ['flowDynamicsEnabled', 'Uint8'], ]; w.writeUint8(this.toolList.length); w.writeUint8(this.getToolSize()); for (tool of this.toolList) { for (field of fields) { w['write' + field[1]](tool[field[0]]); } } } writeMeta(w) { w.writeUint16(this.getMetaSize()); w.writeUint32(Math.ceil(this.startTimeStamp / 1000)); w.writeUint32(Math.ceil(this.endTimeStamp / 1000)); w.writeUint16(this.canvasWidth); w.writeUint16(this.canvasHeight); w.writeUint8(this.bgColor[0]); w.writeUint8(this.bgColor[1]); w.writeUint8(this.bgColor[2]); w.writeUint8(this.toolColor[0]); w.writeUint8(this.toolColor[1]); w.writeUint8(this.toolColor[2]); w.writeUint8(this.toolId); } writeEventStack(w) { var event; w.writeUint32(this.events.length); for (event of this.events) { event.pack(w); } } writeHeader(w, dataSize) { w.writeUint8(0x54); w.writeUint8(0x47); w.writeUint8(0x4B); w.writeUint8(+this.compressed); w.writeUint32(dataSize); w.writeUint8(this.tegakiVersion[0]); w.writeUint8(this.tegakiVersion[1]); w.writeUint8(this.tegakiVersion[2]); w.writeUint8(this.formatVersion); } compressData(w) { return UZIP.deflateRaw(new Uint8Array(w.buf), { level: 9 }); } toUint8Array() { var headerSize, dataSize, data, w, compData, bytes; if (!this.startTimeStamp || !this.endTimeStamp) { return null; } headerSize = this.getHeaderSize(); dataSize = this.getMetaSize() + this.getToolListSize() + this.getEventStackSize(); data = new ArrayBuffer(dataSize); w = new TegakiBinWriter(data); this.writeMeta(w); this.writeToolList(w); this.writeEventStack(w); compData = this.compressData(w); //compData = new Uint8Array(data.slice(0)); w = new TegakiBinWriter(new ArrayBuffer(headerSize + compData.length)); this.writeHeader(w, dataSize); bytes = new Uint8Array(w.buf); bytes.set(compData, headerSize); return bytes; } toBlob() { var ary = this.toUint8Array(); if (!ary) { return null; } return new Blob([ary.buffer], { type: this.mimeType }); } } class TegakiReplayViewer { constructor() { this.formatVersion = 1; this.compressed = true; this.tegakiVersion = [0, 0, 0]; this.dataSize = 0; this.canvasWidth = 0; this.canvasHeight = 0; this.bgColor = [0, 0, 0]; this.toolColor = [0, 0, 0]; this.toolId = 1; this.toolMap = {}; this.startTimeStamp = 0; this.endTimeStamp = 0; this.loaded = false; this.playing = false; this.gapless = true; this.autoPaused = false; this.destroyed = false; this.speedIndex = 1; this.speedList = [0.5, 1.0, 2.0, 5.0, 10.0, 25.0]; this.speed = this.speedList[this.speedIndex]; this.maxEventsPerFrame = 50; this.maxEventCount = 8640000; this.events = []; this.preludePos = 0.0; this.currentPos = 0.0; this.conclusionPos = 0.0; this.duration = 0.0; this.playTimeStart = 0.0; this.playTimeCurrent = 0.0; this.eventIndex = 0; this.maxCanvasWH = 8192; this.maxGapTime = 3000; this.uiAccTime = 0; this.onFrameThis = this.onFrame.bind(this); } destroy() { this.destroyed = true; this.pause(); this.events = null; } speedUp() { if (this.speedIndex + 1 < this.speedList.length) { this.speed = this.speedList[++this.speedIndex]; } } slowDown() { if (this.speedIndex - 1 >= 0) { this.speed = this.speedList[--this.speedIndex]; } } toggleGapless() { this.gapless = !this.gapless; } getCurrentPos() { return this.currentPos; } getDuration() { return this.duration; } loadFromURL(url) { fetch(url) .then((resp) => this.onResponseReady(resp)) .catch((err) => this.onLoadError(err)); } onResponseReady(resp) { if (resp.ok) { resp.arrayBuffer() .then((buf) => this.onResponseBodyReady(buf)) .catch((err) => this.onLoadError(err)); } else { this.onLoadError(resp.statusText); } } onResponseBodyReady(data) { this.loadFromBuffer(data); Tegaki.onReplayLoaded(); } onLoadError(err) { TegakiUI.printMsg(TegakiStrings.errorLoadReplay + err, 0); } autoPause() { this.autoPaused = true; this.pause(); } pause(rewind) { window.cancelAnimationFrame(this.onFrameThis); this.playing = false; if (rewind) { this.currentPos = 0; this.eventIndex = 0; } Tegaki.onReplayTimeChanged(); Tegaki.onReplayPlayPauseChanged(); } rewind() { this.autoPaused = false; this.pause(true); Tegaki.onReplayReset(); } play() { this.playTimeStart = performance.now(); this.playTimeCurrent = this.playTimeStart; this.playing = true; this.autoPaused = false; this.uiAccTime = 0; Tegaki.onReplayPlayPauseChanged(); window.requestAnimationFrame(this.onFrameThis); } togglePlayPause() { if (this.playing) { this.pause(); } else { this.play(); } } onFrame(ts) { var delta = ts - this.playTimeCurrent; if (!this.playing) { return; } this.playTimeCurrent = ts; this.step(delta); this.uiAccTime += delta; if (this.uiAccTime > 1000) { Tegaki.onReplayTimeChanged(); this.uiAccTime = 0; } if (this.currentPos < this.duration) { window.requestAnimationFrame(this.onFrameThis); } else { this.pause(); } } step(delta) { var event, currentEventTime, i; this.currentPos += (delta * this.speed); currentEventTime = this.currentPos + this.preludePos; if (this.gapless && this.eventIndex < this.events.length) { event = this.events[this.eventIndex]; if (event.timeStamp - currentEventTime > this.maxGapTime) { this.currentPos = event.timeStamp - this.preludePos; currentEventTime = event.timeStamp; } } i = 0; while (this.eventIndex < this.events.length) { event = this.events[this.eventIndex]; if (event.timeStamp <= currentEventTime) { if (i >= this.maxEventsPerFrame) { this.currentPos = event.timeStamp - this.preludePos; break; } event.dispatch(); ++this.eventIndex; ++i; } else { break; } } } getEventIdMap() { var map, key, val; map = {}; for (key in TegakiEvents) { val = TegakiEvents[key]; map[val[0]] = val[1]; } return map; } readToolMap(r) { var i, len, size, tool, field, fields, pos; this.toolMap = {}; fields = [ ['id', 'Uint8'], ['size', 'Uint8'], ['alpha', 'Float32'], ['step', 'Float32'], ['sizeDynamicsEnabled', 'Uint8'], ['alphaDynamicsEnabled', 'Uint8'], ['usePreserveAlpha', 'Uint8'], ['tipId', 'Int8'], ['flow', 'Float32'], ['flowDynamicsEnabled', 'Uint8'], ]; len = r.readUint8(); size = r.readUint8(); for (i = 0; i < len; ++i) { pos = r.pos + size; tool = {}; for (field of fields) { if (r.pos >= pos) { break; } tool[field[0]] = r['read' + field[1]](); } this.toolMap[tool.id] = tool; r.pos = pos; } } readHeader(r) { var tgk; tgk = String.fromCharCode(r.readUint8(), r.readUint8(), r.readUint8()); if (tgk !== 'TGK') { throw 'invalid header'; } this.compressed = r.readUint8() === 1; this.dataSize = r.readUint32(); this.tegakiVersion[0] = r.readUint8(); this.tegakiVersion[1] = r.readUint8(); this.tegakiVersion[2] = r.readUint8(); this.formatVersion = r.readUint8(); } decompressData(r) { return UZIP.inflateRaw( new Uint8Array(r.buf, r.pos), new Uint8Array(this.dataSize) ); } readMeta(r) { var pos, size; size = r.readUint16(); pos = r.pos + size - 2; this.startTimeStamp = r.readUint32() * 1000; this.endTimeStamp = r.readUint32() * 1000; this.canvasWidth = r.readUint16(); this.canvasHeight = r.readUint16(); if (this.canvasWidth > this.maxCanvasWH || this.canvasHeight > this.maxCanvasWH) { throw 'canvas too large'; } this.bgColor[0] = r.readUint8(); this.bgColor[1] = r.readUint8(); this.bgColor[2] = r.readUint8(); this.toolColor[0] = r.readUint8(); this.toolColor[1] = r.readUint8(); this.toolColor[2] = r.readUint8(); this.toolId = r.readUint8(); r.pos = pos; } readEventStack(r) { var i, len, type, klass, event, eventMap; eventMap = this.getEventIdMap(); len = r.readUint32(); if (len < 1 || len > this.maxEventCount) { throw 'invalid event count'; } for (i = 0; i < len; ++i) { type = r.readUint8(); klass = eventMap[type]; if (!klass) { throw 'invalid event id'; } event = klass.unpack(r); this.events.push(event); } if (this.events[0].type !== TegakiEvents.TegakiEventPrelude[0]) { throw 'invalid prelude'; } if (this.events[len - 1].type !== TegakiEvents.TegakiEventConclusion[0]) { throw 'invalid conclusion'; } this.preludePos = this.events[0].timeStamp; this.conclusionPos = this.events[len - 1].timeStamp; this.duration = this.conclusionPos - this.preludePos; if (this.duration <= 0) { throw 'invalid duration'; } } loadFromBuffer(buffer) { var r, data; if (this.destroyed || this.loaded) { return false; } r = new TegakiBinReader(buffer); this.readHeader(r); data = this.decompressData(r); r = new TegakiBinReader(data.buffer); this.readMeta(r); this.readToolMap(r); this.readEventStack(r); this.loaded = true; return true; } } var TegakiUI = { draggedNode: null, draggedLabelLastX: 0, draggedLabelFn: null, statusTimeout: 0, layerPreviewCtxCache: new WeakMap(), getLayerPreviewSize: function() { return $T.calcThumbSize(Tegaki.baseWidth, Tegaki.baseHeight, 24); }, setupDragLabel: function(e, moveFn) { TegakiUI.draggedLabelFn = moveFn; TegakiUI.draggedLabelLastX = e.clientX; $T.on(document, 'pointermove', TegakiUI.processDragLabel); $T.on(document, 'pointerup', TegakiUI.clearDragLabel); }, processDragLabel: function(e) { TegakiUI.draggedLabelFn.call(Tegaki, e.clientX - TegakiUI.draggedLabelLastX); TegakiUI.draggedLabelLastX = e.clientX; }, clearDragLabel: function(e) { $T.off(document, 'pointermove', TegakiUI.processDragLabel); $T.off(document, 'pointerup', TegakiUI.clearDragLabel); }, printMsg: function(str, timeout = 5000) { TegakiUI.clearMsg(); $T.id('tegaki-status-output').textContent = str; if (timeout > 0) { TegakiUI.statusTimeout = setTimeout(TegakiUI.clearMsg, 5000); } }, clearMsg: function() { if (TegakiUI.statusTimeout) { clearTimeout(TegakiUI.statusTimeout); TegakiUI.statusTimeout = 0; } $T.id('tegaki-status-output').textContent = ''; }, buildUI: function() { var bg, cnt, el, ctrl, layersCnt, canvasCnt; // // Grid container // bg = $T.el('div'); bg.id = 'tegaki'; // // Menu area // el = $T.el('div'); el.id = 'tegaki-menu-cnt'; if (!Tegaki.replayMode) { el.appendChild(TegakiUI.buildMenuBar()); } else { el.appendChild(TegakiUI.buildViewerMenuBar()); el.appendChild(TegakiUI.buildReplayControls()); } el.appendChild(TegakiUI.buildToolModeBar()); bg.appendChild(el); bg.appendChild(TegakiUI.buildDummyFilePicker()); // // Tools area // cnt = $T.el('div'); cnt.id = 'tegaki-tools-cnt'; cnt.appendChild(TegakiUI.buildToolsMenu()); bg.appendChild(cnt); // // Canvas area // [canvasCnt, layersCnt] = TegakiUI.buildCanvasCnt(); bg.appendChild(canvasCnt); // // Controls area // ctrl = $T.el('div'); ctrl.id = 'tegaki-ctrl-cnt'; // Zoom control ctrl.appendChild(TegakiUI.buildZoomCtrlGroup()); // Colorpicker ctrl.appendChild( TegakiUI.buildColorCtrlGroup(Tegaki.toolColor) ); // Size control ctrl.appendChild(TegakiUI.buildSizeCtrlGroup()); // Alpha control ctrl.appendChild(TegakiUI.buildAlphaCtrlGroup()); // Flow control ctrl.appendChild(TegakiUI.buildFlowCtrlGroup()); // Layers control ctrl.appendChild(TegakiUI.buildLayersCtrlGroup()); // --- bg.appendChild(ctrl); // // Status area // bg.appendChild(TegakiUI.buildStatusCnt()); return [bg, canvasCnt, layersCnt]; }, buildDummyFilePicker: function() { var el = $T.el('input'); el.type = 'file'; el.id = 'tegaki-filepicker'; el.className = 'tegaki-hidden'; el.accept = 'image/png, image/jpeg'; $T.on(el, 'change', Tegaki.onOpenFileSelected); return el; }, buildMenuBar: function() { var frag, btn; frag = $T.el('div'); frag.id = 'tegaki-menu-bar'; btn = $T.el('span'); btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.newCanvas; $T.on(btn, 'click', Tegaki.onNewClick); frag.appendChild(btn); btn = $T.el('span'); btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.open; $T.on(btn, 'click', Tegaki.onOpenClick); frag.appendChild(btn); btn = $T.el('span'); btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.export; $T.on(btn, 'click', Tegaki.onExportClick); frag.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-undo-btn'; btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.undo; btn.title = TegakiKeybinds.getCaption('undo'); $T.on(btn, 'click', Tegaki.onUndoClick); frag.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-redo-btn'; btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.redo; btn.title = TegakiKeybinds.getCaption('redo'); $T.on(btn, 'click', Tegaki.onRedoClick); frag.appendChild(btn); btn = $T.el('span'); btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.close; $T.on(btn, 'click', Tegaki.onCancelClick); frag.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-finish-btn'; btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.finish; $T.on(btn, 'click', Tegaki.onDoneClick); frag.appendChild(btn); return frag; }, buildViewerMenuBar: function() { var frag, btn; frag = $T.el('div'); frag.id = 'tegaki-menu-bar'; btn = $T.el('span'); btn.id = 'tegaki-finish-btn'; btn.className = 'tegaki-mb-btn'; btn.textContent = TegakiStrings.close; $T.on(btn, 'click', Tegaki.onCloseViewerClick); frag.appendChild(btn); return frag; }, buildToolModeBar: function() { var cnt, grp, el, btn; cnt = $T.el('div'); cnt.id = 'tegaki-toolmode-bar'; if (!Tegaki.tool) { cnt.classList.add('tegaki-hidden'); } // Dynamics grp = $T.el('span'); grp.id = 'tegaki-tool-mode-dynamics'; grp.className = 'tegaki-toolmode-grp'; el = $T.el('span'); el.className = 'tegaki-toolmode-lbl'; el.textContent = TegakiStrings.pressure; grp.appendChild(el); el = $T.el('span'); el.id = 'tegaki-tool-mode-dynamics-ctrl'; el.className = 'tegaki-toolmode-ctrl'; btn = $T.el('span'); btn.id = 'tegaki-tool-mode-dynamics-size'; btn.className = 'tegaki-sw-btn'; btn.textContent = TegakiStrings.size; $T.on(btn, 'mousedown', Tegaki.onToolPressureSizeClick); el.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-tool-mode-dynamics-alpha'; btn.className = 'tegaki-sw-btn'; btn.textContent = TegakiStrings.alpha; $T.on(btn, 'mousedown', Tegaki.onToolPressureAlphaClick); el.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-tool-mode-dynamics-flow'; btn.className = 'tegaki-sw-btn'; btn.textContent = TegakiStrings.flow; $T.on(btn, 'mousedown', Tegaki.onToolPressureFlowClick); el.appendChild(btn); grp.appendChild(el); cnt.appendChild(grp); // Preserve Alpha grp = $T.el('span'); grp.id = 'tegaki-tool-mode-mask'; grp.className = 'tegaki-toolmode-grp'; el = $T.el('span'); el.id = 'tegaki-toolmode-ctrl-tip'; el.className = 'tegaki-toolmode-ctrl'; btn = $T.el('span'); btn.id = 'tegaki-tool-mode-mask-alpha'; btn.className = 'tegaki-sw-btn'; btn.textContent = TegakiStrings.preserveAlpha; $T.on(btn, 'mousedown', Tegaki.onToolPreserveAlphaClick); el.appendChild(btn); grp.appendChild(el); cnt.appendChild(grp); // Tip grp = $T.el('span'); grp.id = 'tegaki-tool-mode-tip'; grp.className = 'tegaki-toolmode-grp'; el = $T.el('span'); el.className = 'tegaki-toolmode-lbl'; el.textContent = TegakiStrings.tip; grp.appendChild(el); el = $T.el('span'); el.id = 'tegaki-tool-mode-tip-ctrl'; el.className = 'tegaki-toolmode-ctrl'; grp.appendChild(el); cnt.appendChild(grp); return cnt; }, buildToolsMenu: function() { var grp, el, lbl, name; grp = $T.el('div'); grp.id = 'tegaki-tools-grid'; for (name in Tegaki.tools) { el = $T.el('span'); el.setAttribute('data-tool', name); lbl = TegakiStrings[name]; if (Tegaki.tools[name].keybind) { lbl += ' (' + Tegaki.tools[name].keybind.toUpperCase() + ')'; } el.setAttribute('title', lbl); el.id = 'tegaki-tool-btn-' + name; el.className = 'tegaki-tool-btn tegaki-icon tegaki-' + name; $T.on(el, 'click', Tegaki.onToolClick); grp.appendChild(el); } return grp; }, buildCanvasCnt: function() { var canvasCnt, wrap, layersCnt; canvasCnt = $T.el('div'); canvasCnt.id = 'tegaki-canvas-cnt'; wrap = $T.el('div'); wrap.id = 'tegaki-layers-wrap'; layersCnt = $T.el('div'); layersCnt.id = 'tegaki-layers'; wrap.appendChild(layersCnt); canvasCnt.appendChild(wrap); return [canvasCnt, layersCnt]; }, buildCtrlGroup: function(id, title) { var cnt, el; cnt = $T.el('div'); cnt.className = 'tegaki-ctrlgrp'; if (id) { cnt.id = 'tegaki-ctrlgrp-' + id; } if (title !== undefined) { el = $T.el('div'); el.className = 'tegaki-ctrlgrp-title'; el.textContent = title; cnt.appendChild(el); } return cnt; }, buildLayersCtrlGroup: function() { var el, ctrl, row, cnt; ctrl = this.buildCtrlGroup('layers', TegakiStrings.layers); // Layer options row row = $T.el('div'); row.id = 'tegaki-layers-opts'; // Alpha cnt = $T.el('div'); cnt.id = 'tegaki-layer-alpha-cell'; el = $T.el('span'); el.className = 'tegaki-label-xs tegaki-lbl-c tegaki-drag-lbl'; el.textContent = TegakiStrings.alpha; $T.on(el, 'pointerdown', Tegaki.onLayerAlphaDragStart); cnt.appendChild(el); el = $T.el('input'); el.id = 'tegaki-layer-alpha-opt'; el.className = 'tegaki-stealth-input tegaki-range-lbl-xs'; el.setAttribute('maxlength', 3); $T.on(el, 'input', Tegaki.onLayerAlphaChange); cnt.appendChild(el); row.appendChild(cnt); ctrl.appendChild(row); el = $T.el('div'); el.id = 'tegaki-layers-grid'; ctrl.appendChild(el); row = $T.el('div'); row.id = 'tegaki-layers-ctrl'; el = $T.el('span'); el.title = TegakiStrings.addLayer; el.className = 'tegaki-ui-btn tegaki-icon tegaki-plus'; $T.on(el, 'click', Tegaki.onLayerAddClick); row.appendChild(el); el = $T.el('span'); el.title = TegakiStrings.delLayers; el.className = 'tegaki-ui-btn tegaki-icon tegaki-minus'; $T.on(el, 'click', Tegaki.onLayerDeleteClick); row.appendChild(el); el = $T.el('span'); el.id = 'tegaki-layer-merge'; el.title = TegakiStrings.mergeLayers; el.className = 'tegaki-ui-btn tegaki-icon tegaki-level-down'; $T.on(el, 'click', Tegaki.onMergeLayersClick); row.appendChild(el); el = $T.el('span'); el.id = 'tegaki-layer-up'; el.title = TegakiStrings.moveLayerUp; el.setAttribute('data-up', '1'); el.className = 'tegaki-ui-btn tegaki-icon tegaki-up-open'; $T.on(el, 'click', Tegaki.onMoveLayerClick); row.appendChild(el); el = $T.el('span'); el.id = 'tegaki-layer-down'; el.title = TegakiStrings.moveLayerDown; el.className = 'tegaki-ui-btn tegaki-icon tegaki-down-open'; $T.on(el, 'click', Tegaki.onMoveLayerClick); row.appendChild(el); ctrl.appendChild(row); return ctrl; }, buildSizeCtrlGroup: function() { var el, ctrl, row; ctrl = this.buildCtrlGroup('size', TegakiStrings.size); row = $T.el('div'); row.className = 'tegaki-ctrlrow'; el = $T.el('input'); el.id = 'tegaki-size'; el.className = 'tegaki-ctrl-range'; el.min = 1; el.max = Tegaki.maxSize; el.type = 'range'; el.title = TegakiKeybinds.getCaption('toolSize'); $T.on(el, 'input', Tegaki.onToolSizeChange); row.appendChild(el); el = $T.el('input'); el.id = 'tegaki-size-lbl'; el.setAttribute('maxlength', 3); el.className = 'tegaki-stealth-input tegaki-range-lbl'; $T.on(el, 'input', Tegaki.onToolSizeChange); row.appendChild(el); ctrl.appendChild(row); return ctrl; }, buildAlphaCtrlGroup: function() { var el, ctrl, row; ctrl = this.buildCtrlGroup('alpha', TegakiStrings.alpha); row = $T.el('div'); row.className = 'tegaki-ctrlrow'; el = $T.el('input'); el.id = 'tegaki-alpha'; el.className = 'tegaki-ctrl-range'; el.min = 0; el.max = 100; el.step = 1; el.type = 'range'; $T.on(el, 'input', Tegaki.onToolAlphaChange); row.appendChild(el); el = $T.el('input'); el.id = 'tegaki-alpha-lbl'; el.setAttribute('maxlength', 3); el.className = 'tegaki-stealth-input tegaki-range-lbl'; $T.on(el, 'input', Tegaki.onToolAlphaChange); row.appendChild(el); ctrl.appendChild(row); return ctrl; }, buildFlowCtrlGroup: function() { var el, ctrl, row; ctrl = this.buildCtrlGroup('flow', TegakiStrings.flow); row = $T.el('div'); row.className = 'tegaki-ctrlrow'; el = $T.el('input'); el.id = 'tegaki-flow'; el.className = 'tegaki-ctrl-range'; el.min = 0; el.max = 100; el.step = 1; el.type = 'range'; $T.on(el, 'input', Tegaki.onToolFlowChange); row.appendChild(el); el = $T.el('input'); el.id = 'tegaki-flow-lbl'; el.setAttribute('maxlength', 3); el.className = 'tegaki-stealth-input tegaki-range-lbl'; $T.on(el, 'input', Tegaki.onToolFlowChange); row.appendChild(el); ctrl.appendChild(row); return ctrl; }, buildZoomCtrlGroup: function() { var el, btn, ctrl; ctrl = this.buildCtrlGroup('zoom', TegakiStrings.zoom); btn = $T.el('div'); btn.className = 'tegaki-ui-btn tegaki-icon tegaki-plus'; btn.id = 'tegaki-zoomin-btn'; btn.setAttribute('data-in', 1); $T.on(btn, 'click', Tegaki.onZoomChange); ctrl.appendChild(btn); btn = $T.el('div'); btn.className = 'tegaki-ui-btn tegaki-icon tegaki-minus'; btn.id = 'tegaki-zoomout-btn'; btn.setAttribute('data-out', 1); $T.on(btn, 'click', Tegaki.onZoomChange); ctrl.appendChild(btn); el = $T.el('div'); el.id = 'tegaki-zoom-lbl'; ctrl.appendChild(el); return ctrl; }, buildColorCtrlGroup: function(mainColor) { var el, cnt, btn, ctrl, color, edge, i, palette, cls; edge = / Edge\//i.test(window.navigator.userAgent); ctrl = this.buildCtrlGroup('color', TegakiStrings.color); cnt = $T.el('div'); cnt.id = 'tegaki-color-ctrl'; el = $T.el('div'); el.id = 'tegaki-color'; edge && el.classList.add('tegaki-hidden'); el.style.backgroundColor = mainColor; $T.on(el, 'mousedown', Tegaki.onMainColorClick); cnt.appendChild(el); el = $T.el('div'); el.id = 'tegaki-palette-switcher'; btn = $T.el('span'); btn.id = 'tegaki-palette-prev-btn'; btn.title = TegakiStrings.switchPalette; btn.setAttribute('data-prev', '1'); btn.className = 'tegaki-ui-btn tegaki-icon tegaki-left-open tegaki-disabled'; $T.on(btn, 'click', Tegaki.onSwitchPaletteClick); el.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-palette-next-btn'; btn.title = TegakiStrings.switchPalette; btn.className = 'tegaki-ui-btn tegaki-icon tegaki-right-open'; $T.on(btn, 'click', Tegaki.onSwitchPaletteClick); el.appendChild(btn); cnt.appendChild(el); ctrl.appendChild(cnt); cnt = $T.el('div'); cnt.id = 'tegaki-color-grids'; for (i = 0; i < TegakiColorPalettes.length; ++i) { el = $T.el('div'); el.setAttribute('data-id', i); cls = 'tegaki-color-grid'; palette = TegakiColorPalettes[i]; if (palette.length <= 18) { cls += ' tegaki-color-grid-20'; } else { cls += ' tegaki-color-grid-15'; } if (i > 0) { cls += ' tegaki-hidden'; } el.className = cls; for (color of palette) { btn = $T.el('div'); btn.title = TegakiStrings.paletteSlotReplace; btn.className = 'tegaki-color-btn'; btn.setAttribute('data-color', color); btn.style.backgroundColor = color; $T.on(btn, 'mousedown', Tegaki.onPaletteColorClick); el.appendChild(btn); } cnt.appendChild(el); } ctrl.appendChild(cnt); el = $T.el('input'); el.id = 'tegaki-colorpicker'; !edge && el.classList.add('tegaki-invis'); el.value = color; el.type = 'color'; $T.on(el, 'change', Tegaki.onColorPicked); ctrl.appendChild(el); return ctrl; }, buildStatusCnt: function() { var cnt, el; cnt = $T.el('div'); cnt.id = 'tegaki-status-cnt'; if (Tegaki.saveReplay) { el = $T.el('div'); el.id = 'tegaki-status-replay'; el.textContent = '⬤'; el.setAttribute('title', TegakiStrings.recordingEnabled); cnt.appendChild(el); } el = $T.el('div'); el.id = 'tegaki-status-output'; cnt.appendChild(el); el = $T.el('div'); el.id = 'tegaki-status-version'; el.textContent = 'tegaki.js v' + Tegaki.VERSION; cnt.appendChild(el); return cnt; }, buildReplayControls: function() { var cnt, btn, el; cnt = $T.el('div'); cnt.id = 'tegaki-replay-controls'; cnt.className = 'tegaki-hidden'; btn = $T.el('span'); btn.id = 'tegaki-replay-gapless-btn'; btn.className = 'tegaki-ui-cb-w'; $T.on(btn, 'click', Tegaki.onReplayGaplessClick); el = $T.el('span'); el.id = 'tegaki-replay-gapless-cb'; el.className = 'tegaki-ui-cb'; btn.appendChild(el); el = $T.el('span'); el.className = 'tegaki-menu-lbl'; el.textContent = TegakiStrings.gapless; btn.appendChild(el); cnt.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-replay-play-btn'; btn.className = 'tegaki-ui-btn tegaki-icon tegaki-play'; btn.setAttribute('title', TegakiStrings.play); $T.on(btn, 'click', Tegaki.onReplayPlayPauseClick); cnt.appendChild(btn); btn = $T.el('span'); btn.className = 'tegaki-ui-btn tegaki-icon tegaki-to-start'; btn.setAttribute('title', TegakiStrings.rewind); $T.on(btn, 'click', Tegaki.onReplayRewindClick); cnt.appendChild(btn); btn = $T.el('span'); btn.id = 'tegaki-replay-slower-btn'; btn.className = 'tegaki-ui-btn tegaki-icon tegaki-fast-bw'; btn.setAttribute('title', TegakiStrings.slower); $T.on(btn, 'click', Tegaki.onReplaySlowDownClick); cnt.appendChild(btn); el = $T.el('span'); el.id = 'tegaki-replay-speed-lbl'; el.className = 'tegaki-menu-lbl'; el.textContent = '1.0'; cnt.appendChild(el); btn = $T.el('span'); btn.id = 'tegaki-replay-faster-btn'; btn.className = 'tegaki-ui-btn tegaki-icon tegaki-fast-fw'; btn.setAttribute('title', TegakiStrings.faster); $T.on(btn, 'click', Tegaki.onReplaySpeedUpClick); cnt.appendChild(btn); el = $T.el('span'); el.id = 'tegaki-replay-now-lbl'; el.className = 'tegaki-menu-lbl'; el.textContent = '00:00'; cnt.appendChild(el); el = $T.el('span'); el.id = 'tegaki-replay-end-lbl'; el.className = 'tegaki-menu-lbl'; el.textContent = '00:00'; cnt.appendChild(el); return cnt; }, buildLayerGridCell: function(layer) { var cnt, el, cell; cnt = $T.el('div'); cnt.id = 'tegaki-layers-cell-' + layer.id; cnt.className = 'tegaki-layers-cell'; cnt.setAttribute('data-id', layer.id); cnt.draggable = true; cnt.setAttribute('data-id', layer.id); $T.on(cnt, 'pointerdown', TegakiUI.onLayerSelectorPtrDown); $T.on(cnt, 'pointerup', Tegaki.onLayerSelectorClick); $T.on(cnt, 'dragstart', TegakiUI.onLayerDragStart); $T.on(cnt, 'dragover', TegakiUI.onLayerDragOver); $T.on(cnt, 'drop', TegakiUI.onLayerDragDrop); $T.on(cnt, 'dragend', TegakiUI.onLayerDragEnd); $T.on(cnt, 'dragleave', TegakiUI.onLayerDragLeave); $T.on(cnt, 'dragexit', TegakiUI.onLayerDragLeave); // visibility toggle cell = $T.el('div'); cell.className = 'tegaki-layers-cell-v'; el = $T.el('span'); el.id = 'tegaki-layers-cb-v-' + layer.id; el.className = 'tegaki-ui-cb'; el.setAttribute('data-id', layer.id); el.title = TegakiStrings.toggleVisibility; $T.on(el, 'click', Tegaki.onLayerToggleVisibilityClick); if (layer.visible) { el.className += ' tegaki-ui-cb-a'; } cell.appendChild(el); cnt.appendChild(cell); // preview cell = $T.el('div'); cell.className = 'tegaki-layers-cell-p'; el = $T.el('canvas'); el.id = 'tegaki-layers-p-canvas-' + layer.id; el.className = 'tegaki-alpha-bg-xs'; [el.width, el.height] = TegakiUI.getLayerPreviewSize(); cell.appendChild(el); cnt.appendChild(cell); // name cell = $T.el('div'); cell.className = 'tegaki-layers-cell-n'; el = $T.el('div'); el.id = 'tegaki-layer-name-' + layer.id; el.className = 'tegaki-ellipsis'; el.setAttribute('data-id', layer.id); el.textContent = layer.name; $T.on(el, 'dblclick', Tegaki.onLayerNameChangeClick); cell.appendChild(el); cnt.appendChild(cell); return cnt; }, // --- onLayerSelectorPtrDown: function(e) { if (e.pointerType === 'mouse') { if (this.hasAttribute('data-nodrag')) { this.removeAttribute('data-nodrag'); $T.on(this, 'dragstart', TegakiUI.onLayerDragStart); } } else if (!this.hasAttribute('data-nodrag')) { this.setAttribute('data-nodrag', 1); $T.off(this, 'dragstart', TegakiUI.onLayerDragStart); } }, onLayerDragStart: function(e) { var el, id; if (e.ctrlKey) { return; } TegakiUI.draggedNode = null; if (!$T.id('tegaki-layers-grid').children[1]) { e.preventDefault(); return; } id = +e.target.getAttribute('data-id'); el = $T.el('div'); el.className = 'tegaki-invis'; e.dataTransfer.setDragImage(el, 0, 0); e.dataTransfer.setData('text/plain', id); e.dataTransfer.effectAllowed = 'move'; TegakiUI.draggedNode = e.target; TegakiUI.updateLayersGridDragExt(true); }, onLayerDragOver: function(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; TegakiUI.updateLayersGridDragEffect( e.target, +TegakiUI.draggedNode.getAttribute('data-id') ); }, onLayerDragLeave: function(e) { TegakiUI.updateLayersGridDragEffect(); }, onLayerDragEnd: function(e) { TegakiUI.draggedNode = null; TegakiUI.updateLayersGridDragExt(false); TegakiUI.updateLayersGridDragEffect(); }, onLayerDragDrop: function(e) { var tgtId, srcId, belowPos; e.preventDefault(); TegakiUI.draggedNode = null; [tgtId] = TegakiUI.layersGridFindDropTgt(e.target); srcId = +e.dataTransfer.getData('text/plain'); TegakiUI.updateLayersGridDragEffect(e.target.parentNode); TegakiUI.updateLayersGridDragExt(false); if (!TegakiUI.layersGridCanDrop(tgtId, srcId)) { return; } if (!tgtId) { belowPos = Tegaki.layers.length; } else { belowPos = TegakiLayers.getLayerPosById(tgtId); } if (!TegakiLayers.selectedLayersHas(srcId)) { Tegaki.setActiveLayer(srcId); } Tegaki.moveSelectedLayers(belowPos); }, updateLayersGridDragExt: function(flag) { var cnt, el; cnt = $T.id('tegaki-layers-grid'); if (!cnt.children[1]) { return; } if (flag) { el = $T.el('div'); el.id = 'tegaki-layers-cell-dx'; el.draggable = true; $T.on(el, 'dragover', TegakiUI.onLayerDragOver); $T.on(el, 'drop', TegakiUI.onLayerDragDrop); cnt.parentNode.insertBefore(el, cnt); } else { if (el = $T.id('tegaki-layers-cell-dx')) { el.parentNode.removeChild(el); } } }, updateLayersGridDragEffect: function(tgt, srcId) { var el, nodes, tgtId; nodes = $T.cls('tegaki-layers-cell-d', $T.id('tegaki-ctrlgrp-layers')); for (el of nodes) { el.classList.remove('tegaki-layers-cell-d'); } if (!tgt || !srcId) { return; } [tgtId, tgt] = TegakiUI.layersGridFindDropTgt(tgt); if (!TegakiUI.layersGridCanDrop(tgtId, srcId)) { return; } if (!tgt) { tgt = $T.id('tegaki-layers-grid'); } tgt.classList.add('tegaki-layers-cell-d'); }, layersGridFindDropTgt: function(tgt) { var tgtId, cnt; tgtId = +tgt.getAttribute('data-id'); cnt = $T.id('tegaki-ctrlgrp-layers'); while (!tgt.draggable && tgt !== cnt) { tgt = tgt.parentNode; tgtId = +tgt.getAttribute('data-id'); } if (tgt === cnt || !tgt.draggable) { return [0, null]; } return [tgtId, tgt]; }, layersGridCanDrop: function(tgtId, srcId) { var srcEl; if (tgtId === srcId) { return false; } srcEl = $T.id('tegaki-layers-cell-' + srcId); if (!srcEl.previousElementSibling) { if (!tgtId) { return false; } } else if (+srcEl.previousElementSibling.getAttribute('data-id') === tgtId) { return false; } return true; }, // --- setReplayMode: function(flag) { Tegaki.bg.classList[flag ? 'add' : 'remove']('tegaki-replay-mode'); }, // --- onToolChanged: function() { $T.id('tegaki-toolmode-bar').classList.remove('tegaki-hidden'); TegakiUI.updateToolSize(); TegakiUI.updateToolAlpha(); TegakiUI.updateToolFlow(); TegakiUI.updateToolModes(); }, // --- updateLayerAlphaOpt: function() { var el = $T.id('tegaki-layer-alpha-opt'); el.value = Math.round(Tegaki.activeLayer.alpha * 100); }, updateLayerName: function(layer) { var el; if (el = $T.id('tegaki-layer-name-' + layer.id)) { el.textContent = layer.name; } }, updateLayerPreview: function(layer) { var canvas, ctx; canvas = $T.id('tegaki-layers-p-canvas-' + layer.id); if (!canvas) { return; } ctx = TegakiUI.getLayerPreviewCtx(layer); if (!ctx) { ctx = canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; TegakiUI.setLayerPreviewCtx(layer, ctx); } $T.clearCtx(ctx); ctx.drawImage(layer.canvas, 0, 0, canvas.width, canvas.height); }, updateLayerPreviewSize: function(regen) { var el, layer, size; size = TegakiUI.getLayerPreviewSize(); for (layer of Tegaki.layers) { if (el = $T.id('tegaki-layers-p-canvas-' + layer.id)) { [el.width, el.height] = size; if (regen) { TegakiUI.updateLayerPreview(layer); } } } }, getLayerPreviewCtx: function(layer) { TegakiUI.layerPreviewCtxCache.get(layer); }, setLayerPreviewCtx: function(layer, ctx) { TegakiUI.layerPreviewCtxCache.set(layer, ctx); }, deleteLayerPreviewCtx: function(layer) { TegakiUI.layerPreviewCtxCache.delete(layer); }, updateLayersGridClear: function() { $T.id('tegaki-layers-grid').innerHTML = ''; }, updateLayersGrid: function() { var layer, el, frag, cnt; frag = $T.frag(); for (layer of Tegaki.layers) { el = TegakiUI.buildLayerGridCell(layer); frag.insertBefore(el, frag.firstElementChild); } TegakiUI.updateLayersGridClear(); cnt.appendChild(frag); }, updateLayersGridActive: function(layerId) { var el; el = $T.cls('tegaki-layers-cell-a', $T.id('tegaki-layers-grid'))[0]; if (el) { el.classList.remove('tegaki-layers-cell-a'); } el = $T.id('tegaki-layers-cell-' + layerId); if (el) { el.classList.add('tegaki-layers-cell-a'); } TegakiUI.updateLayerAlphaOpt(); }, updateLayersGridAdd: function(layer, aboveId) { var el, cnt, ref; el = TegakiUI.buildLayerGridCell(layer); cnt = $T.id('tegaki-layers-grid'); if (aboveId) { ref = $T.id('tegaki-layers-cell-' + aboveId); } else { ref = null; } cnt.insertBefore(el, ref); }, updateLayersGridRemove: function(id) { var el; if (el = $T.id('tegaki-layers-cell-' + id)) { el.parentNode.removeChild(el); } }, updayeLayersGridOrder: function() { var layer, cnt, el; cnt = $T.id('tegaki-layers-grid'); for (layer of Tegaki.layers) { el = $T.id('tegaki-layers-cell-' + layer.id); cnt.insertBefore(el, cnt.firstElementChild); } }, updateLayersGridVisibility: function(id, flag) { var el; el = $T.id('tegaki-layers-cb-v-' + id); if (!el) { return; } if (flag) { el.classList.add('tegaki-ui-cb-a'); } else { el.classList.remove('tegaki-ui-cb-a'); } }, updateLayersGridSelectedClear: function() { var layer, el; for (layer of Tegaki.layers) { if (el = $T.id('tegaki-layers-cell-' + layer.id)) { el.classList.remove('tegaki-layers-cell-s'); } } }, updateLayersGridSelectedSet: function(id, flag) { var el; if (el = $T.id('tegaki-layers-cell-' + id)) { if (flag) { el.classList.add('tegaki-layers-cell-s'); } else { el.classList.remove('tegaki-layers-cell-s'); } } }, updateToolSize: function() { var el = $T.id('tegaki-ctrlgrp-size'); if (Tegaki.tool.useSize) { el.classList.remove('tegaki-hidden'); $T.id('tegaki-size-lbl').value = Tegaki.tool.size; $T.id('tegaki-size').value = Tegaki.tool.size; } else { el.classList.add('tegaki-hidden'); } }, updateToolAlpha: function() { var val, el = $T.id('tegaki-ctrlgrp-alpha'); if (Tegaki.tool.useAlpha) { el.classList.remove('tegaki-hidden'); val = Math.round(Tegaki.tool.alpha * 100); $T.id('tegaki-alpha-lbl').value = val; $T.id('tegaki-alpha').value = val; } else { el.classList.add('tegaki-hidden'); } }, updateToolFlow: function() { var val, el = $T.id('tegaki-ctrlgrp-flow'); if (Tegaki.tool.useFlow) { el.classList.remove('tegaki-hidden'); val = Math.round(Tegaki.tool.flow * 100); $T.id('tegaki-flow-lbl').value = val; $T.id('tegaki-flow').value = val; } else { el.classList.add('tegaki-hidden'); } }, updateToolDynamics: function() { var ctrl, cb; ctrl = $T.id('tegaki-tool-mode-dynamics'); if (!Tegaki.tool.usesDynamics()) { ctrl.classList.add('tegaki-hidden'); } else { cb = $T.id('tegaki-tool-mode-dynamics-size'); if (Tegaki.tool.useSizeDynamics) { if (Tegaki.tool.sizeDynamicsEnabled) { cb.classList.add('tegaki-sw-btn-a'); } else { cb.classList.remove('tegaki-sw-btn-a'); } cb.classList.remove('tegaki-hidden'); } else { cb.classList.add('tegaki-hidden'); } cb = $T.id('tegaki-tool-mode-dynamics-alpha'); if (Tegaki.tool.useAlphaDynamics) { if (Tegaki.tool.alphaDynamicsEnabled) { cb.classList.add('tegaki-sw-btn-a'); } else { cb.classList.remove('tegaki-sw-btn-a'); } cb.classList.remove('tegaki-hidden'); } else { cb.classList.add('tegaki-hidden'); } cb = $T.id('tegaki-tool-mode-dynamics-flow'); if (Tegaki.tool.useFlowDynamics) { if (Tegaki.tool.flowDynamicsEnabled) { cb.classList.add('tegaki-sw-btn-a'); } else { cb.classList.remove('tegaki-sw-btn-a'); } cb.classList.remove('tegaki-hidden'); } else { cb.classList.add('tegaki-hidden'); } ctrl.classList.remove('tegaki-hidden'); } }, updateToolShape: function() { var tipId, ctrl, cnt, btn, tipList; ctrl = $T.id('tegaki-tool-mode-tip'); if (!Tegaki.tool.tipList) { ctrl.classList.add('tegaki-hidden'); } else { tipList = Tegaki.tool.tipList; cnt = $T.id('tegaki-tool-mode-tip-ctrl'); cnt.innerHTML = ''; for (tipId = 0; tipId < tipList.length; ++tipId) { btn = $T.el('span'); btn.id = 'tegaki-tool-mode-tip-' + tipId; btn.className = 'tegaki-sw-btn'; btn.setAttribute('data-id', tipId); btn.textContent = TegakiStrings[tipList[tipId]]; $T.on(btn, 'mousedown', Tegaki.onToolTipClick); cnt.appendChild(btn); if (Tegaki.tool.tipId === tipId) { btn.classList.add('tegaki-sw-btn-a'); } } ctrl.classList.remove('tegaki-hidden'); } }, updateToolPreserveAlpha: function() { var cb, ctrl; ctrl = $T.id('tegaki-tool-mode-mask'); if (!Tegaki.tool.usePreserveAlpha) { ctrl.classList.add('tegaki-hidden'); } else { cb = $T.id('tegaki-tool-mode-mask-alpha'); if (Tegaki.tool.preserveAlphaEnabled) { cb.classList.add('tegaki-sw-btn-a'); } else { cb.classList.remove('tegaki-sw-btn-a'); } ctrl.classList.remove('tegaki-hidden'); } }, updateToolModes: function() { var el, flag; TegakiUI.updateToolShape(); TegakiUI.updateToolDynamics(); TegakiUI.updateToolPreserveAlpha(); flag = false; for (el of $T.id('tegaki-toolmode-bar').children) { if (!flag && !el.classList.contains('tegaki-hidden')) { el.classList.add('tegaki-ui-borderless'); flag = true; } else { el.classList.remove('tegaki-ui-borderless'); } } }, updateUndoRedo: function(undoSize, redoSize) { var u, r; if (Tegaki.replayMode) { return; } u = $T.id('tegaki-undo-btn').classList; r = $T.id('tegaki-redo-btn').classList; if (undoSize) { if (u.contains('tegaki-disabled')) { u.remove('tegaki-disabled'); } } else { if (!u.contains('tegaki-disabled')) { u.add('tegaki-disabled'); } } if (redoSize) { if (r.contains('tegaki-disabled')) { r.remove('tegaki-disabled'); } } else { if (!r.contains('tegaki-disabled')) { r.add('tegaki-disabled'); } } }, updateZoomLevel: function() { $T.id('tegaki-zoom-lbl').textContent = (Tegaki.zoomFactor * 100) + '%'; if (Tegaki.zoomLevel + Tegaki.zoomBaseLevel >= Tegaki.zoomFactorList.length) { $T.id('tegaki-zoomin-btn').classList.add('tegaki-disabled'); } else { $T.id('tegaki-zoomin-btn').classList.remove('tegaki-disabled'); } if (Tegaki.zoomLevel + Tegaki.zoomBaseLevel <= 0) { $T.id('tegaki-zoomout-btn').classList.add('tegaki-disabled'); } else { $T.id('tegaki-zoomout-btn').classList.remove('tegaki-disabled'); } }, updateColorPalette: function() { var el, nodes, id; id = Tegaki.colorPaletteId; nodes = $T.cls('tegaki-color-grid', $T.id('tegaki-color-grids')); for (el of nodes) { if (+el.getAttribute('data-id') === id) { el.classList.remove('tegaki-hidden'); } else { el.classList.add('tegaki-hidden'); } } el = $T.id('tegaki-palette-prev-btn'); if (id === 0) { el.classList.add('tegaki-disabled'); } else { el.classList.remove('tegaki-disabled'); } el = $T.id('tegaki-palette-next-btn'); if (id === TegakiColorPalettes.length - 1) { el.classList.add('tegaki-disabled'); } else { el.classList.remove('tegaki-disabled'); } }, updateReplayTime: function(full) { var now, end, r = Tegaki.replayViewer; now = r.getCurrentPos(); end = r.getDuration(); if (now > end) { now = end; } $T.id('tegaki-replay-now-lbl').textContent = $T.msToHms(now); if (full) { $T.id('tegaki-replay-end-lbl').textContent = $T.msToHms(end); } }, updateReplayControls: function() { TegakiUI.updateReplayGapless(); TegakiUI.updateReplayPlayPause(); TegakiUI.updateReplaySpeed(); }, updateReplayGapless: function() { var el, r = Tegaki.replayViewer; el = $T.id('tegaki-replay-gapless-cb'); if (r.gapless) { el.classList.add('tegaki-ui-cb-a'); } else { el.classList.remove('tegaki-ui-cb-a'); } }, updateReplayPlayPause: function() { var el, r = Tegaki.replayViewer; el = $T.id('tegaki-replay-play-btn'); if (r.playing) { el.classList.remove('tegaki-play'); el.classList.add('tegaki-pause'); el.setAttribute('title', TegakiStrings.pause); } else { el.classList.add('tegaki-play'); el.classList.remove('tegaki-pause'); el.setAttribute('title', TegakiStrings.play); if (r.getCurrentPos() < r.getDuration()) { el.classList.remove('tegaki-disabled'); } else { el.classList.add('tegaki-disabled'); } } }, updateReplaySpeed: function() { var el, r = Tegaki.replayViewer; $T.id('tegaki-replay-speed-lbl').textContent = r.speed.toFixed(1); el = $T.id('tegaki-replay-slower-btn'); if (r.speedIndex === 0) { el.classList.add('tegaki-disabled'); } else { el.classList.remove('tegaki-disabled'); } el = $T.id('tegaki-replay-faster-btn'); if (r.speedIndex === r.speedList.length - 1) { el.classList.add('tegaki-disabled'); } else { el.classList.remove('tegaki-disabled'); } }, enableReplayControls: function(flag) { if (flag) { $T.id('tegaki-replay-controls').classList.remove('tegaki-hidden'); } else { $T.id('tegaki-replay-controls').classList.add('tegaki-hidden'); } }, setRecordingStatus: function(flag) { var el = $T.id('tegaki-status-replay'); if (flag) { el.classList.remove('tegaki-hidden'); } else { el.classList.add('tegaki-hidden'); } } }; /*! UZIP.js, © 2018 Photopea, MIT License */ var UZIP = {}; if(typeof module == "object") module.exports = UZIP; UZIP.inflateRaw = function(file, buf) { return UZIP.F.inflate(file, buf); } UZIP.deflateRaw = function(data, opts) { if(opts==null) opts={level:6}; var buf=new Uint8Array(50+Math.floor(data.length*1.1)); var off = UZIP.F.deflateRaw(data, buf, off, opts.level); return new Uint8Array(buf.buffer, 0, off); } UZIP.bin = { readUshort : function(buff,p) { return (buff[p]) | (buff[p+1]<<8); }, writeUshort: function(buff,p,n){ buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, readUint : function(buff,p) { return (buff[p+3]*(256*256*256)) + ((buff[p+2]<<16) | (buff[p+1]<< 8) | buff[p]); }, writeUint : function(buff,p,n){ buff[p]=n&255; buff[p+1]=(n>>8)&255; buff[p+2]=(n>>16)&255; buff[p+3]=(n>>24)&255; }, readASCII : function(buff,p,l){ var s = ""; for(var i=0; i> 6)); buff[p+i+1] = (128|((code>> 0)&63)); i+=2; } else if((code&(0xffffffff-(1<<16)+1))==0) { buff[p+i] = (224|(code>>12)); buff[p+i+1] = (128|((code>> 6)&63)); buff[p+i+2] = (128|((code>>0)&63)); i+=3; } else if((code&(0xffffffff-(1<<21)+1))==0) { buff[p+i] = (240|(code>>18)); buff[p+i+1] = (128|((code>>12)&63)); buff[p+i+2] = (128|((code>>6)&63)); buff[p+i+3] = (128|((code>>0)&63)); i+=4; } else throw "e"; } return i; }, sizeUTF8 : function(str) { var strl = str.length, i=0; for(var ci=0; ci>>3; } var lits = U.lits, strt=U.strt, prev=U.prev, li=0, lc=0, bs=0, ebits=0, c=0, nc=0; // last_item, literal_count, block_start if(dlen>2) { nc=UZIP.F._hash(data,0); strt[nc]=0; } var nmch=0,nmci=0; for(i=0; i14000 || lc>26697) && (dlen-i)>100) { if(cvrd>>16)>>16)>(mch>>>16)) mch=0; }//*/ var len = mch>>>16, dst = mch&0xffff; //if(i-dst<0) throw "e"; if(mch!=0) { var len = mch>>>16, dst = mch&0xffff; //if(i-dst<0) throw "e"; var lgi = goodIndex(len, U.of0); U.lhst[257+lgi]++; var dgi = goodIndex(dst, U.df0); U.dhst[ dgi]++; ebits += U.exb[lgi] + U.dxb[dgi]; lits[li] = (len<<23)|(i-cvrd); lits[li+1] = (dst<<16)|(lgi<<8)|dgi; li+=2; cvrd = i + len; } else { U.lhst[data[i]]++; } lc++; } } if(bs!=i || data.length==0) { if(cvrd>>3; } UZIP.F._bestMatch = function(data, i, prev, c, nice, chain) { var ci = (i&0x7fff), pi=prev[ci]; //console.log("----", i); var dif = ((ci-pi + (1<<15)) & 0x7fff); if(pi==ci || c!=UZIP.F._hash(data,i-dif)) return 0; var tl=0, td=0; // top length, top distance var dlim = Math.min(0x7fff, i); while(dif<=dlim && --chain!=0 && pi!=ci /*&& c==UZIP.F._hash(data,i-dif)*/) { if(tl==0 || (data[i+tl]==data[i+tl-dif])) { var cl = UZIP.F._howLong(data, i, dif); if(cl>tl) { tl=cl; td=dif; if(tl>=nice) break; //* if(dif+2maxd) { maxd=curd; pi = ei; } } //*/ } } ci=pi; pi = prev[ci]; dif += ((ci-pi + (1<<15)) & 0x7fff); } return (tl<<16)|td; } UZIP.F._howLong = function(data, i, dif) { if(data[i]!=data[i-dif] || data[i+1]!=data[i+1-dif] || data[i+2]!=data[i+2-dif]) return 0; var oi=i, l = Math.min(data.length, i+258); i+=3; //while(i+4>>23), end = off+(qb&((1<<23)-1)); while(off>16), lgi=(qc>>8)&255, dgi=(qc&255); pos = UZIP.F._writeLit(257+lgi, ltree, out, pos); putsE(out, pos, len-U.of0[lgi]); pos+=U.exb[lgi]; pos = UZIP.F._writeLit(dgi, dtree, out, pos); putsF(out, pos, dst-U.df0[dgi]); pos+=U.dxb[dgi]; off+=len; } } pos = UZIP.F._writeLit(256, ltree, out, pos); } //console.log(pos-opos, fxdSize, dynSize, cstSize); return pos; } UZIP.F._copyExact = function(data,off,len,out,pos) { var p8 = (pos>>>3); out[p8]=(len); out[p8+1]=(len>>>8); out[p8+2]=255-out[p8]; out[p8+3]=255-out[p8+1]; p8+=4; out.set(new Uint8Array(data.buffer, off, len), p8); //for(var i=0; i4 && U.itree[(U.ordr[numh-1]<<1)+1]==0) numh--; return [ML, MD, MH, numl, numd, numh, lset, dset]; } UZIP.F.getSecond= function(a) { var b=[]; for(var i=0; i>1)+","; return b; } UZIP.F.contSize = function(tree, hst) { var s=0; for(var i=0; i15) { UZIP.F._putsE(out, pos, rst, rsl); pos+=rsl; } } return pos; } UZIP.F._lenCodes = function(tree, set) { var len=tree.length; while(len!=2 && tree[len-1]==0) len-=2; // when no distances, keep one code with length 0 for(var i=0; i>>1, 138); if(zc<11) set.push(17, zc-3); else set.push(18, zc-11); i += zc*2-2; } else if(l==prv && nxt==l && nnxt==l) { var lz = i+5; while(lz+2>>1, 6); set.push(16, zc-3); i += zc*2-2; } else set.push(l, 0); } return len>>>1; } UZIP.F._hufTree = function(hst, tree, MAXL) { var list=[], hl = hst.length, tl=tree.length, i=0; for(i=0; iMAXL) { UZIP.F.restrictDepth(l2, MAXL, maxl); maxl = MAXL; } for(i=0; iMD) { var od=dps[i].d; dps[i].d=MD; dbt+=bCost-(1<<(maxl-od)); } else break; dbt = dbt>>>(maxl-MD); while(dbt>0) { var od=dps[i].d; if(od=0; i--) if(dps[i].d==MD && dbt<0) { dps[i].d--; dbt++; } if(dbt!=0) console.log("debt left"); } UZIP.F._goodIndex = function(v, arr) { var i=0; if(arr[i|16]<=v) i|=16; if(arr[i|8]<=v) i|=8; if(arr[i|4]<=v) i|=4; if(arr[i|2]<=v) i|=2; if(arr[i|1]<=v) i|=1; return i; } UZIP.F._writeLit = function(ch, ltree, out, pos) { UZIP.F._putsF(out, pos, ltree[ch<<1]); return pos+ltree[(ch<<1)+1]; } UZIP.F.inflate = function(data, buf) { var u8=Uint8Array; if(data[0]==3 && data[1]==0) return (buf ? buf : new u8(0)); var F=UZIP.F, bitsF = F._bitsF, bitsE = F._bitsE, decodeTiny = F._decodeTiny, makeCodes = F.makeCodes, codes2map=F.codes2map, get17 = F._get17; var U = F.U; var noBuf = (buf==null); if(noBuf) buf = new u8((data.length>>>2)<<3); var BFINAL=0, BTYPE=0, HLIT=0, HDIST=0, HCLEN=0, ML=0, MD=0; var off = 0, pos = 0; var lmap, dmap; while(BFINAL==0) { BFINAL = bitsF(data, pos , 1); BTYPE = bitsF(data, pos+1, 2); pos+=3; //console.log(BFINAL, BTYPE); if(BTYPE==0) { if((pos&7)!=0) pos+=8-(pos&7); var p8 = (pos>>>3)+4, len = data[p8-4]|(data[p8-3]<<8); //console.log(len);//bitsF(data, pos, 16), if(noBuf) buf=UZIP.F._check(buf, off+len); buf.set(new u8(data.buffer, data.byteOffset+p8, len), off); //for(var i=0; itl)tl=l; } pos+=3*HCLEN; //console.log(itree); makeCodes(U.itree, tl); codes2map(U.itree, tl, U.imap); lmap = U.lmap; dmap = U.dmap; pos = decodeTiny(U.imap, (1<>>24))-1; pos+=(ml&0xffffff); makeCodes(U.ltree, mx0); codes2map(U.ltree, mx0, lmap); //var md = decodeTiny(U.imap, (1<>>24))-1; pos+=(md&0xffffff); makeCodes(U.dtree, mx1); codes2map(U.dtree, mx1, dmap); } //var ooff=off, opos=pos; while(true) { var code = lmap[get17(data, pos) & ML]; pos += code&15; var lit = code>>>4; //U.lhst[lit]++; if((lit>>>8)==0) { buf[off++] = lit; } else if(lit==256) { break; } else { var end = off+lit-254; if(lit>264) { var ebs = U.ldef[lit-257]; end = off + (ebs>>>3) + bitsE(data, pos, ebs&7); pos += ebs&7; } //UZIP.F.dst[end-off]++; var dcode = dmap[get17(data, pos) & MD]; pos += dcode&15; var dlit = dcode>>>4; var dbs = U.ddef[dlit], dst = (dbs>>>4) + bitsF(data, pos, dbs&15); pos += dbs&15; //var o0 = off-dst, stp = Math.min(end-off, dst); //if(stp>20) while(off>>3); } //console.log(UZIP.F.dst); //console.log(tlen, dlen, off-tlen+tcnt); return buf.length==off ? buf : buf.slice(0,off); } UZIP.F._check=function(buf, len) { var bl=buf.length; if(len<=bl) return buf; var nbuf = new Uint8Array(Math.max(bl<<1,len)); nbuf.set(buf,0); //for(var i=0; i>>4; if(lit<=15) { tree[i]=lit; i++; } else { var ll = 0, n = 0; if(lit==16) { n = (3 + bitsE(data, pos, 2)); pos += 2; ll = tree[i-1]; } else if(lit==17) { n = (3 + bitsE(data, pos, 3)); pos += 3; } else if(lit==18) { n = (11 + bitsE(data, pos, 7)); pos += 7; } var ni = i+n; while(i>>1; while(imx)mx=v; i++; } while(i>1; var cl = tree[i+1], val = (lit<<4)|cl; // : (0x8000 | (U.of0[lit-257]<<7) | (U.exb[lit-257]<<4) | cl); var rest = (MAX_BITS-cl), i0 = tree[i]<>>(15-MAX_BITS); while(i0!=i1) { var p0 = r15[i0]>>>(15-MAX_BITS); map[p0]=val; i0++; } } } UZIP.F.revCodes = function(tree, MAX_BITS) { var r15 = UZIP.F.U.rev15, imb = 15-MAX_BITS; for(var i=0; i>>imb; } } // used only in deflate UZIP.F._putsE= function(dt, pos, val ) { val = val<<(pos&7); var o=(pos>>>3); dt[o]|=val; dt[o+1]|=(val>>>8); } UZIP.F._putsF= function(dt, pos, val ) { val = val<<(pos&7); var o=(pos>>>3); dt[o]|=val; dt[o+1]|=(val>>>8); dt[o+2]|=(val>>>16); } UZIP.F._bitsE= function(dt, pos, length) { return ((dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) )>>>(pos&7))&((1<>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16))>>>(pos&7))&((1<>>3] | (dt[(pos>>>3)+1]<<8))>>>(pos&7))&511; } */ UZIP.F._get17= function(dt, pos) { // return at least 17 meaningful bytes return (dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16) )>>>(pos&7); } UZIP.F._get25= function(dt, pos) { // return at least 17 meaningful bytes return (dt[pos>>>3] | (dt[(pos>>>3)+1]<<8) | (dt[(pos>>>3)+2]<<16) | (dt[(pos>>>3)+3]<<24) )>>>(pos&7); } UZIP.F.U = function(){ var u16=Uint16Array, u32=Uint32Array; return { next_code : new u16(16), bl_count : new u16(16), ordr : [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ], of0 : [3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999], exb : [0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0], ldef : new u16(32), df0 : [1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 65535, 65535], dxb : [0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0], ddef : new u32(32), flmap: new u16( 512), fltree: [], fdmap: new u16( 32), fdtree: [], lmap : new u16(32768), ltree : [], ttree:[], dmap : new u16(32768), dtree : [], imap : new u16( 512), itree : [], //rev9 : new u16( 512) rev15: new u16(1<<15), lhst : new u32(286), dhst : new u32( 30), ihst : new u32(19), lits : new u32(15000), strt : new u16(1<<16), prev : new u16(1<<15) }; } (); (function(){ var U = UZIP.F.U; var len = 1<<15; for(var i=0; i>> 1) | ((x & 0x55555555) << 1)); x = (((x & 0xcccccccc) >>> 2) | ((x & 0x33333333) << 2)); x = (((x & 0xf0f0f0f0) >>> 4) | ((x & 0x0f0f0f0f) << 4)); x = (((x & 0xff00ff00) >>> 8) | ((x & 0x00ff00ff) << 8)); U.rev15[i] = (((x >>> 16) | (x << 16)))>>>17; } function pushV(tgt, n, sv) { while(n--!=0) tgt.push(0,sv); } for(var i=0; i<32; i++) { U.ldef[i]=(U.of0[i]<<3)|U.exb[i]; U.ddef[i]=(U.df0[i]<<4)|U.dxb[i]; } pushV(U.fltree, 144, 8); pushV(U.fltree, 255-143, 9); pushV(U.fltree, 279-255, 7); pushV(U.fltree,287-279,8); /* var i = 0; for(; i<=143; i++) U.fltree.push(0,8); for(; i<=255; i++) U.fltree.push(0,9); for(; i<=279; i++) U.fltree.push(0,7); for(; i<=287; i++) U.fltree.push(0,8); */ UZIP.F.makeCodes(U.fltree, 9); UZIP.F.codes2map(U.fltree, 9, U.flmap); UZIP.F.revCodes (U.fltree, 9) pushV(U.fdtree,32,5); //for(i=0;i<32; i++) U.fdtree.push(0,5); UZIP.F.makeCodes(U.fdtree, 5); UZIP.F.codes2map(U.fdtree, 5, U.fdmap); UZIP.F.revCodes (U.fdtree, 5) pushV(U.itree,19,0); pushV(U.ltree,286,0); pushV(U.dtree,30,0); pushV(U.ttree,320,0); /* for(var i=0; i< 19; i++) U.itree.push(0,0); for(var i=0; i<286; i++) U.ltree.push(0,0); for(var i=0; i< 30; i++) U.dtree.push(0,0); for(var i=0; i<320; i++) U.ttree.push(0,0); */ })()