/**
* jQuery xcolor
* Copyright (c) 2010, Robert Eisele (robert@xarg.org)
* Dual licensed under the MIT or GPL Version 2 licenses.
* Date: 06/21/2010
*
* @author Robert Eisele
* @version 1.0
*
* @see http://www.xarg.org/project/jquery-color-plugin-xcolor/
**/

(function ($) {

	/**
	* @constructor
	*/
	function xColor(color) {

		// http://www.w3.org/TR/css3-color/#svg-color
		this.color_names = {
			transparent			: [0, 0, 0, 0],
			aliceblue			: [240, 248, 255],
			antiquewhite		: [250, 235, 215],
			aqua				: [0, 255, 255],
			aquamarine			: [127, 255, 212],
			azure				: [240, 255, 255],
			beige				: [245, 245, 220],
			bisque				: [255, 228, 196],
			black				: [0, 0, 0],
			blanchedalmond		: [255, 235, 205],
			blue				: [0, 0, 255],
			blueviolet			: [138, 43, 226],
			brown				: [165, 42, 42],
			burlywood			: [222, 184, 135],
			cadetblue			: [95, 158, 160],
			chartreuse			: [127, 255, 0],
			chocolate			: [210, 105, 30],
			coral				: [255, 127, 80],
			cornflowerblue		: [100, 149, 237],
			cornsilk			: [255, 248, 220],
			crimson				: [220, 20, 60],
			cyan				: [0, 255, 255],
			darkblue			: [0, 0, 139],
			darkcyan			: [0, 139, 139],
			darkgoldenrod		: [184, 134, 11],
			darkgray			: [169, 169, 169],
			darkgreen			: [0, 100, 0],
			darkgrey			: [169, 169, 169],
			darkkhaki			: [189, 183, 107],
			darkmagenta			: [139, 0, 139],
			darkolivegreen		: [85, 107, 47],
			darkorange			: [255, 140, 0],
			darkorchid			: [153, 50, 204],
			darkred				: [139, 0, 0],
			darksalmon			: [233, 150, 122],
			darkseagreen		: [143, 188, 143],
			darkslateblue		: [72, 61, 139],
			darkslategray		: [47, 79, 79],
			darkslategrey		: [47, 79, 79],
			darkturquoise		: [0, 206, 209],
			darkviolet			: [148, 0, 211],
			deeppink			: [255, 20, 147],
			deepskyblue			: [0, 191, 255],
			dimgray				: [105, 105, 105],
			dimgrey				: [105, 105, 105],
			dodgerblue			: [30, 144, 255],
			firebrick			: [178, 34, 34],
			floralwhite			: [255, 250, 240],
			forestgreen			: [34, 139, 34],
			fuchsia				: [255, 0, 255],
			gainsboro			: [220, 220, 220],
			ghostwhite			: [248, 248, 255],
			gold				: [255, 215, 0],
			goldenrod			: [218, 165, 32],
			gray				: [128, 128, 128],
			green				: [0, 128, 0],
			greenyellow			: [173, 255, 47],
			grey				: [128, 128, 128],
			honeydew			: [240, 255, 240],
			hotpink				: [255, 105, 180],
			indianred			: [205, 92, 92],
			indigo				: [75, 0, 130],
			ivory				: [255, 255, 240],
			khaki				: [240, 230, 140],
			lavender			: [230, 230, 250],
			lavenderblush		: [255, 240, 245],
			lawngreen			: [124, 252, 0],
			lemonchiffon		: [255, 250, 205],
			lightblue			: [173, 216, 230],
			lightcoral			: [240, 128, 128],
			lightcyan			: [224, 255, 255],
			lightgoldenrodyellow: [250, 250, 210],
			lightgray			: [211, 211, 211],
			lightgreen			: [144, 238, 144],
			lightgrey			: [211, 211, 211],
			lightpink			: [255, 182, 193],
			lightsalmon			: [255, 160, 122],
			lightseagreen		: [32, 178, 170],
			lightskyblue		: [135, 206, 250],
			lightslategray		: [119, 136, 153],
			lightslategrey		: [119, 136, 153],
			lightsteelblue		: [176, 196, 222],
			lightyellow			: [255, 255, 224],
			lime				: [0, 255, 0],
			limegreen			: [50, 205, 50],
			linen				: [250, 240, 230],
			magenta				: [255, 0, 255],
			maroon				: [128, 0, 0],
			mediumaquamarine	: [102, 205, 170],
			mediumblue			: [0, 0, 205],
			mediumorchid		: [186, 85, 211],
			mediumpurple		: [147, 112, 219],
			mediumseagreen		: [60, 179, 113],
			mediumslateblue		: [123, 104, 238],
			mediumspringgreen	: [0, 250, 154],
			mediumturquoise		: [72, 209, 204],
			mediumvioletred		: [199, 21, 133],
			midnightblue		: [25, 25, 112],
			mintcream			: [245, 255, 250],
			mistyrose			: [255, 228, 225],
			moccasin			: [255, 228, 181],
			navajowhite			: [255, 222, 173],
			navy				: [0, 0, 128],
			oldlace				: [253, 245, 230],
			olive				: [128, 128, 0],
			olivedrab			: [107, 142, 35],
			orange				: [255, 165, 0],
			orangered			: [255, 69, 0],
			orchid				: [218, 112, 214],
			palegoldenrod		: [238, 232, 170],
			palegreen			: [152, 251, 152],
			paleturquoise		: [175, 238, 238],
			palevioletred		: [219, 112, 147],
			papayawhip			: [255, 239, 213],
			peachpuff			: [255, 218, 185],
			peru				: [205, 133, 63],
			pink				: [255, 192, 203],
			plum				: [221, 160, 221],
			powderblue			: [176, 224, 230],
			purple				: [128, 0, 128],
			red					: [255, 0, 0],
			rosybrown			: [188, 143, 143],
			royalblue			: [65, 105, 225],
			saddlebrown			: [139, 69, 19],
			salmon				: [250, 128, 114],
			sandybrown			: [244, 164, 96],
			seagreen			: [46, 139, 87],
			seashell			: [255, 245, 238],
			sienna				: [160, 82, 45],
			silver				: [192, 192, 192],
			skyblue				: [135, 206, 235],
			slateblue			: [106, 90, 205],
			slategray			: [112, 128, 144],
			slategrey			: [112, 128, 144],
			snow				: [255, 250, 250],
			springgreen			: [0, 255, 127],
			steelblue			: [70, 130, 180],
			tan					: [210, 180, 140],
			teal				: [0, 128, 128],
			thistle				: [216, 191, 216],
			tomato				: [255, 99, 71],
			turquoise			: [64, 224, 208],
			violet				: [238, 130, 238],
			wheat				: [245, 222, 179],
			white				: [255, 255, 255],
			whitesmoke			: [245, 245, 245],
			yellow				: [255, 255, 0],
			yellowgreen			: [154, 205, 50]
		};

		this.color_exp = [
		{// 616064010
			regex: /^([1-9]\d*)$/,
			parse: function (part) {
				c = parseInt(part[1], 10);
				return [ (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff, (_normalize((c >> 24) & 0xff, 100) / 100) || 1];
			}
		}, {// #ff9000, #ff0000
			regex: /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/,
			parse: function (part) {
				return [
				parseInt(part[1], 16),
				parseInt(part[2], 16),
				parseInt(part[3], 16)];
			}
		}, {// #f00, 000
			regex: /^#?([0-9a-f])([0-9a-f])([0-9a-f])$/,
			parse: function (part) {
				return [
				parseInt(part[1] + part[1], 16),
				parseInt(part[2] + part[2], 16),
				parseInt(part[3] + part[3], 16)];
			}
		}, {// rgb(1, 234, 56)
			regex: /^rgba?\((\d{1,3}),(\d{1,3}),(\d{1,3})(,([0-9.]+))?\)$/,
			parse: function (part) {
				return [
				parseInt(part[1], 10),
				parseInt(part[2], 10),
				parseInt(part[3], 10),
				part[5] ? parseFloat(part[5]) : 1
				];
			}
		}, {// 1, 234, 56
			regex: /^(\d{1,3}),(\d{1,3}),(\d{1,3})(,([0-9.]+))?$/,
			parse: function (part) {
				return [
				parseInt(part[1], 10),
				parseInt(part[2], 10),
				parseInt(part[3], 10),
				part[5] ? parseFloat(part[5]) : 1
				];
			}
		}, {// hsv(64, 40, 16) in [0, 360], [0,100], [0,100]
			regex: /^hs[bv]a?\((\d{1,3}),(\d{1,3}),(\d{1,3})(,([0-9.]+))?\)$/,
			parse: function (part) {

				return _hsva(parseInt(part[1], 10), parseInt(part[2], 10), parseInt(part[3], 10), part[5] ? parseFloat(part[5]) : 1);
			}
		}, {// hsl(64, 40, 16) in [0, 360], [0,100], [0,100]
			regex: /^hsla?\((\d{1,3}),(\d{1,3}),(\d{1,3})(,([0-9.]+))?\)$/,
			parse: function (part) {

				return _hsla(parseInt(part[1], 10), parseInt(part[2], 10), parseInt(part[3], 10), part[5] ? parseFloat(part[5]) : 1);
			}
		}];


		function _normalize(n, s) {

			/**
			* Some value.
			* @type {number|undefined}
			*/
			if (undefined === s) {
				s = 255;
			}

			if (isNaN(n) || n <= 0) {
				return 0;
			} if (s < n) {
				return s;
			} if (n <= 1) {
				return n * s;
			}
			return n;
		}

		function _hsla(h,s,l,a) {

			h = _normalize(h, 360) / 360;
			s = _normalize(s, 100) / 100;
			l = _normalize(l, 100) / 100;

			if (0 == s) {
				l = Math.round(l * 255);
				return [l, l, l, a];
			}

			function _hue(v1, v2, h) {
				if (h < 0) h++;
				if (h > 1) h--;
				if (6 * h < 1) return v1 + (v2 - v1) * 6 * h;
				if (2 * h < 1) return v2;
				if (3 * h < 2) return v1 + (v2 - v1) * (4 - 6 * h);
				return v1;
			}

			var v = l < .5 ? (l * (1 + s)) : (l + s - l * s);
			var m = l + l - v;

			return [
			Math.round(255 *_hue(m, v, h + 1 / 3)),
			Math.round(255 *_hue(m, v, h)),
			Math.round(255 *_hue(m, v, h - 1 / 3)), a ];
		}

		function _hsva(h,s,v,a) {

			h = _normalize(h, 360) / 60;
			s = _normalize(s, 100) / 100;
			v = _normalize(v, 100) / 100;

			var hi = Math.floor(h);
			var f = h - hi;

			if (!(hi & 1)) f = 1 - f;

			var m = Math.round(255 * (v * (1 - s)));
			var n = Math.round(255 * (v * (1 - s * f)));

			v = Math.round(255 * v);

			switch (hi) {
			case 6:
			case 0: return [v, n, m, a];
			case 1: return [n, v, m, a];
			case 2: return [m, v, n, a];
			case 3: return [m, n, v, a];
			case 4: return [n, m, v, a];
			case 5: return [v, m, n, a];
			}
		}

		this.setColor = function (color) {

			this.r = null;
			this.g = null;
			this.b = null;
			this.a = 1;

			this.success = false;

			function A(o, c) {
				o.success = true;

				if (undefined !== c) o.a = _normalize(c, 100) / 100;
			}

			if (typeof color == "object") {

				if (color[0] !== undefined && color[1] !== undefined && color[2] !== undefined) {
					this.r = _normalize(color[0]);
					this.g = _normalize(color[1]);
					this.b = _normalize(color[2]);

					A(this, color[3]);

				} else if (color.r !== undefined && color.g !== undefined && color.b !== undefined) {
					this.r = _normalize(color.r);
					this.g = _normalize(color.g);
					this.b = _normalize(color.b);

					A(this, color.a);

				} else if (color.v !== undefined && color.h !== undefined && color.s !== undefined) {
					var set =_hsva(color.h, color.s, color.v, 1);
					this.r = set[0];
					this.g = set[1];
					this.b = set[2];

					A(this, color.a);

				} else if (color.l !== undefined && color.h !== undefined && color.s !== undefined) {
					var set =_hsla(color.h, color.s, color.l, 1);
					this.r = set[0];
					this.g = set[1];
					this.b = set[2];

					A(this, color.a);
				}
				return;
			}

			if (typeof color == "number") {
				this.success = true;

				this.r = (color >> 16) & 0xff;
				this.g = (color >> 8) & 0xff;
				this.b = color & 0xff;

				return;
			}

			if (typeof color != "string") {
				return;
			}

			color = color.toLowerCase().replace(/[^a-z0-9,.()#]/g, '');

			if (undefined !== this.color_names[color]) {

				var c = this.color_names[color];

				this.r = c[0];
				this.g = c[1];
				this.b = c[2];
				A(this, c[3]);

				return;
			}

			var exp = this.color_exp;

			for (var i = 0; i < exp.length; i++) {

				var parts = exp[i].regex.exec(color);

				if (parts) {

					var ret = exp[i].parse(parts);

					this.r = _normalize(ret[0]);
					this.g = _normalize(ret[1]);
					this.b = _normalize(ret[2]);
					A(this, ret[3]);

					break;
				}
			}
		}

		this.getColor = function (type) {

			if (undefined !== type) switch (type.toLowerCase()) {
			case "rgb":
				return this.getRGB();
			case "hsv":
			case "hsb":
				return this.getHSV();
			case "hsl":
				return this.getHSL();
			case "int":
				return this.getInt();
			case "fraction":
				return this.getFraction();
			case "css":
			case "style":
				return this.getCSS();
			case "name":
				return this.getName();
			}
			return this.getHex();
		}

		this.getRGB = function () {

			if (this.success) {

				return {r: this.r, g: this.g, b: this.b, a: this.a};
			}
			return null;
		}

		this.getCSS = function () {

			if (this.success) {

				if (this.a == 1) {
					return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
				}
				return 'rgba(' + this.r + ', ' + this.g + ', ' + this.b + ', ' + this.a + ')';
			}
			return null;
		}

		this.getName = function () {

			if (this.success) {

				var lowest = null;
				var lowest_ndx;

				var table = this.color_names;

				var a = this.getHSL();

				for (var i in table) {

					var b = new xColor(table[i]).getHSL();

					var tmp = Math.sqrt(0.5 * Math.pow(a.h - b.h, 2) + 0.5 * Math.pow(a.s - b.s, 2) + Math.pow(a.l - b.l, 2));

					if (lowest === null || tmp < lowest) {
						lowest = tmp;
						lowest_ndx = i;
					}
				}
				return lowest_ndx;

			}
			return null;
		}

		this.getFraction = function () {

			if (this.success) {

				return {r: this.r / 255, g: this.g / 255, b: this.b / 255, a: this.a};
			}
			return null;
		}

		this.getHSL = function () {

			// inspiration: http://130.113.54.154/~monger/hsl-rgb.html
			if (this.success) {

				var r = this.r / 255;
				var g = this.g / 255;
				var b = this.b / 255;

				var min = Math.min(r, g, b);
				var max = Math.max(r, g, b);
				var delta = max - min;

				var h, s, l = (max + min) / 2;

				if (0 == delta) {
					h = 0;
					s = 0;
				} else {

					if (l < .5) {
						s = delta / (max + min);
					} else {
						s = delta / (2.0 - (max + min));
					}

					if (max == r) {
						h = (g - b) / delta;
					} else if (max == g) {
						h = 2.0 + (b - r) / delta;
					} else if (max == b) {
						h = 4.0 + (r - g) / delta;
					}

					if (h < 0) {
						h+= 6;
					}
				}
				return { h : Math.round(h * 60), s: Math.round(s * 100), l: Math.round(l * 100), a: this.a };
			}
			return null;
		}

		this.getHSV = function () {

			if (this.success) {

				var r = this.r / 255;
				var g = this.g / 255;
				var b = this.b / 255;

				var min = Math.min(r, g, b);
				var max = Math.max(r, g, b);
				var delta = max - min;

				var h, s, v = max;

				if (0 == delta) {
					h = 0;
					s = 0;
				} else {
					s = delta / max;

					delta*= 6;

					var dR = .5 + (max - r) / delta;
					var dG = .5 + (max - g) / delta;
					var dB = .5 + (max - b) / delta;

					if (r == max) {
						h = dB - dG;
					} else if (g == max) {
						h = 1 / 3 + dR - dB;
					} else if (b == max) {
						h = 2 / 3 + dG - dR;
					}

					if (h < 0) h++;
					if (h > 1) h--;
				}

				return { h : Math.round(h * 360), s: Math.round(s * 100), v: Math.round(v * 100), a: this.a };
			}
			return null;
		}

		this.getHex = function () {

			if (this.success) {

				var chars = "0123456789abcdef"

				var r1 = this.r >> 4;
				var g1 = this.g >> 4;
				var b1 = this.b >> 4;

				var r2 = this.r & 0xf;
				var g2 = this.g & 0xf;
				var b2 = this.b & 0xf;

				if (r1 == r2 && g1 == g2 && b1 == b2) {
					return '#' + chars.charAt(r1) + chars.charAt(g1) + chars.charAt(b1);
				}
				return '#'
				+ chars.charAt(r1) + chars.charAt(r2)
				+ chars.charAt(g1) + chars.charAt(g2)
				+ chars.charAt(b1) + chars.charAt(b2);
			}
			return null;
		}

		this.getInt = function () {

			if (this.success) {
				return ((this.r << 16) + (this.g << 8) + this.b) & 0xffffff;
			}
			return null;
		}

		this.toString = function () {
			return this.getHex();
		}

		this.setColor(color);
	}

	function _supportedBrowser() {
		/* todo: implement a bedder browser detection */

		var ua = navigator.userAgent.toLowerCase();

		if (ua.indexOf("firefox") != -1) {
			return ua.indexOf("3.") != -1;
		} if (ua.indexOf("opera") != -1) {
			return ua.indexOf("10.") != -1;
		} if (ua.indexOf("safari") != -1) {
			return true;
		} if (ua.indexOf("chrome") != -1) {
			return true;
		}
		return false;
	}

	function _fx_setup(fx) {

		fx.end = new xColor(fx.end);

		if (0 == (fx.start.r | fx.start.g | fx.start.b | fx.start.a)) {
			fx.start.r = fx.end.r;
			fx.start.g = fx.end.g;
			fx.start.b = fx.end.b;
		} else if (0 == (fx.end.r | fx.end.g | fx.end.b | fx.end.a)) {
			fx.end.r = fx.start.r;
			fx.end.g = fx.start.g;
			fx.end.b = fx.start.b;
		}
	}

	$.each(['color', 'backgroundColor', 'borderColor', 'borderTopColor', 'borderBottomColor', 'borderLeftColor', 'borderRightColor', 'outlineColor'], function(i, attr) {

		// todo: gradientarray implementation

		if (_supportedBrowser()) {

			$.fx.step[attr] = function(fx) {

				if (0 == fx.state) {
					fx.start = findColor(fx.elem, attr);
					_fx_setup(fx);
				}

				var s = fx.start;
				var e = fx.end;

				if (s.success && e.success) {
					fx.elem.style[attr] = "rgba(" +
					Math.floor(fx.pos * (e.r - s.r) + s.r) + "," +
					Math.floor(fx.pos * (e.g - s.g) + s.g) + "," +
					Math.floor(fx.pos * (e.b - s.b) + s.b) + "," +
					(fx.pos * (e.a - s.a) + s.a) + ")";
				}
			}
		} else {

			$.fx.step[attr] = function(fx) {

				if (0 == fx.state) {
					fx.start = findColor(fx.elem, attr);
					_fx_setup(fx);
				}

				var s = fx.start;
				var e = fx.end;

				if (s.success && e.success) {
					fx.elem.style[attr] = "rgb(" +
					Math.floor(fx.pos * (e.r - s.r) + s.r) + "," +
					Math.floor(fx.pos * (e.g - s.g) + s.g) + "," +
					Math.floor(fx.pos * (e.b - s.b) + s.b) + ")";
				}
			}
		}
	});

	function findColor(elem, attr) {

		var color = "";

		if (_supportedBrowser()) {

			do {
				color = $.curCSS(elem, attr);

				if ("" != color || $.nodeName(elem, "body")) break;

			} while (elem = elem.parentNode);

			if ("" == color) {
				color = "transparent";
			}

		} else {

			do {
				color = $.curCSS(elem, attr);

				if ("" != color && "transparent" != color && "rgba(0, 0, 0, 0)" != color || $.nodeName(elem, "body")) break;

			} while (elem = elem.parentNode);

			if ("" == color) {
				if ("backgroundColor" == attr) {
					color = "white";
				} else {
					color = "black";
				}
			}
		}

		return new xColor(color);
	}

	/**
	* @constructor
	*/
	function xColorMix() {

		this.test = function (col) {

			var c = new xColor(col);

			if (c.success) {
				return c;
			}
			return null;
		}

		this.red = function (col) {

			var c = new xColor(col);

			if (c.success) {
				c.g = 0xff;
				c.b = 0xff;
				return c;
			}
			return null;
		}

		this.blue = function (col) {

			var c = new xColor(col);

			if (c.success) {
				c.r = 0xff;
				c.g = 0xff;
				return c;
			}
			return null;
		}

		this.green = function (col) {

			var c = new xColor(col);

			if (c.success) {
				c.r = 0xff;
				c.b = 0xff;
				return c;
			}
			return null;
		}

		this.random = function () {

			var c = new xColor([
			Math.floor(255 * Math.random()),
			Math.floor(255 * Math.random()),
			Math.floor(255 * Math.random())
			]);

			if (c.success) {
				return c;
			}
			return null;
		}

		this.complementary = function (col) {

			var c = new xColor(col);

			if (c.success) {
				c.r^= 0xff;
				c.g^= 0xff;
				c.b^= 0xff;
				return c;
			}
			return null;
		}

		this.opacity = function (x, y, o) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {

				if (o > 1) {
					o/= 100;
				}

				o = Math.max(o - 1 + b.a, 0);

				a.r = Math.round((b.r - a.r) * o + a.r);
				a.g = Math.round((b.g - a.g) * o + a.g);
				a.b = Math.round((b.b - a.b) * o + a.b);

				return a;
			}
			return null;
		}

		this.greyfilter = function (col, formula) {

			var v, c = new xColor(col);

			if (c.success) {
				switch (formula) {
				case 1:
					// My own formula
					v = .35 + 13 * (c.r + c.g + c.b) / 60;
					break;
				case 2:
					// Sun's formula: (1 - avg) / (100 / 35) + avg)
					v = (13 * (c.r + c.g + c.b) + 5355) / 60;
					break;
					default:
					v = c.r * .3 + c.g * .59 + c.b * .11;
				}
				c.r = c.g = c.b = Math.min(Math.floor(v), 255);

				return c;
			}
			return null;
		}

		this.webround = function (col) {

			var c = new xColor(col);

			if (c.success) {
				if ((c.r+= 0x33 - c.r % 0x33) > 0xff) c.r = 0xff;
				if ((c.g+= 0x33 - c.g % 0x33) > 0xff) c.g = 0xff;
				if ((c.b+= 0x33 - c.b % 0x33) > 0xff) c.b = 0xff;
				return c;
			}
			return null;
		}

		this.distance = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {
				// Approximation attempt of http://www.compuphase.com/cmetric.htm
				return Math.sqrt(3 * (b.r - a.r) * (b.r - a.r) + 4 * (b.g - a.g) * (b.g - a.g) + 2 * (b.b - a.b) * (b.b - a.b));
			}
			return null;
		}

		this.readable = function (bg, col) {

			var a = new xColor(col);
			var b = new xColor(bg);

			if (a.success && b.success) {
				return (
				(b.r - a.r) * (b.r - a.r) +
				(b.g - a.g) * (b.g - a.g) +
				(b.b - a.b) * (b.b - a.b)) > 0x28A4;
			}
			return null;
		}

		this.combine = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {
				a.r^= b.r;
				a.g^= b.g;
				a.b^= b.b;
				return a;
			}
			return null;
		}

		this.breed = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			var mask = 0;

			if (a.success && b.success) {

				for (var i = 0; i < 6; i++) {
					if (Math.random() < .5) {
						mask|= 0x0f << (i << 2);
					}
				}

				a.r = (a.r & ((mask >> 0x10) & 0xff)) | (b.r & (((mask >> 0x10) & 0xff) ^ 0xff));
				a.g = (a.g & ((mask >> 0x08) & 0xff)) | (b.g & (((mask >> 0x08) & 0xff) ^ 0xff));
				a.b = (a.b & ((mask >> 0x00) & 0xff)) | (b.b & (((mask >> 0x00) & 0xff) ^ 0xff));
				return a;
			}
			return null;
		}

		this.additive = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {

				if ((a.r+= b.r) > 0xff) a.r = 0xff;
				if ((a.g+= b.g) > 0xff) a.g = 0xff;
				if ((a.b+= b.b) > 0xff) a.b = 0xff;

				return a;
			}
			return null;
		}

		this.subtractive = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {

				if ((a.r+= b.r - 0xff) < 0) a.r = 0;
				if ((a.g+= b.g - 0xff) < 0) a.g = 0;
				if ((a.b+= b.b - 0xff) < 0) a.b = 0;

				return a;
			}
			return null;
		}

		this.subtract = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {

				if ((a.r-= b.r) < 0) a.r = 0;
				if ((a.g-= b.g) < 0) a.g = 0;
				if ((a.b-= b.b) < 0) a.b = 0;

				return a;
			}
			return null;
		}

		this.multiply = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {
				a.r = Math.floor(a.r / 255 * b.r);
				a.g = Math.floor(a.g / 255 * b.g);
				a.b = Math.floor(a.b / 255 * b.b);
				return a;
			}
			return null;
		}

		this.average = function (x, y) {

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {
				a.r = (a.r + b.r) >> 1;
				a.g = (a.g + b.g) >> 1;
				a.b = (a.b + b.b) >> 1;
				return a;
			}
			return null;
		}

		this.triad = function (col) {

			var c = new xColor(col);

			if (c.success) {

				return [c,
				new xColor([c.b, c.r, c.g]),
				new xColor([c.g, c.b, c.r])];
			}
			return null;
		}

		this.tetrad = function (col) {

			var c = new xColor(col);

			if (c.success) {

				return [c,
				new xColor([c.b, c.r, c.b]),
				new xColor([c.b, c.g, c.r]),
				new xColor([c.r, c.b, c.r])];
			}
			return null;
		}

		this.gradientlevel = function (x, y, level, deg) {

			if (level > deg) return null;

			var a = new xColor(x);
			var b = new xColor(y);

			if (a.success && b.success) {

				a.r = Math.floor(a.r + ((b.r - a.r) / deg) * level);
				a.g = Math.floor(a.g + ((b.g - a.g) / deg) * level);
				a.b = Math.floor(a.b + ((b.b - a.b) / deg) * level);

				return a;
			}
			return null;
		}

		this.gradientarray = function (arr, ndx, size) {

			if (ndx > size) return null;

			var e = Math.floor(ndx * (arr.length - 1) / size);
			var m = (ndx - size * e / (arr.length - 1)) / size;

			var a = new xColor(arr[e]);
			var b = new xColor(arr[e + 1]);

			if (a.success && b.success) {

				a.r = Math.floor(a.r + arr.length * (b.r - a.r) * m);
				a.g = Math.floor(a.g + arr.length * (b.g - a.g) * m);
				a.b = Math.floor(a.b + arr.length * (b.b - a.b) * m);

				return a;
			}
			return null;
		},

		this.nearestname = function (a) {

			a = new xColor(a);

			if (a.success) {
				return a.getName();
			}
			return null;
		}

		this.darken = function (col, by, shade) {

			if (by === undefined) {
				by = 1;
			} else if (by < 0) return this.lighten(col, -by, shade);

			if (shade === undefined) {
				shade = 32;
			}

			var c = new xColor(col);

			if (c.success) {
				if ((c.r-= shade * by) < 0) c.r = 0;
				if ((c.g-= shade * by) < 0) c.g = 0;
				if ((c.b-= shade * by) < 0) c.b = 0;
				return c;
			}
			return null;
		}

		this.lighten = function (col, by, shade) {

			if (by === undefined) {
				by = 1;
			} else if (by < 0) return this.darken(col, -by, shade);

			if (shade === undefined) {
				shade = 32;
			}

			var c = new xColor(col);

			if (c.success) {
				if ((c.r+= shade * by) > 0xff) c.r = 0xff;
				if ((c.g+= shade * by) > 0xff) c.g = 0xff;
				if ((c.b+= shade * by) > 0xff) c.b = 0xff;
				return c;
			}
			return null;
		}

		this.analogous = function (col, results, slices) {

			if (results === undefined) {
				results = 8;
			}

			if (slices === undefined) {
				slices = 30;
			}

			var c = new xColor(col);

			if (c.success) {

				var hsv = c.getHSV();
				var part = 360 / slices, ret = [ c ];

				for (hsv.h = ((hsv.h - (part * results >> 1)) + 720) % 360; --results; ) {
					hsv.h+= part;
					hsv.h%= 360;
					ret.push(new xColor(hsv));
				}
				return ret;
			}
			return null;
		}

		this.splitcomplements = function (col) {

			var c = new xColor(col);

			if (c.success) {

				var hsv = c.getHSV();
				var ret = [ c ];

				hsv.h+= 72;
				hsv.h%= 360;
				ret.push(new xColor(hsv));

				hsv.h+= 144;
				hsv.h%= 360;
				ret.push(new xColor(hsv));

				return ret;
			}
			return null;
		}

		this.monochromatic = function (col, results) {

			if (results === undefined) {
				results = 6;
			}

			var c = new xColor(col);

			if (c.success) {

				var hsv = c.getHSV();
				var ret = [ c ];

				while (--results) {
					hsv.v+= 20;
					hsv.v%= 100;
					ret.push(new xColor(hsv));
				}
				return ret;
			}
			return null;
		}
	}

	$.xcolor = new xColorMix();

	$.fn.isReadable = function () {

		var elem = this[0];
		var f = "";
		var b = "";

		do {

			if ("" == f && ("transparent" == (f = $.curCSS(elem, "color")) || "rgba(0, 0, 0, 0)" == f)) {
				f = "";
			}

			if ("" == b && ("transparent" == (b = $.curCSS(elem, "backgroundColor")) || "rgba(0, 0, 0, 0)" == b)) {
				b = "";
			}

			if ("" != f && "" != b || $.nodeName(elem, "body")) {
				break;
			}

		} while (elem = elem.parentNode);

		if ("" == f) {
			f = "black";
		}

		if ("" == b) {
			b = "white";
		}

		// todo: if alpha != 1, use opacity() to calculate correct color on certain element and it's parent
		return $.xcolor.readable(b, f);
	}

})(jQuery);

