Last active
January 24, 2017 05:59
-
-
Save loganzartman/377399df1c89d201acc2 to your computer and use it in GitHub Desktop.
A standalone library for working with color in Javascript.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Represents a color that can be expressed as an RGB value. | |
| * RGB values are in the range 0 ~ 255 (rounded for display). | |
| * HSL values are in the range 0 ~ 1. | |
| * A color can be constructed using any of the following: | |
| * - No arguments; will be interpreted as black | |
| * - A single integer representing a packed RGB value | |
| * - Three integers or floats representing RGB or HSL values (depending on Color.DEFAULT_MODE) | |
| * - A hex #NNNNNN color string | |
| * - A canvas2d/CSS rgb(n,n,n) or hsl(n,n%,n%) color string | |
| * - an object in the form {r: n, g: n, b: n} or {h: n, s: n, l: n} | |
| */ | |
| var Color = function(args) { | |
| if (arguments.length === 3) { | |
| switch (Color.DEFAULT_MODE) { | |
| case "RGB": | |
| this._setRGB(arguments[0], arguments[1], arguments[2]); | |
| break; | |
| case "HSL": | |
| this._setHSL(arguments[0], arguments[1], arguments[2]); | |
| break; | |
| default: | |
| throw new Error("Unsupported Color.DEFAULT_MODE, use 'HSL' or 'RGB'."); | |
| } | |
| } | |
| else if (!args) { | |
| this._setRGB(0,0,0); | |
| } | |
| else if (typeof args === "number") { | |
| this._setRGB((args >> 16) & 255, (args >> 8) & 255, args & 255); | |
| } | |
| else if (typeof args === "string") { | |
| args = args.toLowerCase(); | |
| if (args.indexOf("#") === 0) { | |
| var r,g,b; | |
| if (args.length === 7) { | |
| r = parseInt(args.substring(1,3),16); | |
| g = parseInt(args.substring(3,5),16); | |
| b = parseInt(args.substring(5,7),16); | |
| } | |
| else { | |
| throw new Error("Invalid string! Use #NNNNNN"); | |
| } | |
| this._setRGB(r,g,b); | |
| } | |
| else { | |
| var components = args.substring(4, args.length-1).split(","); | |
| if (args.indexOf("rgb") === 0) { | |
| components = components.map(function(str){return parseInt(str) || 0;}); | |
| this._setRGB(components[0], components[1], components[2]); | |
| } | |
| else if (args.indexOf("hsl") === 0) { | |
| components[1] = components[1].substring(0,components[1].length-1); | |
| components[2] = components[2].substring(0,components[2].length-1); | |
| components = components.map(function(str){return parseInt(str) || 0;}); | |
| this._setHSL(components[0]/360, components[1]/100, components[2]/100); | |
| } | |
| else { | |
| throw new Error("Invalid string! Use rgb(n,n,n) or hsl(n,n,n)"); | |
| } | |
| } | |
| } | |
| else if (typeof args === "object") { | |
| var r = typeof args.r !== "undefined" ? args.r : 0, | |
| g = typeof args.g !== "undefined" ? args.g : 0, | |
| b = typeof args.b !== "undefined" ? args.b : 0; | |
| var h = typeof args.h !== "undefined" ? args.h : 0, | |
| s = typeof args.s !== "undefined" ? args.s : 0, | |
| l = typeof args.l !== "undefined" ? args.l : 0; | |
| var a = typeof args.a !== "undefined" ? args.a : 1; | |
| if (r) this._setRGB(r,g,b,a); | |
| else this._setHSL(h,s,l,a); | |
| } | |
| else { | |
| throw new Error("Invalid color constructor!"); | |
| } | |
| Object.freeze(this); | |
| }; | |
| /** | |
| * Determines what color format should be assumed for the three-argument constructor. | |
| * Valid values are "RGB" or "HSL". | |
| */ | |
| Color.DEFAULT_MODE = "RGB"; | |
| /** | |
| * Returns a new Color with a modified alpha value. | |
| * @return {Color} | |
| */ | |
| Color.prototype.setAlpha = function(a) { | |
| return new Color({r: this.r, g: this.g, b: this.b, a: a}); | |
| }; | |
| /** | |
| * Returns a new Color with a modified alpha value. | |
| * @return {Color} | |
| */ | |
| Color.prototype.addAlpha = function(a) { | |
| return new Color({r: this.r, g: this.g, b: this.b, a: this.a + a}); | |
| }; | |
| /** | |
| * Returns a new Color that is the result of adding given RGB values. | |
| * @return {Color} | |
| */ | |
| Color.prototype.addRGB = function(r,g,b) { | |
| return new Color({ | |
| r: this.r + r, | |
| g: this.g + g, | |
| b: this.b + b | |
| }); | |
| }; | |
| /** | |
| * Returns a new Color that is the result of adding given HSL values. | |
| * @return {Color} | |
| */ | |
| Color.prototype.addHSL = function(h,s,l) { | |
| return new Color({ | |
| h: this.h + h, | |
| s: this.s + s, | |
| l: this.l + l | |
| }); | |
| }; | |
| /** | |
| * Used in constructor to mutate color values. | |
| * This function should be avoided to preserve immutability. | |
| * @param {Number} r | |
| * @param {Number} g | |
| * @param {Number} b | |
| * @param {Number} a | |
| */ | |
| Color.prototype._setRGB = function(r,g,b,a) { | |
| if (typeof a === "undefined") a = 255; | |
| var hsl = Color.rgbToHsl(r,g,b); | |
| this.r = Color.clipRGB(r); | |
| this.g = Color.clipRGB(g); | |
| this.b = Color.clipRGB(b); | |
| this.h = Color.clipHSL(hsl[0]%1); | |
| this.s = Color.clipHSL(hsl[1]); | |
| this.l = Color.clipHSL(hsl[2]); | |
| this.a = Color.clipRGB(a); | |
| return this; | |
| }; | |
| /** | |
| * Used in constructor to mutate color values. | |
| * This function should be avoided to preserve immutability. | |
| * @param {Number} h | |
| * @param {Number} s | |
| * @param {Number} l | |
| * @param {Number} a | |
| */ | |
| Color.prototype._setHSL = function(h,s,l,a) { | |
| if (typeof a === "undefined") a = 255; | |
| var rgb = Color.hslToRgb(h,s,l); | |
| this.r = Color.clipRGB(rgb[0]); | |
| this.g = Color.clipRGB(rgb[1]); | |
| this.b = Color.clipRGB(rgb[2]); | |
| this.h = Color.clipHSL(h%1); | |
| this.s = Color.clipHSL(s); | |
| this.l = Color.clipHSL(l); | |
| this.a = Color.clipRGB(a); | |
| return this; | |
| }; | |
| /** | |
| * @param {Number} h | |
| * @param {Number} s | |
| * @param {Number} l | |
| * @param {Number} a | |
| */ | |
| Color.prototype.setHSL = function(h,s,l,a) { | |
| return new Color({ | |
| h: typeof h !== "number" ? this.h : h, | |
| s: typeof s !== "number" ? this.s : s, | |
| l: typeof l !== "number" ? this.l : l, | |
| a: typeof a !== "number" ? this.a : a, | |
| }); | |
| }; | |
| /** | |
| * Returns a new Color that complementary to this one. | |
| * @return {Color} | |
| */ | |
| Color.prototype.complementary = function() { | |
| return new Color({ | |
| h: this.h + 0.5, | |
| s: this.s, | |
| l: this.l | |
| }); | |
| }; | |
| /** | |
| * Returns an array of two Colors that are analogous to this one. | |
| * By default, these colors are shifted by 1/12 of the color wheel. | |
| * @param {Number} distance the distance to shift from the original color | |
| * @return {Color[]} | |
| */ | |
| Color.prototype.analogous = function(distance) { | |
| distance = distance || 1/12; | |
| var col = this; | |
| return [new Color(), new Color()].map(function(color){ | |
| distance = -distance; | |
| return color.setHSL( | |
| col.h + distance, | |
| col.s, | |
| col.l | |
| ); | |
| }); | |
| }; | |
| /** | |
| * Returns an array of Colors that are a triad, including this one. | |
| * @return {Color[]} | |
| */ | |
| Color.prototype.triad = function() { | |
| return this.nSet(3); | |
| }; | |
| /** | |
| * Returns an array of Colors that are a tetrad, including this one. | |
| * @return {Color[]} | |
| */ | |
| Color.prototype.tetrad = function() { | |
| return this.nSet(4); | |
| }; | |
| /** | |
| * Returns an array of Color that are evenly spaced around the color wheel, including this one. | |
| * @param {Number} n the number of Colors | |
| * @return {Color[]} | |
| */ | |
| Color.prototype.nSet = function(n) { | |
| var colors = []; | |
| for (var i=0; i<n; i++) { | |
| colors[i] = new Color({ | |
| h: this.h + i * (1/n), | |
| s: this.s, | |
| l: this.l | |
| }); | |
| } | |
| return colors; | |
| }; | |
| /** | |
| * Returns the result of mixing this Color with a given Color in a given ratio. | |
| * This performs mixing using the HSL color model. | |
| * @param {Color} color the Color to mix in | |
| * @param {Number} f the ratio of this Color to the mix-in Color | |
| * @return {Color} | |
| */ | |
| Color.prototype.mix = function(color, f) { | |
| var h1 = this.h, h2 = color.h, h; | |
| var d = h2 - h1; | |
| if (h1 > h2) { | |
| var temp = h1; | |
| h1 = h2; | |
| h2 = temp; | |
| d = -d; | |
| f = 1 - f; | |
| } | |
| if (d > 0.5) { | |
| h1 = h1 + 1; | |
| h = ( h1 + f * (h2 - h1) ) % 1; | |
| } | |
| if (d <= 0.5) { | |
| h = h1 + f * d; | |
| } | |
| return new Color({ | |
| h: h, | |
| s: this.s * f + color.s * (1-f), | |
| l: this.l * f + color.l * (1-f) | |
| }); | |
| }; | |
| /** | |
| * Returns the result of mixing this Color with a given Color in a given ratio. | |
| * This performs mixing using the RGB color model. | |
| * @param {Color} color the Color to mix in | |
| * @param {Number} f the ratio of this Color to the mix-in Color | |
| * @return {Color} | |
| */ | |
| Color.prototype.mixRGB = function(color, f) { | |
| return new Color({ | |
| r: this.r*(1-f) + color.r*f, | |
| g: this.g*(1-f) + color.g*f, | |
| b: this.b*(1-f) + color.b*f, | |
| a: this.a*(1-f) + color.a*f | |
| }); | |
| }; | |
| /** | |
| * Returns a String representation of this Color in rgba(n,n,n,n) format. | |
| * @return {String} | |
| */ | |
| Color.prototype.toRGBString = function () { | |
| return "rgba(" + Math.round(this.r) + "," + | |
| Math.round(this.g) + "," + | |
| Math.round(this.b) + "," + | |
| this.a + ")"; | |
| }; | |
| /** | |
| * Returns the packed integer RGB representation (e.g. 0xFF0000) of this Color. | |
| * @return {Number} | |
| */ | |
| Color.prototype.toHex = function() { | |
| return (Math.floor(this.r) << 16) + (Math.floor(this.g) << 8) + Math.floor(this.b); | |
| }; | |
| /** | |
| * Returns the packed integer RGB representation (e.g. 0xFF0000FF) of this Color. | |
| * This method includes the alpha value in the bytes of lowest significance. | |
| * @return {Number} | |
| */ | |
| Color.prototype.toHexAlpha = function() { | |
| return (Math.floor(this.r) << 24) + (Math.floor(this.g) << 16) + (Math.floor(this.b) << 8) + Math.floor(this.a); | |
| }; | |
| /** | |
| * Returns a String representation of this Color in hsla(n,n%,n%,n) format. | |
| * @return {String} | |
| */ | |
| Color.prototype.toHSLString = function () { | |
| return "hsla(" + Math.round(this.h*360) + "," + | |
| Math.round(this.s*100) + "%," + | |
| Math.round(this.l*100) + "%," + | |
| this.a + ")"; | |
| }; | |
| /** | |
| * Returns a String representation of this Color in rgb(n,n,n) format. | |
| * @return {String} | |
| */ | |
| Color.prototype.toString = function() { | |
| return this.toRGBString(); | |
| }; | |
| /** | |
| * Logs this color to the console as a color swatch. (browser-dependent) | |
| */ | |
| Color.prototype.log = function() { | |
| console.log("%c ", "background: rgb("+(~~(this.r))+","+(~~(this.g))+","+(~~(this.b))+"); font-size: 30px;"); | |
| }; | |
| /** | |
| * Converts an HSL color value to RGB. Conversion formula | |
| * adapted from http://en.wikipedia.org/wiki/HSL_color_space. | |
| * Assumes h, s, and l 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} l The lightness | |
| * @return {Array} The RGB representation | |
| */ | |
| Color.hslToRgb = function(h, s, l){ | |
| var r, g, b; | |
| if(s === 0){ | |
| r = g = b = l; // achromatic | |
| }else{ | |
| var hue2rgb = function hue2rgb(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; | |
| }; | |
| var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
| var p = 2 * l - q; | |
| r = hue2rgb(p, q, h + 1/3); | |
| g = hue2rgb(p, q, h); | |
| b = hue2rgb(p, q, h - 1/3); | |
| } | |
| return [Math.round(r * 255), Math.round(g * 255), Math.round(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 | |
| */ | |
| Color.rgbToHsl = function(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]; | |
| }; | |
| /** | |
| * Mixes a series of colors as a gradient. | |
| * As of right now, the stops are evenly-spaced. | |
| * You could modify this function, or weight the stops elsewhere. | |
| * | |
| * @param {Color[]} colors an array of stop colors | |
| * @param {Number} f [0-1] position in the gradient | |
| * @param {Boolean} b Whether or not to use RGB mixing instead of HSL | |
| * @return {Color} The mixed output | |
| */ | |
| Color.gradientMix = function(colors, f, mixRGB) { | |
| f = Math.abs(f%1); | |
| var len = colors.length-1; | |
| var c0 = Math.floor(f*len), | |
| c1 = Math.ceil(f*len); | |
| f = len*f-c0; | |
| if (mixRGB) return colors[c0].mixRGB(colors[c1], f); | |
| return colors[c0].mix(colors[c1], f); | |
| }; | |
| /** | |
| * Clips a value to the range [0, 255] | |
| * @return {Number} | |
| */ | |
| Color.clipRGB = function(v) {return v<0?0:v>255?255:v;}; | |
| /** | |
| * Clips a value to the range [0, 1] | |
| * @return {Number} | |
| */ | |
| Color.clipHSL = function(v) {return v<0?0:v>1?1:v;}; | |
| //Useful color constants | |
| Color.BLACK = new Color(0x000000); | |
| Color.WHITE = new Color(0xFFFFFF); | |
| Color.GRAY = Color.GREY = new Color({r:255/2,g:255/2,b:255/2}); | |
| Color.PRIMARIES = [ | |
| new Color({h: 60/360, s: 1, l: 0.5}), | |
| new Color({h: 180/360, s: 1, l: 0.5}), | |
| new Color({h: 300/360, s: 1, l: 0.5}) | |
| ]; | |
| Color.RGBS = [ | |
| new Color(0xFF0000), | |
| new Color(0x00FF00), | |
| new Color(0x0000FF) | |
| ]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment