/* Canvas Query 1.0.1 http://canvasquery.org (c) 2012-2014 http://rezoner.net Canvas Query may be freely distributed under the MIT license. */ (function() { var COCOONJS = false; var NODEJS = !(typeof exports === "undefined"); if (NODEJS) { var Canvas = require('canvas'); var Image = Canvas.Image; var fs = require("fs"); } else { var Canvas = window.HTMLCanvasElement; var Image = window.HTMLImageElement; var COCOONJS = navigator.isCocoonJS; } var cq = function(selector) { if (arguments.length === 0) { var canvas = cq.createCanvas(window.innerWidth, window.innerHeight); window.addEventListener("resize", function() { // canvas.width = window.innerWidth; // canvas.height = window.innerHeight; }); } else if (typeof selector === "string") { var canvas = document.querySelector(selector); } else if (typeof selector === "number") { var canvas = cq.createCanvas(arguments[0], arguments[1]); } else if (selector instanceof Image) { var canvas = cq.createCanvas(selector); } else if (selector instanceof cq.Layer) { return selector; } else { var canvas = selector; } return new cq.Layer(canvas); }; cq.lineSpacing = 1.0; cq.cocoon = function(selector) { if (arguments.length === 0) { var canvas = cq.createCocoonCanvas(window.innerWidth, window.innerHeight); window.addEventListener("resize", function() {}); } else if (typeof selector === "string") { var canvas = document.querySelector(selector); } else if (typeof selector === "number") { var canvas = cq.createCocoonCanvas(arguments[0], arguments[1]); } else if (selector instanceof Image) { var canvas = cq.createCocoonCanvas(selector); } else if (selector instanceof cq.Layer) { return selector; } else { var canvas = selector; } return new cq.Layer(canvas); } cq.extend = function() { for (var i = 1; i < arguments.length; i++) { for (var j in arguments[i]) { arguments[0][j] = arguments[i][j]; } } return arguments[0]; }; cq.augment = function() { for (var i = 1; i < arguments.length; i++) { _.extend(arguments[0], arguments[i]); arguments[i](arguments[0]); } }; cq.distance = function(x1, y1, x2, y2) { if (arguments.length > 2) { var dx = x1 - x2; var dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } else { return Math.abs(x1 - y1); } }; cq.extend(cq, { smoothing: true, blend: function(below, above, mode, mix) { if (typeof mix === "undefined") mix = 1; var below = cq(below); var mask = below.clone(); var above = cq(above); below.save(); below.globalAlpha(mix); below.globalCompositeOperation(mode); below.drawImage(above.canvas, 0, 0); below.restore(); mask.save(); mask.globalCompositeOperation("source-in"); mask.drawImage(below.canvas, 0, 0); mask.restore(); return mask; }, matchColor: function(color, palette) { var rgbPalette = []; for (var i = 0; i < palette.length; i++) { rgbPalette.push(cq.color(palette[i])); } var imgData = cq.color(color); var difList = []; for (var j = 0; j < rgbPalette.length; j++) { var rgbVal = rgbPalette[j]; var rDif = Math.abs(imgData[0] - rgbVal[0]), gDif = Math.abs(imgData[1] - rgbVal[1]), bDif = Math.abs(imgData[2] - rgbVal[2]); difList.push(rDif + gDif + bDif); } var closestMatch = 0; for (var j = 0; j < palette.length; j++) { if (difList[j] < difList[closestMatch]) { closestMatch = j; } } return palette[closestMatch]; }, temp: function(width, height) { if (!this.tempLayer) { this.tempLayer = cq(1, 1); } if (width instanceof Image) { this.tempLayer.width = width.width; this.tempLayer.height = width.height; this.tempLayer.context.drawImage(width, 0, 0); } else if (width instanceof Canvas) { this.tempLayer.width = width.width; this.tempLayer.height = width.height; this.tempLayer.context.drawImage(width, 0, 0); } else if (width instanceof CanvasQuery.Layer) { this.tempLayer.width = width.width; this.tempLayer.height = width.height; this.tempLayer.context.drawImage(width.canvas, 0, 0); } else { this.tempLayer.width = width; this.tempLayer.height = height; } return this.tempLayer; }, wrapValue: function(value, min, max) { if (value < min) return max + (value % max); if (value >= max) return value % max; return value; }, limitValue: function(value, min, max) { return value < min ? min : value > max ? max : value; }, mix: function(a, b, amount) { return a + (b - a) * amount; }, hexToRgb: function(hex) { if (hex.length === 7) return ['0x' + hex[1] + hex[2] | 0, '0x' + hex[3] + hex[4] | 0, '0x' + hex[5] + hex[6] | 0]; else return ['0x' + hex[1] + hex[1] | 0, '0x' + hex[2] + hex[2] | 0, '0x' + hex[3] + hex[3] | 0]; }, rgbToHex: function(r, g, b) { return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1, 7); }, /* author: http://mjijackson.com/ */ rgbToHsl: function(r, g, b) { if (r instanceof Array) { b = r[2]; g = r[1]; r = r[0]; } r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max == min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, l]; }, /* author: http://mjijackson.com/ */ hue2rgb: function(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }, hslToRgb: function(h, s, l) { var r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = this.hue2rgb(p, q, h + 1 / 3); g = this.hue2rgb(p, q, h); b = this.hue2rgb(p, q, h - 1 / 3); } return [r * 255 | 0, g * 255 | 0, b * 255 | 0]; }, rgbToHsv: function(r, g, b) { if (r instanceof Array) { b = r[2]; g = r[1]; r = r[0]; } r = r / 255, g = g / 255, b = b / 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, v = max; var d = max - min; s = max == 0 ? 0 : d / max; if (max == min) { h = 0; // achromatic } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [h, s, v]; }, hsvToRgb: function(h, s, v) { var r, g, b; var i = Math.floor(h * 6); var f = h * 6 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return [r * 255, g * 255, b * 255]; }, color: function() { var result = new cq.Color(); result.parse(arguments[0], arguments[1]); return result; }, poolArray: [], pool: function() { if (!this.poolArray.length) { for (var i = 0; i < 100; i++) { this.poolArray.push(this.createCanvas(1, 1)); } } return this.poolArray.pop(); }, createCanvas: function(width, height) { if (NODEJS) { var result = new Canvas; } else { var result = document.createElement("canvas"); } if (arguments[0] instanceof Image || arguments[0] instanceof Canvas) { var image = arguments[0]; result.width = image.width; result.height = image.height; result.getContext("2d").drawImage(image, 0, 0); } else { result.width = width; result.height = height; } return result; }, createCocoonCanvas: function(width, height) { var result = document.createElement("screencanvas"); if (arguments[0] instanceof Image) { var image = arguments[0]; result.width = image.width; result.height = image.height; result.getContext("2d").drawImage(image, 0, 0); } else { result.width = width; result.height = height; } return result; }, createImageData: function(width, height) { return cq.createCanvas(width, height).getContext("2d").createImageData(width, height); } }); cq.Layer = function(canvas) { this.context = canvas.getContext("2d"); this.canvas = canvas; this.update(); } cq.Layer.prototype = { update: function() { this.context.webkitImageSmoothingEnabled = cq.smoothing; this.context.mozImageSmoothingEnabled = cq.smoothing; this.context.msImageSmoothingEnabled = cq.smoothing; this.context.imageSmoothingEnabled = cq.smoothing; if (COCOONJS) CocoonJS.App.setAntialias(cq.smoothing); }, appendTo: function(selector) { if (typeof selector === "object") { var element = selector; } else { var element = document.querySelector(selector); } element.appendChild(this.canvas); return this; }, a: function(a) { if (arguments.length) { this.previousAlpha = this.globalAlpha(); return this.globalAlpha(a); } else return this.globalAlpha(); }, ra: function() { return this.a(this.previousAlpha); }, drawRegion: function(image, region, x, y, scale) { scale = scale || 1; return this.drawImage( image, region[0], region[1], region[2], region[3], x | 0, y | 0, region[2] * scale | 0, region[3] * scale | 0 ); }, cache: function() { return this.clone().canvas; /* FFS .... image.src is no longer synchronous when assigning dataURL */ var image = new Image; image.src = this.canvas.toDataURL(); return image; }, blendOn: function(what, mode, mix) { cq.blend(what, this, mode, mix); return this; }, posterize: function(pc, inc) { pc = pc || 32; inc = inc || 4; var imgdata = this.getImageData(0, 0, this.width, this.height); var data = imgdata.data; for (var i = 0; i < data.length; i += inc) { data[i] -= data[i] % pc; // set value to nearest of 8 possibilities data[i + 1] -= data[i + 1] % pc; // set value to nearest of 8 possibilities data[i + 2] -= data[i + 2] % pc; // set value to nearest of 8 possibilities } this.putImageData(imgdata, 0, 0); // put image data to canvas }, bw: function(pc) { pc = 128; var imgdata = this.getImageData(0, 0, this.width, this.height); var data = imgdata.data; // 8-bit: rrr ggg bb for (var i = 0; i < data.length; i += 4) { var v = ((data[i] + data[i + 1] + data[i + 2]) / 3); v = (v / 128 | 0) * 128; //data[i] = v; // set value to nearest of 8 possibilities //data[i + 1] = v; // set value to nearest of 8 possibilities data[i + 2] = (v / 255) * data[i]; // set value to nearest of 8 possibilities } this.putImageData(imgdata, 0, 0); // put image data to canvas }, blend: function(what, mode, mix) { if (typeof what === "string") { var color = what; what = cq(this.canvas.width, this.canvas.height); what.fillStyle(color).fillRect(0, 0, this.canvas.width, this.canvas.height); } var result = cq.blend(this, what, mode, mix); this.canvas = result.canvas; this.context = result.context; return this; }, textWithBackground: function(text, x, y, background, padding) { var w = this.measureText(text).width; var h = this.fontHeight() * 0.8; var f = this.fillStyle(); var padding = padding || 2; this.fillStyle(background).fillRect(x - w / 2 - padding * 2, y - padding, w + padding * 4, h + padding * 2) this.fillStyle(f).textAlign("center").textBaseline("top").fillText(text, x, y); return this; }, fillCircle: function(x, y, r) { this.context.beginPath(); this.context.arc(x, y, r, 0, Math.PI * 2); this.context.fill(); return this; }, strokeCircle: function(x, y, r) { this.context.beginPath(); this.context.arc(x, y, r, 0, Math.PI * 2); this.context.stroke(); return this; }, circle: function(x, y, r) { this.context.arc(x, y, r, 0, Math.PI * 2); return this; }, crop: function(x, y, w, h) { if (arguments.length === 1) { var y = arguments[0][1]; var w = arguments[0][2]; var h = arguments[0][3]; var x = arguments[0][0]; } var canvas = cq.createCanvas(w, h); var context = canvas.getContext("2d"); context.drawImage(this.canvas, x, y, w, h, 0, 0, w, h); this.canvas.width = w; this.canvas.height = h; this.clear(); this.context.drawImage(canvas, 0, 0); return this; }, set: function(properties) { cq.extend(this.context, properties); }, resize: function(width, height) { var w = width, h = height; if (arguments.length === 1) { w = arguments[0] * this.canvas.width | 0; h = arguments[0] * this.canvas.height | 0; } else { if (height === false) { if (this.canvas.width > width) { h = this.canvas.height * (width / this.canvas.width) | 0; w = width; } else { w = this.canvas.width; h = this.canvas.height; } } else if (width === false) { if (this.canvas.width > width) { w = this.canvas.width * (height / this.canvas.height) | 0; h = height; } else { w = this.canvas.width; h = this.canvas.height; } } } var cqresized = cq(w, h).drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height, 0, 0, w, h); this.canvas = cqresized.canvas; this.context = cqresized.context; return this; }, imageLine: function(image, region, x, y, ex, ey, scale) { if (!region) region = [0, 0, image.width, image.height]; var distance = cq.distance(x, y, ex, ey); var count = distance / region[3] + 0.5 | 0; var angle = Math.atan2(ey - y, ex - x) + Math.PI / 2; this.save(); this.translate(x, y); this.rotate(angle); if (scale) this.scale(scale, 1.0); for (var i = 0; i <= count; i++) { this.drawRegion(image, region, -region[2] / 2 | 0, -region[3] * (i + 1)); } this.restore(); return this; }, trim: function(color, changes) { var transparent; if (color) { color = cq.color(color).toArray(); transparent = !color[3]; } else transparent = true; var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; var bound = [this.canvas.width, this.canvas.height, 0, 0]; var width = this.canvas.width; var height = this.canvas.height; for (var i = 0, len = sourcePixels.length; i < len; i += 4) { if (transparent) { if (!sourcePixels[i + 3]) continue; } else if (sourcePixels[i + 0] === color[0] && sourcePixels[i + 1] === color[1] && sourcePixels[i + 2] === color[2]) continue; var x = (i / 4 | 0) % this.canvas.width | 0; var y = (i / 4 | 0) / this.canvas.width | 0; if (x < bound[0]) bound[0] = x; if (x > bound[2]) bound[2] = x; if (y < bound[1]) bound[1] = y; if (y > bound[3]) bound[3] = y; } if (bound[2] === 0 && bound[3] === 0) {} else { if (changes) { changes.left = bound[0]; changes.top = bound[1]; changes.bottom = height - bound[3]; changes.right = width - bound[2] - bound[0]; changes.width = bound[2] - bound[0]; changes.height = bound[3] - bound[1]; } this.crop(bound[0], bound[1], bound[2] - bound[0] + 1, bound[3] - bound[1] + 1); } return this; }, matchPalette: function(palette) { var imgData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var rgbPalette = []; for (var i = 0; i < palette.length; i++) { rgbPalette.push(cq.color(palette[i])); } for (var i = 0; i < imgData.data.length; i += 4) { var difList = []; if (!imgData.data[i + 3]) continue; for (var j = 0; j < rgbPalette.length; j++) { var rgbVal = rgbPalette[j]; var rDif = Math.abs(imgData.data[i] - rgbVal[0]), gDif = Math.abs(imgData.data[i + 1] - rgbVal[1]), bDif = Math.abs(imgData.data[i + 2] - rgbVal[2]); difList.push(rDif + gDif + bDif); } var closestMatch = 0; for (var j = 0; j < palette.length; j++) { if (difList[j] < difList[closestMatch]) { closestMatch = j; } } var paletteRgb = cq.hexToRgb(palette[closestMatch]); imgData.data[i] = paletteRgb[0]; imgData.data[i + 1] = paletteRgb[1]; imgData.data[i + 2] = paletteRgb[2]; /* dithering */ //imgData.data[i + 3] = (255 * Math.random() < imgData.data[i + 3]) ? 255 : 0; //imgData.data[i + 3] = imgData.data[i + 3] > 128 ? 255 : 0; /* if (i % 3 === 0) { imgData.data[i] -= cq.limitValue(imgData.data[i] - 50, 0, 255); imgData.data[i + 1] -= cq.limitValue(imgData.data[i + 1] - 50, 0, 255); imgData.data[i + 2] -= cq.limitValue(imgData.data[i + 2] - 50, 0, 255); } */ } this.context.putImageData(imgData, 0, 0); return this; }, getPalette: function() { var palette = []; var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; for (var i = 0, len = sourcePixels.length; i < len; i += 4) { if (sourcePixels[i + 3]) { var hex = cq.rgbToHex(sourcePixels[i + 0], sourcePixels[i + 1], sourcePixels[i + 2]); if (palette.indexOf(hex) === -1) palette.push(hex); } } return palette; }, mapPalette: function() { }, colorToMask: function(color, inverted) { color = cq.color(color).toArray(); var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; var mask = []; for (var i = 0, len = sourcePixels.length; i < len; i += 4) { if (sourcePixels[i + 0] == color[0] && sourcePixels[i + 1] == color[1] && sourcePixels[i + 2] == color[2]) mask.push(inverted || false); else mask.push(!inverted); } return mask; }, grayscaleToMask: function() { var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; var mask = []; for (var i = 0, len = sourcePixels.length; i < len; i += 4) { mask.push(((sourcePixels[i + 0] + sourcePixels[i + 1] + sourcePixels[i + 2]) / 3) / 255); } return mask; }, applyMask: function(mask) { var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; var mode = typeof mask[0] === "boolean" ? "bool" : "byte"; for (var i = 0, len = sourcePixels.length; i < len; i += 4) { var value = mask[i / 4]; sourcePixels[i + 3] = value * 255 | 0; } this.context.putImageData(sourceData, 0, 0); return this; }, fillMask: function(mask) { var sourceData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var sourcePixels = sourceData.data; var maskType = typeof mask[0] === "boolean" ? "bool" : "byte"; var colorMode = arguments.length === 2 ? "normal" : "gradient"; var color = cq.color(arguments[1]); if (colorMode === "gradient") colorB = cq.color(arguments[2]); for (var i = 0, len = sourcePixels.length; i < len; i += 4) { var value = mask[i / 4]; if (maskType === "byte") value /= 255; if (colorMode === "normal") { if (value) { sourcePixels[i + 0] = color[0] | 0; sourcePixels[i + 1] = color[1] | 0; sourcePixels[i + 2] = color[2] | 0; sourcePixels[i + 3] = value * 255 | 0; } } else { sourcePixels[i + 0] = color[0] + (colorB[0] - color[0]) * value | 0; sourcePixels[i + 1] = color[1] + (colorB[1] - color[1]) * value | 0; sourcePixels[i + 2] = color[2] + (colorB[2] - color[2]) * value | 0; sourcePixels[i + 3] = 255; } } this.context.putImageData(sourceData, 0, 0); return this; }, clear: function(color) { if (color) { this.context.fillStyle = color; this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); } else { this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); } return this; }, clone: function() { // var result = cq.createCanvas(this.canvas); var result = cq.pool(); result.width = this.width; result.height = this.height; result.getContext("2d").drawImage(this.canvas, 0, 0); return cq(result); }, gradientText: function(text, x, y, maxWidth, gradient) { var words = text.split(" "); var h = this.fontHeight() * 2; var ox = 0; var oy = 0; if (maxWidth) { var line = 0; var lines = [""]; for (var i = 0; i < words.length; i++) { var word = words[i] + " "; var wordWidth = this.context.measureText(word).width; if (ox + wordWidth > maxWidth) { lines[++line] = ""; ox = 0; } lines[line] += word; ox += wordWidth; } } else var lines = [text]; for (var i = 0; i < lines.length; i++) { var oy = y + i * h * 0.6 | 0; var lingrad = this.context.createLinearGradient(0, oy, 0, oy + h * 0.6 | 0); for (var j = 0; j < gradient.length; j += 2) { lingrad.addColorStop(gradient[j], gradient[j + 1]); } var text = lines[i]; this.fillStyle(lingrad).fillText(text, x, oy); } return this; }, outline: function() { var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var pixels = data.data; var newData = this.createImageData(this.canvas.width, this.canvas.height); var newPixels = newData.data; var canvas = this.canvas; function check(x, y) { if (x < 0) return 0; if (x >= canvas.width) return 0; if (y < 0) return 0; if (y >= canvas.height) return 0; var i = (x + y * canvas.width) * 4; return pixels[i + 3] > 0; } for (var x = 0; x < this.canvas.width; x++) { for (var y = 0; y < this.canvas.height; y++) { var full = 0; var i = (y * canvas.width + x) * 4; if (!pixels[i + 3]) continue; full += check(x - 1, y); full += check(x + 1, y); full += check(x, y - 1); full += check(x, y + 1); if (full !== 4) { newPixels[i] = 255; newPixels[i + 1] = 255; newPixels[i + 2] = 255; newPixels[i + 3] = 255; } } } this.context.putImageData(newData, 0, 0); return this; }, setHsl: function() { if (arguments.length === 1) { var args = arguments[0]; } else { var args = arguments; } var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var pixels = data.data; var r, g, b, a, h, s, l, hsl = [], newPixel = []; for (var i = 0, len = pixels.length; i < len; i += 4) { hsl = cq.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); h = args[0] === false ? hsl[0] : cq.limitValue(args[0], 0, 1); s = args[1] === false ? hsl[1] : cq.limitValue(args[1], 0, 1); l = args[2] === false ? hsl[2] : cq.limitValue(args[2], 0, 1); newPixel = cq.hslToRgb(h, s, l); pixels[i + 0] = newPixel[0]; pixels[i + 1] = newPixel[1]; pixels[i + 2] = newPixel[2]; } this.context.putImageData(data, 0, 0); return this; }, shiftHsl: function() { if (arguments.length === 1) { var args = arguments[0]; } else { var args = arguments; } var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var pixels = data.data; var r, g, b, a, h, s, l, hsl = [], newPixel = []; for (var i = 0, len = pixels.length; i < len; i += 4) { hsl = cq.rgbToHsl(pixels[i + 0], pixels[i + 1], pixels[i + 2]); if (pixels[i + 0] !== pixels[i + 1] || pixels[i + 1] !== pixels[i + 2]) { h = args[0] === false ? hsl[0] : cq.wrapValue(hsl[0] + args[0], 0, 1); s = args[1] === false ? hsl[1] : cq.limitValue(hsl[1] + args[1], 0, 1); } else { h = hsl[0]; s = hsl[1]; } l = args[2] === false ? hsl[2] : cq.limitValue(hsl[2] + args[2], 0, 1); newPixel = cq.hslToRgb(h, s, l); pixels[i + 0] = newPixel[0]; pixels[i + 1] = newPixel[1]; pixels[i + 2] = newPixel[2]; } this.context.putImageData(data, 0, 0); return this; }, applyColor: function(color) { if (COCOONJS) return this; this.save(); this.globalCompositeOperation("source-in"); this.clear(color); this.restore(); return this; }, negative: function(src, dst) { var data = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); var pixels = data.data; var r, g, b, a, h, s, l, hsl = [], newPixel = []; for (var i = 0, len = pixels.length; i < len; i += 4) { pixels[i + 0] = 255 - pixels[i + 0]; pixels[i + 1] = 255 - pixels[i + 1]; pixels[i + 2] = 255 - pixels[i + 2]; } this.context.putImageData(data, 0, 0); return this; }, roundRect: function(x, y, width, height, radius) { this.beginPath(); this.moveTo(x + radius, y); this.lineTo(x + width - radius, y); this.quadraticCurveTo(x + width, y, x + width, y + radius); this.lineTo(x + width, y + height - radius); this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); this.lineTo(x + radius, y + height); this.quadraticCurveTo(x, y + height, x, y + height - radius); this.lineTo(x, y + radius); this.quadraticCurveTo(x, y, x + radius, y); this.closePath(); return this; }, markupText: function(text) { }, wrappedText: function(text, x, y, maxWidth, newlineCallback) { var words = text.split(" "); var lineHeight = this.fontHeight(); var ox = 0; var oy = 0; if (maxWidth) { var line = 0; var lines = [""]; for (var i = 0; i < words.length; i++) { var word = words[i] + " "; var wordWidth = this.context.measureText(word).width; if (ox + wordWidth > maxWidth || words[i] === "\n") { lines[++line] = ""; ox = 0; } if (words[i] !== "\n") { lines[line] += word; ox += wordWidth; } } } else { var lines = [text]; } for (var i = 0; i < lines.length; i++) { var oy = y + i * lineHeight | 0; var text = lines[i]; if (newlineCallback) newlineCallback.call(this, x, y + oy); this.fillText(text, x, oy); } return this; }, fontHeights: {}, fontHeight: function() { var font = this.font(); if (!this.fontHeights[font]) { var temp = cq(100, 100); var height = 0; var changes = {}; temp.font(font).fillStyle("#fff"); temp.textBaseline("bottom").fillText("gM", 25, 100); temp.trim(false, changes); height += changes.bottom; var temp = cq(100, 100); var changes = {}; temp.font(font).fillStyle("#fff"); temp.textBaseline("top").fillText("gM", 25, 0); temp.trim(false, changes); height += changes.top; var temp = cq(100, 100); var changes = {}; temp.font(font).fillStyle("#fff"); temp.textBaseline("alphabetic").fillText("gM", 50, 50); temp.trim(false, changes); height += temp.height; this.fontHeights[font] = height; } return this.fontHeights[font]; }, textBoundaries: function(text, maxWidth) { var words = text.split(" "); var h = this.fontHeight(); var ox = 0; var oy = 0; if (maxWidth) { var line = 0; var lines = [""]; for (var i = 0; i < words.length; i++) { var word = words[i] + " "; var wordWidth = this.context.measureText(word).width; if (ox + wordWidth > maxWidth || words[i] === "\n") { lines[++line] = ""; ox = 0; } if (words[i] !== "\n") { lines[line] += word; ox += wordWidth; } } } else { var lines = [text]; maxWidth = this.measureText(text).width; } return { height: lines.length * h, width: maxWidth, lines: lines.length, lineHeight: h } }, repeatImageRegion: function(image, sx, sy, sw, sh, dx, dy, dw, dh) { this.save(); this.rect(dx, dy, dw, dh); this.clip(); for (var x = 0, len = Math.ceil(dw / sw); x < len; x++) { for (var y = 0, leny = Math.ceil(dh / sh); y < leny; y++) { this.drawImage(image, sx, sy, sw, sh, dx + x * sw, dy + y * sh, sw, sh); } } this.restore(); return this; }, repeatImage: function(image, x, y, w, h) { // if (!env.details) return this; if (arguments.length < 9) { this.repeatImageRegion(image, 0, 0, image.width, image.height, x, y, w, h); } else { this.repeatImageRegion.apply(this, arguments); } return this; }, borderImage: function(image, x, y, w, h, t, r, b, l, fill) { // if (!env.details) return this; if (typeof t === "object") { var bottomLeft = t.bottomLeft || [0, 0, 0, 0]; var bottomRight = t.bottomRight || [0, 0, 0, 0]; var topLeft = t.topLeft || [0, 0, 0, 0]; var topRight = t.topRight || [0, 0, 0, 0]; var clh = bottomLeft[3] + topLeft[3]; var crh = bottomRight[3] + topRight[3]; var ctw = topLeft[2] + topRight[2]; var cbw = bottomLeft[2] + bottomRight[2]; t.fillPadding = [0, 0, 0, 0]; if (t.left) t.fillPadding[0] = t.left[2]; if (t.top) t.fillPadding[1] = t.top[3]; if (t.right) t.fillPadding[2] = t.right[2]; if (t.bottom) t.fillPadding[3] = t.bottom[3]; // if (!t.fillPadding) t.fillPadding = [0, 0, 0, 0]; if (t.fill) { this.drawImage(image, t.fill[0], t.fill[1], t.fill[2], t.fill[3], x + t.fillPadding[0], y + t.fillPadding[1], w - t.fillPadding[2] - t.fillPadding[0], h - t.fillPadding[3] - t.fillPadding[1]); } else { // this.fillRect(x + t.fillPadding[0], y + t.fillPadding[1], w - t.fillPadding[2] - t.fillPadding[0], h - t.fillPadding[3] - t.fillPadding[1]); } if (t.left) this[t.left[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.left[0], t.left[1], t.left[2], t.left[3], x, y + topLeft[3], t.left[2], h - clh); if (t.right) this[t.right[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.right[0], t.right[1], t.right[2], t.right[3], x + w - t.right[2], y + topRight[3], t.right[2], h - crh); if (t.top) this[t.top[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.top[0], t.top[1], t.top[2], t.top[3], x + topLeft[2], y, w - ctw, t.top[3]); if (t.bottom) this[t.bottom[4] === "stretch" ? "drawImage" : "repeatImage"](image, t.bottom[0], t.bottom[1], t.bottom[2], t.bottom[3], x + bottomLeft[2], y + h - t.bottom[3], w - cbw, t.bottom[3]); if (t.bottomLeft) this.drawImage(image, t.bottomLeft[0], t.bottomLeft[1], t.bottomLeft[2], t.bottomLeft[3], x, y + h - t.bottomLeft[3], t.bottomLeft[2], t.bottomLeft[3]); if (t.topLeft) this.drawImage(image, t.topLeft[0], t.topLeft[1], t.topLeft[2], t.topLeft[3], x, y, t.topLeft[2], t.topLeft[3]); if (t.topRight) this.drawImage(image, t.topRight[0], t.topRight[1], t.topRight[2], t.topRight[3], x + w - t.topRight[2], y, t.topRight[2], t.topRight[3]); if (t.bottomRight) this.drawImage(image, t.bottomRight[0], t.bottomRight[1], t.bottomRight[2], t.bottomRight[3], x + w - t.bottomRight[2], y + h - t.bottomRight[3], t.bottomRight[2], t.bottomRight[3]); } else { /* top */ if (t > 0 && w - l - r > 0) this.drawImage(image, l, 0, image.width - l - r, t, x + l, y, w - l - r, t); /* bottom */ if (b > 0 && w - l - r > 0) this.drawImage(image, l, image.height - b, image.width - l - r, b, x + l, y + h - b, w - l - r, b); // console.log(x, y, w, h, t, r, b, l); // console.log(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); /* left */ if (l > 0 && h - b - t > 0) this.drawImage(image, 0, t, l, image.height - b - t, x, y + t, l, h - b - t); /* right */ if (r > 0 && h - b - t > 0) this.drawImage(image, image.width - r, t, r, image.height - b - t, x + w - r, y + t, r, h - b - t); /* top-left */ if (l > 0 && t > 0) this.drawImage(image, 0, 0, l, t, x, y, l, t); /* top-right */ if (r > 0 && t > 0) this.drawImage(image, image.width - r, 0, r, t, x + w - r, y, r, t); /* bottom-right */ if (r > 0 && b > 0) this.drawImage(image, image.width - r, image.height - b, r, b, x + w - r, y + h - b, r, b); /* bottom-left */ if (l > 0 && b > 0) this.drawImage(image, 0, image.height - b, l, b, x, y + h - b, l, b); if (fill) { if (typeof fill === "string") { this.fillStyle(fill).fillRect(x + l, y + t, w - l - r, h - t - b); } else { if (w - l - r > 0 && h - t - b > 0) this.drawImage(image, l, t, image.width - r - l, image.height - b - t, x + l, y + t, w - l - r, h - t - b); } } } }, setPixel: function(color, x, y) { /* fillRect is slow! */ return this.fillStyle(color).fillRect(x, y, 1, 1); /* this is how it should work - but it does not */ color = cq.color(color); var pixel = this.createImageData(1, 1); pixel.data[0] = color[0]; pixel.data[1] = color[1]; pixel.data[2] = color[2]; pixel.data[3] = 1.0; this.putImageData(pixel, x, y); return this; }, getPixel: function(x, y) { var pixel = this.context.getImageData(x, y, 1, 1).data; return cq.color([pixel[0], pixel[1], pixel[2], pixel[3]]); }, createImageData: function(width, height) { if (false && this.context.createImageData) { return this.context.createImageData.apply(this.context, arguments); } else { if (!this.emptyCanvas) { this.emptyCanvas = cq.createCanvas(width, height); this.emptyCanvasContext = this.emptyCanvas.getContext("2d"); } this.emptyCanvas.width = width; this.emptyCanvas.height = height; return this.emptyCanvasContext.getImageData(0, 0, width, height); } }, setLineDash: function(dash) { if (this.context.setLineDash) { this.context.setLineDash(dash); return this; } else return this; }, measureText: function() { return this.context.measureText.apply(this.context, arguments); }, getLineDash: function() { return this.context.getLineDash(); }, createRadialGradient: function() { return this.context.createRadialGradient.apply(this.context, arguments); }, createLinearGradient: function() { return this.context.createLinearGradient.apply(this.context, arguments); }, createPattern: function() { return this.context.createPattern.apply(this.context, arguments); }, getImageData: function() { return this.context.getImageData.apply(this.context, arguments); }, get width() { return this.canvas.width; }, get height() { return this.canvas.height; }, set width(w) { this.canvas.width = w; this.update(); return this.canvas.width; }, set height(h) { this.canvas.height = h; this.update(); return this.canvas.height; } }; /* extend Layer with drawing context methods */ var methods = ["arc", "arcTo", "beginPath", "bezierCurveTo", "clearRect", "clip", "closePath", "createLinearGradient", "createRadialGradient", "createPattern", "drawFocusRing", "drawImage", "fill", "fillRect", "fillText", "getImageData", "isPointInPath", "lineTo", "measureText", "moveTo", "putImageData", "quadraticCurveTo", "rect", "restore", "rotate", "save", "scale", "setTransform", "stroke", "strokeRect", "strokeText", "transform", "translate", "setLineDash"]; for (var i = 0; i < methods.length; i++) { var name = methods[i]; // this.debug = true; if (cq.Layer.prototype[name]) continue; if (!this.debug) { // if (!cq.Layer.prototype[name]) cq.Layer.prototype[name] = Function("this.context." + name + ".apply(this.context, arguments); return this;"); var self = this; (function(name) { cq.Layer.prototype[name] = function() { this.context[name].apply(this.context, arguments); return this; } })(name); } else { var self = this; (function(name) { cq.Layer.prototype[name] = function() { try { this.context[name].apply(this.context, arguments); return this; } catch (e) { var err = new Error(); console.log(err.stack); throw (e + err.stack); console.log(e, name, arguments); } } })(name); } }; /* create setters and getters */ var properties = ["canvas", "fillStyle", "font", "globalAlpha", "globalCompositeOperation", "lineCap", "lineJoin", "lineWidth", "miterLimit", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "strokeStyle", "textAlign", "textBaseline", "lineDashOffset"]; for (var i = 0; i < properties.length; i++) { var name = properties[i]; if (!cq.Layer.prototype[name]) cq.Layer.prototype[name] = Function("if(arguments.length) { this.context." + name + " = arguments[0]; return this; } else { return this.context." + name + "; }"); }; /* color */ cq.Color = function(data, type) { if (arguments.length) this.parse(data, type); } cq.Color.prototype = { toString: function() { return this.toRgb(); }, parse: function(args, type) { if (args[0] instanceof cq.Color) { this[0] = args[0][0]; this[1] = args[0][1]; this[2] = args[0][2]; this[3] = args[0][3]; return; } if (typeof args === "string") { var match = null; if (args[0] === "#") { var rgb = cq.hexToRgb(args); this[0] = rgb[0]; this[1] = rgb[1]; this[2] = rgb[2]; this[3] = 1.0; } else if (match = args.match(/rgb\((.*),(.*),(.*)\)/)) { this[0] = match[1] | 0; this[1] = match[2] | 0; this[2] = match[3] | 0; this[3] = 1.0; } else if (match = args.match(/rgba\((.*),(.*),(.*)\)/)) { this[0] = match[1] | 0; this[1] = match[2] | 0; this[2] = match[3] | 0; this[3] = match[4] | 0; } else if (match = args.match(/hsl\((.*),(.*),(.*)\)/)) { this.fromHsl(match[1], match[2], match[3]); } else if (match = args.match(/hsv\((.*),(.*),(.*)\)/)) { this.fromHsv(match[1], match[2], match[3]); } } else { switch (type) { case "hsl": case "hsla": this.fromHsl(args[0], args[1], args[2], args[3]); break; case "hsv": case "hsva": this.fromHsv(args[0], args[1], args[2], args[3]); break; default: this[0] = args[0]; this[1] = args[1]; this[2] = args[2]; this[3] = typeof args[3] === "undefined" ? 1.0 : args[3]; break; } } }, a: function(a) { return this.alpha(a); }, alpha: function(a) { this[3] = a; return this; }, fromHsl: function() { var components = arguments[0] instanceof Array ? arguments[0] : arguments; var color = cq.hslToRgb(components[0], components[1], components[2]); this[0] = color[0]; this[1] = color[1]; this[2] = color[2]; this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; }, fromHsv: function() { var components = arguments[0] instanceof Array ? arguments[0] : arguments; var color = cq.hsvToRgb(components[0], components[1], components[2]); this[0] = color[0]; this[1] = color[1]; this[2] = color[2]; this[3] = typeof arguments[3] === "undefined" ? 1.0 : arguments[3]; }, toArray: function() { return [this[0], this[1], this[2], this[3]]; }, toRgb: function() { return "rgb(" + this[0] + ", " + this[1] + ", " + this[2] + ")"; }, toRgba: function() { return "rgba(" + this[0] + ", " + this[1] + ", " + this[2] + ", " + this[3] + ")"; }, toHex: function() { return cq.rgbToHex(this[0], this[1], this[2]); }, toHsl: function() { var c = cq.rgbToHsl(this[0], this[1], this[2]); c[3] = this[3]; return c; }, toHsv: function() { var c = cq.rgbToHsv(this[0], this[1], this[2]); c[3] = this[3]; return c; }, gradient: function(target, steps) { var targetColor = cq.color(target); }, shiftHsl: function() { var hsl = this.toHsl(); if (this[0] !== this[1] || this[1] !== this[2]) { var h = arguments[0] === false ? hsl[0] : cq.wrapValue(hsl[0] + arguments[0], 0, 1); var s = arguments[1] === false ? hsl[1] : cq.limitValue(hsl[1] + arguments[1], 0, 1); } else { var h = hsl[0]; var s = hsl[1]; } var l = arguments[2] === false ? hsl[2] : cq.limitValue(hsl[2] + arguments[2], 0, 1); this.fromHsl(h, s, l); return this; }, setHsl: function() { var hsl = this.toHsl(); var h = arguments[0] === false ? hsl[0] : cq.limitValue(arguments[0], 0, 1); var s = arguments[1] === false ? hsl[1] : cq.limitValue(arguments[1], 0, 1); var l = arguments[2] === false ? hsl[2] : cq.limitValue(arguments[2], 0, 1); this.fromHsl(h, s, l); return this; } }; if (NODEJS) global["cq"] = global["CanvasQuery"] = cq; else window["cq"] = window["CanvasQuery"] = cq; /* nodejs specific stuff */ cq.Layer.prototype.saveAsPNG = function(path) { fs.writeFileSync(path, this.canvas.toBuffer()); } cq.loadFromFile = function(path) { var buffer = fs.readFileSync(path); var img = new Image; img.src = buffer; return cq(img); } })();