├── index.js ├── lib ├── validators.js ├── util.js ├── manipulations.js ├── colour.js └── converters.js ├── .gitignore ├── package.json ├── LICENSE ├── README.md └── test └── colourSpec.js /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Module entry point 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | module.exports = require('./lib/colour'); -------------------------------------------------------------------------------- /lib/validators.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Validator functions 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | module.exports = { 9 | 10 | /* 11 | * Check if colour is grayscale 12 | * 13 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 14 | * @return {Boolean} Is colour grayscale? 15 | */ 16 | isGrayscale: function(colour) { 17 | 18 | var s = colour[1]; 19 | 20 | return s === 0; 21 | 22 | } 23 | 24 | }; -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Util functions 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | module.exports = { 9 | /* 10 | * Scale function 11 | * 12 | * @param {Number} val The value to be scaled 13 | * @param {Array} inputRange The current range that the value sits within 14 | * @param {Array} outputRange The range to be scaled to 15 | * @return {Number} The scaled value 16 | */ 17 | scale: function(val, inputRange, outputRange) { 18 | return ( val - inputRange[ 0 ] ) * ( outputRange[ 1 ] - outputRange[ 0 ] ) / ( inputRange[ 1 ] - inputRange[ 0 ] ) + outputRange[ 0 ]; 19 | } 20 | 21 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 29 | node_modules 30 | 31 | # Webstorm IDE 32 | .idea 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colourjs", 3 | "version": "0.1.2", 4 | "description": "A library that provides a convenient interface to manipulate colours in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/colourSpec.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/d4ncer/colour.git" 12 | }, 13 | "keywords": [ 14 | "javascript", 15 | "colour", 16 | "color" 17 | ], 18 | "author": "Raghuvir Kasturi", 19 | "licenses": [ 20 | { 21 | "type": "MIT", 22 | "url": "https://github.com/d4ncer/colour/blob/master/LICENSE" 23 | } 24 | ], 25 | "bugs": { 26 | "url": "https://github.com/d4ncer/colour/issues" 27 | }, 28 | "homepage": "https://github.com/d4ncer/colour", 29 | "devDependencies": { 30 | "chai": "^1.10.0", 31 | "mocha": "^2.1.0" 32 | }, 33 | "dependencies": { 34 | "underscore": "^1.7.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Raghuvir Kasturi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Colour.js](http://i.imgur.com/UImGik3.png) 2 | 3 | A lightweight JavaScript library for creating and manipulating colours. 4 | 5 | --- 6 | 7 | ## Installation 8 | 9 | Colour depends on Underscore.js for right now, but there are plans to remove it in a future release. 10 | 11 | Install via npm 12 | 13 | ``` 14 | $ npm install colourjs 15 | ``` 16 | 17 | Require & use in your project 18 | 19 | ```javascript 20 | var Colour = require('colour'); 21 | 22 | var colourHex = new Colour('#e27a3f', { alpha: 0.7 }); 23 | var colourRGB = new Colour({ type: 'RGB', value: [226, 122, 63] }, { alpha: 0.7 }); 24 | var colourHSL = new Colour({ type: 'HSL', value: [75, 21.9, 65] }, { alpha: 0.7 }); 25 | 26 | // Conversions 27 | colourRGB.toHex(); // '#e27a3f' 28 | colourHex.toRGBa(); // [226, 122, 63, 0.7] 29 | colourHSL.toHSV(); // [74, 21.1, 72.5] 30 | colourHSL.desaturate(5).toHexa(); // ['#aeb596', 0.7] 31 | 32 | // Manipulations 33 | colourHex.lighten(15).saturate(10).toHSLa(); // [22, 71.7, 83.8, 0.7] 34 | colourHSL.darken().toHSL(); // [75, 21.9, 75] 35 | colourRGB.grayscale().toHSL(); // [22, 0, 88.6] 36 | 37 | // Validators 38 | colourHex.isGrayscale(); // false 39 | colourRGB.grayscale().isColour(); // false 40 | ``` 41 | 42 | ## Tests 43 | 44 | Tests depend on Mocha & Chai. 45 | 46 | ``` 47 | $ npm test 48 | ``` 49 | 50 | ## Contributing 51 | 52 | Fork and submit a PR. Issues and next steps are available [here](https://github.com/d4ncer/colour/issues) 53 | 54 | ## Release History 55 | 56 | * 0.1.0 Initial release 57 | 58 | ## License 59 | 60 | The MIT License (MIT) 61 | 62 | Copyright (c) 2015 Raghuvir Kasturi 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining a copy 65 | of this software and associated documentation files (the "Software"), to deal 66 | in the Software without restriction, including without limitation the rights 67 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 68 | copies of the Software, and to permit persons to whom the Software is 69 | furnished to do so, subject to the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be included in all 72 | copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 75 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 76 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 77 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 78 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 79 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 80 | SOFTWARE. 81 | 82 | --- 83 | 84 | Built during [HackReactor](http://www.hackreactor.com/) 85 | -------------------------------------------------------------------------------- /lib/manipulations.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Manipulation functions 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | module.exports = { 9 | 10 | /* 11 | * Lighten a colour by a set amount. 12 | * 13 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 14 | * @param {Number} value The value to add to the lightness (v: {0,100}). Defaults to 10. 15 | * @return {Array} The lightened colour in HSL 16 | */ 17 | lighten: function(colour, value) { 18 | 19 | value = value || 10; 20 | 21 | var h = colour[0]; 22 | var s = colour[1]; 23 | var l = colour[2]; 24 | var lV = l + value; 25 | 26 | l = lV > 100 ? 100 : lV; 27 | 28 | return [h,s,l]; 29 | 30 | }, 31 | 32 | 33 | /* 34 | * Darken a colour by a set amount. 35 | * 36 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 37 | * @param {Number} value The value to remove from the lightness (v: {0,100}). Defaults to 10. 38 | * @return {Array} The darkened colour in HSL 39 | */ 40 | darken: function(colour, value) { 41 | 42 | value = value || 10; 43 | 44 | var h = colour[0]; 45 | var s = colour[1]; 46 | var l = colour[2]; 47 | var dV = l - value; 48 | 49 | l = dV < 0 ? 0 : dV; 50 | 51 | return [h,s,l]; 52 | }, 53 | 54 | 55 | /* 56 | * Saturate a colour by a set amount. 57 | * 58 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 59 | * @param {Number} value The value to add to the saturation (s: {0,100}). Defaults to 10. 60 | * @return {Array} The saturated colour in HSL 61 | */ 62 | saturate: function(colour, value) { 63 | 64 | value = value || 10; 65 | 66 | var h = colour[0]; 67 | var s = colour[1]; 68 | var l = colour[2]; 69 | var sS = s + value; 70 | 71 | s = sS > 100 ? 100 : sS; 72 | 73 | return [h,s,l]; 74 | }, 75 | 76 | 77 | /* 78 | * Desaturate a colour by a set amount. 79 | * 80 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 81 | * @param {Number} value The value to remove from the saturation (s: {0,100}). Defaults to 10. 82 | * @return {Array} The desaturated colour in HSL 83 | */ 84 | desaturate: function(colour, value) { 85 | 86 | value = value || 10; 87 | 88 | var h = colour[0]; 89 | var s = colour[1]; 90 | var l = colour[2]; 91 | var dS = s - value; 92 | 93 | s = dS < 0 ? 0 : dS; 94 | 95 | return [h,s,l]; 96 | }, 97 | 98 | 99 | /* 100 | * Transforms colour to grayscale 101 | * 102 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 103 | * @return {Array} The grayscale colour in HSL 104 | */ 105 | grayscale: function(colour) { 106 | 107 | var h = colour[0]; 108 | var s = colour[1]; 109 | var l = colour[2]; 110 | 111 | s = s === 0 ? s : 0; 112 | 113 | return [h,s,l]; 114 | } 115 | }; -------------------------------------------------------------------------------- /lib/colour.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Colour constructor & prototype 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | var convert = require('./converters'); 9 | var manipulate = require('./manipulations'); 10 | var validate = require('./validators'); 11 | 12 | /* 13 | * Colour constructor 14 | * 15 | * @param {String, Object} colour The input colour as hex or object 16 | * @param {Object} opts Additional parameters (alpha only at the moment) 17 | * @return {Object} The new Colour instance 18 | */ 19 | var Colour = function(colour, opts) { 20 | 21 | var _tmpRGB; 22 | 23 | colour = colour || { type: 'RGB', value: [0,0,0] }; 24 | opts = opts || { alpha: 1 }; 25 | 26 | this._r = 0; 27 | this._g = 0; 28 | this._b = 0; 29 | this._a = 1; 30 | 31 | if (Array.isArray(colour) || Array.isArray(opts)) { 32 | throw new TypeError('Colour does not take array arguments'); 33 | } 34 | 35 | if (typeof colour === 'string') { 36 | _tmpRGB = convert.hexToRGB(colour); 37 | this._r = _tmpRGB[0]; 38 | this._g = _tmpRGB[1]; 39 | this._b = _tmpRGB[2]; 40 | this._a = opts.alpha; 41 | } 42 | 43 | if (typeof colour === 'object') { 44 | var type = colour.type.toLowerCase(); 45 | 46 | if (type.indexOf('rgb') > -1) { 47 | this._r = colour.value[0]; 48 | this._g = colour.value[1]; 49 | this._b = colour.value[2]; 50 | this._a = opts.alpha; 51 | } 52 | 53 | var convertFn = type + 'ToRGB'; 54 | _tmpRGB = convert[convertFn](colour.value); 55 | this._r = _tmpRGB[0]; 56 | this._g = _tmpRGB[1]; 57 | this._b = _tmpRGB[2]; 58 | this._a = opts.alpha || 1; 59 | } 60 | 61 | }; 62 | 63 | Colour.prototype = { 64 | constructor: Colour, 65 | 66 | /* 67 | * Get/Set alpha 68 | * 69 | * @param {Number} alpha The number to set current alpha/transparency. If not provided, returns existing alpha. 70 | * @return {Number} The alpha/transparency (a: {0,1}) 71 | */ 72 | alpha: function(alpha) { 73 | 74 | if (alpha && typeof alpha === 'number') { 75 | this._a = alpha; 76 | } 77 | 78 | return this._a; 79 | }, 80 | 81 | 82 | /* 83 | * Convert RGB values into an array 84 | * 85 | * @return {Array} The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 86 | */ 87 | arrarize: function() { 88 | return [this._r, this._g, this._b]; 89 | }, 90 | 91 | 92 | /* 93 | * Sets the RGB values of the instance to some new value 94 | * 95 | * @param {Array} colour The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 96 | * @return {Object} The modified Colour instance 97 | */ 98 | objectify: function(colour) { 99 | if (Array.isArray(colour)) { 100 | this._r = colour[0]; 101 | this._g = colour[1]; 102 | this._b = colour[2]; 103 | } 104 | return this; 105 | }, 106 | 107 | 108 | /* 109 | * Convert colour to Hex 110 | * 111 | * @return {String} The Hex representation 112 | */ 113 | toHex: function() { 114 | var colour = this.arrarize(); 115 | return convert.rgbToHex(colour); 116 | }, 117 | 118 | 119 | /* 120 | * Convert colour to Hex with alpha 121 | * 122 | * @return {Array} Array of hex representation and alpha [hex, alpha] 123 | */ 124 | toHexa: function() { 125 | var hex = this.toHex(); 126 | var alpha = this.alpha(); 127 | 128 | return [hex, alpha]; 129 | }, 130 | 131 | 132 | /* 133 | * Convert colour to RGB 134 | * 135 | * @return {Array} The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 136 | */ 137 | toRGB: function() { 138 | return this.arrarize(); 139 | }, 140 | 141 | 142 | /* 143 | * Convert colour to RGB with alpha 144 | * 145 | * @return {Array} The RGBa representation (r: {0,255}, g: {0,255}, b: {0,255}, a: {0,1}) 146 | */ 147 | toRGBa: function() { 148 | var colour = this.arrarize(); 149 | var alpha = this.alpha(); 150 | 151 | colour.push(alpha); 152 | 153 | return colour; 154 | }, 155 | 156 | 157 | /* 158 | * Convert colour to HSL 159 | * 160 | * @return {Array} The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 161 | */ 162 | toHSL: function() { 163 | var colour = this.arrarize(); 164 | 165 | return convert.rgbToHSL(colour); 166 | }, 167 | 168 | 169 | /* 170 | * Convert colour to HSLa 171 | * 172 | * @return {Array} The HSLa representation (h: {0,360}, s: {0,100}, l: {0,100}, a: {0,1}) 173 | */ 174 | toHSLa: function() { 175 | var hsl = this.toHSL(); 176 | var alpha = this.alpha(); 177 | 178 | hsl.push(alpha); 179 | 180 | return hsl; 181 | }, 182 | 183 | 184 | /* 185 | * Convert colour to HSV 186 | * 187 | * @return {Array} The HSV representation (h: {0,360}, s: {0,100}, v: {0,100}) 188 | */ 189 | toHSV: function() { 190 | var colour = this.arrarize(); 191 | 192 | return convert.rgbToHSV(colour); 193 | }, 194 | 195 | 196 | /* 197 | * Convert colour to HSVa 198 | * 199 | * @return {Array} The HSVa representation (h: {0,360}, s: {0,100}, v: {0,100}, a: {0,1}) 200 | */ 201 | toHSVa: function() { 202 | var hsv = this.toHSV(); 203 | var alpha = this.alpha(); 204 | 205 | hsv.push(alpha); 206 | 207 | return hsv; 208 | }, 209 | 210 | 211 | /* 212 | * Lighten colour by value 213 | * 214 | * @param {Number} value The amount to lighten the colour by (value: {0,100}) 215 | * @return {Object} The modified Colour instance 216 | */ 217 | lighten: function(value) { 218 | var hsl = this.toHSL(); 219 | var lC = value ? manipulate.lighten(hsl, value) : manipulate.lighten(hsl); 220 | var rgb = convert.hslToRGB(lC); 221 | 222 | return this.objectify(rgb); 223 | }, 224 | 225 | 226 | /* 227 | * Darken colour by value 228 | * 229 | * @param {Number} value The amount to darken the colour by (value: {0,100}) 230 | * @return {Object} The modified Colour instance 231 | */ 232 | darken: function(value) { 233 | var hsl = this.toHSL(); 234 | var dC = value ? manipulate.darken(hsl, value) : manipulate.darken(hsl); 235 | var rgb = convert.hslToRGB(dC); 236 | 237 | return this.objectify(rgb); 238 | }, 239 | 240 | 241 | /* 242 | * Saturate colour by value 243 | * 244 | * @param {Number} value The amount to saturate the colour by (value: {0,100}) 245 | * @return {Object} The modified Colour instance 246 | */ 247 | saturate: function(value) { 248 | var hsl = this.toHSL(); 249 | var sC = value ? manipulate.saturate(hsl, value) : manipulate.saturate(hsl); 250 | var rgb = convert.hslToRGB(sC); 251 | 252 | return this.objectify(rgb); 253 | }, 254 | 255 | 256 | /* 257 | * Desaturate colour by value 258 | * 259 | * @param {Number} value The amount to desaturate the colour by (value: {0,100}) 260 | * @return {Object} The modified Colour instance 261 | */ 262 | desaturate: function(value) { 263 | var hsl = this.toHSL(); 264 | var dsC = value ? manipulate.desaturate(hsl, value) : manipulate.desaturate(hsl); 265 | var rgb = convert.hslToRGB(dsC); 266 | 267 | return this.objectify(rgb); 268 | }, 269 | 270 | 271 | /* 272 | * Transform colour to grayscale 273 | * 274 | * @return {Object} The modified Colour instance 275 | */ 276 | grayscale: function() { 277 | var hsl = this.toHSL(); 278 | var gC = manipulate.grayscale(hsl); 279 | var rgb = convert.hslToRGB(gC); 280 | 281 | return this.objectify(rgb); 282 | }, 283 | 284 | 285 | /* 286 | * Checks if colour is grayscale 287 | * 288 | * @return {Boolean} Is colour grayscale? 289 | */ 290 | isGrayscale: function() { 291 | var hsl = this.toHSL(); 292 | 293 | return validate.isGrayscale(hsl); 294 | }, 295 | 296 | 297 | /* 298 | * Checks if colour is a colour (not grayscale) 299 | * 300 | * @return {Boolean} Is colour a colour? 301 | */ 302 | isColour: function() { 303 | return !this.isGrayscale(); 304 | } 305 | }; 306 | 307 | module.exports = Colour; -------------------------------------------------------------------------------- /lib/converters.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Converter functions 3 | * 4 | * Author: Raghuvir Kasturi 5 | * Date: 20/01/2015 6 | */ 7 | 8 | var _ = require('underscore'); 9 | var util = require('./util'); 10 | 11 | module.exports = { 12 | 13 | /* 14 | * Converts hex to RGB 15 | * 16 | * @param {String} colour The hexcode representation 17 | * @return {Array} The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 18 | */ 19 | hexToRGB: function(colour) { 20 | 21 | // Return black for invalid hex length 22 | if (colour.length > 7 || colour.length === 5 || colour.length === 1 || colour.length === 2) { 23 | return [0,0,0]; 24 | } 25 | 26 | // Strip the # 27 | if (colour[0] === '#' && (colour.length === 7 || colour.length === 4)) { 28 | if (colour.indexOf('#') > -1) { 29 | colour = colour.slice(1); 30 | } 31 | } 32 | 33 | // Expand shortform hex (#000) 34 | if (colour.length === 3) { 35 | colour = _.reduce(colour.split(''), function(memo, val) { 36 | if (memo.length === 1) { 37 | memo = memo + memo; 38 | } 39 | var double = val + val; 40 | return memo + double; 41 | }); 42 | } 43 | 44 | // Validate 45 | var validNums = _.range(0,10).toString().split(''); 46 | var validChars = 'ABCDEF'.split(''); 47 | var isValid = _.every(colour.split(''), function(val) { 48 | var fVal = val.toString().toUpperCase(); 49 | return (_.contains(validNums, fVal) || _.contains(validChars, fVal)); 50 | }); 51 | 52 | // Convert 53 | if (isValid) { 54 | var full = parseInt(colour, 16); 55 | return [(full >> 16) & 255, (full >> 8) & 255, full & 255]; 56 | } else { 57 | return [0,0,0]; 58 | } 59 | }, 60 | 61 | 62 | /* 63 | * Converts RGB to Hex 64 | * 65 | * @param {Array} colour The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 66 | * @return {String} The Hexcode representation 67 | */ 68 | rgbToHex: function(colour) { 69 | return '#'+colour[0].toString(16)+colour[1].toString(16)+colour[2].toString(16); 70 | }, 71 | 72 | 73 | /* 74 | * Converts HSL to RGB 75 | * 76 | * @param {Array} colour The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 77 | * @return {Array} The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 78 | */ 79 | hslToRGB: function(colour) { 80 | 81 | // Check input 82 | if (!Array.isArray(colour)) { 83 | return [0,0,0]; 84 | } 85 | 86 | var h = colour[0]; 87 | var s = colour[1]; 88 | var l = colour[2]; 89 | 90 | // normalize S & L 91 | var iR = [0,100]; 92 | var oR = [0,1]; 93 | s = util.scale(s, iR, oR); 94 | l = util.scale(l, iR, oR); 95 | 96 | var c = (1 - Math.abs((2*l) - 1)) * s; 97 | var x = c * (1 - Math.abs(((h/60) % 2) - 1)); 98 | var m = l - (c/2); 99 | 100 | var rgbP; 101 | 102 | switch (true) { 103 | case ((0 <= h && h < 60) || h === 360): 104 | rgbP = [c,x,0]; 105 | break; 106 | case (60 <= h && h < 120): 107 | rgbP = [x,c,0]; 108 | break; 109 | case (120 <= h && h < 180): 110 | rgbP = [0,c,x]; 111 | break; 112 | case (180 <= h && h < 240): 113 | rgbP = [0,x,c]; 114 | break; 115 | case (240 <= h && h < 300): 116 | rgbP = [x,0,c]; 117 | break; 118 | case (300 <= h && h < 360): 119 | rgbP = [c,0,x]; 120 | break; 121 | } 122 | 123 | return _.map(rgbP, function(val) { 124 | return Math.round((val + m) * 255); 125 | }); 126 | }, 127 | 128 | 129 | /* 130 | * Converts RGB to HSL 131 | * 132 | * @param {Array} colour The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 133 | * @return {Array} The HSL representation (h: {0,360}, s: {0,100}, l: {0,100}) 134 | */ 135 | 136 | rgbToHSL: function(colour) { 137 | 138 | // Check input 139 | if (!Array.isArray(colour)) { 140 | return [0,0,0]; 141 | } 142 | 143 | // Set up rgb vars 144 | var iR = [0,255]; 145 | var oR = [0,1]; 146 | var r = util.scale(colour[0], iR, oR); 147 | var g = util.scale(colour[1], iR, oR); 148 | var b = util.scale(colour[2], iR, oR); 149 | 150 | var max = Math.max(r,g,b); 151 | var min = Math.min(r,g,b); 152 | 153 | var delta = max - min; 154 | 155 | var h, s, l; 156 | 157 | // Calculate Hue 158 | switch (max) { 159 | case r: 160 | h = Math.round(60 * (((g - b) / delta) % 6)); 161 | break; 162 | case g: 163 | h = Math.round(60 * (((b - r) / delta) + 2)); 164 | break; 165 | case b: 166 | h = Math.round(60 * (((r - g) / delta) + 4)); 167 | break; 168 | } 169 | 170 | // S & L scales 171 | var slInputR = [0,1]; 172 | var slOutputR = [0,100]; 173 | 174 | 175 | // Calculate Lightness 176 | l = util.scale(((max + min) / 2), slInputR, slOutputR); 177 | l = +l.toFixed(1); 178 | 179 | // Calculate Saturation 180 | if (delta === 0) { 181 | s = 0; 182 | } else { 183 | s = util.scale(delta / (1 - Math.abs((2 * ((max + min)/2)) - 1)), slInputR, slOutputR); 184 | s = +s.toFixed(1); 185 | } 186 | 187 | return [h,s,l]; 188 | }, 189 | 190 | 191 | /* 192 | * Converts HSV to RGB 193 | * 194 | * @param {Array} colour The HSV representation (h: {0,360}, s: {0,100}, v: {0,100}) 195 | * @return {Array} The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 196 | */ 197 | hsvToRGB: function(colour) { 198 | // Check input 199 | if (!Array.isArray(colour)) { 200 | return [0,0,0]; 201 | } 202 | 203 | var h = colour[0]; 204 | var s = colour[1]; 205 | var v = colour[2]; 206 | 207 | // normalize S & V 208 | var iR = [0,100]; 209 | var oR = [0,1]; 210 | s = util.scale(s, iR, oR); 211 | v = util.scale(v, iR, oR); 212 | 213 | var c = v * s; 214 | var x = c * (1 - Math.abs(((h/60) % 2) - 1)); 215 | var m = v - c; 216 | 217 | var rgbP; 218 | 219 | switch (true) { 220 | case ((0 <= h && h < 60) || h === 360): 221 | rgbP = [c,x,0]; 222 | break; 223 | case (60 <= h && h < 120): 224 | rgbP = [x,c,0]; 225 | break; 226 | case (120 <= h && h < 180): 227 | rgbP = [0,c,x]; 228 | break; 229 | case (180 <= h && h < 240): 230 | rgbP = [0,x,c]; 231 | break; 232 | case (240 <= h && h < 300): 233 | rgbP = [x,0,c]; 234 | break; 235 | case (300 <= h && h < 360): 236 | rgbP = [c,0,x]; 237 | break; 238 | } 239 | 240 | return _.map(rgbP, function(val) { 241 | return Math.round((val + m) * 255); 242 | }); 243 | }, 244 | 245 | 246 | /* 247 | * Converts RGB to HSV 248 | * 249 | * @param {Array} colour The RGB representation (r: {0,255}, g: {0,255}, b: {0,255}) 250 | * @return {Array} The HSV representation (h: {0,360}, s: {0,100}, v: {0,100}) 251 | */ 252 | 253 | rgbToHSV: function(colour) { 254 | 255 | // Check input 256 | if (!Array.isArray(colour)) { 257 | return [0,0,0]; 258 | } 259 | 260 | // Set up rgb vars 261 | var iR = [0,255]; 262 | var oR = [0,1]; 263 | var r = util.scale(colour[0], iR, oR); 264 | var g = util.scale(colour[1], iR, oR); 265 | var b = util.scale(colour[2], iR, oR); 266 | 267 | var max = Math.max(r,g,b); 268 | var min = Math.min(r,g,b); 269 | 270 | var delta = max - min; 271 | 272 | var h, s, v; 273 | 274 | // Calculate Hue 275 | switch (max) { 276 | case r: 277 | h = Math.round(60 * (((g - b) / delta) % 6)); 278 | break; 279 | case g: 280 | h = Math.round(60 * (((b - r) / delta) + 2)); 281 | break; 282 | case b: 283 | h = Math.round(60 * (((r - g) / delta) + 4)); 284 | break; 285 | } 286 | 287 | // S & L scales 288 | var slInputR = [0,1]; 289 | var slOutputR = [0,100]; 290 | 291 | // Calculate Lightness 292 | v = util.scale(max, slInputR, slOutputR); 293 | v = +v.toFixed(1); 294 | 295 | // Calculate Saturation 296 | if (delta === 0) { 297 | s = 0; 298 | } else { 299 | s = util.scale((delta / max), slInputR, slOutputR); 300 | s = +s.toFixed(1); 301 | } 302 | 303 | return [h,s,v]; 304 | } 305 | }; -------------------------------------------------------------------------------- /test/colourSpec.js: -------------------------------------------------------------------------------- 1 | var mocha = require('mocha'); 2 | var chai = require('chai'); 3 | var Colour = require('../index'); 4 | var convert = require('../lib/converters'); 5 | var manipulate = require('../lib/manipulations'); 6 | chai.should(); 7 | 8 | describe('Create', function() { 9 | it('should not accept array arguments', function() { 10 | (function() { 11 | new Colour([1,2,3]); 12 | }).should.throw(TypeError); 13 | }); 14 | 15 | it('should create a new colour object', function() { 16 | var colour = new Colour('#e27a3f'); 17 | colour.should.be.a('object'); 18 | }); 19 | 20 | it('should have the _r, _g, _b, _a properties', function() { 21 | var colour = new Colour('#e27a3f'); 22 | colour.should.have.property('_r'); 23 | colour.should.have.property('_g'); 24 | colour.should.have.property('_b'); 25 | colour.should.have.property('_a'); 26 | }); 27 | 28 | it('should have the arrarize, objectify, toHex, toHexa, toRGB, toRGBa, toHSL, toHSLa, toHSV, toHSVa methods', function() { 29 | var colour = new Colour('#e27a3f'); 30 | (colour.arrarize).should.be.a('function'); 31 | (colour.objectify).should.be.a('function'); 32 | (colour.toHex).should.be.a('function'); 33 | (colour.toHexa).should.be.a('function'); 34 | (colour.toRGB).should.be.a('function'); 35 | (colour.toRGBa).should.be.a('function'); 36 | (colour.toHSL).should.be.a('function'); 37 | (colour.toHSLa).should.be.a('function'); 38 | (colour.toHSV).should.be.a('function'); 39 | (colour.toHSVa).should.be.a('function'); 40 | }) 41 | }); 42 | 43 | describe('Convert (internal)', function(){ 44 | it('should convert hex to RGB with full hex', function() { 45 | var colour = new Colour('#e27a3f'); 46 | (colour._r).should.equal(226); 47 | (colour._g).should.equal(122); 48 | (colour._b).should.equal(63); 49 | }); 50 | 51 | it('should convert hex to RGB with shortform hex', function() { 52 | var colour = new Colour('a3f'); 53 | (colour._r).should.equal(170); 54 | (colour._g).should.equal(51); 55 | (colour._b).should.equal(255); 56 | }); 57 | 58 | it('should return black for hex with invalid length', function() { 59 | var colour = new Colour('argxbasdfs'); 60 | (colour._r).should.equal(0); 61 | (colour._g).should.equal(0); 62 | (colour._b).should.equal(0); 63 | }); 64 | 65 | it('should return black for hex with invalid value', function() { 66 | var colour = new Colour('#j7za3f'); 67 | (colour._r).should.equal(0); 68 | (colour._g).should.equal(0); 69 | (colour._b).should.equal(0); 70 | }); 71 | 72 | it('should convert RGB to hex', function() { 73 | var hex = convert.rgbToHex([226, 122, 63]); 74 | hex.should.equal('#e27a3f'); 75 | }); 76 | 77 | it('should convert HSL to RGB', function() { 78 | var colour = new Colour({type: 'HSL', value: [359, 50.2, 59.8], alpha: 1}); 79 | (colour._r).should.equal(204); 80 | (colour._g).should.equal(101); 81 | (colour._b).should.equal(103); 82 | }); 83 | 84 | it('should convert HSLa to RGBa', function() { 85 | var colour = new Colour({type: 'HSL', value: [359, 50.2, 59.8]}, { alpha: 0.5 }); 86 | (colour._r).should.equal(204); 87 | (colour._g).should.equal(101); 88 | (colour._b).should.equal(103); 89 | (colour._a).should.equal(0.5); 90 | }); 91 | 92 | it('should convert RGB to HSL', function() { 93 | var hsl = convert.rgbToHSL([226, 122, 63]); 94 | (hsl[0]).should.equal(22); 95 | (hsl[1]).should.equal(73.8); 96 | (hsl[2]).should.equal(56.7); 97 | }); 98 | 99 | it('should convert HSV to RGB', function() { 100 | var colour = new Colour({ type: 'HSV', value: [22, 72.1, 88.6] }); 101 | (colour._r).should.equal(226); 102 | (colour._g).should.equal(123); 103 | (colour._b).should.equal(63); 104 | }); 105 | 106 | it('should convert HSVa to RGBa', function() { 107 | var colour = new Colour({ type: 'HSV', value: [22, 72.1, 88.6] }, { alpha: 0.5 }); 108 | (colour._r).should.equal(226); 109 | (colour._g).should.equal(123); 110 | (colour._b).should.equal(63); 111 | (colour._a).should.equal(0.5); 112 | }); 113 | 114 | it('should convert RGB to HSV', function() { 115 | var hsv = convert.rgbToHSV([226, 123, 63]); 116 | (hsv[0]).should.equal(22); 117 | (hsv[1]).should.equal(72.1); 118 | (hsv[2]).should.equal(88.6); 119 | }); 120 | }); 121 | 122 | describe('Convert (external)', function() { 123 | 124 | var colour; 125 | 126 | beforeEach(function(done) { 127 | colour = new Colour('#e27a3f', { alpha: 0.7 }); 128 | done(); 129 | }); 130 | 131 | it('should convert to RGB', function() { 132 | var rgb = colour.toRGB(); 133 | (rgb[0]).should.equal(226); 134 | (rgb[1]).should.equal(122); 135 | (rgb[2]).should.equal(63); 136 | }); 137 | 138 | it('should convert to RGBa', function() { 139 | var rgba = colour.toRGBa(); 140 | (rgba[0]).should.equal(226); 141 | (rgba[1]).should.equal(122); 142 | (rgba[2]).should.equal(63); 143 | (rgba[3]).should.equal(0.7); 144 | }); 145 | 146 | it('should convert to Hex', function() { 147 | var hex = colour.toHex(); 148 | hex.should.equal('#e27a3f'); 149 | }); 150 | 151 | it('should convert to Hex with alpha', function() { 152 | var hexa = colour.toHexa(); 153 | (hexa[0]).should.equal('#e27a3f'); 154 | (hexa[1]).should.equal(0.7); 155 | }); 156 | 157 | it('should convert to HSL', function() { 158 | var hsl = colour.toHSL(); 159 | (hsl[0]).should.equal(22); 160 | (hsl[1]).should.equal(73.8); 161 | (hsl[2]).should.equal(56.7); 162 | }); 163 | 164 | it('should convert to HSLa', function() { 165 | var hsla = colour.toHSLa(); 166 | (hsla[0]).should.equal(22); 167 | (hsla[1]).should.equal(73.8); 168 | (hsla[2]).should.equal(56.7); 169 | (hsla[3]).should.equal(0.7); 170 | }); 171 | 172 | it('should convert to HSV', function() { 173 | var hsv = colour.toHSV(); 174 | (hsv[0]).should.equal(22); 175 | (hsv[1]).should.equal(72.1); 176 | (hsv[2]).should.equal(88.6); 177 | }); 178 | 179 | it('should convert to HSVa', function() { 180 | var hsva = colour.toHSVa(); 181 | (hsva[0]).should.equal(22); 182 | (hsva[1]).should.equal(72.1); 183 | (hsva[2]).should.equal(88.6); 184 | (hsva[3]).should.equal(0.7); 185 | }); 186 | }); 187 | 188 | describe('Manipulate (internal)', function() { 189 | it('should change the alpha if provided a value', function() { 190 | var colour = new Colour('#e27a3f', { alpha: 1 }); 191 | (colour.alpha()).should.equal(1); 192 | (colour.alpha(0.3)).should.equal(0.3); 193 | }); 194 | 195 | it('should lighten a colour', function() { 196 | var lightened = manipulate.lighten([359, 50.2, 59.8], 15); 197 | (lightened[0]).should.equal(359); 198 | (lightened[1]).should.equal(50.2); 199 | (lightened[2]).should.equal(74.8); 200 | }); 201 | 202 | it('should lighten a colour without value', function() { 203 | var lightened = manipulate.lighten([359, 50.2, 59.8]); 204 | (lightened[0]).should.equal(359); 205 | (lightened[1]).should.equal(50.2); 206 | (lightened[2]).should.equal(69.8); 207 | }); 208 | 209 | it('should darken a colour', function() { 210 | var darkened = manipulate.darken([359, 50.2, 59.8], 15); 211 | (darkened[0]).should.equal(359); 212 | (darkened[1]).should.equal(50.2); 213 | (darkened[2]).should.equal(44.8); 214 | }); 215 | 216 | it('should darken a colour without value', function() { 217 | var darkened = manipulate.darken([359, 50.2, 59.8]); 218 | (darkened[0]).should.equal(359); 219 | (darkened[1]).should.equal(50.2); 220 | (darkened[2]).should.equal(49.8); 221 | }); 222 | 223 | it('should saturate a colour', function() { 224 | var saturated = manipulate.saturate([359, 50.2, 59.8], 15); 225 | (saturated[0]).should.equal(359); 226 | (saturated[1]).should.equal(65.2); 227 | (saturated[2]).should.equal(59.8); 228 | }); 229 | 230 | it('should saturate a colour without value', function() { 231 | var saturated = manipulate.saturate([359, 50.2, 59.8]); 232 | (saturated[0]).should.equal(359); 233 | (saturated[1]).should.equal(60.2); 234 | (saturated[2]).should.equal(59.8); 235 | }); 236 | 237 | it('should desaturate a colour', function() { 238 | var desaturated = manipulate.desaturate([359, 50.2, 59.8], 15); 239 | (desaturated[0]).should.equal(359); 240 | (desaturated[1]).should.equal(35.2); 241 | (desaturated[2]).should.equal(59.8); 242 | }); 243 | 244 | it('should desaturate a colour without value', function() { 245 | var desaturated = manipulate.desaturate([359, 50.2, 59.8]); 246 | (desaturated[0]).should.equal(359); 247 | (desaturated[1]).should.equal(40.2); 248 | (desaturated[2]).should.equal(59.8); 249 | }); 250 | 251 | it('should transform a colour to grayscale', function() { 252 | var grayscale = manipulate.grayscale([100, 50, 70]); 253 | (grayscale[0]).should.equal(100); 254 | (grayscale[1]).should.equal(0); 255 | (grayscale[2]).should.equal(70); 256 | }); 257 | }); 258 | 259 | describe('Manipulate (external)', function() { 260 | var colour; 261 | 262 | beforeEach(function(done) { 263 | colour = new Colour('#e27a3f', { alpha: 0.7 }); 264 | done(); 265 | }); 266 | 267 | it('should return the object for chaining', function() { 268 | var nC = colour.lighten(10); 269 | nC.should.be.an.instanceof(Colour); 270 | }); 271 | 272 | it('should lighten the colour', function() { 273 | var originalLightness = colour.toHSL()[2]; 274 | colour.lighten(); 275 | var newLightness = colour.toHSL()[2]; 276 | newLightness.should.equal(originalLightness+10); 277 | }); 278 | 279 | it('should darken the colour', function() { 280 | var originalLightness = colour.toHSL()[2]; 281 | colour.darken(); 282 | var newLightness = colour.toHSL()[2]; 283 | newLightness.should.equal(originalLightness-10); 284 | }); 285 | 286 | it('should saturate the colour', function() { 287 | var originalSaturation = colour.toHSL()[1]; 288 | colour.saturate(9); 289 | var newSaturation = colour.toHSL()[1]; 290 | newSaturation.should.equal(originalSaturation+9); 291 | }); 292 | 293 | it('should desaturate the colour', function() { 294 | var originalSaturation = colour.toHSL()[1]; 295 | colour.desaturate(); 296 | var newSaturation = colour.toHSL()[1]; 297 | newSaturation.should.equal(originalSaturation-10); 298 | }); 299 | 300 | it('should convert a colour to grayscale', function() { 301 | colour.grayscale(); 302 | var newSaturation = colour.toHSL()[1]; 303 | newSaturation.should.equal(0); 304 | }); 305 | }); 306 | 307 | describe('Validate', function() { 308 | 309 | it('should test for grayscale', function() { 310 | var colour = new Colour('#d1d1d1'); 311 | (colour.isGrayscale()).should.be.true; 312 | }); 313 | 314 | it('should test for colour', function() { 315 | var colour = new Colour('#e27a3f'); 316 | (colour.isColour()).should.be.true; 317 | }); 318 | }); 319 | --------------------------------------------------------------------------------