var RGB2XYZ = function(R, G, B) {
  if (R > 255) {
    console.warn('Red value was higher than 255. It has been set to 255.');
    R = 255;
  } else if (R < 0) {
    console.warn('Red value was smaller than 0. It has been set to 0.');
    R = 0;
  }
  if (G > 255) {
    console.warn('Green value was higher than 255. It has been set to 255.');
    G = 255;
  } else if (G < 0) {
    console.warn('Green value was smaller than 0. It has been set to 0.');
    G = 0;
  }
  if (B > 255) {
    console.warn('Blue value was higher than 255. It has been set to 255.');
    B = 255;
  } else if (B < 0) {
    console.warn('Blue value was smaller than 0. It has been set to 0.');
    B = 0;
  }
  var r = R / 255;
  var g = G / 255;
  var b = B / 255;
  // step 1
  if (r > 0.04045) {
    r = Math.pow(((r + 0.055) / 1.055), 2.4);
  } else {
    r = r / 12.92;
  }
  if (g > 0.04045) {
    g = Math.pow(((g + 0.055) / 1.055), 2.4);
  } else {
    g = g / 12.92;
  }
  if (b > 0.04045) {
    b = Math.pow(((b + 0.055) / 1.055), 2.4);
  } else {
    b = b / 12.92;
  }
  // step 2
  r = r * 100;
  g = g * 100;
  b = b * 100;
  // step 3
  var X = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
  var Y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
  var Z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
  return [X, Y, Z];
}
var XYZ2LAB = function(X, Y, Z) {
  // using 10o Observer (CIE 1964)
  // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
  var ReferenceX = 94.811;
  var ReferenceY = 100;
  var ReferenceZ = 107.304;
  // step 1
  var x = X / ReferenceX;
  var y = Y / ReferenceY;
  var z = Z / ReferenceZ;
  // step 2
  if (x > 0.008856) {
    x = Math.pow(x, (1 / 3));
  } else {
    x = (7.787 * x) + (16 / 116);
  }
  if (y > 0.008856) {
    y = Math.pow(y, (1 / 3));
  } else {
    y = (7.787 * y) + (16 / 116);
  }
  if (z > 0.008856) {
    z = Math.pow(z, (1 / 3));
  } else {
    z = (7.787 * z) + (16 / 116);
  }
  // step 3
  var L = (116 * y) - 16;
  var A = 500 * (x - y);
  var B = 200 * (y - z);
  return [L, A, B];
};
function LAB2XYZ(L, A, B) {
  // using 10o Observer (CIE 1964)
  // CIE10_D65 = {94.811f, 100f, 107.304f} => Daylight
  var ReferenceX = 94.811;
  var ReferenceY = 100;
  var ReferenceZ = 107.304;

  var var_Y = ( L + 16 ) / 116;
  var var_X = A / 500 + var_Y;
  var var_Z = var_Y - B / 200;

  if ( Math.pow(var_Y, 3)  > 0.008856 ) {
    var_Y = Math.pow(var_Y, 3);
  } else {
    var_Y = ( var_Y - 16 / 116 ) / 7.787;
  }
  if ( Math.pow(var_X, 3)  > 0.008856 ) {
    var_X = Math.pow(var_X, 3);
  } else {
    var_X = ( var_X - 16 / 116 ) / 7.787;
  }
  if ( Math.pow(var_Z, 3)  > 0.008856 ) {
    var_Z = Math.pow(var_Z, 3);
  } else {
    var_Z = ( var_Z - 16 / 116 ) / 7.787;
  }

  var X = var_X * ReferenceX;
  var Y = var_Y * ReferenceY;
  var Z = var_Z * ReferenceZ;

  return [X, Y, Z];
}
var XYZ2RGB = function(X, Y, Z) {
  var var_X = X / 100;
  var var_Y = Y / 100;
  var var_Z = Z / 100;

  var var_R = (var_X *  3.2406) + (var_Y * -1.5372) + (var_Z * -0.4986);
  var var_G = (var_X * -0.9689) + (var_Y *  1.8758) + (var_Z *  0.0415);
  var var_B = (var_X *  0.0557) + (var_Y * -0.2040) + (var_Z *  1.0570);

  if ( var_R > 0.0031308 ) {
    var_R = 1.055 * Math.pow(var_R, (1 / 2.4) ) - 0.055;
  } else {
    var_R = 12.92 * var_R;
  }
  if ( var_G > 0.0031308 ) {
    var_G = 1.055 * Math.pow(var_G, (1 / 2.4) ) - 0.055;
  } else {
    var_G = 12.92 * var_G;
  }
  if ( var_B > 0.0031308 ) {
    var_B = 1.055 * Math.pow(var_B, (1 / 2.4) ) - 0.055;
  } else {
    var_B = 12.92 * var_B;
  }

  var R = Math.round(var_R * 255);
  var G = Math.round(var_G * 255);
  var B = Math.round(var_B * 255);

  return [R, G, B];
}
function rgbToLab(r,g,b) {
  return XYZ2LAB.apply(null, RGB2XYZ(r,g,b))
};
function labToRgb(r,g,b) {
  return XYZ2RGB.apply(null, LAB2XYZ(r,g,b))
};
function rgbToHsv(r, g, b) {
  r /= 255, g /= 255, 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 ];
}
var labDistance = function(l1,l2) {
  return DeltaE00(l1,l2);
};
var DeltaE00 = function(lab1, lab2) {
  var l1 = lab1[0], a1 = lab1[1], b1 = lab1[2],
    l2 = lab2[0], a2 = lab2[1], b2 = lab2[2];
  // Utility functions added to Math Object
  Math.rad2deg = function(rad) {
    return 360 * rad / (2 * Math.PI);
  };
  Math.deg2rad = function(deg) {
    return (2 * Math.PI * deg) / 360;
  };
  // Start Equation
  // Equation exist on the following URL http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html
  var avgL = (l1 + l2) / 2;
  var C1 = Math.sqrt(Math.pow(a1, 2) + Math.pow(b1, 2));
  var C2 = Math.sqrt(Math.pow(a2, 2) + Math.pow(b2, 2));
  var avgC = (C1 + C2) / 2;
  var G = (1 - Math.sqrt(Math.pow(avgC, 7) / (Math.pow(avgC, 7) + Math.pow(25, 7)))) / 2;

  var A1p = a1 * (1 + G);
  var A2p = a2 * (1 + G);

  var C1p = Math.sqrt(Math.pow(A1p, 2) + Math.pow(b1, 2));
  var C2p = Math.sqrt(Math.pow(A2p, 2) + Math.pow(b2, 2));

  var avgCp = (C1p + C2p) / 2;

  var h1p = Math.rad2deg(Math.atan2(b1, A1p));
  if (h1p < 0) {
    h1p = h1p + 360;
  }

  var h2p = Math.rad2deg(Math.atan2(b2, A2p));
  if (h2p < 0) {
    h2p = h2p + 360;
  }

  var avghp = Math.abs(h1p - h2p) > 180 ? (h1p + h2p + 360) / 2 : (h1p + h1p) / 2;

  var T = 1 - 0.17 * Math.cos(Math.deg2rad(avghp - 30)) + 0.24 * Math.cos(Math.deg2rad(2 * avghp)) + 0.32 * Math.cos(Math.deg2rad(3 * avghp + 6)) - 0.2 * Math.cos(Math.deg2rad(4 * avghp - 63));

  var deltahp = h2p - h1p;
  if (Math.abs(deltahp) > 180) {
    if (h2p <= h1p) {
      deltahp += 360;
    } else {
      deltahp -= 360;
    }
  }

  var delta_lp = l2 - l1;
  var delta_cp = C2p - C1p;

  deltahp = 2 * Math.sqrt(C1p * C2p) * Math.sin(Math.deg2rad(deltahp) / 2);

  var Sl = 1 + ((0.015 * Math.pow(avgL - 50, 2)) / Math.sqrt(20 + Math.pow(avgL - 50, 2)));
  var Sc = 1 + 0.045 * avgCp;
  var Sh = 1 + 0.015 * avgCp * T;

  var deltaro = 30 * Math.exp(-(Math.pow((avghp - 275) / 25, 2)));
  var Rc = 2 * Math.sqrt(Math.pow(avgCp, 7) / (Math.pow(avgCp, 7) + Math.pow(25, 7)));
  var Rt = -Rc * Math.sin(2 * Math.deg2rad(deltaro));

  var kl = 1;
  var kc = 1;
  var kh = 1;

  var deltaE = Math.sqrt(Math.pow(delta_lp / (kl * Sl), 2) + Math.pow(delta_cp / (kc * Sc), 2) + Math.pow(deltahp / (kh * Sh), 2) + Rt * (delta_cp / (kc * Sc)) * (deltahp / (kh * Sh)));

  return deltaE;
};


/**
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 255].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
 */
function hsvToRgb(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 ];
}

/**
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes r, g, and b are contained in the set [0, 255] and
 * returns h, s, and l in the set [0, 1].
 *
 * @param   Number  r       The red color value
 * @param   Number  g       The green color value
 * @param   Number  b       The blue color value
 * @return  Array           The HSL representation
 */
function rgbToHsl(r, g, b) {
  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 ];
}
function HEX2RGB(hex){
  var color = (hex+'').substr( 1 ) || '',
    r = parseInt( color[ 0 ] + color[ 1 ], 16 ),
    g = parseInt( color[ 2 ] + color[ 3 ], 16 ),
    b = parseInt( color[ 4 ] + color[ 4 ], 16 );
  return [r,g,b];
}