function RGBColor (r,g,b) {
  this.r = r;
  this.g = g;
  this.b = b;
}

function XYZColor (x,y,z) {
  this.x = x;
  this.y = y;
  this.z = z;
}

function YUVColor (y,u,v) {
  this.y = y;
  this.u = u;
  this.v = v;
}

function LUVColor (l,u,v) {
  this.l = l;
  this.u = u;
  this.v = v;
}

function LHCColor (l,h,c) {
  this.l = l;
  this.h = h;
  this.c = c;
}

function LABColor (l,a,b) {
  this.l = l;
  this.a = a;
  this.b = b;
}

function PolarColor (l,h,c) {
  this.l = l;
  this.h = h;
  this.c = c;
}

RGBColor.prototype.toXYZ = function () {
  var x, y, z;
  x = 0.412453 * this.r + 0.357580 * this.g + 0.180423 * this.b;
  y = 0.212671 * this.r + 0.715160 * this.g + 0.072169 * this.b;
  z = 0.019334 * this.r + 0.119193 * this.g + 0.950227 * this.b;
  return new XYZColor(x,y,z);
}

XYZColor.prototype.toRGB = function () {
  var r, g, b;
  r =  3.240479 * this.x - 1.537150 * this.y - 0.498535 * this.z;
  g = -0.969256 * this.x + 1.875992 * this.y + 0.041556 * this.z;
  b =  0.055648 * this.x - 0.204043 * this.y + 1.057311 * this.z;
  return new RGBColor(r,g,b);
}

XYZColor.prototype.toYUV = function () {
  var d;
  d = this.x + 15*this.y + 3*this.z;
  if (d <= 0) d = 1; 
  return new YUVColor(this.y, 4*this.x/d, 9*this.y/d);
}

YUVColor.prototype.toXYZ = function() {
  var d, x, z;
  if (this.v > 0) d = 9*this.y/this.v; else d = 0;
  x = 0.25 * d * this.u;
  z = (d - x - 15*this.y)/3;
  return new XYZColor(x, this.y, z);
}
  
xyzWhitePoint = new RGBColor(1,1,1).toXYZ();
whitePoint = xyzWhitePoint.toYUV();
labE = 216.0/24389.0;
labK = 24389.0/27.0;

LUVColor.prototype.toLHC = function () {
  var l, h, c;
  l = this.l;
  if (l > 0) {
    h = huemod (Math.atan2(this.v, this.u) / Math.PI * 3);
    c = Math.sqrt (this.u * this.u + this.v * this.v) / l;
  } else {
    h = 0;
    c = 0;
  }
  return new LHCColor(l,h,c);
}

LABColor.prototype.toPolar = function () {
  var l, h, c;
  l = this.l;
  if (l > 0) {
    h = huemod (Math.atan2(this.b, this.a) / Math.PI * 3);
    c = Math.sqrt (this.a * this.a + this.b * this.b) / l;
  } else {
    h = 0;
    c = 0;
  }
  return new PolarColor(l,h,c);
}

LHCColor.prototype.toLUV = function () {
  var u, v;
  u = Math.cos(this.h/3*Math.PI)*this.c*this.l;
  v = Math.sin(this.h/3*Math.PI)*this.c*this.l;
  return new LUVColor(this.l, u, v);
}

PolarColor.prototype.toLAB = function () {
  var a, b;;
  a = Math.cos(this.h/3*Math.PI)*this.c*this.l;
  b = Math.sin(this.h/3*Math.PI)*this.c*this.l;
  return new LABColor(this.l, a, b);
}

YUVColor.prototype.toLUV = function () {
  var l, u, v;
  if (this.y > 0.008856) 
    l = 116.0 * Math.exp(Math.log(this.y / whitePoint.y)/3.0) - 16;
  else
    l = 903.29 * this.y;
  u = 13 * l * (this.u - whitePoint.u);
  v = 13 * l * (this.v - whitePoint.v);
  return new LUVColor(l,u,v);
}

LUVColor.prototype.toYUV = function() {
  var y, u, v, l;
  l = this.l;	
  if (l > 8.0) 
    y = Math.exp(Math.log((l + 16.0) / 116.0)*3.0) * whitePoint.y;
  else
    y = l / 903.29;
  l = Math.max(l, 1);
  u = this.u / (13*l) + whitePoint.u;
  v = this.v / (13*l) + whitePoint.v;
  return new YUVColor(y,u,v);
}

LABColor.prototype.toXYZ = function() {
  var x, y, z, fx, fy, fz;
  if (this.l > labK*labE) y = Math.exp(Math.log((this.l+16.0)/116.0)*3.0);
  else y = this.l/labK;
  if (y > labE) fy = (this.l+16.0)/116.0; else fy = (labK*y+16.0)/116.0;
  fx = this.a/500.0 + fy;
  fz = fy - this.b/200.0;
  if (fx*fx*fx > labE) x = fx*fx*fx; else x = (116.0*fx-16.0)/labK;
  if (fz*fz*fz > labE) z = fz*fz*fz; else z = (116.0*fz-16.0)/labK;
  return new XYZColor(x*xyzWhitePoint.x, y*xyzWhitePoint.y, z*xyzWhitePoint.z);
}

XYZColor.prototype.toLAB = function() {
  var x, y, z, fx, fy, fz;
  x = this.x/xyzWhitePoint.x;
  y = this.y/xyzWhitePoint.y;
  z = this.z/xyzWhitePoint.z;
  if (x>labE) fx = Math.exp(Math.log(x)/3.0); else fx = (labK*x+16.0)/116.0;
  if (y>labE) fy = Math.exp(Math.log(y)/3.0); else fy = (labK*y+16.0)/116.0;
  if (z>labE) fz = Math.exp(Math.log(z)/3.0); else fz = (labK*z+16.0)/116.0;
  return new LABColor(116.0*fy - 16.0, 500.0*(fx-fy), 200.0*(fy-fz));
}

LHCColor.prototype.rotatehue = function (rot) {
  return new LHCColor(this.l, huemod(this.h + rot), this.c);
}

PolarColor.prototype.rotatehue = function (rot) {
  return new PolarColor(this.l, huemod(this.h + rot), this.c);
}

RGBColor.prototype.toLHC = function () {
  return this.toXYZ().toYUV().toLUV().toLHC();
}

RGBColor.prototype.toPolar = function () {
  return this.toXYZ().toLAB().toPolar();
}

LHCColor.prototype.toRGB = function () {
  return this.toLUV().toYUV().toXYZ().toRGB();
}

LABColor.prototype.toRGB = function () {
  return this.toXYZ().toRGB();
}

PolarColor.prototype.toRGB = function () {
  return this.toLAB().toXYZ().toRGB();
}

RGBColor.prototype.toLAB = function () {
  return this.toXYZ().toLAB();
}

RGBColor.prototype.rotate = function(rot) {
  return this.toLHC().rotatehue(rot).toRGB();
}

RGBColor.prototype.dim = function(factor) {
  lhc = this.toLHC();
  lhc.l *= factor;
  return lhc.toRGB();
}

RGBColor.prototype.saturate = function(factor) {
  lhc = this.toLHC();
  lhc.c *= factor;
  return lhc.toRGB();
}

RGBColor.prototype.toLUV = function() {
  return this.toXYZ().toLUV();
}

LUVColor.prototype.toRGB = function() {
  return this.toXYZ().toRGB();
}

RGBColor.prototype.edgedistance = function() {
  var mx =         -this.r;
  mx = Math.max(mx, this.r-1);
  mx = Math.max(mx,-this.g);
  mx = Math.max(mx, this.g-1);
  mx = Math.max(mx,-this.b);
  mx = Math.max(mx, this.b-1);
  return mx;
}

RGBColor.prototype.outofgamut = function(prec) {
  return this.edgedistance() > prec;
}

function s255(x) {
  x = Math.round(x*255);
  x = Math.max(x,0);
  x = Math.min(x,255);
  return x;
}

function hexify(v) {
  var s;
  s = '00' + v.toString(16);
  return s.slice(-2);
}

RGBColor.prototype.hex = function () {
  var r, g, b;
  r = s255(this.r);
  g = s255(this.g);
  b = s255(this.b);
  return "#" + hexify(r) + hexify(g) + hexify(b);
}

function hex2rgb (hex) {
  var h1, h2, h3;
  var r, g, b;
  if (hex.substr(0,1) == "(") {
    var t;
    t = hex.substr(1,hex.length-2);
    var a;
    a = t.split(',');
    r = a[0]/255.0;
    g = a[1]/255.0;
    b = a[2]/255.0;
  } else {
    if (hex.substr(0,1) == "#") hex = hex.substr(1);
    r = parseInt(hex.substr(0,2), 16) / 255.0;
    g = parseInt(hex.substr(2,2), 16) / 255.0;
    b = parseInt(hex.substr(4,2), 16) / 255.0;
  }
  return new RGBColor (r,g,b);
}

RGBColor.prototype.fromHex = hex2rgb;

LHCColor.prototype.applyMod = function (lm1, lm2, hm, cm) {
  return new LHCColor( (100-this.l)*lm1 + this.l*lm2,this.h + hm, this.c*cm );
}

function dandc (f, x1, x2, diff) {
  var x;
  while (Math.abs(x2 - x1) > diff) {
    x = (x2 + x1) / 2;
    if (f(x)) x2 = x; else x1 = x;
  }
  return x1;
}

function satcheck (L) {
  return function (x) { 
    var T = new LHCColor(L.l, L.h, x); 
    return T.toRGB().outofgamut(0);
  }
}

function lumcheck (L) {
  return function (x) { 
    var T = new LHCColor(x, L.h, L.c); 
    return T.toRGB().outofgamut(0);
  }
}

LHCColor.prototype.maxsat = function () {
  var maxs;
  maxs = dandc (satcheck(this), 0, 8, 0.001);
  return new LHCColor(this.l, this.h, maxs);
}

LHCColor.prototype.maxlum = function () {
  var maxl;
  maxl = dandc (lumcheck(this), 0, 125, 0.01);
  return new LHCColor(maxl, this.h, this.c);
}

RGBColor.prototype.maxsat = function () {
  return this.toLHC().maxsat().toRGB();
}

RGBColor.prototype.maxlum = function () {
  return this.toLHC().maxlum().toRGB();
}

RGBColor.prototype.rgbmax = function () {
  return Math.max(this.r, Math.max(this.g, this.b));
}

RGBColor.prototype.rgbmin = function () {
  return Math.min(this.r, Math.min(this.g, this.b));
}

function mincheck (L) {
  return function (x) { 
    var T = new LHCColor(x, L.h, L.c).maxsat();
    var X1 = T.toRGB().rgbmax();
    var X2 = T.toRGB().rgbmin();
    return ! (X2 > 0.1/255);
  }
}

function maxcheck (L) {
  return function (x) { 
    var T = new LHCColor(x, L.h, L.c).maxsat();
    var X1 = T.toRGB().rgbmax();
    var X2 = T.toRGB().rgbmin();
    return X1 < 0.995;
  }
}

LHCColor.prototype.minmax = function () {
  var maxl;
  maxl = dandc (mincheck(this), 125, 0, 0.001);
  return new LHCColor(maxl, this.h, this.c).maxsat();
}


function satcheckP (L) {
  return function (x) { 
    var T = new PolarColor(L.l, L.h, x); 
    return T.toRGB().outofgamut(0);
  }
}

function lumcheckP (L) {
  return function (x) { 
    var T = new PolarColor(x, L.h, L.c); 
    return T.toRGB().outofgamut(0);
  }
}

PolarColor.prototype.maxsat = function () {
  var maxs;
  maxs = dandc (satcheckP(this), 0, 8, 0.001);
  return new PolarColor(this.l, this.h, maxs);
}

PolarColor.prototype.maxlum = function () {
  var maxl;
  maxl = dandc (lumcheckP(this), 0, 125, 0.01);
  return new PolarColor(maxl, this.h, this.c);
}

RGBColor.prototype.maxsatP = function () {
  return this.toPolar().maxsatP().toRGB();
}

RGBColor.prototype.maxlumP = function () {
  return this.toPolar().maxlumP().toRGB();
}

function mincheckP (L) {
  return function (x) { 
    var T = new PolarColor(x, L.h, L.c).maxsat();
    var X1 = T.toRGB().rgbmax();
    var X2 = T.toRGB().rgbmin();
    return ! (X2 > 0.01/255);
  }
}

function maxcheckP (L) {
  return function (x) { 
    var T = new PolarColor(x, L.h, L.c).maxsat();
    var X1 = T.toRGB().rgbmax();
    var X2 = T.toRGB().rgbmin();
    return X1 < 1-(0.01/255);
  }
}

PolarColor.prototype.minmax = function () {
  var maxl;
  maxl = dandc (mincheckP(this), 125, 0, 0.001);
  return new PolarColor(maxl, this.h, this.c).maxsat();
}

RGBColor.prototype.toString = RGBColor.prototype.hex;

LHCColor.prototype.toString = function () {
  return 'LHC('+this.l+','+this.h+','+this.c+')';
}

LABColor.prototype.toString = function () {
  return 'Lab('+this.l+','+this.a+','+this.b+')';
}

PolarColor.prototype.toString = function () {
  return 'Polar('+this.l+','+this.h+','+this.c+')';
}

function huemod (hue) {
  while (hue < 0) hue += 6;
  while (hue >= 6) hue -= 6;
  return hue;
}

var huedata = [ ['red',       new RGBColor(1.0,  0.0,  0.0)],
		['coral',     new RGBColor(1.0,  0.25, 0.0)],
		['orange',    new RGBColor(1.0,  0.5,  0.0)],
		['tangerine', new RGBColor(1.0,  0.75, 0.0)],
		['yellow',    new RGBColor(1.0,  1.0,  0.0)],
		['peridot',   new RGBColor(0.75, 1.0,  0.0)],
		['green',     new RGBColor(0.0,  1.0,  0.0)],
		['mint',      new RGBColor(0.0,  1.0,  0.75)],
		['cyan',      new RGBColor(0.0,  1.0,  1.0)],
		['sky',       new RGBColor(0.0,  0.75, 1.0)],
		['azure',     new RGBColor(0.0,  0.5,  1.0)],
		['blue',      new RGBColor(0.0,  0.0,  1.0)],
		['royal',     new RGBColor(0.25, 0.0,  1.0)],
		['violet',    new RGBColor(0.5,  0.0,  1.0)],
		['plum',      new RGBColor(0.75, 0.0,  1.0)],
		['magenta',   new RGBColor(1.0,  0.0,  1.0)],
		['berry',     new RGBColor(1.0,  0.0,  0.75)],
		['pink',      new RGBColor(1.0,  0.0,  0.5)],
		['cherry',    new RGBColor(1.0,  0.0,  0.25)] ];

var hues = [];
var huesP = [];

function huediff (h1, h2) {
  var d1 = Math.abs(h1-h2);
  var d2 = Math.abs(h1-h2+6);
  var d3 = Math.abs(h1-h2-6);
  return Math.min(d1, Math.min(d2,d3));
}

for (var i = 0; i < huedata.length; i++) {
  hues[i] = Array(huedata[i][0], huedata[i][1].toLHC());
  huesP[i] = Array(huedata[i][0], huedata[i][1].toPolar());
}    

LHCColor.prototype.colorname = function() {
  if (this.c == 0) return 'gray';
  var hd1 = Number.POSITIVE_INFINITY, hd2 = Number.POSITIVE_INFINITY;
  var best1, best2;
  var name;
  for (var h = 0; h < hues.length; h++) {
    var hd = huediff(this.h, hues[h][1].h);
    if (hd1 > hd) { best2 = best1; best1 = hues[h][0]; hd1 = hd; }
    if (hd2 > hd && hd1 < hd) { best2 = hues[h][0]; hd2 = hd; }
  }
  name = best1; 
  return name;
 
}

RGBColor.prototype.colorname = function() {
  return this.toLHC().colorname();
}

PolarColor.prototype.colorname = function() {
  if (this.c == 0) return 'gray';
  var hd1 = Number.POSITIVE_INFINITY, hd2 = Number.POSITIVE_INFINITY;
  var best1, best2;
  var name;
  for (var h = 0; h < huesP.length; h++) {
    var hd = huediff(this.h, huesP[h][1].h);
    if (hd1 > hd) { best2 = best1; best1 = huesP[h][0]; hd1 = hd; }
    if (hd2 > hd && hd1 < hd) { best2 = huesP[h][0]; hd2 = hd; }
  }
  name = best1; 
  return name;
 
}

RGBColor.prototype.colornameP = function() {
  return this.toPolar().colorname();
}

LABColor.prototype.blend = function(c2, p) {
  var q = 1 - p;
  return new LABColor(this.l * p + c2.l * q,
		      this.a * p + c2.a * q,
                      this.b * p + c2.b * q);
}
