ld31-space-diggers/public/vendors/canvasquery.js

1635 lines
44 KiB
JavaScript

/*
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);
}
})();