Skip to content

Instantly share code, notes, and snippets.

@neolitec
Created November 7, 2011 10:19
Show Gist options
  • Save neolitec/1344610 to your computer and use it in GitHub Desktop.
Save neolitec/1344610 to your computer and use it in GitHub Desktop.
Javascript Color Class
var Color = function() {
this.r = this.g = this.b = 0;
this.h = this.s = this.l = 0;
this.a = 1;
};
/** RGB */
Color.prototype.cssRGB = function() {
return "rgb("+Math.round(255*this.r)+","+Math.round(255*this.g)+","+Math.round(255*this.b)+")";
};
Color.prototype.cssRGBA = function() {
return "rgba("+Math.round(255*this.r)+","+Math.round(255*this.g)+","+Math.round(255*this.b)+","+Math.round(this.a)+")";
};
Color.prototype.red = function() { return this.r; };
Color.prototype.green = function() { return this.g; };
Color.prototype.blue = function() { return this.b; };
/** HSL */
Color.prototype.cssHSL = function() {
return "hsl("+Math.round(360*this.h)+","+Math.round(100*this.s)+"%,"+Math.round(100*this.l)+"%)";
};
Color.prototype.cssHSLA = function() {
return "hsla("+Math.round(360*this.h)+","+Math.round(100*this.s)+"%,"+Math.round(100*this.l)+"%,"+Math.round(this.a)+")";
};
Color.prototype.hue = function() { return this.h; };
Color.prototype.saturation = function() { return this.s; };
Color.prototype.lightness = function() { return this.l; };
/** HEX */
Color.prototype.cssHEX = function() {
return "#" +
(255*this.r < 16 ? "0" : "") + Math.round(255*this.r).toString(16) +
(255*this.g < 16 ? "0" : "") + Math.round(255*this.g).toString(16) +
(255*this.b < 16 ? "0" : "") + Math.round(255*this.b).toString(16);
}
/** Transparency */
Color.prototype.alpha = function() { return this.a; };
/** Modifiers */
Color.prototype.saturate = function(v) {
if("string" == typeof v && v.indexOf("%") > -1 && (v = parseInt(v)) != 'NaN')
this.s += v/100;
else if("number" == typeof v) // range 255
this.s += v/255;
else throw new Error("error: bad modifier format (percent or number)");
if(this.s > 1) this.s = 1; else if(this.s < 0) this.s = 0;
Color.Convertor.HSLToRGB.apply(this);
};
Color.prototype.desaturate = function(v) {
this.saturate("-" + v);
};
Color.prototype.lighten = function(v) {
if("string" == typeof v && v.indexOf("%") > -1 && (v = parseInt(v)) != 'NaN')
this.l += v/100;
else if("number" == typeof v) // range 255
this.l += v/255;
else throw new Error("error: bad modifier format (percent or number)");
if(this.l > 1) this.l = 1; else if(this.l < 0) this.l = 0;
Color.Convertor.HSLToRGB.apply(this);
};
Color.prototype.darken = function(v) {
this.ligthen("-" + v);
};
Color.prototype.fadein = function(v) {
if("string" == typeof v && v.indexOf("%") > -1 && (v = parseInt(v)) != 'NaN')
this.a += v/100;
else if("number" == typeof v) // range 255
this.a += v/255;
else throw new Error("error: bad modifier format (percent or number)");
if(this.a > 1) this.a = 1; else if(this.a < 0) this.a = 0;
Color.Convertor.HSLToRGB.apply(this);
};
Color.prototype.fadeout = function(v) {
this.fadein("-" + v);
};
Color.prototype.spin = function(v) {
if("string" == typeof v && v.indexOf("%") > -1 && (v = parseInt(v)) != 'NaN')
this.h += v/100;
else if("number" == typeof v) // range 360
this.h += v/360;
else throw new Error("error: bad modifier format (percent or number)");
if(this.h > 1) this.h = 1; else if(this.h < 0) this.h = 0;
Color.Convertor.HSLToRGB.apply(this);
};
/** Debug */
Color.prototype.toString = function() {
return "<span style=\"color: "+this.cssRGB()+"\">"+this.cssRGB()+"</span> / <span style=\"color: "+this.cssHSL()+"\">"+this.cssHSL()+"</span> / <span style=\"color: "+this.cssHEX()+"\">"+this.cssHEX()+"</span> / alpha: "+this.a+"";
};
Color.makeRGB = function() {
var c = new Color(),
sanitized;
if(arguments.length < 3 || arguments.length > 4)
throw new Error("error: 3 or 4 arguments");
sanitized = Color.Sanitizer.RGB(arguments[0], arguments[1], arguments[2]);
c.r = sanitized[0];
c.g = sanitized[1];
c.b = sanitized[2];
if(arguments.length == 4) c.a = arguments[3];
Color.Convertor.RGBToHSL.apply(c);
return c;
};
Color.makeHSL = function() {
var c = new Color(),
sanitized;
if(arguments.length < 3 || arguments.length > 4)
throw new Error("error: 3 or 4 arguments");
sanitized = Color.Sanitizer.HSL(arguments[0], arguments[1], arguments[2]);
c.h = sanitized[0];
c.s = sanitized[1];
c.l = sanitized[2];
if(arguments.length == 4) c.a = arguments[3];
Color.Convertor.HSLToRGB.apply(c);
return c;
};
Color.makeHEX = function(value) {
var c = new Color(),
sanitized;
Color.Validator.checkHEX(value);
if(value.length == 3) {
sanitized = Color.Sanitizer.RGB(
parseInt(value.substr(0, 1) + value.substr(0, 1), 16),
parseInt(value.substr(1, 1) + value.substr(1, 1), 16),
parseInt(value.substr(2, 1) + value.substr(2, 1), 16)
);
} else if(value.length == 6) {
sanitized = Color.Sanitizer.RGB(
parseInt(value.substr(0, 2), 16),
parseInt(value.substr(2, 2), 16),
parseInt(value.substr(4, 2), 16)
);
} else throw new Error("error: 3 or 6 arguments");
c.r = sanitized[0];
c.g = sanitized[1];
c.b = sanitized[2];
Color.Convertor.RGBToHSL.apply(c);
return c;
};
Color.Sanitizer = {
RGB: function() {
var o = [];
if(arguments.length == 0) return;
for(var i = 0 ; i < arguments.length ; i++) {
var c = arguments[i];
if("string" == typeof c && c.indexOf("%") > -1) {
if((c = parseInt(c)) == 'NaN')
throw new Error("Bad format");
if(c < 0 || c > 100)
throw new Error("Bad format");
o[i] = c/100;
} else {
if("string" == typeof c && (c = parseInt(c)) == 'NaN') throw new Error("Bad format");
if(c < 0) throw new Error("Bad format");
else if(c >= 0 && c < 1) o[i] = c;
else if(c >= 1 && c < 256) o[i] = c/255;
else throw new Error("Bad format");
}
}
return o;
},
HSL: function() {
if(arguments.length < 3 || arguments.length > 4) throw new Error("3 or 4 arguments required");
var h = arguments[0],
s = arguments[1],
l = arguments[2];
if("string" == typeof h && (h = parseFloat(h)) == 'NaN') throw new Error("Bad format for hue");
if(h < 0 || h > 360) throw new Error("Hue out of range (0..360)");
else if(((""+h).indexOf(".") > -1 && h > 1) || (""+h).indexOf(".") == -1) h /= 360;
if("string" == typeof s && s.indexOf("%") > -1) {
if((s = parseInt(s)) == 'NaN')
throw new Error("Bad format for saturation");
if(s < 0 || s > 100)
throw new Error("Bad format for saturation");
s /= 100;
} else if(s < 0 || s > 1) throw new Error("Bad format for saturation");
if("string" == typeof l && l.indexOf("%") > -1) {
if((l = parseInt(l)) == 'NaN')
throw new Error("Bad format for lightness");
if(l < 0 || l > 100)
throw new Error("Bad format for lightness");
l /= 100;
} else if(l < 0 || l > 1) throw new Error("Bad format for lightness");
return [h, s, l];
},
};
Color.Validator = {
/**
* Check a hexa color (without #)
*/
checkHEX: function(value) {
if(value.length != 6)
throw new Error("Hexa color: bad length");
value = value.toLowerCase();
for(i in value) {
var c = value.charCodeAt(i);
if( !((c>=49 && c<=57) || (c>=97 && c<=102)) )
throw new Error("Hexa color: out of range for " + value + " at position " + i);
}
},
};
Color.Convertor = {
/**
* Calculates HSL Color
* RGB must be normalized
* Must be executed in a Color object context
* http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
*/
RGBToHSL: function() {
//
var r = this.r,
g = this.g,
b = this.b,
max = Math.max(r, g, b), min = Math.min(r, g, b);
this.l = (max + min) / 2;
if(max == min){
this.h = this.s = 0; // achromatic
} else {
var d = max - min;
this.s = this.l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: this.h = (g - b) / d + (g < b ? 6 : 0); break;
case g: this.h = (b - r) / d + 2; break;
case b: this.h = (r - g) / d + 4; break;
}
this.h /= 6;
}
},
/**
* Calculates RGB color (nomalized)
* HSL must be normalized
* Must be executed in a Color object context
* http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
*/
HSLToRGB: function() {
var h = this.h,
s = this.s,
l = this.l,
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;
};
if(s == 0) {
this.r = this.g = this.b = l; // achromatic
} else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
this.r = hue2rgb(p, q, h + 1/3);
this.g = hue2rgb(p, q, h);
this.b = hue2rgb(p, q, h - 1/3);
}
},
};
@IkarosKappler
Copy link

In my experience different people have different preferences. Trying an OO approach with mutable values here. In most cases the color is parsed by some input or config string anyway, but feel free to modify the constructor.

And comparing colors ... sounds very fiddly.

@neolitec
Copy link
Author

neolitec commented Sep 10, 2024

Wow, I'm glad this can still be useful after several years 😁
@IkarosKappler do you still use it in your projects or did you change for another library (chroma.js, ...)? I'm curious weather publishing a small npm package could be interesting or not...

@IkarosKappler
Copy link

Hi @neolitec , your color class was exactly what I was looking for some years ago: simple and small. I am still using it in a fun project and adapted it a bit to my needs.
https://github.com/IkarosKappler/plotboilerplate/blob/main/src/ts/utils/datastructures/Color.ts

I like the idea of publishing an npm package. Go ahead :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment