├── demo ├── amd │ ├── amd.css │ ├── rjs │ │ ├── config │ │ │ ├── unified │ │ │ │ └── build-config.js │ │ │ └── jsbin-parts │ │ │ │ ├── vendor-config.js │ │ │ │ └── app-config.js │ │ ├── output │ │ │ └── parts │ │ │ │ └── app.js │ │ └── build-commands.md │ ├── main.js │ ├── require-config.js │ └── index.html ├── .bowerrc ├── demo.js ├── demo.css ├── about.txt ├── bower.json ├── common.css ├── close-build-info.js ├── index.html └── bower-check.js ├── .gitignore ├── .jshintrc ├── bower.json ├── LICENSE ├── web-mocha ├── test-framework.css ├── help.html └── _index.html ├── package.json ├── karma.conf.js ├── spec ├── helpers │ └── data-provider.js └── color.spec.js ├── dist ├── colorful.min.js ├── colorful.min.js.map └── colorful.js ├── Gruntfile.js ├── README.md └── src └── colorful.js /demo/amd/amd.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | -------------------------------------------------------------------------------- /demo/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_demo_components" 3 | } 4 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | ( function( _ ) { 2 | "use strict"; 3 | 4 | }( _ )); -------------------------------------------------------------------------------- /demo/amd/rjs/config/unified/build-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.main", 5 | out: "../../output/unified/build.js" 6 | }) -------------------------------------------------------------------------------- /demo/amd/main.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | 3 | require( [ 4 | 5 | 'jquery', 6 | 'underscore', 7 | 'vue', 8 | 'colorful' 9 | 10 | ], function ( $, _, Vue, Colorful ) { 11 | 12 | var Color = Colorful.Color; 13 | 14 | } ); 15 | -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/vendor-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.main", 5 | excludeShallow: [ 6 | "local.main" 7 | ], 8 | out: "../../output/parts/vendor.js" 9 | }) -------------------------------------------------------------------------------- /demo/demo.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin: 0.25rem; 5 | } 6 | 7 | #header { 8 | margin: 1.25rem 0; 9 | } 10 | 11 | ul { 12 | list-style: none; 13 | margin-left: 0; 14 | } 15 | li { 16 | padding: 0; 17 | margin: 0; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | *.swo 4 | *.orig 5 | tmp/ 6 | ext/ 7 | node_modules/ 8 | bower_components/ 9 | _SpecRunner.html 10 | .idea/ 11 | web-mocha/index.html 12 | reports/ 13 | demo/bower_demo_components/ 14 | spec-helpers-library/ 15 | todo.md 16 | -------------------------------------------------------------------------------- /demo/amd/rjs/output/parts/app.js: -------------------------------------------------------------------------------- 1 | // main.js 2 | 3 | require( [ 4 | 5 | 'jquery', 6 | 'underscore', 7 | 'vue', 8 | 'colorful' 9 | 10 | ], function ( $, _, Vue, Colorful ) { 11 | 12 | var Color = Colorful.Color; 13 | 14 | } ); 15 | 16 | define("local.main", function(){}); 17 | 18 | -------------------------------------------------------------------------------- /demo/amd/rjs/config/jsbin-parts/app-config.js: -------------------------------------------------------------------------------- 1 | ({ 2 | mainConfigFile: "../../../require-config.js", 3 | optimize: "none", 4 | name: "local.main", 5 | exclude: [ 6 | "jquery", 7 | "underscore", 8 | "vue", 9 | "colorful" 10 | ], 11 | out: "../../output/parts/app.js" 12 | }) -------------------------------------------------------------------------------- /demo/about.txt: -------------------------------------------------------------------------------- 1 | If you'd like to build a demo or an interactive playground for this project, 2 | edit the files in this directory. 3 | 4 | With `grunt demo`, the index.html file is displayed in the browser, and the 5 | demo, src and spec directories are monitored for changes. If anything is altered 6 | there, the grunt task will live-reload the demo page. 7 | -------------------------------------------------------------------------------- /demo/amd/require-config.js: -------------------------------------------------------------------------------- 1 | requirejs.config( { 2 | 3 | // Base URL: project root 4 | baseUrl: "../../", 5 | 6 | paths: { 7 | "usertiming": "demo/bower_demo_components/usertiming/src/usertiming", 8 | 9 | "underscore": "bower_components/underscore/underscore", 10 | "jquery": "demo/bower_demo_components/jquery/dist/jquery", 11 | "vue": "demo/bower_demo_components/vue/dist/vue", 12 | 13 | "colorful": "dist/colorful", 14 | 15 | "local.main": "demo/amd/main" 16 | } 17 | 18 | } ); 19 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": false, 4 | "eqeqeq": true, 5 | "es3": true, 6 | "freeze": true, 7 | "immed": true, 8 | "latedef": "nofunc", 9 | "newcap": true, 10 | "noarg": true, 11 | "noempty": true, 12 | "nonbsp": true, 13 | "undef": true, 14 | 15 | "eqnull": true, 16 | "expr": true, 17 | "sub": true, 18 | 19 | "browser": true, 20 | "jquery": true, 21 | 22 | "strict": true, 23 | 24 | "globals": { 25 | "Backbone": true, 26 | "_": true, 27 | "$": true, 28 | 29 | "JSON": false, 30 | "require": false, 31 | "define": false, 32 | "module": false, 33 | "exports": true 34 | } 35 | } -------------------------------------------------------------------------------- /demo/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "main": "index.html", 4 | "version": "0.0.0", 5 | "homepage": "https://github.com/hashchange/colorful.js", 6 | "authors": [ 7 | "hashchange " 8 | ], 9 | "description": "Colorful.js demo and playground", 10 | "keywords": [ 11 | "demo" 12 | ], 13 | "license": "MIT", 14 | "private": true, 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "bower_demo_components", 20 | "test", 21 | "tests" 22 | ], 23 | "dependencies": { 24 | "foundation": "~5.5.3", 25 | "jquery": "~3.1.1", 26 | "modernizr": "^2 || ~3.3.1", 27 | "requirejs": "~2.3.2", 28 | "usertiming": "~0.1.8", 29 | "vue": "~2.1.8" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /demo/common.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin: 0.25rem; 5 | } 6 | 7 | #header { 8 | margin-bottom: 2rem; 9 | } 10 | 11 | #header h1, #header h2 { 12 | margin-bottom: 1rem; 13 | } 14 | 15 | h1 { 16 | font-size: 2.5rem; 17 | } 18 | 19 | h2 { 20 | font-size: 1.75rem; 21 | } 22 | 23 | #build-info { 24 | position: relative; 25 | border: 1px solid #d8d8d8; 26 | border-radius: 3px; 27 | padding: 1.25rem; 28 | margin-bottom: 1.25rem; 29 | background-color: #ecfaff; 30 | } 31 | 32 | #build-info a { 33 | display: block; 34 | position: absolute; 35 | top: 0.15rem; 36 | right: 0.6rem; 37 | color: lightgrey; 38 | } 39 | 40 | #build-info a:hover, #build-info a:active { 41 | color: #007b9f; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colorful.js", 3 | "version": "0.3.0", 4 | "homepage": "https://github.com/hashchange/colorful.js", 5 | "authors": [ 6 | "Michael Heim " 7 | ], 8 | "description": "A tiny, robust color utility", 9 | "main": "dist/colorful.js", 10 | "keywords": [ 11 | "color", 12 | "colour", 13 | "conversion", 14 | "rgb", 15 | "rgba", 16 | "hex", 17 | "keywords", 18 | "alpha", 19 | "colourful", 20 | "css", 21 | "lightroom", 22 | "lrcolor", 23 | "agcolor" 24 | ], 25 | "license": "MIT", 26 | "ignore": [ 27 | "**/.*", 28 | "node_modules", 29 | "bower_components", 30 | "test", 31 | "tests", 32 | "public", 33 | "reports", 34 | "demo", 35 | "lib-other", 36 | "web-mocha", 37 | "spec", 38 | "src", 39 | "Gruntfile.js", 40 | "karma.conf.js", 41 | "package.json" 42 | ], 43 | "devDependencies": {}, 44 | "dependencies": { 45 | "underscore": "^1.5.0 <1.9.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Michael Heim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /demo/close-build-info.js: -------------------------------------------------------------------------------- 1 | // Closes the #build-info box when clicking on the close button. Hides the box immediately on JS Bin and Codepen. 2 | // 3 | // Works in any browser. 4 | ( function ( window, document ) { 5 | "use strict"; 6 | 7 | var isLocalDemo =!window.location.hostname.match( /jsbin\.|codepen\./i ), 8 | 9 | box = document.getElementById( "build-info" ), 10 | closeButton = document.getElementById( "close-build-info" ), 11 | 12 | close = function ( event ) { 13 | if ( event ) event.preventDefault(); 14 | box.style.display = "none"; 15 | }; 16 | 17 | addEventHandler( closeButton, "click", close ); 18 | if ( !isLocalDemo ) close(); 19 | 20 | function addEventHandler( element, event, handler ) { 21 | 22 | if ( element ) { 23 | 24 | if ( element.addEventListener ) { 25 | element.addEventListener( event, handler, false ); 26 | } else if ( element.attachEvent ) { 27 | element.attachEvent( "on" + event, handler ) 28 | } else { 29 | element["on" + event] = handler; 30 | } 31 | 32 | } else if ( window.console && window.console.log ) { 33 | window.console.log( "close-build-info.js: Build info box (or its close button) not found" ); 34 | } 35 | 36 | } 37 | 38 | } ( window, document ) ); -------------------------------------------------------------------------------- /web-mocha/test-framework.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | .aux { 4 | margin-left: 65px; 5 | position: absolute; 6 | top: 15px; 7 | } 8 | 9 | #test-framework-help-link { 10 | width: 12em; 11 | /*margin-left: 450px;*/ 12 | } 13 | 14 | #alternate-setup { 15 | top: 15px; 16 | } 17 | .aux, 18 | .test-framework-help nav { 19 | margin-top: 1em; 20 | margin-bottom: 1em; 21 | font: 12px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 22 | color: #888; 23 | } 24 | .aux a, 25 | .test-framework-help a { 26 | text-decoration: none; 27 | color: inherit; 28 | } 29 | 30 | .aux a:hover, 31 | .aux a:active, 32 | .test-framework-help nav a:hover, 33 | .test-framework-help nav a:active 34 | { 35 | text-decoration: underline; 36 | } 37 | 38 | .test-framework-help section a { 39 | color: #555; 40 | border-bottom: 1px dotted black; 41 | } 42 | 43 | .test-framework-help section a:hover, 44 | .test-framework-help section a:active { 45 | border-bottom-style: solid; 46 | border-bottom-color: #888; 47 | } 48 | 49 | .test-framework-help { 50 | margin: 0; 51 | padding: 15px 65px; 52 | font: 16px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif 53 | } 54 | 55 | .test-framework-help h1 { 56 | font-size: 20px; 57 | font-weight: 200; 58 | } 59 | 60 | .test-framework-help ul { 61 | padding-left: 0; 62 | } 63 | 64 | .test-framework-help li { 65 | margin-bottom: 0.5em; 66 | } 67 | -------------------------------------------------------------------------------- /web-mocha/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test Framework Help 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Test Framework: Help and Reference

14 |

The test framework is based on Mocha and Chai. It includes these components:

15 | 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Colorful.js: Demo and Playground 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 |
32 |
33 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /web-mocha/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mocha Spec Runner 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /demo/amd/rjs/build-commands.md: -------------------------------------------------------------------------------- 1 | # Generating r.js builds 2 | 3 | ## Using a Grunt task 4 | 5 | Instead of individual r.js calls, the following command will create all builds: 6 | 7 | ``` 8 | grunt requirejs 9 | ``` 10 | 11 | The grunt task simply reads the build profiles described below, and feeds them to r.js. 12 | 13 | 14 | ## Split builds with two build files, for JS Bin demos 15 | 16 | The demo HTML files for JS Bin reference two concatenated build files: 17 | 18 | - `vendor.js` for the third-party dependencies. It includes Colorful.js. 19 | - `app.js` for the demo code, consisting of local modules. 20 | 21 | The code is not rolled up into a single file because that file would be massive, making it unnecessarily difficult to examine the demo code. The purpose of the demo is to see how Colorful.js is used, so it makes sense to keep the client code separate. 22 | 23 | ### Adjustments 24 | 25 | Care must be taken to avoid duplication. A module pulled into `vendor.js` must not be part of `app.js`, and vice versa. Update the module exclusions in **all** build config files when new modules are added to a demo. 26 | 27 | ### r.js calls 28 | 29 | Open a command prompt in the **project root** directory. 30 | 31 | ``` 32 | # For vendor.js: 33 | 34 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/vendor-config.js 35 | 36 | # For app.js: 37 | 38 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/jsbin-parts/app-config.js 39 | ``` 40 | 41 | ### Output files 42 | 43 | The output is written to the directory `demo/amd/rjs/output/parts`. 44 | 45 | 46 | ## Single-file builds, for local demos 47 | 48 | Builds for local demos are created to test that the setup continues to work after optimization with r.js. All modules of a demo end up in a single file. For easier examination, the file is not minified. 49 | 50 | For more info, see the comments in `index.html`. 51 | 52 | ### r.js calls 53 | 54 | For building the output file, open a command prompt in the **project root** directory, and run this command: 55 | 56 | ``` 57 | node node_modules/requirejs/bin/r.js -o demo/amd/rjs/config/unified/build-config.js 58 | ``` 59 | 60 | ### Output files 61 | 62 | The output is written to the directory `demo/amd/rjs/output/unified`. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colorful.js", 3 | "version": "0.3.0", 4 | "homepage": "https://github.com/hashchange/colorful.js", 5 | "bugs": "https://github.com/hashchange/colorful.js/issues", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/hashchange/colorful.js.git" 9 | }, 10 | "author": "Michael Heim (http://www.zeilenwechsel.de/)", 11 | "description": "A tiny, robust color utility", 12 | "main": "dist/colorful.js", 13 | "keywords": [ 14 | "color", 15 | "colour", 16 | "conversion", 17 | "rgb", 18 | "rgba", 19 | "hex", 20 | "keywords", 21 | "alpha", 22 | "colourful", 23 | "css", 24 | "lightroom", 25 | "lrcolor", 26 | "agcolor" 27 | ], 28 | "license": "MIT", 29 | "scripts": { 30 | "cleanup": "del-cli bower_components demo/bower_demo_components node_modules -f", 31 | "setup": "npm install && bower install && cd demo && bower install && cd ..", 32 | "reinstall": "npm run cleanup -s && npm run setup || npm run setup" 33 | }, 34 | "dependencies": { 35 | "underscore": "^1.5.0 <1.9.0" 36 | }, 37 | "devDependencies": { 38 | "bower": "^1.8.0", 39 | "connect-livereload": "~0.6.0", 40 | "del-cli": "~0.2.1", 41 | "grunt": "^1.0.1", 42 | "grunt-cli": "^1.2.0", 43 | "grunt-concurrent": "~2.3.1", 44 | "grunt-contrib-concat": "~1.0.1", 45 | "grunt-contrib-connect": "~1.0.2", 46 | "grunt-contrib-jshint": "~1.1.0", 47 | "grunt-contrib-requirejs": "^1.0.0", 48 | "grunt-contrib-uglify": "~2.2.0", 49 | "grunt-contrib-watch": "~1.0.0", 50 | "grunt-focus": "~1.0.0", 51 | "grunt-karma": "~2.0.0", 52 | "grunt-mocha": "~1.0.2", 53 | "grunt-preprocess": "~5.1.0", 54 | "grunt-sails-linker": "~1.0.4", 55 | "grunt-text-replace": "~0.4.0", 56 | "karma": "~1.5.0", 57 | "karma-chai-plugins": "~0.8.0", 58 | "karma-chrome-launcher": "~2.0.0", 59 | "karma-firefox-launcher": "~1.0.0", 60 | "karma-html2js-preprocessor": "~1.1.0", 61 | "karma-ie-launcher": "~1.0.0", 62 | "karma-mocha": "~1.3.0", 63 | "karma-mocha-reporter": "~2.2.1", 64 | "karma-opera-launcher": "~1.0.0", 65 | "karma-phantomjs-launcher": "~1.0.2", 66 | "karma-requirejs": "~1.1.0", 67 | "karma-safari-launcher": "1.0.0", 68 | "karma-script-launcher": "~1.0.0", 69 | "karma-slimerjs-launcher": "~1.1.0", 70 | "mocha": "~3.2.0", 71 | "phantomjs-prebuilt": "^2.1.14", 72 | "require-from-string": "^1.2.1", 73 | "requirejs": "^2.3.2" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Dec 30 2013 16:14:03 GMT+0100 (CET) 3 | 4 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 | // + For automated testing with Grunt, some settings in this config file + 6 | // + are overridden in Gruntfile.js. Check both locations. + 7 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | module.exports = function(config) { 10 | config.set({ 11 | 12 | // base path, that will be used to resolve files and exclude 13 | basePath: '', 14 | 15 | 16 | // frameworks to use 17 | // 18 | // - Available for chai (installed with karma-chai-plugins): 19 | // sinon-chai, chai-as-promised, chai-jquery. Enable as needed. 20 | // 21 | // NB sinon-chai includes Sinon; chai-jquery does _not_ include jQuery 22 | // 23 | // - For Jasmine, use ['jasmine'] only, or ['jasmine', 'jasmine-matchers']. 24 | frameworks: ['mocha', 'chai'], 25 | 26 | 27 | // list of files / patterns to load in the browser 28 | files: [ 29 | // Component dependencies 30 | 'bower_components/underscore/underscore.js', 31 | 32 | // Component under test 33 | 'src/colorful.js', 34 | 35 | // Test helpers 36 | 'spec/helpers/**/*.js', 37 | 38 | // Tests 39 | 'spec/**/*.+(spec|test|tests).js' 40 | ], 41 | 42 | 43 | // list of files to exclude 44 | exclude: [ 45 | 46 | ], 47 | 48 | 49 | // test results reporter to use 50 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage', 'mocha' 51 | reporters: ['progress'], 52 | 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | 58 | // enable / disable colors in the output (reporters and logs) 59 | colors: true, 60 | 61 | 62 | // level of logging 63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 64 | logLevel: config.LOG_INFO, 65 | 66 | 67 | // enable / disable watching file and executing tests whenever any file changes 68 | autoWatch: false, 69 | 70 | 71 | // Start these browsers, currently available: 72 | // - Chrome 73 | // - ChromeCanary 74 | // - Firefox 75 | // - Opera 76 | // - Safari 77 | // - PhantomJS 78 | // - SlimerJS 79 | // - IE (Windows only) 80 | // 81 | // ATTN Interactive debugging in PhpStorm/WebStorm doesn't work with PhantomJS. Use Firefox or Chrome instead. 82 | browsers: ['PhantomJS'], 83 | 84 | 85 | // If browser does not capture in given timeout [ms], kill it 86 | captureTimeout: 60000, 87 | 88 | 89 | // Continuous Integration mode 90 | // if true, it capture browsers, run tests and exit 91 | singleRun: false 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /demo/amd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Colorful.js: Demo (AMD) 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | 59 | 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /demo/bower-check.js: -------------------------------------------------------------------------------- 1 | // Checks that 'bower install' has been run in the demo dir if demo/bower.json isn't empty. 2 | 3 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 | // + + 5 | // + This is a blocking script, triggering _synchronous_ http subrequests. Use locally only. + 6 | // + + 7 | // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 | 9 | ( function ( window ) { 10 | "use strict"; 11 | 12 | var BOWER_DEMO_COMPONENTS_DIR = "/demo/bower_demo_components", 13 | 14 | msg = "
" + 15 | "

Bower components for the demo seem to be missing. Install them first:

" + 16 | "
    " + 17 | "
  • Open a command prompt in the demo directory of the project.
  • " + 18 | "
  • Run bower install
  • " + 19 | "
" + 20 | "

If this is a false positive and the packages are in fact in place, " + 21 | "disable the check by removing bower-check.js at the top of the <body>.

" + 22 | "
"; 23 | 24 | getJSON( "/demo/bower.json", false, function ( data ) { 25 | 26 | var i, j, depNames, 27 | exists = false, 28 | files = [ 29 | 'bower.json', 'package.json', 30 | 'readme.md', 'Readme.md', 'README.md', 'README', 31 | 'license.txt', 'LICENSE.txt', 'LICENSE', 32 | 'Gruntfile.js', 33 | 'composer.json', 'component.json' 34 | ]; 35 | 36 | if ( data && data.dependencies ) { 37 | 38 | depNames = Object.keys( data.dependencies ); 39 | 40 | // Bower packages don't necessarily have a bower.json. file. The only file guaranteed to be there after 41 | // install is `.bower.json` (note the leading dot), but it is hidden and won't be served over http. 42 | // 43 | // So instead, we are looking for a bunch of files which are very likely to be there. If none of them is, 44 | // for none of the projects, the dependencies are most likely not installed. 45 | for ( i = 0; i < depNames.length; i++ ) { 46 | for ( j = 0; j < files.length; j++ ) { 47 | 48 | get( 49 | BOWER_DEMO_COMPONENTS_DIR + "/" + depNames[i] + "/" + files[j], false, 50 | function ( data ) { 51 | exists = !!data; 52 | } 53 | ); 54 | 55 | if ( exists ) return; 56 | 57 | } 58 | } 59 | 60 | window.document.write( msg ); 61 | } 62 | 63 | } ); 64 | 65 | 66 | // Helper functions in the absence of a library like jQuery (not loaded at this point) 67 | function get ( url, async, cb, cbError ) { 68 | var data, 69 | request = new XMLHttpRequest; 70 | 71 | request.open( 'GET', url, async ); 72 | 73 | request.onload = function () { 74 | if ( this.status >= 200 && this.status < 400 ) { 75 | // Success! 76 | data = this.response; 77 | } else { 78 | // We reached our target server, but it returned an error. Most likely, the bower.json file is missing. 79 | // We just return undefined here. 80 | data = undefined; 81 | } 82 | cb( data ); 83 | }; 84 | 85 | request.onerror = function ( err ) { 86 | // There was a connection error of some sort. 87 | if ( cbError ) cbError( err ); 88 | }; 89 | 90 | request.send(); 91 | } 92 | 93 | function getJSON ( url, async, cb, cbError ) { 94 | get( url, async, function ( data ) { 95 | 96 | if ( data ) data = JSON.parse( data ); 97 | cb( data ); 98 | 99 | }, cbError ); 100 | } 101 | 102 | }( window )); -------------------------------------------------------------------------------- /spec/helpers/data-provider.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Code lifted from Leche because I didn't get it to work with Karma. 3 | * 4 | * See https://github.com/box/leche#mocha-data-provider for usage instructions. 5 | * 6 | * LIMITATION: 7 | * 8 | * - You can't use data in your dataSets which is created or populated in an outer before() or beforeEach() hook. The 9 | * hooks run _after_ withData() has been invoked. 10 | * 11 | * Tests contained in the withData callback are executed in order, though, so you can use data from outer before() and 12 | * beforeEach() hooks in there. 13 | * 14 | * To clarify the execution order, consider this setup: 15 | * 16 | * var foo=0; 17 | * 18 | * beforeEach( function () { foo = 10 } ); 19 | * 20 | * withData( [foo], function ( importedFoo ) { 21 | * 22 | * console.log( foo ); // => prints 0, beforeEach hasn't run yet 23 | * console.log( importedFoo ); // => prints 0, beforeEach hadn't run when the data set for withData 24 | * // was created 25 | * 26 | * describe( "a suite", function () { 27 | * 28 | * console.log( foo ); // => prints 0, beforeEach hasn't run yet 29 | * 30 | * beforeEach( function () { 31 | * console.log( foo ); // => prints 10, beforeEach has run 32 | * } ); 33 | * 34 | * it( "is a test", function () { 35 | * console.log( foo ); // => prints 10, beforeEach has run 36 | * } ); 37 | * 38 | * } ); 39 | * 40 | * } ); 41 | * 42 | * tldr; before/beforeEach cannot be used for withData itself, or its data sets. Refer to it only inside tests or 43 | * hooks. 44 | * 45 | * Legal 46 | * ----- 47 | * 48 | * Original file available at https://github.com/box/leche 49 | * 50 | * Copyright 51 | * (c) 2014 Box, Inc. 52 | * (c) 2015-2017 Michael Heim, Zeilenwechsel.de 53 | * 54 | * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 55 | * except in compliance with the License. You may obtain a copy of the License at 56 | * http://www.apache.org/licenses/LICENSE-2.0 57 | * 58 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 59 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 60 | * specific language governing permissions and limitations under the License. 61 | */ 62 | 63 | /** 64 | * A data provider for use with Mocha or Jasmine. Generates describe() blocks. Use this around a call to it() to run the 65 | * test over a series of data. 66 | * 67 | * @throws {Error} if dataset is missing or an empty array 68 | * @param {Object|Array} dataset the data to test 69 | * @param {Function} testFunction the function to call for each piece of data 70 | */ 71 | function describeWithData ( dataset, testFunction ) { 72 | _withData( describe, dataset, testFunction ); 73 | } 74 | 75 | /** 76 | * A data provider for use with Mocha or Jasmine. Generates it() blocks. Use this around test code which would normally 77 | * be placed into it(). Each key in the data set becomes the name of a test. 78 | * 79 | * @throws {Error} if dataset is missing or an empty array 80 | * @param {Object|Array} dataset the data to test 81 | * @param {Function} testFunction the function to call for each piece of data 82 | */ 83 | function itWithData ( dataset, testFunction ) { 84 | _withData( it, dataset, testFunction ); 85 | } 86 | 87 | /** 88 | * A data provider for use with Mocha or Jasmine. Use this to run tests over a series of data. 89 | * 90 | * @throws {Error} if dataset is missing or an empty array 91 | * @param {Function} blockFunction the Mocha/Jasmine function to use for generating the block, ie `describe` or `it` 92 | * @param {Object|Array} dataset the data to test 93 | * @param {Function} testFunction the function to call for each piece of data 94 | * @param {string} [prefix=""] prefix for each dataset name (when turned into the test name) 95 | * @param {string} [suffix=""] prefix for each dataset name (when turned into the test name) 96 | */ 97 | function _withData ( blockFunction, dataset, testFunction, prefix, suffix ) { 98 | 99 | prefix || ( prefix = "" ); 100 | suffix || ( suffix = "" ); 101 | 102 | // check for missing or null argument 103 | if ( typeof dataset !== 'object' || dataset === null ) { 104 | throw new Error( 'First argument must be an object or non-empty array.' ); 105 | } 106 | 107 | /* 108 | * The dataset needs to be normalized so it looks like: 109 | * { 110 | * "name1": [ "data1", "data2" ], 111 | * "name2": [ "data3", "data4" ], 112 | * } 113 | */ 114 | var namedDataset = dataset; 115 | if ( dataset instanceof Array ) { 116 | 117 | // arrays must have at least one item 118 | if ( dataset.length ) { 119 | namedDataset = createNamedDataset( dataset ); 120 | } else { 121 | throw new Error( 'First argument must be an object or non-empty array.' ); 122 | } 123 | } 124 | 125 | /* 126 | * For each name, create a new describe() or it() block containing the name. 127 | * This causes the dataset info to be output into the console, making 128 | * it easier to determine which dataset caused a problem when there's an 129 | * error. 130 | */ 131 | for ( var name in namedDataset ) { 132 | if ( namedDataset.hasOwnProperty( name ) ) { 133 | //jshint loopfunc:true 134 | 135 | blockFunction( prefix + name + suffix, (function ( name ) { 136 | return function () { 137 | 138 | var args = namedDataset[name]; 139 | 140 | if ( !(args instanceof Array) ) { 141 | args = [args]; 142 | } 143 | 144 | testFunction.apply( this, args ); 145 | }; 146 | }( name )) ); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Converts an array into an object whose keys are a string representation of the each array item and whose values are 153 | * each array item. This is to normalize the information into an object so that other operations can assume objects are 154 | * always used. For an array like this: 155 | * 156 | * [ "foo", "bar" ] 157 | * 158 | * It creates an object like this: 159 | * 160 | * { "foo": "foo", "bar": "bar" } 161 | * 162 | * If there are duplicate values in the array, only the last value is represented in the resulting object. 163 | * 164 | * @param {Array} array the array to convert 165 | * @returns {Object} an object representing the array 166 | * @private 167 | */ 168 | function createNamedDataset ( array ) { 169 | var result = {}; 170 | 171 | for ( var i = 0, len = array.length; i < len; i++ ) { 172 | result[array[i].toString()] = array[i]; 173 | } 174 | 175 | return result; 176 | } 177 | 178 | 179 | -------------------------------------------------------------------------------- /dist/colorful.min.js: -------------------------------------------------------------------------------- 1 | // Colorful.js, v0.3.0 2 | // Copyright (c) 2016-2017 Michael Heim, Zeilenwechsel.de 3 | // Distributed under MIT license 4 | // http://github.com/hashchange/colorful.js 5 | 6 | 7 | !function(a,b){"use strict";var c="object"==typeof exports&&exports&&!exports.nodeType&&"object"==typeof module&&module&&!module.nodeType;"function"==typeof define&&"object"==typeof define.amd&&define.amd?define(["exports","underscore"],b):c?b(exports,require("underscore")):b(a,_)}(this,function(a,_){"use strict";function b(a,b,c){var d;return"undefined"==typeof c||0===+c?Math[a](b):(b=+b,c=+c,isNaN(b)||"number"!=typeof c||c%1!==0?NaN:(d=-c,b=b.toString().split("e"),b=Math[a](+(b[0]+"e"+(b[1]?+b[1]-d:-d))),b=b.toString().split("e"),+(b[0]+"e"+(b[1]?+b[1]+d:d))))}function c(a){var b=a+"";return b=b.replace(/^(\d+)(?:\.(\d+))?e-(\d+)$/i,function(b,c,d,e){var f=+e+(d?d.length:0);return f>20&&(O.round(a,20),f=20),a.toFixed(f)})}function d(a,b,c){var d="#"===a,e=d?"":"\\s*,\\s*",f=d?"":"\\s*\\)",g=[b,b,b];return c&&g.push(u),d||(a+="\\s*\\(\\s*"),new RegExp("^\\s*"+a+g.join(e)+f+"\\s*$")}function e(a,b){var c=a.match(b);return{isMatch:!!c,rgb:c&&[c[1],c[2],c[3]],a:c&&c[4]}}function f(a){var b=e(a,F);return b.isMatch||(b=e(a,G)),b.isMatch||(b=e(a,H),b.isMatch||(b=e(a,I)),b.isMatch&&(b.rgb=_.map(b.rgb,function(a){return a+a}))),b.isMatch&&(b.rgb=_.map(b.rgb,function(a){return parseInt(a,16)})),b}function g(a){var b=e(a,J);return b.isMatch||(b=e(a,K)),b.isMatch&&(b.rgb=_.map(b.rgb,function(a){return+a})),b}function h(a){var b=e(a,L);return b.isMatch||(b=e(a,M)),b.isMatch&&(b.rgb=_.map(b.rgb,function(a){return 255*a/100})),b}function i(a){var b=e(a,N);return b.isMatch&&(b.rgb=_.map(b.rgb,function(a){return 255*a})),b}function j(a){var b,c=-1;return _.isNumber(a)?c=a:_.isString(a)&&(b=a.match(C),b?c=+b[1]:(b=a.match(D),b&&(c=255*b[1]/100))),c>=0&&c<=255?c:void 0}function k(a){var b,c=-1;return _.isNumber(a)?c=a:_.isString(a)&&(b=a.match(E),b&&(c=+b[1])),c>=0&&c<=1?c:void 0}function l(a){var b,c=[a[0],a[1],a[2]],d=a[3],e=_.map(c,j),f=k(d),g=!_.some(e,_.isUndefined)&&(void 0===d||void 0!==f);return g&&(b=_.object(["r","g","b"],e),b.a=f),b}function m(a){var b,c,d,e;return a instanceof r?b=_.clone(a._rawColor):!_.isArray(a)||3!==a.length&&4!==a.length?_.isObject(a)?(c=_.keys(a),3!==_.intersection(["r","g","b"],c).length&&4!==_.intersection(["r","g","b","a"],c).length||(d=[a.r,a.g,a.b],4===c.length&&d.push(a.a),b=l(d))):_.isString(a)&&(s[a]?b=_.object(["r","g","b"],s[a]):"transparent"===a.toLowerCase()?b={r:0,g:0,b:0,a:0}:(e=f(a),e.isMatch||(e=g(a)),e.isMatch||(e=h(a)),e.isMatch||(e=i(a)),e.isMatch&&(b=_.object(["r","g","b"],e.rgb),b.a=e.a))):b=l(a),b&&void 0!==b.a&&(b.a=+b.a),b&&void 0===b.a&&(b.a=1),b}function n(a,b){var c=b&&(b.upperCase||b.lowerCase===!1),d=O.round(a).toString(16);return d.length<2&&(d="0"+d),c?d.toUpperCase():d.toLowerCase()}function o(a,b){var c=b&&b.precision||0;return"max"===c?a:O.round(a,c)}function p(a,b){var d=b&&b.precision||0,e=100*a/255;return"max"!==d&&(e=O.round(e,d)),c(e)+"%"}function q(a){return a/255}function r(a){return this instanceof r?(this._input=a,void(this._rawColor=m(a))):new r(a)}var s={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},t="(?:1(?:\\.0+)?|0?\\.\\d+|0)",u="("+t+")",v="(255(?:\\.0+)?|25[0-4](?:\\.\\d+)?|2[0-4]\\d(?:\\.\\d+)?|(?:[0-1]?\\d)?\\d(?:\\.\\d+)?)",w="(100(?:\\.0+)?|\\d{0,2}(?:\\.\\d+)|\\d{1,2})\\s*%",x="([a-f\\d]{2})",y="([A-F\\d]{2})",z="([a-f\\d])",A="([A-F\\d])",B=u,C=new RegExp("^\\s*"+v+"\\s*$"),D=new RegExp("^\\s*"+w+"\\s*$"),E=new RegExp("^\\s*"+u+"\\s*$"),F=d("#",x),G=d("#",y),H=d("#",z),I=d("#",A),J=d("rgb",v),K=d("rgba",v,!0),L=d("rgb",w),M=d("rgba",w,!0),N=d("AgColor",B,!0),O={};O.round=function(a,c){return b("round",a,c)},O.floor=function(a,c){return b("floor",a,c)},O.ceil=function(a,c){return b("ceil",a,c)},r.version="0.3.0",_.extend(r.prototype,{isColor:function(){return!_.isUndefined(this._rawColor)},ensureColor:function(){if(!this.isColor())throw new Error("Color.ensureColor: The color object does not represent a valid color. It was created from the value "+this._input);return!0},ensureOpaque:function(){if(this.ensureColor(),!this.isOpaque())throw new Error("Color.ensureOpaque: Color is required to be opaque, but it is not (a = "+this._rawColor.a+")");return!0},ensureTransparent:function(){if(this.ensureColor(),!this.isTransparent())throw new Error("Color.ensureTransparent: Color is required to be transparent, but it is not");return!0},isOpaque:function(){return this.isColor()&&1===this._rawColor.a},isTransparent:function(){return this.isColor()&&this._rawColor.a<1},asHex:function(a){var b=a&&a.prefix===!1?"":"#";return this.ensureOpaque(),b+_.map(this._getRawArrayRgb(),_.partial(n,_,a)).join("")},asHexUC:function(){return this.asHex({upperCase:!0})},asHexLC:function(){return this.asHex({lowerCase:!0})},asRgb:function(){return this.ensureOpaque(),"rgb("+this.asRgbArray().join(", ")+")"},asRgbPercent:function(a){var b=a&&a.precision?_.partial(p,_,a):p;return this.ensureOpaque(),"rgb("+_.map(this._getRawArrayRgb(),b).join(", ")+")"},asRgba:function(){return this.ensureColor(),"rgba("+this._asRgbArray().concat(c(this._rawColor.a)).join(", ")+")"},asRgbaPercent:function(a){var b=a&&a.precision?_.partial(p,_,a):p;return this.ensureColor(),"rgba("+_.map(this._getRawArrayRgb(),b).concat(c(this._rawColor.a)).join(", ")+")"},asAgColor:function(){return this.ensureColor(),"AgColor( "+_.map(this._asAgColorArray(),c).join(", ")+" )"},asRgbArray:function(a){return this.ensureOpaque(),this._asRgbArray(a)},asRgbaArray:function(a){return this.ensureColor(),this._asRgbArray(a).concat(this._rawColor.a)},asComputed:function(){return this.isOpaque()?this.asRgb():this.asRgba()},equals:function(a,b){var c,d,e,f,g=b&&b.tolerance||0;return a instanceof r||(a=new r(a)),d=this.isColor()&&a.isColor(),c=d&&this.asRgba()===a.asRgba(),d&&!c&&g>0&&(e=_.zip(this.asRgbaArray(),a.asRgbaArray()),f=e.pop(),c=_.every(e,function(a){return a[0]<=a[1]+g&&a[0]>=a[1]-g}),c=c&&f[0]===f[1]),c},strictlyEquals:function(a){return a instanceof r||(a=new r(a)),this.isColor()&&a.isColor()&&this.asRgbaPercent({precision:"max"})===a.asRgbaPercent({precision:"max"})},_asAgColorArray:function(){return _.map(this._getRawArrayRgb(),q).concat(this._rawColor.a)},_asRgbArray:function(a){var b=a&&a.precision?_.partial(o,_,a):o;return _.map(this._getRawArrayRgb(),b)},_getRawArrayRgb:function(){return[this._rawColor.r,this._rawColor.g,this._rawColor.b]}}),a.Color=r}); 8 | //# sourceMappingURL=colorful.min.js.map -------------------------------------------------------------------------------- /dist/colorful.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["colorful.js"],"names":["root","factory","supportsExports","exports","nodeType","module","define","amd","require","_","this","_decimalAdjust","operation","value","precision","exp","Math","isNaN","NaN","toString","split","toDecimalNotation","number","numStr","replace","match","intDigits","fractionalDigits","absExponent","digits","length","Nums","round","toFixed","buildColorRx","prefix","rxRgbChannel","withAlpha","isHex","channelSeparator","suffix","rxChannels","push","rxstrAlpha","RegExp","join","matchColor","color","rx","matches","isMatch","rgb","a","getHexData","data","rxHexLC","rxHexUC","rxHexShortLC","rxHexShortUC","map","parseInt","getBase256Data","rxRgbBase256","rxRgbaBase256","getPercentData","rxRgbPercent","rxRgbaPercent","getAgColorData","rxAgColor","parseRgbChannel","channel","parsedNum","isNumber","isString","rxRgbChannelBase256","rxRgbChannelPercent","undefined","parseAlphaChannel","rxAlphaChannel","parseColorArray","colorArray","parsed","rawRgb","rawAlpha","parsedRgb","parsedAlpha","success","some","isUndefined","object","parseColor","keys","colorArr","Color","clone","_rawColor","isArray","isObject","intersection","r","g","b","CssColorNames","toLowerCase","rawToHex","rawChannel","options","upperCase","lowerCase","hex","toUpperCase","rawToBase256","rawToPercent","percentage","rawToFraction","_input","aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkgrey","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkslategrey","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dimgrey","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","green","greenyellow","grey","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightgrey","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightslategrey","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","slategrey","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen","_rxstrFraction","rxstrRgbChannelBase256","rxstrRgbChannelPercent","rxstrRgbChannelHexLC","rxstrRgbChannelHexUC","rxstrRgbChannelShortHexLC","rxstrRgbChannelShortHexUC","rxstrRgbChannelFraction","floor","ceil","version","extend","prototype","isColor","ensureColor","Error","ensureOpaque","isOpaque","ensureTransparent","isTransparent","asHex","_getRawArrayRgb","partial","asHexUC","asHexLC","asRgb","asRgbArray","asRgbPercent","mapCb","asRgba","_asRgbArray","concat","asRgbaPercent","asAgColor","_asAgColorArray","asRgbaArray","asComputed","equals","otherColor","isEqual","pairedChannels","pairedAlpha","tolerance","zip","pop","every","pairedChannel","strictlyEquals"],"mappings":"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","file":"colorful.min.js"} -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function (grunt) { 3 | 4 | var LIVERELOAD_PORT = 35731, 5 | HTTP_PORT = 9400, 6 | KARMA_PORT = 9877, 7 | WATCHED_FILES_SRC = [ 8 | 'src/**/*' 9 | ], 10 | WATCHED_FILES_SPEC = [ 11 | 'spec/**/*' 12 | ], 13 | WATCHED_FILES_DIST = [ 14 | 'dist/**/*' 15 | ], 16 | WATCHED_FILES_DEMO = [ 17 | 'demo/**/*' 18 | ], 19 | 20 | path = require( "path" ), 21 | requireFromString = require( "require-from-string" ), 22 | 23 | /** 24 | * Receives an object and the name of a path property on that object. Translates the path property to a new path, 25 | * based on a directory prefix. Does not return anything, modifies the object itself. 26 | * 27 | * The directory prefix can be relative (e.g. "../../"). It may or may not end in a slash. 28 | * 29 | * @param {string} dirPrefix 30 | * @param {Object} object 31 | * @param {string} propertyName 32 | * @param {boolean} [verbose=false] 33 | */ 34 | translatePathProperty = function ( dirPrefix, object, propertyName, verbose ) { 35 | var originalPath = object[propertyName]; 36 | 37 | if ( originalPath ) { 38 | object[propertyName] = path.normalize( dirPrefix + path.sep + originalPath ); 39 | if ( verbose ) grunt.log.writeln( 'Translating path property "' + propertyName + '": ' + originalPath + " => " + object[propertyName] ); 40 | } 41 | }, 42 | 43 | /** 44 | * Reads an r.js build profile and returns it as an options set for a grunt-contrib-requirejs task. 45 | * 46 | * For a discussion, see https://github.com/gruntjs/grunt-contrib-requirejs/issues/13 47 | * 48 | * Paths in the build profile are relative to the profile location. In the returned options object, they are 49 | * transformed to be relative to the Gruntfile. (The list is nowhere near complete. More properties need to be 50 | * transformed as build profiles become more complex.) 51 | * 52 | * @param {string} profilePath relative to the Gruntfile 53 | * @param {boolean} [verbose=false] 54 | * @returns {Object} 55 | */ 56 | getRequirejsBuildProfile = function ( profilePath, verbose ) { 57 | var profileContent = grunt.file.read( profilePath ), 58 | profile = requireFromString( "module.exports = " + profileContent ), 59 | 60 | dirPrefix = path.dirname( profilePath ); 61 | 62 | if ( verbose ) grunt.log.writeln( "Loading r.js build profile " + profilePath ); 63 | 64 | // Add more paths here as needed. 65 | translatePathProperty( dirPrefix, profile, "mainConfigFile", verbose ); 66 | translatePathProperty( dirPrefix, profile, "out", verbose ); 67 | 68 | if ( verbose ) grunt.log.writeln(); 69 | 70 | return profile; 71 | }; 72 | 73 | // Project configuration. 74 | grunt.config.init({ 75 | pkg: grunt.file.readJSON('package.json'), 76 | meta: { 77 | version: '<%= pkg.version %>', 78 | banner: '// Colorful.js, v<%= meta.version %>\n' + 79 | '// Copyright (c) 2016-<%= grunt.template.today("yyyy") %> Michael Heim, Zeilenwechsel.de\n' + 80 | '// Distributed under MIT license\n' + 81 | '// http://github.com/hashchange/colorful.js\n' + 82 | '\n' 83 | }, 84 | 85 | preprocess: { 86 | build: { 87 | files: { 88 | 'dist/colorful.js': 'src/colorful.js' 89 | } 90 | }, 91 | interactive: { 92 | files: { 93 | 'web-mocha/index.html': 'web-mocha/_index.html' 94 | } 95 | } 96 | }, 97 | 98 | concat: { 99 | options: { 100 | banner: "<%= meta.banner %>", 101 | process: function( src, filepath ) { 102 | var bowerVersion = grunt.file.readJSON( "bower.json" ).version, 103 | npmVersion = grunt.file.readJSON( "package.json" ).version; 104 | 105 | if ( npmVersion === undefined || npmVersion === "" ) grunt.fail.fatal( "Version number not specified in package.json. Specify it in bower.json and package.json" ); 106 | if ( npmVersion !== bowerVersion ) grunt.fail.fatal( "Version numbers in package.json and bower.json are not identical. Make them match." + " " + npmVersion ); 107 | if ( ! /^\d+\.\d+.\d+$/.test( npmVersion ) ) grunt.fail.fatal( 'Version numbers in package.json and bower.json are not semantic. Provide a version number in the format n.n.n, e.g "1.2.3"' ); 108 | return src.replace( "__COMPONENT_VERSION_PLACEHOLDER__", npmVersion ); 109 | } 110 | }, 111 | build: { 112 | src: 'dist/colorful.js', 113 | dest: 'dist/colorful.js' 114 | } 115 | }, 116 | 117 | uglify: { 118 | options: { 119 | banner: "<%= meta.banner %>", 120 | mangle: { 121 | except: ['jQuery', 'Zepto', 'Backbone', '_'] 122 | }, 123 | sourceMap: true 124 | }, 125 | core: { 126 | src: 'dist/colorful.js', 127 | dest: 'dist/colorful.min.js' 128 | } 129 | }, 130 | 131 | karma: { 132 | options: { 133 | configFile: 'karma.conf.js', 134 | browsers: ['PhantomJS'], 135 | port: KARMA_PORT 136 | }, 137 | test: { 138 | reporters: ['progress'], 139 | singleRun: true 140 | }, 141 | build: { 142 | reporters: ['progress'], 143 | singleRun: true 144 | } 145 | }, 146 | 147 | jshint: { 148 | components: { 149 | // Workaround for merging .jshintrc with Gruntfile options, see http://goo.gl/Of8QoR 150 | options: grunt.util._.merge({ 151 | globals: { 152 | // Add vars which are shared between various sub-components 153 | // (before concatenation makes them local) 154 | } 155 | }, grunt.file.readJSON('.jshintrc')), 156 | files: { 157 | src: ['src/**/*.js'] 158 | } 159 | }, 160 | concatenated: { 161 | options: grunt.util._.merge({ 162 | // Suppressing 'W034: Unnecessary directive "use strict"'. 163 | // Redundant nested "use strict" is ok in concatenated file, 164 | // no adverse effects. 165 | '-W034': true 166 | }, grunt.file.readJSON('.jshintrc')), 167 | files: { 168 | src: 'dist/**/colorful.js' 169 | } 170 | } 171 | }, 172 | 173 | 'sails-linker': { 174 | options: { 175 | startTag: '', 176 | endTag: '', 177 | fileTmpl: '', 178 | // relative doesn't seem to have any effect, ever 179 | relative: true, 180 | // appRoot is a misnomer for "strip out this prefix from the file path before inserting", 181 | // should be stripPrefix 182 | appRoot: '' 183 | }, 184 | interactive_spec: { 185 | options: { 186 | startTag: '', 187 | endTag: '' 188 | }, 189 | files: { 190 | // the target file is changed in place; for generating copies, run preprocess first 191 | 'web-mocha/index.html': ['spec/**/*.+(spec|test|tests).js'] 192 | } 193 | } 194 | }, 195 | 196 | // Use `concurrent` to run multiple continuous, blocking task simultaneously. 197 | // 198 | // Use cases: 199 | // 200 | // - You can combine a `watch` or `focus` task, which runs forever, with other non-ending tasks. One such task is 201 | // `compass`, which provides an independent watch functionality of its own. It runs forever if that watch is used. 202 | // 203 | // - You can combine a task like `weinre`, which runs forever, with a task which must not start before it, e.g. 204 | // `open` (opening a browser and navigating to the Weinre page). 205 | // 206 | // NB: 207 | // 208 | // - `concurrent` is not needed for running `watch` with multiple targets - use `focus` for that. 209 | // - Compass builds could be made part of a Grunt task which is kicked off by Grunt `watch`, rather than Compass' 210 | // own watch functionality. The setup would be easier because `concurrent` is not needed then. But that setup has 211 | // proven to be a major annoyance because it is very, very slow indeed. 212 | concurrent: { 213 | options: { 214 | logConcurrentOutput: true 215 | }, 216 | 217 | // `weinre`/`open` example 218 | debug: { 219 | tasks: ['weinre:debug', 'open:debug'] 220 | }, 221 | 222 | // `focus`/`compass` examples 223 | demo: { 224 | tasks: ['compass:watchDemo', 'focus:demo'] 225 | }, 226 | demoCi: { 227 | tasks: ['compass:watchDemo', 'focus:demoCi'] 228 | }, 229 | demoCiDirty: { 230 | tasks: ['compass:watchDemo', 'focus:demoCiDirty'] 231 | } 232 | }, 233 | 234 | requirejs : { 235 | unifiedBuild : { 236 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/unified/build-config.js', false ) 237 | }, 238 | splitBuildVendor : { 239 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/vendor-config.js', false ) 240 | }, 241 | splitBuildApp : { 242 | options : getRequirejsBuildProfile( 'demo/amd/rjs/config/jsbin-parts/app-config.js', false ) 243 | } 244 | }, 245 | 246 | // Use focus to run Grunt watch with a hand-picked set of simultaneous watch targets. 247 | focus: { 248 | demo: { 249 | include: ['livereloadDemo'] 250 | }, 251 | demoCi: { 252 | include: ['build', 'livereloadDemo'] 253 | }, 254 | demoCiDirty: { 255 | include: ['buildDirty', 'livereloadDemo'] 256 | } 257 | }, 258 | 259 | // Use watch to monitor files for changes, and to kick off a task then. 260 | watch: { 261 | options: { 262 | nospawn: false 263 | }, 264 | // Live-reloads the web page when the source files or the spec files change. Meant for test pages. 265 | livereloadTest: { 266 | options: { 267 | livereload: LIVERELOAD_PORT 268 | }, 269 | files: WATCHED_FILES_SRC.concat( WATCHED_FILES_SPEC ) 270 | }, 271 | // Live-reloads the web page when the dist files or the demo files change. Meant for demo pages. 272 | livereloadDemo: { 273 | options: { 274 | livereload: LIVERELOAD_PORT 275 | }, 276 | files: WATCHED_FILES_DEMO.concat( WATCHED_FILES_DIST ) 277 | }, 278 | // Runs the "build" task (ie, runs linter and tests, then compiles the dist files) when the source files or the 279 | // spec files change. Meant for continuous integration tasks ("ci", "demo-ci"). 280 | build: { 281 | tasks: ['build'], 282 | files: WATCHED_FILES_SRC.concat( WATCHED_FILES_SPEC ) 283 | }, 284 | // Runs the "build-dirty" task (ie, compiles the dist files without running linter and tests) when the source 285 | // files change. Meant for "dirty" continuous integration tasks ("ci-dirty", "demo-ci-dirty"). 286 | buildDirty: { 287 | tasks: ['build-dirty'], 288 | files: WATCHED_FILES_SRC 289 | } 290 | }, 291 | 292 | // Spins up a web server. 293 | connect: { 294 | options: { 295 | port: HTTP_PORT, 296 | // For restricting access to localhost only, change the hostname from '*' to 'localhost' 297 | hostname: '*', 298 | open: true, 299 | base: '.' 300 | }, 301 | livereload: { 302 | livereload: LIVERELOAD_PORT 303 | }, 304 | test: { 305 | options: { 306 | open: 'http://localhost:<%= connect.options.port %>/web-mocha/', 307 | livereload: LIVERELOAD_PORT 308 | } 309 | }, 310 | testNoReload: { 311 | options: { 312 | open: 'http://localhost:<%= connect.options.port %>/web-mocha/', 313 | keepalive: true 314 | } 315 | }, 316 | demo: { 317 | options: { 318 | open: 'http://localhost:<%= connect.options.port %>/demo/', 319 | livereload: LIVERELOAD_PORT 320 | } 321 | } 322 | }, 323 | 324 | replace: { 325 | version: { 326 | src: ['bower.json', 'package.json'], 327 | overwrite: true, 328 | replacements: [{ 329 | from: /"version"\s*:\s*"((\d+\.\d+\.)(\d+))"\s*,/, 330 | to: function (matchedWord, index, fullText, regexMatches) { 331 | var version = grunt.option('inc') ? regexMatches[1] + (parseInt(regexMatches[2], 10) + 1) : grunt.option('to'); 332 | 333 | if (version === undefined) grunt.fail.fatal('Version number not specified. Use the --to option, e.g. --to=1.2.3, or the --inc option to increment the revision'); 334 | if (typeof version !== "string") grunt.fail.fatal('Version number is not a string. Provide a semantic version number, e.g. --to=1.2.3'); 335 | if (!/^\d+\.\d+.\d+$/.test(version)) grunt.fail.fatal('Version number is not semantic. Provide a version number in the format n.n.n, e.g. --to=1.2.3'); 336 | 337 | grunt.log.writeln('Modifying file: Changing the version number from ' + regexMatches[0] + ' to ' + version); 338 | return '"version": "' + version + '",'; 339 | } 340 | }] 341 | } 342 | }, 343 | getver: { 344 | files: ['bower.json', 'package.json'] 345 | } 346 | }); 347 | 348 | grunt.loadNpmTasks('grunt-preprocess'); 349 | grunt.loadNpmTasks('grunt-contrib-concat'); 350 | grunt.loadNpmTasks('grunt-concurrent'); 351 | grunt.loadNpmTasks('grunt-contrib-jshint'); 352 | grunt.loadNpmTasks('grunt-contrib-uglify'); 353 | grunt.loadNpmTasks('grunt-karma'); 354 | grunt.loadNpmTasks('grunt-contrib-watch'); 355 | grunt.loadNpmTasks('grunt-contrib-connect'); 356 | grunt.loadNpmTasks('grunt-sails-linker'); 357 | grunt.loadNpmTasks('grunt-text-replace'); 358 | grunt.loadNpmTasks('grunt-contrib-requirejs'); 359 | grunt.loadNpmTasks('grunt-focus'); 360 | 361 | grunt.registerTask('lint', ['jshint:components']); 362 | grunt.registerTask('hint', ['jshint:components']); // alias 363 | grunt.registerTask('test', ['jshint:components', 'karma:test']); 364 | grunt.registerTask('webtest', ['preprocess:interactive', 'sails-linker:interactive_spec', 'connect:testNoReload']); 365 | grunt.registerTask('interactive', ['preprocess:interactive', 'sails-linker:interactive_spec', 'connect:test', 'watch:livereloadTest']); 366 | grunt.registerTask('demo', ['connect:demo', 'focus:demo']); 367 | grunt.registerTask('build', ['jshint:components', 'karma:build', 'preprocess:build', 'concat', 'uglify', 'jshint:concatenated', 'requirejs']); 368 | grunt.registerTask('ci', ['build', 'watch:build']); 369 | grunt.registerTask('setver', ['replace:version']); 370 | grunt.registerTask('getver', function () { 371 | grunt.config.get('getver.files').forEach(function (file) { 372 | var config = grunt.file.readJSON(file); 373 | grunt.log.writeln('Version number in ' + file + ': ' + config.version); 374 | }); 375 | }); 376 | 377 | // Special tasks, not mentioned in Readme documentation: 378 | // 379 | // - requirejs: 380 | // creates build files for the AMD demo with r.js 381 | // - build-dirty: 382 | // builds the project without running checks (no linter, no tests) 383 | // - ci-dirty: 384 | // builds the project without running checks (no linter, no tests) on every source change 385 | // - demo-ci: 386 | // Runs the demo (= "demo" task), and also rebuilds the project on every source change (= "ci" task) 387 | // - demo-ci-dirty: 388 | // Runs the demo (= "demo" task), and also rebuilds the project "dirty", without tests or linter, on every source 389 | // change (= "ci-dirty" task) 390 | grunt.registerTask('build-dirty', ['preprocess:build', 'concat', 'uglify', 'requirejs']); 391 | grunt.registerTask('ci-dirty', ['build-dirty', 'watch:buildDirty']); 392 | grunt.registerTask('demo-ci', ['build', 'connect:demo', 'focus:demoCi']); 393 | grunt.registerTask('demo-ci-dirty', ['build-dirty', 'connect:demo', 'focus:demoCiDirty']); 394 | 395 | // Make 'build' the default task. 396 | grunt.registerTask('default', ['build']); 397 | 398 | 399 | }; 400 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Colorful.js 2 | 3 | [Setup][setup] – [Defining a color][color definition] – [Output][output] – [Comparisons][comparisons] – [Queries][queries] – [Validation][validation] – [Build and test][build] 4 | 5 | Colorful.js is a tiny component which creates color objects from a variety of input formats. It outputs color in as many formats, checks if colors are equal, and provides insights into other properties, like transparency. 6 | 7 | In a nutshell: 8 | 9 | ```js 10 | var color = new Color( "rgb(0, 50%, 50%)" ); 11 | 12 | color.asHexUC() // => "#008080" 13 | color.asRgba() // => "rgba(0, 128, 128, 1)" 14 | color.asRgbArray() // => [0, 128, 128] 15 | // etc 16 | 17 | color.equals( "teal" ) // => true 18 | color.equals( { r: 0, g: 128, b: 128 } ) // => true 19 | 20 | color.isTransparent() // => false 21 | ``` 22 | 23 | ## Dependencies and setup 24 | 25 | [Underscore][] is the only dependency. Include colorful.js after Underscore is loaded. 26 | 27 | Colorful.js can be loaded in the global context of the browser, or as a module (AMD, CJS/Node). The module contains the [Color][color definition] class: 28 | 29 | ```js 30 | var Color = require( "colorful.js" ).Color; 31 | ``` 32 | 33 | As a browser global, the Color class is attached to the `window` directly - simply use `Color`. 34 | 35 | The stable version of Colorful.js is available in the `dist` directory ([dev][dist-dev], [prod][dist-prod]). If you use Bower, fetch the files with `bower install colorful.js`. With npm, it is `npm install colorful.js`. 36 | 37 | ## Defining a color 38 | 39 | A color object can be created from an array, object, or a host of string color formats. The `new` keyword is optional. 40 | 41 | ```js 42 | var c1 = new Color( "rgb(0, 50%, 50%)" ), 43 | c2 = Color( "rgb(0, 50%, 50%)" ); // identical 44 | ``` 45 | 46 | ### Input formats 47 | 48 | All of the formats below are recognized. 49 | 50 | ```js 51 | Color( "turquoise" ) // CSS color keywords 52 | Color( "transparent" ) // CSS value "transparent" 53 | 54 | Color( "#FF90A3" ) // long hex format, upper case 55 | Color( "#ff90a3" ) // long hex format, lower case 56 | Color( "#09A" ) // short hex format, upper case 57 | Color( "#09a" ) // short hex format, lower case 58 | 59 | Color( "rgb(0, 153, 170)" ) // rgb() string 60 | Color( "rgba(0, 153, 170, 1)" ) // rgba() string 61 | Color( "rgb(0%, 6.152%, 67%)" ) // rgb() string, percentages 62 | Color( "rgba(0%, 6.152%, 67%, 0.3)" ) // rgba() string, RGB percentages 63 | 64 | Color( [4, 144.23, 163] ) // RGB array 65 | Color( [4, 144.23, 163, 0.5] ) // RGBA array 66 | Color( [".623%", "100.0%", "67.258%"] ) // RGB array, percentages 67 | Color( [".623%", "100%", "67.2%", 0.5] ) // RGBA array, RGB percentages 68 | 69 | Color( { r: 4, g: 144.23, b: 163 } ) // RGB hash 70 | Color( { r: 4, g: 144.23, b: 163, a: 0.5 } ) // RGBA hash 71 | Color( { r: ".623%", g: "100.0%", b: "67.2%" } ) // RGB hash, percentages 72 | Color( { r: "0.6%", g: "10%", b: "7%", a: 0.5 } ) // RGBA hash, RGB percentages 73 | 74 | // In an array or a hash, numeric and percentage values can be mixed: 75 | Color( ["0.6%", 144.23, "7%"] ) 76 | Color( { r: "0.6%", g: 144.23, b: "7%", a: 0.5 } ) 77 | ``` 78 | 79 | The obscure `AgColor` format used by Adobe Lightroom is [also supported][agcolor-support]. 80 | 81 | ### Invalid color values 82 | 83 | Arguments which don't match any of the patterns above are not considered to be a color. 84 | 85 | ```js 86 | var c = Color( "foo" ); 87 | c.isColor() // => false 88 | ``` 89 | 90 | Invalid arguments don't cause an error when the color object is created. But they might throw an error later on, e.g. if a method is called which requires a valid color. 91 | 92 | ```js 93 | var c = Color( "foo" ), 94 | rgbFormat = c.asRgb(); // throws an error 95 | ``` 96 | 97 | ## Output 98 | 99 | All of the output methods require that the input represents a valid color. Non-color inputs throw an error when an output method is called. 100 | 101 | In addition, an error is thrown if the color is transparent but the output format doesn't include an alpha channel: 102 | 103 | ```js 104 | var color = Color( "rgba(255, 128, 255, 0.5)" ), // transparent 105 | rgba = color.asRgba(), // works 106 | rgb = color.asRgb(), // throws an error 107 | hex = color.asHex(); // throws an error 108 | ``` 109 | 110 | ### `asHex( [options] )` 111 | 112 | Returns the color as a hex string (`#RRGGBB`), in lower case and with a leading `#` by default. 113 | 114 | Output can be configured with the boolean options `lowerCase`, or `upperCase` , and `prefix` (`true`: with leading `#`). 115 | 116 | ```js 117 | var color = Color( "darkturquoise" ); 118 | 119 | color.asHex() // => "#00ced1" 120 | color.asHex( { lowerCase: false, prefix: false } ) // => "00CED1" 121 | color.asHex( { upperCase: true } ) // => "#00CED1" 122 | ``` 123 | 124 | ### `asHexLC()` 125 | 126 | Returns the color as a lower-case hex string. 127 | 128 | ```js 129 | Color( "darkturquoise" ).asHexLC() // => "#00ced1" 130 | ``` 131 | 132 | ### `asHexUC()` 133 | 134 | Returns the color as an upper-case hex string. 135 | 136 | ```js 137 | Color( "darkturquoise" ).asHexUC() // => "#00CED1" 138 | ``` 139 | 140 | ### `asRgb()` 141 | 142 | Returns the color as an `rgb()` string. Each channel is represented by an integer on a scale of 0 to 255. 143 | 144 | ```js 145 | Color( "darkturquoise" ).asRgb() // => "rgb(0, 206, 209)" 146 | ``` 147 | 148 | ### `asRgbPercent( [options] )` 149 | 150 | Returns the color as an `rgb()` string. Each channel is represented by a percentage. The number of decimal digits can be specified with `options.precision`. 151 | 152 | The precision can be set to a number in the range of 0 to 20, or to the string `"max"` for maximum precision. By default, percentages are returned as integers (precision: 0). 153 | 154 | ```js 155 | var color = Color( "darkturquoise" ); 156 | 157 | color.asRgbPercent() // => "rgb(0%, 81%, 82%)" 158 | color.asRgbPercent( { precision: 3 } ) // => "rgb(0%, 80.784%, 81.961%)" 159 | color.asRgbPercent( { precision: "max" } ) // => "rgb(0%, 80.7843137254902%, 81.96078431372548%)" 160 | ``` 161 | 162 | ### `asRgbArray( [options] )` 163 | 164 | Returns the color as an RGB array `[R, G, B]`. Each channel is represented by an integer on a scale of 0 to 255. 165 | 166 | For greater accuracy, use the `precision` option, [as in `asRgbPercent()`][format-rgb-percent]. 167 | 168 | ```js 169 | Color( "darkturquoise" ).asRgbArray() // => [0, 206, 209] 170 | ``` 171 | 172 | ### `asRgba()` 173 | 174 | Returns the color as an `rgba()` string. Each RGB channel is represented by an integer on a scale of 0 to 255, and the alpha channel on a scale of 0 to 1. 175 | 176 | ```js 177 | Color( "darkturquoise" ).asRgba() // => "rgba(0, 206, 209, 1)" 178 | ``` 179 | 180 | ### `asRgbaPercent( [options] )` 181 | 182 | Returns the color as an `rgba()` string. Each RGB channel is represented by a percentage, and the alpha channel on a scale of 0 to 1. 183 | 184 | Percentages are returned as integers by default. For greater accuracy, use the `precision` option, [as in `asRgbPercent()`][format-rgb-percent]. 185 | 186 | ```js 187 | var color = Color( "darkturquoise" ); 188 | 189 | color.asRgbPercent() // => "rgba(0%, 81%, 82%, 1)" 190 | color.asRgbPercent( { precision: 3 } ) // => "rgba(0%, 80.784%, 81.961%, 1)" 191 | ``` 192 | 193 | ### `asRgbaArray( [options] )` 194 | 195 | Returns the color as an RGBA array `[R, G, B, A]`. Each RGB channel is represented on a scale of 0 to 255, and the alpha channel on a scale of 0 to 1. 196 | 197 | RGB channels are returned as integers by default. For greater accuracy, use the `precision` option, [as in `asRgbPercent()`][format-rgb-percent]. 198 | 199 | ```js 200 | Color( "darkturquoise" ).asRgbaArray() // => [0, 206, 209, 1] 201 | ``` 202 | 203 | ### `asComputed()` 204 | 205 | Returns the color as a browser would. When querying the [computed value of a color][mdn-color-property], a browser returns it as an `rgb()` string if the color is opaque, and as an `rgba()` string if the color is transparent. 206 | 207 | ```js 208 | Color( [0, 206, 209, 1.0] ).asComputed() // => "rgb(0, 206, 209)" 209 | Color( [0, 206, 209, 0.5] ).asComputed() // => "rgba(0, 206, 209, 0.5)" 210 | ``` 211 | 212 | ## Comparisons 213 | 214 | ### `equals( otherColor, [options] )` 215 | 216 | Returns whether or not a color equals another, with an optional tolerance. 217 | 218 | The other color can be passed in [any of the formats][input formats] acceptable to `Color`, or as a `Color` object. 219 | 220 | ```js 221 | var color = Color( "#00FF7F" ); 222 | 223 | color.equals( "rgba(0, 255, 127, 1)" ) // => true 224 | color.equals( "springgreen" ) // => true 225 | color.equals( Color( [0, 255, 127] ) ) // => true 226 | ``` 227 | 228 | #### Rounding 229 | 230 | Color channels are converted to a 0-255 RGB scale _and rounded to integers_ before comparison. 231 | 232 | That loss of precision is deliberate. It is supposed to compensate for insignificant fractional differences which can easily occur during color computations (especially when colors are expressed in percentages). 233 | 234 | ```js 235 | var c1 = Color( "rgb(0, 128, 254)" ); 236 | 237 | // When expressed in percentages with a precision of 10 decimals, c1 would be 238 | // 0%, 50.1960784314%, 99.6078431373% 239 | c1.equals( "rgb(0%, 50%, 99.6%)" ) // => true, despite not matching exactly 240 | 241 | // Colors expressed in full percentage points (integers) are too coarse to 242 | // match some colors, though. 243 | c1.equals( "rgb(0%, 50%, 100%)" ) // => false, maps to 0, 128, 255 244 | c1.equals( "rgb(0%, 50%, 99%)" ) // => false, maps to 0, 128, 252 245 | ``` 246 | 247 | #### `options.tolerance` 248 | 249 | A tolerance for the RGB channels can be specified as an option. 250 | 251 | The tolerance is meant to allow additional leeway for rounding errors. It can also be used for insignificant differences in color which are not visible to the human eye. 252 | 253 | The tolerance does not apply to the alpha channel. It must always match exactly. 254 | 255 | ```js 256 | var c1 = Color( [0, 128, 254] ); 257 | c1.equals( [0, 127, 255], { tolerance: 1 } ) // => true 258 | 259 | // Tolerance is not applied to the alpha channel 260 | var c2 = Color( [0, 128, 254, 0.50000000001] ); 261 | c2.equals( [0, 128, 254, 0.5], { tolerance: 1 } ) // => false 262 | ``` 263 | 264 | #### Non-colors 265 | 266 | The method returns true only if both values represent equal _colors_. Non-colors are never considered equal, even if they are created from the same input. 267 | 268 | ```js 269 | var c1 = Color( "foo" ), 270 | c2 = Color( "foo" ); 271 | 272 | c1.equals( c2 ) // => false 273 | ``` 274 | 275 | ### `strictlyEquals( otherColor )` 276 | 277 | Returns whether or not a color exactly equals another. 278 | 279 | No [rounding to integer values][equals-rounding] is applied here. 280 | 281 | The other color can be passed in [any of the formats][input formats] acceptable to `Color`, or as a `Color` object. 282 | 283 | Non-colors are never considered equal, even if they are created from the same value. 284 | 285 | ```js 286 | var c1 = Color( "rgb(0, 128, 254)" ); 287 | 288 | c1.strictlyEquals( "#0080FE" ) // => true 289 | 290 | // When expressed in percentages with a precision of 10 decimals, c1 would be 291 | // 0%, 50.1960784314%, 99.6078431373% 292 | c1.strictlyEquals( "rgb(0%, 50%, 99.6%)" ) // => false, does not match exactly 293 | c1.equals( "rgb(0%, 50%, 99.6%)" ) // => true, see equals() 294 | 295 | ``` 296 | 297 | ## Queries 298 | 299 | ### `isColor()` 300 | 301 | Returns whether or not the color object represents a valid color. 302 | 303 | ```js 304 | Color( "#AABBCC" ).isColor() // => true 305 | Color( "foo" ).isColor() // => false 306 | ``` 307 | 308 | ### `isOpaque()` 309 | 310 | Returns whether or not the color object represents a valid color and is opaque. 311 | 312 | ```js 313 | Color( "#AABBCC" ).isOpaque() // => true 314 | Color( "rgba(0%, 60%, 67%, .5)" ).isOpaque() // => false 315 | Color( "foo" ).isOpaque() // => false 316 | ``` 317 | 318 | ### `isTransparent()` 319 | 320 | Returns whether or not the color object represents a valid color and is transparent. 321 | 322 | ```js 323 | Color( "rgba(0%, 60%, 67%, .5)" ).isTransparent() // => true 324 | Color( "foo" ).isTransparent() // => false 325 | ``` 326 | 327 | ## Validation 328 | 329 | ### `ensureColor()` 330 | 331 | Throws an error if the color object does not represent a valid color. 332 | 333 | ```js 334 | Color( "#AABBCC" ).ensureColor() // ok 335 | Color( "foo" ).ensureColor() // throws an error 336 | ``` 337 | 338 | ### `ensureOpaque()` 339 | 340 | Throws an error if the color object does not represent a valid, opaque color. 341 | 342 | ```js 343 | Color( "#AABBCC" ).ensureOpaque() // ok 344 | Color( "rgba(0%, 60%, 67%, .5)" ).ensureOpaque() // throws an error 345 | Color( "foo" ).ensureOpaque() // throws an error 346 | ``` 347 | 348 | ### `ensureTransparent()` 349 | 350 | Throws an error if the color object does not represent a valid, transparent color. 351 | 352 | ```js 353 | Color( "rgba(0%, 60%, 67%, .5)" ).ensureTransparent() // ok 354 | Color( "#AABBCC" ).ensureTransparent() // throws an error 355 | Color( "foo" ).ensureTransparent() // throws an error 356 | ``` 357 | 358 | ## `AgColor` support 359 | 360 | The obscure `AgColor` format, used internally by Adobe Lightroom, is also recognized. 361 | 362 | Hardly anyone will need it, but for the sake of completeness, it should be mentioned here. 363 | 364 | `AgColor` is defined in RGBA format, with the channels expressed as fractions on a scale of 0 to 1. `AgColor` is supported both as an input format 365 | 366 | ```js 367 | var color = Color( "AgColor(0, 1, 0.672587491, 0.5)" ); 368 | ``` 369 | 370 | and as an output format 371 | 372 | ```js 373 | color.asAgColor() // => "AgColor( 0, 1, 0.672587491, 0.5 )" 374 | ``` 375 | 376 | ## Build process and tests 377 | 378 | If you'd like to fix, customize or otherwise improve the project: here are your tools. 379 | 380 | ### Setup 381 | 382 | [npm][] sets up the environment for you. 383 | 384 | - The only thing you've got to have on your machine (besides Git) is [Node.js]. Download the installer [here][Node.js]. 385 | - Clone the project and open a command prompt in the project directory. 386 | - Run the setup with `npm run setup`. 387 | - Make sure the Grunt CLI is installed as a global Node module. If not, or if you are not sure, run `npm install -g grunt-cli` from the command prompt. 388 | 389 | ### Running tests, creating a new build 390 | 391 | #### Considerations for testing 392 | 393 | To run the tests on remote clients (e.g. mobile devices), start a web server with `grunt interactive` and visit `http://[your-host-ip]:9400/web-mocha/` with the client browser. Running the tests in a browser like this is slow, so it might make sense to disable the power-save/sleep/auto-lock timeout on mobile devices. Use `grunt test` (see below) for faster local testing. 394 | 395 | #### Tool chain and commands 396 | 397 | The test tool chain: [Grunt][] (task runner), [Karma][] (test runner), [Mocha][] (test framework), [Chai][] (assertion library), [Sinon][] (mocking framework). The good news: you don't need to worry about any of this. 398 | 399 | A handful of commands manage everything for you: 400 | 401 | - Run the tests in a terminal with `grunt test`. 402 | - Run the tests in a browser interactively, live-reloading the page when the source or the tests change: `grunt interactive`. 403 | - If the live reload bothers you, you can also run the tests in a browser without it: `grunt webtest`. 404 | - Run the linter only with `grunt lint` or `grunt hint`. (The linter is part of `grunt test` as well.) 405 | - Build the dist files (also running tests and linter) with `grunt build`, or just `grunt`. 406 | - Build continuously on every save with `grunt ci`. 407 | - Change the version number throughout the project with `grunt setver --to=1.2.3`. Or just increment the revision with `grunt setver --inc`. (Remember to rebuild the project with `grunt` afterwards.) 408 | - `grunt getver` will quickly tell you which version you are at. 409 | 410 | Finally, if need be, you can set up a quick demo page to play with the code. First, edit the files in the `demo` directory. Then display `demo/index.html`, live-reloading your changes to the code or the page, with `grunt demo`. Libraries needed for the demo/playground should go into the Bower dev dependencies – in the project-wide `bower.json` – or else be managed by the dedicated `bower.json` in the demo directory. 411 | 412 | _The `grunt interactive` and `grunt demo` commands spin up a web server, opening up the **whole project** to access via http._ So please be aware of the security implications. You can restrict that access to localhost in `Gruntfile.js` if you just use browsers on your machine. 413 | 414 | ### Changing the tool chain configuration 415 | 416 | In case anything about the test and build process needs to be changed, have a look at the following config files: 417 | 418 | - `karma.conf.js` (changes to dependencies, additional test frameworks) 419 | - `Gruntfile.js` (changes to the whole process) 420 | - `web-mocha/_index.html` (changes to dependencies, additional test frameworks) 421 | 422 | New test files in the `spec` directory are picked up automatically, no need to edit the configuration for that. 423 | 424 | ## Release notes 425 | 426 | ### v0.3.0 427 | 428 | - Added `asComputed()` 429 | 430 | ### v0.2.0 431 | 432 | - Renamed array output methods 433 | - Allowed decimals for numeric RGB input (in `rgb()`, `rgba()` strings, arrays, hashes) 434 | - Added a `precision` option to `asRgbArray()`, `asRgbaArray()` 435 | - Fixed accidental rounding of percentages in `strictlyEquals()` 436 | 437 | ### v0.1.0 438 | 439 | - Initial public release 440 | 441 | ## License 442 | 443 | MIT. 444 | 445 | Copyright (c) 2016-2025 Michael Heim. 446 | 447 | Code in the data provider test helper: (c) 2014 Box, Inc., Apache 2.0 license. [See file][data-provider.js]. 448 | 449 | [dist-dev]: https://raw.github.com/hashchange/colorful.js/master/dist/colorful.js "colorful.js" 450 | [dist-prod]: https://raw.github.com/hashchange/colorful.js/master/dist/colorful.min.js "colorful.min.js" 451 | 452 | [setup]: #dependencies-and-setup "Setup" 453 | [color definition]: #defining-a-color 454 | [output]: #output 455 | [comparisons]: #comparisons 456 | [queries]: #queries 457 | [validation]: #validation 458 | [build]: #build-process-and-tests "Build process and tests" 459 | 460 | [input formats]: #input-formats 461 | [equals-rounding]: #rounding 462 | [agcolor-support]: #agcolor-support 463 | [format-rgb-percent]: #asrgbpercent-options- 464 | 465 | [mdn-color-property]: https://developer.mozilla.org/en-US/docs/Web/CSS/color "MDN: color property" 466 | 467 | [data-provider.js]: https://github.com/hashchange/colorful.js/blob/master/spec/helpers/data-provider.js "Source code of data-provider.js" 468 | 469 | [Underscore]: http://underscorejs.org/ "Underscore.js" 470 | [Node.js]: http://nodejs.org/ "Node.js" 471 | [Bower]: http://bower.io/ "Bower: a package manager for the web" 472 | [npm]: https://npmjs.org/ "npm: Node Packaged Modules" 473 | [Grunt]: http://gruntjs.com/ "Grunt: The JavaScript Task Runner" 474 | [Karma]: http://karma-runner.github.io/ "Karma – Spectacular Test Runner for Javascript" 475 | [Mocha]: http://mochajs.org/ "Mocha – the fun, simple, flexible JavaScript test framework" 476 | [Chai]: http://chaijs.com/ "Chai: a BDD / TDD assertion library" 477 | [Sinon]: http://sinonjs.org/ "Sinon.JS – Versatile standalone test spies, stubs and mocks for JavaScript" 478 | [JSHint]: http://www.jshint.com/ "JSHint, a JavaScript Code Quality Tool" -------------------------------------------------------------------------------- /src/colorful.js: -------------------------------------------------------------------------------- 1 | ;( function ( root, factory ) { 2 | "use strict"; 3 | 4 | // UMD for a Backbone plugin. Supports AMD, Node.js, CommonJS and globals. 5 | // 6 | // - Code lives in the Backbone namespace. 7 | // - The module does not export a meaningful value. 8 | // - The module does not create a global. 9 | 10 | var supportsExports = typeof exports === "object" && exports && !exports.nodeType && typeof module === "object" && module && !module.nodeType; 11 | 12 | // AMD: 13 | // - Some AMD build optimizers like r.js check for condition patterns like the AMD check below, so keep it as is. 14 | // - Check for `exports` after `define` in case a build optimizer adds an `exports` object. 15 | // - The AMD spec requires the dependencies to be an array **literal** of module IDs. Don't use a variable there, 16 | // or optimizers may fail. 17 | if ( typeof define === "function" && typeof define.amd === "object" && define.amd ) { 18 | 19 | // AMD module 20 | define( [ "exports", "underscore" ], factory ); 21 | 22 | } else if ( supportsExports ) { 23 | 24 | // Node module, CommonJS module 25 | factory( exports, require( "underscore" ) ); 26 | 27 | } else { 28 | 29 | // Global (browser or Rhino) 30 | factory( root, _ ); 31 | 32 | } 33 | 34 | }( this, function ( exports, _ ) { 35 | "use strict"; 36 | 37 | /** Source for CSS color name mapping: https://github.com/dfcreative/color-name 38 | * @readonly 39 | */ 40 | var CssColorNames = { 41 | "aliceblue": [240, 248, 255], 42 | "antiquewhite": [250, 235, 215], 43 | "aqua": [0, 255, 255], 44 | "aquamarine": [127, 255, 212], 45 | "azure": [240, 255, 255], 46 | "beige": [245, 245, 220], 47 | "bisque": [255, 228, 196], 48 | "black": [0, 0, 0], 49 | "blanchedalmond": [255, 235, 205], 50 | "blue": [0, 0, 255], 51 | "blueviolet": [138, 43, 226], 52 | "brown": [165, 42, 42], 53 | "burlywood": [222, 184, 135], 54 | "cadetblue": [95, 158, 160], 55 | "chartreuse": [127, 255, 0], 56 | "chocolate": [210, 105, 30], 57 | "coral": [255, 127, 80], 58 | "cornflowerblue": [100, 149, 237], 59 | "cornsilk": [255, 248, 220], 60 | "crimson": [220, 20, 60], 61 | "cyan": [0, 255, 255], 62 | "darkblue": [0, 0, 139], 63 | "darkcyan": [0, 139, 139], 64 | "darkgoldenrod": [184, 134, 11], 65 | "darkgray": [169, 169, 169], 66 | "darkgreen": [0, 100, 0], 67 | "darkgrey": [169, 169, 169], 68 | "darkkhaki": [189, 183, 107], 69 | "darkmagenta": [139, 0, 139], 70 | "darkolivegreen": [85, 107, 47], 71 | "darkorange": [255, 140, 0], 72 | "darkorchid": [153, 50, 204], 73 | "darkred": [139, 0, 0], 74 | "darksalmon": [233, 150, 122], 75 | "darkseagreen": [143, 188, 143], 76 | "darkslateblue": [72, 61, 139], 77 | "darkslategray": [47, 79, 79], 78 | "darkslategrey": [47, 79, 79], 79 | "darkturquoise": [0, 206, 209], 80 | "darkviolet": [148, 0, 211], 81 | "deeppink": [255, 20, 147], 82 | "deepskyblue": [0, 191, 255], 83 | "dimgray": [105, 105, 105], 84 | "dimgrey": [105, 105, 105], 85 | "dodgerblue": [30, 144, 255], 86 | "firebrick": [178, 34, 34], 87 | "floralwhite": [255, 250, 240], 88 | "forestgreen": [34, 139, 34], 89 | "fuchsia": [255, 0, 255], 90 | "gainsboro": [220, 220, 220], 91 | "ghostwhite": [248, 248, 255], 92 | "gold": [255, 215, 0], 93 | "goldenrod": [218, 165, 32], 94 | "gray": [128, 128, 128], 95 | "green": [0, 128, 0], 96 | "greenyellow": [173, 255, 47], 97 | "grey": [128, 128, 128], 98 | "honeydew": [240, 255, 240], 99 | "hotpink": [255, 105, 180], 100 | "indianred": [205, 92, 92], 101 | "indigo": [75, 0, 130], 102 | "ivory": [255, 255, 240], 103 | "khaki": [240, 230, 140], 104 | "lavender": [230, 230, 250], 105 | "lavenderblush": [255, 240, 245], 106 | "lawngreen": [124, 252, 0], 107 | "lemonchiffon": [255, 250, 205], 108 | "lightblue": [173, 216, 230], 109 | "lightcoral": [240, 128, 128], 110 | "lightcyan": [224, 255, 255], 111 | "lightgoldenrodyellow": [250, 250, 210], 112 | "lightgray": [211, 211, 211], 113 | "lightgreen": [144, 238, 144], 114 | "lightgrey": [211, 211, 211], 115 | "lightpink": [255, 182, 193], 116 | "lightsalmon": [255, 160, 122], 117 | "lightseagreen": [32, 178, 170], 118 | "lightskyblue": [135, 206, 250], 119 | "lightslategray": [119, 136, 153], 120 | "lightslategrey": [119, 136, 153], 121 | "lightsteelblue": [176, 196, 222], 122 | "lightyellow": [255, 255, 224], 123 | "lime": [0, 255, 0], 124 | "limegreen": [50, 205, 50], 125 | "linen": [250, 240, 230], 126 | "magenta": [255, 0, 255], 127 | "maroon": [128, 0, 0], 128 | "mediumaquamarine": [102, 205, 170], 129 | "mediumblue": [0, 0, 205], 130 | "mediumorchid": [186, 85, 211], 131 | "mediumpurple": [147, 112, 219], 132 | "mediumseagreen": [60, 179, 113], 133 | "mediumslateblue": [123, 104, 238], 134 | "mediumspringgreen": [0, 250, 154], 135 | "mediumturquoise": [72, 209, 204], 136 | "mediumvioletred": [199, 21, 133], 137 | "midnightblue": [25, 25, 112], 138 | "mintcream": [245, 255, 250], 139 | "mistyrose": [255, 228, 225], 140 | "moccasin": [255, 228, 181], 141 | "navajowhite": [255, 222, 173], 142 | "navy": [0, 0, 128], 143 | "oldlace": [253, 245, 230], 144 | "olive": [128, 128, 0], 145 | "olivedrab": [107, 142, 35], 146 | "orange": [255, 165, 0], 147 | "orangered": [255, 69, 0], 148 | "orchid": [218, 112, 214], 149 | "palegoldenrod": [238, 232, 170], 150 | "palegreen": [152, 251, 152], 151 | "paleturquoise": [175, 238, 238], 152 | "palevioletred": [219, 112, 147], 153 | "papayawhip": [255, 239, 213], 154 | "peachpuff": [255, 218, 185], 155 | "peru": [205, 133, 63], 156 | "pink": [255, 192, 203], 157 | "plum": [221, 160, 221], 158 | "powderblue": [176, 224, 230], 159 | "purple": [128, 0, 128], 160 | "rebeccapurple": [102, 51, 153], 161 | "red": [255, 0, 0], 162 | "rosybrown": [188, 143, 143], 163 | "royalblue": [65, 105, 225], 164 | "saddlebrown": [139, 69, 19], 165 | "salmon": [250, 128, 114], 166 | "sandybrown": [244, 164, 96], 167 | "seagreen": [46, 139, 87], 168 | "seashell": [255, 245, 238], 169 | "sienna": [160, 82, 45], 170 | "silver": [192, 192, 192], 171 | "skyblue": [135, 206, 235], 172 | "slateblue": [106, 90, 205], 173 | "slategray": [112, 128, 144], 174 | "slategrey": [112, 128, 144], 175 | "snow": [255, 250, 250], 176 | "springgreen": [0, 255, 127], 177 | "steelblue": [70, 130, 180], 178 | "tan": [210, 180, 140], 179 | "teal": [0, 128, 128], 180 | "thistle": [216, 191, 216], 181 | "tomato": [255, 99, 71], 182 | "turquoise": [64, 224, 208], 183 | "violet": [238, 130, 238], 184 | "wheat": [245, 222, 179], 185 | "white": [255, 255, 255], 186 | "whitesmoke": [245, 245, 245], 187 | "yellow": [255, 255, 0], 188 | "yellowgreen": [154, 205, 50] 189 | }, 190 | 191 | _rxstrFraction = "(?:1(?:\\.0+)?|0?\\.\\d+|0)", 192 | rxstrAlpha = "(" + _rxstrFraction + ")", 193 | rxstrOptionalAlpha = "(" + _rxstrFraction + "?)", 194 | 195 | rxstrRgbChannelBase256 = "(255(?:\\.0+)?|25[0-4](?:\\.\\d+)?|2[0-4]\\d(?:\\.\\d+)?|(?:[0-1]?\\d)?\\d(?:\\.\\d+)?)", 196 | rxstrRgbChannelPercent = "(100(?:\\.0+)?|\\d{0,2}(?:\\.\\d+)|\\d{1,2})\\s*%", 197 | rxstrRgbChannelHexLC = "([a-f\\d]{2})", 198 | rxstrRgbChannelHexUC = "([A-F\\d]{2})", 199 | rxstrRgbChannelShortHexLC = "([a-f\\d])", 200 | rxstrRgbChannelShortHexUC = "([A-F\\d])", 201 | rxstrRgbChannelFraction = rxstrAlpha, 202 | 203 | rxRgbChannelBase256 = new RegExp( "^\\s*" + rxstrRgbChannelBase256 + "\\s*$" ), 204 | rxRgbChannelPercent = new RegExp( "^\\s*" + rxstrRgbChannelPercent + "\\s*$" ), 205 | rxAlphaChannel = new RegExp( "^\\s*" + rxstrAlpha + "\\s*$" ), 206 | 207 | rxHexLC = buildColorRx( "#", rxstrRgbChannelHexLC ), 208 | rxHexUC = buildColorRx( "#", rxstrRgbChannelHexUC ), 209 | rxHexShortLC = buildColorRx( "#", rxstrRgbChannelShortHexLC ), 210 | rxHexShortUC = buildColorRx( "#", rxstrRgbChannelShortHexUC ), 211 | 212 | rxRgbBase256 = buildColorRx( "rgb", rxstrRgbChannelBase256 ), 213 | rxRgbaBase256 = buildColorRx( "rgba", rxstrRgbChannelBase256, true ), 214 | 215 | rxRgbPercent = buildColorRx( "rgb", rxstrRgbChannelPercent ), 216 | rxRgbaPercent = buildColorRx( "rgba", rxstrRgbChannelPercent, true ), 217 | 218 | rxAgColor = buildColorRx( "AgColor", rxstrRgbChannelFraction, true ); 219 | 220 | 221 | /* 222 | * Rounding helpers 223 | */ 224 | 225 | /** 226 | * Adjusts a number to a given precision, working around the buggy floating-point math of Javascript. Works for 227 | * round, floor, ceil operations. 228 | * 229 | * Lifted from the Math.round entry of MDN. Minor changes without effect on the algorithm. 230 | * 231 | * @param {string} operation "round", "floor", "ceil" 232 | * @param {number} value 233 | * @param {number} [precision=0] can be negative: round( 104,-1 ) => 100 234 | * @returns {number} 235 | */ 236 | function _decimalAdjust ( operation, value, precision ) { 237 | var exp; 238 | 239 | if ( typeof precision === 'undefined' || +precision === 0 ) return Math[operation]( value ); 240 | 241 | value = +value; 242 | precision = +precision; 243 | 244 | // Return NaN if the value is not a number or the precision is not an integer 245 | if ( isNaN( value ) || !(typeof precision === 'number' && precision % 1 === 0) ) return NaN; 246 | 247 | exp = -precision; 248 | 249 | // Shift 250 | value = value.toString().split( 'e' ); 251 | value = Math[operation]( +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)) ); 252 | 253 | // Shift back 254 | value = value.toString().split( 'e' ); 255 | return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); 256 | } 257 | 258 | var Nums = {}; 259 | 260 | Nums.round = function ( value, precision ) { 261 | return _decimalAdjust( 'round', value, precision ); 262 | }; 263 | 264 | Nums.floor = function ( value, precision ) { 265 | return _decimalAdjust( 'floor', value, precision ); 266 | }; 267 | 268 | Nums.ceil = function ( value, precision ) { 269 | return _decimalAdjust( 'ceil', value, precision ); 270 | }; 271 | 272 | /** 273 | * Converts a number to a decimal string. Ensures that even very small numbers are returned in decimal notation, 274 | * rather than scientific notation. 275 | * 276 | * Numbers are allowed a maximum of 20 decimal digits when expressed in decimal notation (a limitation caused by the 277 | * Javascript function .toFixed()). Any digits beyond that limit are rounded. 278 | * 279 | * NB The function doesn't handle scientific notation for very large numbers because they don't occur in the context 280 | * of colour. 281 | * 282 | * @param {number} number 283 | * @returns {string} 284 | */ 285 | function toDecimalNotation ( number ) { 286 | var numStr = number + ""; 287 | 288 | // If we encounter scientific notation in the stringified number, we don't just modify parts of it. Instead, we 289 | // reconstruct the entire stringified number from scratch. (That's possible because we use a regex matching the 290 | // stringified number in full.) 291 | numStr = numStr.replace( /^(\d+)(?:\.(\d+))?e-(\d+)$/i, function ( match, intDigits, fractionalDigits, absExponent ) { 292 | var digits = +absExponent + ( fractionalDigits ? fractionalDigits.length : 0 ); 293 | 294 | // The argument for String.toFixed( digits ) is allowed to be between 0 and 20, so we have to limit the 295 | // range accordingly. 296 | if ( digits > 20 ) { 297 | Nums.round( number, 20 ); 298 | digits = 20; 299 | } 300 | 301 | return number.toFixed( digits ); 302 | } ); 303 | 304 | return numStr; 305 | } 306 | 307 | 308 | /* 309 | * Color parsing 310 | */ 311 | 312 | function buildColorRx ( prefix, rxRgbChannel, withAlpha ) { 313 | var isHex = prefix === "#", 314 | channelSeparator = isHex ? "" : "\\s*,\\s*", 315 | suffix = isHex ? "" : "\\s*\\)", 316 | rxChannels = [ rxRgbChannel, rxRgbChannel, rxRgbChannel ]; 317 | 318 | if ( withAlpha ) rxChannels.push( rxstrAlpha ); 319 | if ( !isHex ) prefix += "\\s*\\(\\s*"; 320 | 321 | return new RegExp( "^\\s*" + prefix + rxChannels.join( channelSeparator ) + suffix + "\\s*$" ); 322 | } 323 | 324 | function matchColor ( color, rx ) { 325 | var matches = color.match( rx ); 326 | return { 327 | isMatch: !!matches, 328 | rgb: matches && [ matches[1], matches[2], matches[3] ], 329 | a: matches && matches[4] 330 | }; 331 | } 332 | 333 | function getHexData ( color ) { 334 | // Long form #RRGGBB 335 | var data = matchColor( color, rxHexLC ); 336 | if ( !data.isMatch ) data = matchColor( color, rxHexUC ); 337 | 338 | // Short form #RGB 339 | if ( !data.isMatch ) { 340 | data = matchColor( color, rxHexShortLC ); 341 | if ( !data.isMatch ) data = matchColor( color, rxHexShortUC ); 342 | 343 | // Converting short form to long form 344 | if ( data.isMatch ) data.rgb = _.map( data.rgb, function ( value ) { 345 | return value + value; 346 | } ); 347 | } 348 | 349 | // Converting hex values to base 256 350 | if ( data.isMatch ) { 351 | data.rgb = _.map( data.rgb, function ( value ) { 352 | return parseInt( value, 16 ); 353 | } ); 354 | } 355 | 356 | return data; 357 | } 358 | 359 | function getBase256Data ( color ) { 360 | var data = matchColor( color, rxRgbBase256 ); 361 | if ( !data.isMatch ) data = matchColor( color, rxRgbaBase256 ); 362 | 363 | // Conversion to base 256 is not necessary here, just convert to numbers 364 | if ( data.isMatch ) { 365 | data.rgb = _.map( data.rgb, function ( value ) { 366 | return +value; 367 | } ); 368 | } 369 | 370 | return data; 371 | } 372 | 373 | function getPercentData ( color ) { 374 | var data = matchColor( color, rxRgbPercent ); 375 | if ( !data.isMatch ) data = matchColor( color, rxRgbaPercent ); 376 | 377 | // Converting percentages to base 256 (but without removing fractional parts) 378 | if ( data.isMatch ) { 379 | data.rgb = _.map( data.rgb, function ( value ) { 380 | return value * 255 / 100; 381 | } ); 382 | } 383 | 384 | return data; 385 | } 386 | 387 | function getAgColorData ( color ) { 388 | var data = matchColor( color, rxAgColor ); 389 | 390 | // Converting fractions to base 256 (but without removing fractional parts) 391 | if ( data.isMatch ) { 392 | data.rgb = _.map( data.rgb, function ( value ) { 393 | return value * 255; 394 | } ); 395 | } 396 | 397 | return data; 398 | } 399 | 400 | function parseRgbChannel ( channel ) { 401 | var matches, 402 | parsedNum = -1; 403 | 404 | if ( _.isNumber( channel ) ) { 405 | 406 | parsedNum = channel; 407 | 408 | } else if ( _.isString( channel ) ) { 409 | 410 | matches = channel.match( rxRgbChannelBase256 ); 411 | 412 | if ( matches ) { 413 | // Converting to number 414 | parsedNum = +matches[1]; 415 | } else { 416 | matches = channel.match( rxRgbChannelPercent ); 417 | 418 | // Converting percentages to base 256 (but without removing fractional parts) 419 | if ( matches ) parsedNum = matches[1] * 255 / 100; 420 | } 421 | 422 | } 423 | 424 | return ( parsedNum >= 0 && parsedNum <= 255 ) ? parsedNum : undefined; 425 | } 426 | 427 | function parseAlphaChannel ( channel ) { 428 | var matches, 429 | parsedNum = -1; 430 | 431 | if ( _.isNumber( channel ) ) { 432 | 433 | parsedNum = channel; 434 | 435 | } else if ( _.isString( channel ) ) { 436 | 437 | matches = channel.match( rxAlphaChannel ); 438 | if ( matches ) parsedNum = +matches[1]; 439 | } 440 | 441 | return ( parsedNum >= 0 && parsedNum <= 1 ) ? parsedNum : undefined; 442 | } 443 | 444 | function parseColorArray ( colorArray ) { 445 | var parsed, 446 | 447 | rawRgb = [colorArray[0], colorArray[1], colorArray[2]], 448 | rawAlpha = colorArray[3], 449 | 450 | parsedRgb = _.map( rawRgb, parseRgbChannel ), 451 | parsedAlpha = parseAlphaChannel( rawAlpha ), 452 | 453 | success = !_.some( parsedRgb, _.isUndefined ) && ( rawAlpha === undefined || parsedAlpha !== undefined ); 454 | 455 | // Conversion of data object to parsed data 456 | if ( success ) { 457 | parsed = _.object( [ "r", "g", "b" ], parsedRgb ); 458 | parsed.a = parsedAlpha; 459 | } 460 | 461 | return parsed; 462 | } 463 | 464 | // NB We can't handle the CSS keyword "currentcolor" here because we lack the context for it. 465 | function parseColor ( color ) { 466 | var parsed, keys, colorArr, data; 467 | 468 | if ( color instanceof Color ) { 469 | 470 | parsed = _.clone( color._rawColor ); 471 | 472 | } else if ( _.isArray( color ) && ( color.length === 3 || color.length === 4 ) ) { 473 | 474 | parsed = parseColorArray( color ); 475 | 476 | } else if ( _.isObject( color ) ) { 477 | keys = _.keys( color ); 478 | 479 | if ( _.intersection( [ "r", "g", "b" ], keys ).length === 3 || _.intersection( [ "r", "g", "b", "a" ], keys ).length === 4 ) { 480 | 481 | colorArr = [ color.r, color.g, color.b ]; 482 | if ( keys.length === 4 ) colorArr.push( color.a ); 483 | 484 | parsed = parseColorArray( colorArr ); 485 | 486 | } 487 | 488 | } else if ( _.isString( color ) ) { 489 | 490 | if ( CssColorNames[color] ) { 491 | parsed = _.object( [ "r", "g", "b" ], CssColorNames[color] ); 492 | } else if ( color.toLowerCase() === "transparent" ) { 493 | parsed = { r: 0, g: 0, b: 0, a: 0 }; 494 | } else { 495 | 496 | data = getHexData( color ); 497 | if ( !data.isMatch ) data = getBase256Data( color ); 498 | if ( !data.isMatch ) data = getPercentData( color ); 499 | if ( !data.isMatch ) data = getAgColorData( color ); 500 | 501 | // Conversion of data object to parsed data 502 | if ( data.isMatch ) { 503 | parsed = _.object( [ "r", "g", "b" ], data.rgb ); 504 | parsed.a = data.a; 505 | } 506 | 507 | } 508 | 509 | } 510 | 511 | // Make sure alpha is converted to a number, and set to 1 (opaque) if not defined 512 | if ( parsed && parsed.a !== undefined ) parsed.a = +parsed.a; 513 | if ( parsed && parsed.a === undefined ) parsed.a = 1; 514 | 515 | return parsed; 516 | } 517 | 518 | 519 | /* 520 | * Color conversion 521 | */ 522 | 523 | function rawToHex( rawChannel, options ) { 524 | // Defaults to lower case 525 | var upperCase = options && ( options.upperCase || options.lowerCase === false ), 526 | hex = Nums.round( rawChannel ).toString( 16 ); 527 | 528 | if ( hex.length < 2 ) hex = "0" + hex; 529 | return upperCase ? hex.toUpperCase() : hex.toLowerCase(); 530 | } 531 | 532 | function rawToBase256 ( rawChannel, options ) { 533 | var precision = options && options.precision || 0; 534 | 535 | return precision === "max" ? rawChannel : Nums.round( rawChannel, precision ); 536 | } 537 | 538 | function rawToPercent ( rawChannel, options ) { 539 | var precision = options && options.precision || 0, 540 | percentage = rawChannel * 100 / 255; 541 | 542 | if ( precision !== "max" ) percentage = Nums.round( percentage, precision ); 543 | return toDecimalNotation( percentage ) + "%"; 544 | } 545 | 546 | function rawToFraction ( rawChannel ) { 547 | return rawChannel / 255; 548 | } 549 | 550 | 551 | /* 552 | * Color object 553 | */ 554 | 555 | function Color ( value ) { 556 | if ( !(this instanceof Color) ) return new Color( value ); 557 | 558 | this._input = value; 559 | this._rawColor = parseColor( value ); 560 | } 561 | 562 | Color.version = "__COMPONENT_VERSION_PLACEHOLDER__"; 563 | 564 | _.extend( Color.prototype, { 565 | 566 | isColor: function () { 567 | return !_.isUndefined( this._rawColor ); 568 | }, 569 | 570 | ensureColor: function () { 571 | if ( !this.isColor() ) throw new Error( "Color.ensureColor: The color object does not represent a valid color. It was created from the value " + this._input ); 572 | return true; 573 | }, 574 | 575 | ensureOpaque: function () { 576 | this.ensureColor(); 577 | if ( !this.isOpaque() ) throw new Error( "Color.ensureOpaque: Color is required to be opaque, but it is not (a = " + this._rawColor.a + ")" ); 578 | return true; 579 | }, 580 | 581 | ensureTransparent: function () { 582 | this.ensureColor(); 583 | if ( !this.isTransparent() ) throw new Error( "Color.ensureTransparent: Color is required to be transparent, but it is not" ); 584 | return true; 585 | }, 586 | 587 | isOpaque: function () { 588 | return this.isColor() && this._rawColor.a === 1; 589 | }, 590 | 591 | isTransparent: function () { 592 | return this.isColor() && this._rawColor.a < 1; 593 | }, 594 | 595 | /** 596 | * @param {Object} [options] 597 | * @param {boolean} [options.lowerCase=true] 598 | * @param {boolean} [options.upperCase=false] 599 | * @param {boolean} [options.prefix=true] 600 | * @returns {string} 601 | */ 602 | asHex: function ( options ) { 603 | var prefix = ( options && options.prefix === false ) ? "" : "#"; 604 | this.ensureOpaque(); 605 | return prefix + _.map( this._getRawArrayRgb(), _.partial( rawToHex, _, options ) ).join( "" ); 606 | }, 607 | 608 | asHexUC: function () { 609 | return this.asHex( { upperCase: true } ); 610 | }, 611 | 612 | asHexLC: function () { 613 | return this.asHex( { lowerCase: true } ); 614 | }, 615 | 616 | asRgb: function () { 617 | this.ensureOpaque(); 618 | return "rgb(" + this.asRgbArray().join( ", " ) + ")"; 619 | }, 620 | 621 | /** 622 | * @param {Object} [options] 623 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 624 | * @returns {string} 625 | */ 626 | asRgbPercent: function ( options ) { 627 | var mapCb = options && options.precision ? _.partial( rawToPercent, _, options ) : rawToPercent; 628 | 629 | this.ensureOpaque(); 630 | return "rgb(" + _.map( this._getRawArrayRgb(), mapCb ).join( ", " ) + ")"; 631 | }, 632 | 633 | asRgba: function () { 634 | this.ensureColor(); 635 | return "rgba(" + this._asRgbArray().concat( toDecimalNotation( this._rawColor.a ) ).join( ", " ) + ")"; 636 | }, 637 | 638 | /** 639 | * @param {Object} [options] 640 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 641 | * @returns {string} 642 | */ 643 | asRgbaPercent: function ( options ) { 644 | var mapCb = options && options.precision ? _.partial( rawToPercent, _, options ) : rawToPercent; 645 | 646 | this.ensureColor(); 647 | return "rgba(" + _.map( this._getRawArrayRgb(), mapCb ).concat( toDecimalNotation( this._rawColor.a ) ).join( ", " ) + ")"; 648 | }, 649 | 650 | asAgColor: function () { 651 | this.ensureColor(); 652 | return "AgColor( " + _.map( this._asAgColorArray(), toDecimalNotation ).join( ", " ) + " )"; 653 | }, 654 | 655 | /** 656 | * @param {Object} [options] 657 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 658 | * @returns {number[]} 659 | */ 660 | asRgbArray: function ( options ) { 661 | this.ensureOpaque(); 662 | return this._asRgbArray( options ); 663 | }, 664 | 665 | /** 666 | * @param {Object} [options] 667 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 668 | * @returns {number[]} 669 | */ 670 | asRgbaArray: function ( options ) { 671 | this.ensureColor(); 672 | return this._asRgbArray( options ).concat( this._rawColor.a ); 673 | }, 674 | 675 | asComputed: function () { 676 | return this.isOpaque() ? this.asRgb() : this.asRgba(); 677 | }, 678 | 679 | /** 680 | * @param {*} otherColor 681 | * @param {Object} [options] 682 | * @param {number} [options.tolerance=0] 683 | * @returns {boolean} 684 | */ 685 | equals: function ( otherColor, options ) { 686 | var isEqual, isColor, pairedChannels, pairedAlpha, 687 | tolerance = options && options.tolerance || 0; 688 | 689 | if ( !( otherColor instanceof Color ) ) otherColor = new Color( otherColor ); 690 | 691 | isColor = this.isColor() && otherColor.isColor(); 692 | isEqual = isColor && this.asRgba() === otherColor.asRgba(); 693 | 694 | if ( isColor && !isEqual && tolerance > 0 ) { 695 | 696 | pairedChannels = _.zip( this.asRgbaArray(), otherColor.asRgbaArray() ); 697 | pairedAlpha = pairedChannels.pop(); 698 | 699 | isEqual = _.every( pairedChannels, function ( pairedChannel ) { 700 | return pairedChannel[0] <= pairedChannel[1] + tolerance && pairedChannel[0] >= pairedChannel[1] - tolerance; 701 | } ); 702 | 703 | isEqual = isEqual && pairedAlpha[0] === pairedAlpha[1]; 704 | } 705 | 706 | return isEqual; 707 | }, 708 | 709 | /** 710 | * @param {*} otherColor 711 | * @returns {boolean} 712 | */ 713 | strictlyEquals: function ( otherColor ) { 714 | if ( !( otherColor instanceof Color ) ) otherColor = new Color( otherColor ); 715 | return this.isColor() && otherColor.isColor() && this.asRgbaPercent( { precision: "max" } ) === otherColor.asRgbaPercent( { precision: "max" } ); 716 | }, 717 | 718 | _asAgColorArray: function () { 719 | return _.map( this._getRawArrayRgb(), rawToFraction ).concat( this._rawColor.a ); 720 | }, 721 | 722 | _asRgbArray: function ( options ) { 723 | var mapCb = options && options.precision ? _.partial( rawToBase256, _, options ) : rawToBase256; 724 | 725 | return _.map( this._getRawArrayRgb(), mapCb ); 726 | }, 727 | 728 | _getRawArrayRgb: function () { 729 | return [ this._rawColor.r, this._rawColor.g, this._rawColor.b ]; 730 | } 731 | 732 | } ); 733 | 734 | // Module return value 735 | exports.Color = Color; 736 | 737 | } ) ); 738 | 739 | -------------------------------------------------------------------------------- /dist/colorful.js: -------------------------------------------------------------------------------- 1 | // Colorful.js, v0.3.0 2 | // Copyright (c) 2016-2017 Michael Heim, Zeilenwechsel.de 3 | // Distributed under MIT license 4 | // http://github.com/hashchange/colorful.js 5 | 6 | ;( function ( root, factory ) { 7 | "use strict"; 8 | 9 | // UMD for a Backbone plugin. Supports AMD, Node.js, CommonJS and globals. 10 | // 11 | // - Code lives in the Backbone namespace. 12 | // - The module does not export a meaningful value. 13 | // - The module does not create a global. 14 | 15 | var supportsExports = typeof exports === "object" && exports && !exports.nodeType && typeof module === "object" && module && !module.nodeType; 16 | 17 | // AMD: 18 | // - Some AMD build optimizers like r.js check for condition patterns like the AMD check below, so keep it as is. 19 | // - Check for `exports` after `define` in case a build optimizer adds an `exports` object. 20 | // - The AMD spec requires the dependencies to be an array **literal** of module IDs. Don't use a variable there, 21 | // or optimizers may fail. 22 | if ( typeof define === "function" && typeof define.amd === "object" && define.amd ) { 23 | 24 | // AMD module 25 | define( [ "exports", "underscore" ], factory ); 26 | 27 | } else if ( supportsExports ) { 28 | 29 | // Node module, CommonJS module 30 | factory( exports, require( "underscore" ) ); 31 | 32 | } else { 33 | 34 | // Global (browser or Rhino) 35 | factory( root, _ ); 36 | 37 | } 38 | 39 | }( this, function ( exports, _ ) { 40 | "use strict"; 41 | 42 | /** Source for CSS color name mapping: https://github.com/dfcreative/color-name 43 | * @readonly 44 | */ 45 | var CssColorNames = { 46 | "aliceblue": [240, 248, 255], 47 | "antiquewhite": [250, 235, 215], 48 | "aqua": [0, 255, 255], 49 | "aquamarine": [127, 255, 212], 50 | "azure": [240, 255, 255], 51 | "beige": [245, 245, 220], 52 | "bisque": [255, 228, 196], 53 | "black": [0, 0, 0], 54 | "blanchedalmond": [255, 235, 205], 55 | "blue": [0, 0, 255], 56 | "blueviolet": [138, 43, 226], 57 | "brown": [165, 42, 42], 58 | "burlywood": [222, 184, 135], 59 | "cadetblue": [95, 158, 160], 60 | "chartreuse": [127, 255, 0], 61 | "chocolate": [210, 105, 30], 62 | "coral": [255, 127, 80], 63 | "cornflowerblue": [100, 149, 237], 64 | "cornsilk": [255, 248, 220], 65 | "crimson": [220, 20, 60], 66 | "cyan": [0, 255, 255], 67 | "darkblue": [0, 0, 139], 68 | "darkcyan": [0, 139, 139], 69 | "darkgoldenrod": [184, 134, 11], 70 | "darkgray": [169, 169, 169], 71 | "darkgreen": [0, 100, 0], 72 | "darkgrey": [169, 169, 169], 73 | "darkkhaki": [189, 183, 107], 74 | "darkmagenta": [139, 0, 139], 75 | "darkolivegreen": [85, 107, 47], 76 | "darkorange": [255, 140, 0], 77 | "darkorchid": [153, 50, 204], 78 | "darkred": [139, 0, 0], 79 | "darksalmon": [233, 150, 122], 80 | "darkseagreen": [143, 188, 143], 81 | "darkslateblue": [72, 61, 139], 82 | "darkslategray": [47, 79, 79], 83 | "darkslategrey": [47, 79, 79], 84 | "darkturquoise": [0, 206, 209], 85 | "darkviolet": [148, 0, 211], 86 | "deeppink": [255, 20, 147], 87 | "deepskyblue": [0, 191, 255], 88 | "dimgray": [105, 105, 105], 89 | "dimgrey": [105, 105, 105], 90 | "dodgerblue": [30, 144, 255], 91 | "firebrick": [178, 34, 34], 92 | "floralwhite": [255, 250, 240], 93 | "forestgreen": [34, 139, 34], 94 | "fuchsia": [255, 0, 255], 95 | "gainsboro": [220, 220, 220], 96 | "ghostwhite": [248, 248, 255], 97 | "gold": [255, 215, 0], 98 | "goldenrod": [218, 165, 32], 99 | "gray": [128, 128, 128], 100 | "green": [0, 128, 0], 101 | "greenyellow": [173, 255, 47], 102 | "grey": [128, 128, 128], 103 | "honeydew": [240, 255, 240], 104 | "hotpink": [255, 105, 180], 105 | "indianred": [205, 92, 92], 106 | "indigo": [75, 0, 130], 107 | "ivory": [255, 255, 240], 108 | "khaki": [240, 230, 140], 109 | "lavender": [230, 230, 250], 110 | "lavenderblush": [255, 240, 245], 111 | "lawngreen": [124, 252, 0], 112 | "lemonchiffon": [255, 250, 205], 113 | "lightblue": [173, 216, 230], 114 | "lightcoral": [240, 128, 128], 115 | "lightcyan": [224, 255, 255], 116 | "lightgoldenrodyellow": [250, 250, 210], 117 | "lightgray": [211, 211, 211], 118 | "lightgreen": [144, 238, 144], 119 | "lightgrey": [211, 211, 211], 120 | "lightpink": [255, 182, 193], 121 | "lightsalmon": [255, 160, 122], 122 | "lightseagreen": [32, 178, 170], 123 | "lightskyblue": [135, 206, 250], 124 | "lightslategray": [119, 136, 153], 125 | "lightslategrey": [119, 136, 153], 126 | "lightsteelblue": [176, 196, 222], 127 | "lightyellow": [255, 255, 224], 128 | "lime": [0, 255, 0], 129 | "limegreen": [50, 205, 50], 130 | "linen": [250, 240, 230], 131 | "magenta": [255, 0, 255], 132 | "maroon": [128, 0, 0], 133 | "mediumaquamarine": [102, 205, 170], 134 | "mediumblue": [0, 0, 205], 135 | "mediumorchid": [186, 85, 211], 136 | "mediumpurple": [147, 112, 219], 137 | "mediumseagreen": [60, 179, 113], 138 | "mediumslateblue": [123, 104, 238], 139 | "mediumspringgreen": [0, 250, 154], 140 | "mediumturquoise": [72, 209, 204], 141 | "mediumvioletred": [199, 21, 133], 142 | "midnightblue": [25, 25, 112], 143 | "mintcream": [245, 255, 250], 144 | "mistyrose": [255, 228, 225], 145 | "moccasin": [255, 228, 181], 146 | "navajowhite": [255, 222, 173], 147 | "navy": [0, 0, 128], 148 | "oldlace": [253, 245, 230], 149 | "olive": [128, 128, 0], 150 | "olivedrab": [107, 142, 35], 151 | "orange": [255, 165, 0], 152 | "orangered": [255, 69, 0], 153 | "orchid": [218, 112, 214], 154 | "palegoldenrod": [238, 232, 170], 155 | "palegreen": [152, 251, 152], 156 | "paleturquoise": [175, 238, 238], 157 | "palevioletred": [219, 112, 147], 158 | "papayawhip": [255, 239, 213], 159 | "peachpuff": [255, 218, 185], 160 | "peru": [205, 133, 63], 161 | "pink": [255, 192, 203], 162 | "plum": [221, 160, 221], 163 | "powderblue": [176, 224, 230], 164 | "purple": [128, 0, 128], 165 | "rebeccapurple": [102, 51, 153], 166 | "red": [255, 0, 0], 167 | "rosybrown": [188, 143, 143], 168 | "royalblue": [65, 105, 225], 169 | "saddlebrown": [139, 69, 19], 170 | "salmon": [250, 128, 114], 171 | "sandybrown": [244, 164, 96], 172 | "seagreen": [46, 139, 87], 173 | "seashell": [255, 245, 238], 174 | "sienna": [160, 82, 45], 175 | "silver": [192, 192, 192], 176 | "skyblue": [135, 206, 235], 177 | "slateblue": [106, 90, 205], 178 | "slategray": [112, 128, 144], 179 | "slategrey": [112, 128, 144], 180 | "snow": [255, 250, 250], 181 | "springgreen": [0, 255, 127], 182 | "steelblue": [70, 130, 180], 183 | "tan": [210, 180, 140], 184 | "teal": [0, 128, 128], 185 | "thistle": [216, 191, 216], 186 | "tomato": [255, 99, 71], 187 | "turquoise": [64, 224, 208], 188 | "violet": [238, 130, 238], 189 | "wheat": [245, 222, 179], 190 | "white": [255, 255, 255], 191 | "whitesmoke": [245, 245, 245], 192 | "yellow": [255, 255, 0], 193 | "yellowgreen": [154, 205, 50] 194 | }, 195 | 196 | _rxstrFraction = "(?:1(?:\\.0+)?|0?\\.\\d+|0)", 197 | rxstrAlpha = "(" + _rxstrFraction + ")", 198 | rxstrOptionalAlpha = "(" + _rxstrFraction + "?)", 199 | 200 | rxstrRgbChannelBase256 = "(255(?:\\.0+)?|25[0-4](?:\\.\\d+)?|2[0-4]\\d(?:\\.\\d+)?|(?:[0-1]?\\d)?\\d(?:\\.\\d+)?)", 201 | rxstrRgbChannelPercent = "(100(?:\\.0+)?|\\d{0,2}(?:\\.\\d+)|\\d{1,2})\\s*%", 202 | rxstrRgbChannelHexLC = "([a-f\\d]{2})", 203 | rxstrRgbChannelHexUC = "([A-F\\d]{2})", 204 | rxstrRgbChannelShortHexLC = "([a-f\\d])", 205 | rxstrRgbChannelShortHexUC = "([A-F\\d])", 206 | rxstrRgbChannelFraction = rxstrAlpha, 207 | 208 | rxRgbChannelBase256 = new RegExp( "^\\s*" + rxstrRgbChannelBase256 + "\\s*$" ), 209 | rxRgbChannelPercent = new RegExp( "^\\s*" + rxstrRgbChannelPercent + "\\s*$" ), 210 | rxAlphaChannel = new RegExp( "^\\s*" + rxstrAlpha + "\\s*$" ), 211 | 212 | rxHexLC = buildColorRx( "#", rxstrRgbChannelHexLC ), 213 | rxHexUC = buildColorRx( "#", rxstrRgbChannelHexUC ), 214 | rxHexShortLC = buildColorRx( "#", rxstrRgbChannelShortHexLC ), 215 | rxHexShortUC = buildColorRx( "#", rxstrRgbChannelShortHexUC ), 216 | 217 | rxRgbBase256 = buildColorRx( "rgb", rxstrRgbChannelBase256 ), 218 | rxRgbaBase256 = buildColorRx( "rgba", rxstrRgbChannelBase256, true ), 219 | 220 | rxRgbPercent = buildColorRx( "rgb", rxstrRgbChannelPercent ), 221 | rxRgbaPercent = buildColorRx( "rgba", rxstrRgbChannelPercent, true ), 222 | 223 | rxAgColor = buildColorRx( "AgColor", rxstrRgbChannelFraction, true ); 224 | 225 | 226 | /* 227 | * Rounding helpers 228 | */ 229 | 230 | /** 231 | * Adjusts a number to a given precision, working around the buggy floating-point math of Javascript. Works for 232 | * round, floor, ceil operations. 233 | * 234 | * Lifted from the Math.round entry of MDN. Minor changes without effect on the algorithm. 235 | * 236 | * @param {string} operation "round", "floor", "ceil" 237 | * @param {number} value 238 | * @param {number} [precision=0] can be negative: round( 104,-1 ) => 100 239 | * @returns {number} 240 | */ 241 | function _decimalAdjust ( operation, value, precision ) { 242 | var exp; 243 | 244 | if ( typeof precision === 'undefined' || +precision === 0 ) return Math[operation]( value ); 245 | 246 | value = +value; 247 | precision = +precision; 248 | 249 | // Return NaN if the value is not a number or the precision is not an integer 250 | if ( isNaN( value ) || !(typeof precision === 'number' && precision % 1 === 0) ) return NaN; 251 | 252 | exp = -precision; 253 | 254 | // Shift 255 | value = value.toString().split( 'e' ); 256 | value = Math[operation]( +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)) ); 257 | 258 | // Shift back 259 | value = value.toString().split( 'e' ); 260 | return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); 261 | } 262 | 263 | var Nums = {}; 264 | 265 | Nums.round = function ( value, precision ) { 266 | return _decimalAdjust( 'round', value, precision ); 267 | }; 268 | 269 | Nums.floor = function ( value, precision ) { 270 | return _decimalAdjust( 'floor', value, precision ); 271 | }; 272 | 273 | Nums.ceil = function ( value, precision ) { 274 | return _decimalAdjust( 'ceil', value, precision ); 275 | }; 276 | 277 | /** 278 | * Converts a number to a decimal string. Ensures that even very small numbers are returned in decimal notation, 279 | * rather than scientific notation. 280 | * 281 | * Numbers are allowed a maximum of 20 decimal digits when expressed in decimal notation (a limitation caused by the 282 | * Javascript function .toFixed()). Any digits beyond that limit are rounded. 283 | * 284 | * NB The function doesn't handle scientific notation for very large numbers because they don't occur in the context 285 | * of colour. 286 | * 287 | * @param {number} number 288 | * @returns {string} 289 | */ 290 | function toDecimalNotation ( number ) { 291 | var numStr = number + ""; 292 | 293 | // If we encounter scientific notation in the stringified number, we don't just modify parts of it. Instead, we 294 | // reconstruct the entire stringified number from scratch. (That's possible because we use a regex matching the 295 | // stringified number in full.) 296 | numStr = numStr.replace( /^(\d+)(?:\.(\d+))?e-(\d+)$/i, function ( match, intDigits, fractionalDigits, absExponent ) { 297 | var digits = +absExponent + ( fractionalDigits ? fractionalDigits.length : 0 ); 298 | 299 | // The argument for String.toFixed( digits ) is allowed to be between 0 and 20, so we have to limit the 300 | // range accordingly. 301 | if ( digits > 20 ) { 302 | Nums.round( number, 20 ); 303 | digits = 20; 304 | } 305 | 306 | return number.toFixed( digits ); 307 | } ); 308 | 309 | return numStr; 310 | } 311 | 312 | 313 | /* 314 | * Color parsing 315 | */ 316 | 317 | function buildColorRx ( prefix, rxRgbChannel, withAlpha ) { 318 | var isHex = prefix === "#", 319 | channelSeparator = isHex ? "" : "\\s*,\\s*", 320 | suffix = isHex ? "" : "\\s*\\)", 321 | rxChannels = [ rxRgbChannel, rxRgbChannel, rxRgbChannel ]; 322 | 323 | if ( withAlpha ) rxChannels.push( rxstrAlpha ); 324 | if ( !isHex ) prefix += "\\s*\\(\\s*"; 325 | 326 | return new RegExp( "^\\s*" + prefix + rxChannels.join( channelSeparator ) + suffix + "\\s*$" ); 327 | } 328 | 329 | function matchColor ( color, rx ) { 330 | var matches = color.match( rx ); 331 | return { 332 | isMatch: !!matches, 333 | rgb: matches && [ matches[1], matches[2], matches[3] ], 334 | a: matches && matches[4] 335 | }; 336 | } 337 | 338 | function getHexData ( color ) { 339 | // Long form #RRGGBB 340 | var data = matchColor( color, rxHexLC ); 341 | if ( !data.isMatch ) data = matchColor( color, rxHexUC ); 342 | 343 | // Short form #RGB 344 | if ( !data.isMatch ) { 345 | data = matchColor( color, rxHexShortLC ); 346 | if ( !data.isMatch ) data = matchColor( color, rxHexShortUC ); 347 | 348 | // Converting short form to long form 349 | if ( data.isMatch ) data.rgb = _.map( data.rgb, function ( value ) { 350 | return value + value; 351 | } ); 352 | } 353 | 354 | // Converting hex values to base 256 355 | if ( data.isMatch ) { 356 | data.rgb = _.map( data.rgb, function ( value ) { 357 | return parseInt( value, 16 ); 358 | } ); 359 | } 360 | 361 | return data; 362 | } 363 | 364 | function getBase256Data ( color ) { 365 | var data = matchColor( color, rxRgbBase256 ); 366 | if ( !data.isMatch ) data = matchColor( color, rxRgbaBase256 ); 367 | 368 | // Conversion to base 256 is not necessary here, just convert to numbers 369 | if ( data.isMatch ) { 370 | data.rgb = _.map( data.rgb, function ( value ) { 371 | return +value; 372 | } ); 373 | } 374 | 375 | return data; 376 | } 377 | 378 | function getPercentData ( color ) { 379 | var data = matchColor( color, rxRgbPercent ); 380 | if ( !data.isMatch ) data = matchColor( color, rxRgbaPercent ); 381 | 382 | // Converting percentages to base 256 (but without removing fractional parts) 383 | if ( data.isMatch ) { 384 | data.rgb = _.map( data.rgb, function ( value ) { 385 | return value * 255 / 100; 386 | } ); 387 | } 388 | 389 | return data; 390 | } 391 | 392 | function getAgColorData ( color ) { 393 | var data = matchColor( color, rxAgColor ); 394 | 395 | // Converting fractions to base 256 (but without removing fractional parts) 396 | if ( data.isMatch ) { 397 | data.rgb = _.map( data.rgb, function ( value ) { 398 | return value * 255; 399 | } ); 400 | } 401 | 402 | return data; 403 | } 404 | 405 | function parseRgbChannel ( channel ) { 406 | var matches, 407 | parsedNum = -1; 408 | 409 | if ( _.isNumber( channel ) ) { 410 | 411 | parsedNum = channel; 412 | 413 | } else if ( _.isString( channel ) ) { 414 | 415 | matches = channel.match( rxRgbChannelBase256 ); 416 | 417 | if ( matches ) { 418 | // Converting to number 419 | parsedNum = +matches[1]; 420 | } else { 421 | matches = channel.match( rxRgbChannelPercent ); 422 | 423 | // Converting percentages to base 256 (but without removing fractional parts) 424 | if ( matches ) parsedNum = matches[1] * 255 / 100; 425 | } 426 | 427 | } 428 | 429 | return ( parsedNum >= 0 && parsedNum <= 255 ) ? parsedNum : undefined; 430 | } 431 | 432 | function parseAlphaChannel ( channel ) { 433 | var matches, 434 | parsedNum = -1; 435 | 436 | if ( _.isNumber( channel ) ) { 437 | 438 | parsedNum = channel; 439 | 440 | } else if ( _.isString( channel ) ) { 441 | 442 | matches = channel.match( rxAlphaChannel ); 443 | if ( matches ) parsedNum = +matches[1]; 444 | } 445 | 446 | return ( parsedNum >= 0 && parsedNum <= 1 ) ? parsedNum : undefined; 447 | } 448 | 449 | function parseColorArray ( colorArray ) { 450 | var parsed, 451 | 452 | rawRgb = [colorArray[0], colorArray[1], colorArray[2]], 453 | rawAlpha = colorArray[3], 454 | 455 | parsedRgb = _.map( rawRgb, parseRgbChannel ), 456 | parsedAlpha = parseAlphaChannel( rawAlpha ), 457 | 458 | success = !_.some( parsedRgb, _.isUndefined ) && ( rawAlpha === undefined || parsedAlpha !== undefined ); 459 | 460 | // Conversion of data object to parsed data 461 | if ( success ) { 462 | parsed = _.object( [ "r", "g", "b" ], parsedRgb ); 463 | parsed.a = parsedAlpha; 464 | } 465 | 466 | return parsed; 467 | } 468 | 469 | // NB We can't handle the CSS keyword "currentcolor" here because we lack the context for it. 470 | function parseColor ( color ) { 471 | var parsed, keys, colorArr, data; 472 | 473 | if ( color instanceof Color ) { 474 | 475 | parsed = _.clone( color._rawColor ); 476 | 477 | } else if ( _.isArray( color ) && ( color.length === 3 || color.length === 4 ) ) { 478 | 479 | parsed = parseColorArray( color ); 480 | 481 | } else if ( _.isObject( color ) ) { 482 | keys = _.keys( color ); 483 | 484 | if ( _.intersection( [ "r", "g", "b" ], keys ).length === 3 || _.intersection( [ "r", "g", "b", "a" ], keys ).length === 4 ) { 485 | 486 | colorArr = [ color.r, color.g, color.b ]; 487 | if ( keys.length === 4 ) colorArr.push( color.a ); 488 | 489 | parsed = parseColorArray( colorArr ); 490 | 491 | } 492 | 493 | } else if ( _.isString( color ) ) { 494 | 495 | if ( CssColorNames[color] ) { 496 | parsed = _.object( [ "r", "g", "b" ], CssColorNames[color] ); 497 | } else if ( color.toLowerCase() === "transparent" ) { 498 | parsed = { r: 0, g: 0, b: 0, a: 0 }; 499 | } else { 500 | 501 | data = getHexData( color ); 502 | if ( !data.isMatch ) data = getBase256Data( color ); 503 | if ( !data.isMatch ) data = getPercentData( color ); 504 | if ( !data.isMatch ) data = getAgColorData( color ); 505 | 506 | // Conversion of data object to parsed data 507 | if ( data.isMatch ) { 508 | parsed = _.object( [ "r", "g", "b" ], data.rgb ); 509 | parsed.a = data.a; 510 | } 511 | 512 | } 513 | 514 | } 515 | 516 | // Make sure alpha is converted to a number, and set to 1 (opaque) if not defined 517 | if ( parsed && parsed.a !== undefined ) parsed.a = +parsed.a; 518 | if ( parsed && parsed.a === undefined ) parsed.a = 1; 519 | 520 | return parsed; 521 | } 522 | 523 | 524 | /* 525 | * Color conversion 526 | */ 527 | 528 | function rawToHex( rawChannel, options ) { 529 | // Defaults to lower case 530 | var upperCase = options && ( options.upperCase || options.lowerCase === false ), 531 | hex = Nums.round( rawChannel ).toString( 16 ); 532 | 533 | if ( hex.length < 2 ) hex = "0" + hex; 534 | return upperCase ? hex.toUpperCase() : hex.toLowerCase(); 535 | } 536 | 537 | function rawToBase256 ( rawChannel, options ) { 538 | var precision = options && options.precision || 0; 539 | 540 | return precision === "max" ? rawChannel : Nums.round( rawChannel, precision ); 541 | } 542 | 543 | function rawToPercent ( rawChannel, options ) { 544 | var precision = options && options.precision || 0, 545 | percentage = rawChannel * 100 / 255; 546 | 547 | if ( precision !== "max" ) percentage = Nums.round( percentage, precision ); 548 | return toDecimalNotation( percentage ) + "%"; 549 | } 550 | 551 | function rawToFraction ( rawChannel ) { 552 | return rawChannel / 255; 553 | } 554 | 555 | 556 | /* 557 | * Color object 558 | */ 559 | 560 | function Color ( value ) { 561 | if ( !(this instanceof Color) ) return new Color( value ); 562 | 563 | this._input = value; 564 | this._rawColor = parseColor( value ); 565 | } 566 | 567 | Color.version = "0.3.0"; 568 | 569 | _.extend( Color.prototype, { 570 | 571 | isColor: function () { 572 | return !_.isUndefined( this._rawColor ); 573 | }, 574 | 575 | ensureColor: function () { 576 | if ( !this.isColor() ) throw new Error( "Color.ensureColor: The color object does not represent a valid color. It was created from the value " + this._input ); 577 | return true; 578 | }, 579 | 580 | ensureOpaque: function () { 581 | this.ensureColor(); 582 | if ( !this.isOpaque() ) throw new Error( "Color.ensureOpaque: Color is required to be opaque, but it is not (a = " + this._rawColor.a + ")" ); 583 | return true; 584 | }, 585 | 586 | ensureTransparent: function () { 587 | this.ensureColor(); 588 | if ( !this.isTransparent() ) throw new Error( "Color.ensureTransparent: Color is required to be transparent, but it is not" ); 589 | return true; 590 | }, 591 | 592 | isOpaque: function () { 593 | return this.isColor() && this._rawColor.a === 1; 594 | }, 595 | 596 | isTransparent: function () { 597 | return this.isColor() && this._rawColor.a < 1; 598 | }, 599 | 600 | /** 601 | * @param {Object} [options] 602 | * @param {boolean} [options.lowerCase=true] 603 | * @param {boolean} [options.upperCase=false] 604 | * @param {boolean} [options.prefix=true] 605 | * @returns {string} 606 | */ 607 | asHex: function ( options ) { 608 | var prefix = ( options && options.prefix === false ) ? "" : "#"; 609 | this.ensureOpaque(); 610 | return prefix + _.map( this._getRawArrayRgb(), _.partial( rawToHex, _, options ) ).join( "" ); 611 | }, 612 | 613 | asHexUC: function () { 614 | return this.asHex( { upperCase: true } ); 615 | }, 616 | 617 | asHexLC: function () { 618 | return this.asHex( { lowerCase: true } ); 619 | }, 620 | 621 | asRgb: function () { 622 | this.ensureOpaque(); 623 | return "rgb(" + this.asRgbArray().join( ", " ) + ")"; 624 | }, 625 | 626 | /** 627 | * @param {Object} [options] 628 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 629 | * @returns {string} 630 | */ 631 | asRgbPercent: function ( options ) { 632 | var mapCb = options && options.precision ? _.partial( rawToPercent, _, options ) : rawToPercent; 633 | 634 | this.ensureOpaque(); 635 | return "rgb(" + _.map( this._getRawArrayRgb(), mapCb ).join( ", " ) + ")"; 636 | }, 637 | 638 | asRgba: function () { 639 | this.ensureColor(); 640 | return "rgba(" + this._asRgbArray().concat( toDecimalNotation( this._rawColor.a ) ).join( ", " ) + ")"; 641 | }, 642 | 643 | /** 644 | * @param {Object} [options] 645 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 646 | * @returns {string} 647 | */ 648 | asRgbaPercent: function ( options ) { 649 | var mapCb = options && options.precision ? _.partial( rawToPercent, _, options ) : rawToPercent; 650 | 651 | this.ensureColor(); 652 | return "rgba(" + _.map( this._getRawArrayRgb(), mapCb ).concat( toDecimalNotation( this._rawColor.a ) ).join( ", " ) + ")"; 653 | }, 654 | 655 | asAgColor: function () { 656 | this.ensureColor(); 657 | return "AgColor( " + _.map( this._asAgColorArray(), toDecimalNotation ).join( ", " ) + " )"; 658 | }, 659 | 660 | /** 661 | * @param {Object} [options] 662 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 663 | * @returns {number[]} 664 | */ 665 | asRgbArray: function ( options ) { 666 | this.ensureOpaque(); 667 | return this._asRgbArray( options ); 668 | }, 669 | 670 | /** 671 | * @param {Object} [options] 672 | * @param {number|"max"} [options.precision=0] number of fractional digits, or "max" for all digits 673 | * @returns {number[]} 674 | */ 675 | asRgbaArray: function ( options ) { 676 | this.ensureColor(); 677 | return this._asRgbArray( options ).concat( this._rawColor.a ); 678 | }, 679 | 680 | asComputed: function () { 681 | return this.isOpaque() ? this.asRgb() : this.asRgba(); 682 | }, 683 | 684 | /** 685 | * @param {*} otherColor 686 | * @param {Object} [options] 687 | * @param {number} [options.tolerance=0] 688 | * @returns {boolean} 689 | */ 690 | equals: function ( otherColor, options ) { 691 | var isEqual, isColor, pairedChannels, pairedAlpha, 692 | tolerance = options && options.tolerance || 0; 693 | 694 | if ( !( otherColor instanceof Color ) ) otherColor = new Color( otherColor ); 695 | 696 | isColor = this.isColor() && otherColor.isColor(); 697 | isEqual = isColor && this.asRgba() === otherColor.asRgba(); 698 | 699 | if ( isColor && !isEqual && tolerance > 0 ) { 700 | 701 | pairedChannels = _.zip( this.asRgbaArray(), otherColor.asRgbaArray() ); 702 | pairedAlpha = pairedChannels.pop(); 703 | 704 | isEqual = _.every( pairedChannels, function ( pairedChannel ) { 705 | return pairedChannel[0] <= pairedChannel[1] + tolerance && pairedChannel[0] >= pairedChannel[1] - tolerance; 706 | } ); 707 | 708 | isEqual = isEqual && pairedAlpha[0] === pairedAlpha[1]; 709 | } 710 | 711 | return isEqual; 712 | }, 713 | 714 | /** 715 | * @param {*} otherColor 716 | * @returns {boolean} 717 | */ 718 | strictlyEquals: function ( otherColor ) { 719 | if ( !( otherColor instanceof Color ) ) otherColor = new Color( otherColor ); 720 | return this.isColor() && otherColor.isColor() && this.asRgbaPercent( { precision: "max" } ) === otherColor.asRgbaPercent( { precision: "max" } ); 721 | }, 722 | 723 | _asAgColorArray: function () { 724 | return _.map( this._getRawArrayRgb(), rawToFraction ).concat( this._rawColor.a ); 725 | }, 726 | 727 | _asRgbArray: function ( options ) { 728 | var mapCb = options && options.precision ? _.partial( rawToBase256, _, options ) : rawToBase256; 729 | 730 | return _.map( this._getRawArrayRgb(), mapCb ); 731 | }, 732 | 733 | _getRawArrayRgb: function () { 734 | return [ this._rawColor.r, this._rawColor.g, this._rawColor.b ]; 735 | } 736 | 737 | } ); 738 | 739 | // Module return value 740 | exports.Color = Color; 741 | 742 | } ) ); 743 | 744 | -------------------------------------------------------------------------------- /spec/color.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach, expect, describeWithData, Color */ 2 | /* jshint -W024, -W064 */ 3 | 4 | (function () { 5 | "use strict"; 6 | 7 | describe( 'Invocation', function () { 8 | 9 | describe( 'When Color is called without the `new` keyword, a valid colour argument', function () { 10 | 11 | var colour; 12 | 13 | beforeEach( function () { 14 | colour = Color( "#0490A3" ); 15 | } ); 16 | 17 | it( 'is recognized as a colour', function () { 18 | expect( colour.isColor() ).to.be.true; 19 | } ); 20 | 21 | it( 'is represented by the correct RGBA value', function () { 22 | expect( colour.asRgbaArray() ).to.eql( [4, 144, 163, 1] ); 23 | } ); 24 | 25 | } ); 26 | 27 | } ); 28 | 29 | describe( 'Colour parsing', function () { 30 | 31 | describe( 'A valid CSS colour', function () { 32 | 33 | var inputColourScenario = { 34 | "as a keyword": { input: "turquoise", expectedRGBA: [64, 224, 208, 1] }, 35 | 'defined by the keyword "transparent"': { input: "transparent", expectedRGBA: [0, 0, 0, 0] }, 36 | 37 | "in hex format #RRGGBB": { input: "#0490A3", expectedRGBA: [4, 144, 163, 1] }, 38 | "in hex format #rrggbb": { input: "#0490a3", expectedRGBA: [4, 144, 163, 1] }, 39 | "in hex format #RGB": { input: "#09A", expectedRGBA: [0, 153, 170, 1] }, 40 | "in hex format #rgb": { input: "#09a", expectedRGBA: [0, 153, 170, 1] }, 41 | 42 | "in rgb() format": { input: "rgb(0, 153, 170)", expectedRGBA: [0, 153, 170, 1] }, 43 | "in rgb() format without spaces": { input: "rgb(0,153,170)", expectedRGBA: [0, 153, 170, 1] }, 44 | "in rgb() format with excess spaces": { input: " rgb ( 0 , 153 , 170 ) ", expectedRGBA: [0, 153, 170, 1] }, 45 | 46 | "in rgb() percent format": { input: "rgb(0%, 60%, 67%)", expectedRGBA: [0, 153, 171, 1] }, 47 | "in rgb() percent format with fractions": { input: "rgb(0.0000%, 100.0%, 67.2587491%)", expectedRGBA: [0, 255, 172, 1] }, 48 | "in rgb() percent format with fractions (0.x as .x)": { input: "rgb(.623%, 100.0%, 67.2587491%)", expectedRGBA: [2, 255, 172, 1] }, 49 | "in rgb() percent format without spaces": { input: "rgb(0%,60%,67%)", expectedRGBA: [0, 153, 171, 1] }, 50 | "in rgb() percent format with excess spaces": { input: " rgb ( 0% , 60% , 67% ) ", expectedRGBA: [0, 153, 171, 1] }, 51 | 52 | "in rgba() format (opacity 1)": { input: "rgba(0, 153, 170, 1)", expectedRGBA: [0, 153, 170, 1] }, 53 | "in rgba() format (opacity 0.5)": { input: "rgba(0, 153, 170, 0.5)", expectedRGBA: [0, 153, 170, 0.5] }, 54 | "in rgba() format (opacity .5)": { input: "rgba(0, 153, 170, .5)", expectedRGBA: [0, 153, 170, 0.5] }, 55 | "in rgba() format (opacity 0)": { input: "rgba(0, 153, 170, 0)", expectedRGBA: [0, 153, 170, 0] }, 56 | "in rgba() format without spaces": { input: "rgba(0,153,170,1)", expectedRGBA: [0, 153, 170, 1] }, 57 | "in rgba() format with excess spaces": { input: " rgba ( 0 , 153 , 170, 1 ) ", expectedRGBA: [0, 153, 170, 1] }, 58 | 59 | "in rgba() percent format": { input: "rgba(0%, 60%, 67%, .5)", expectedRGBA: [0, 153, 171, 0.5] }, 60 | "in rgba() percent format with fractions": { input: "rgba(0.0000%, 100.0%, 67.2587491%, 0.5)", expectedRGBA: [0, 255, 172, 0.5] }, 61 | "in rgba() percent format with fractions (0.x as .x)": { input: "rgba(.623%, 100.0%, 67.2587491%, .5)", expectedRGBA: [2, 255, 172, 0.5] }, 62 | "in rgba() percent format without spaces": { input: "rgba(0%,60%,67%,.5)", expectedRGBA: [0, 153, 171, 0.5] }, 63 | "in rgba() percent format with excess spaces": { input: " rgba ( 0% , 60% , 67% , .5 ) ", expectedRGBA: [0, 153, 171, 0.5] }, 64 | 65 | "in AgColor format (0 as 0, 1 as 1)": { input: "AgColor(0, 1, 0.672587491, 0.5)", expectedRGBA: [0, 255, 172, 0.5] }, 66 | "in AgColor format (0 as 0.0000, 1 as 1.0)": { input: "AgColor(0.0000, 1.0, 0.672587491, 0.5)", expectedRGBA: [0, 255, 172, 0.5] }, 67 | "in AgColor format (0.x as .x)": { input: "AgColor(.00623, 1, .672587491, .5)", expectedRGBA: [2, 255, 172, 0.5] }, 68 | "in AgColor format without spaces": { input: "AgColor(.00623,1,.672587491,.5)", expectedRGBA: [2, 255, 172, 0.5] }, 69 | "in AgColor format with excess spaces": { input: " AgColor ( .00623 , 1 , .672587491 , .5 )", expectedRGBA: [2, 255, 172, 0.5] }, 70 | 71 | "as an array of integer RGB values": { input: [4, 144, 163], expectedRGBA: [4, 144, 163, 1] }, 72 | "as an array of integer RGBA values": { input: [4, 144, 163, 0.5], expectedRGBA: [4, 144, 163, 0.5] }, 73 | "as an array of RGB values with decimals": { input: [4.499999999999999, 143.5, 163.00000010101010101], expectedRGBA: [4, 144, 163, 1], expectedPreciseRGBA: [4.499999999999999, 143.5, 163.00000010101010101, 1] }, 74 | "as an array of RGBA values with decimals": { input: [4.499999999999999, 143.5, 163.00000010101010101, 0.5], expectedRGBA: [4, 144, 163, 0.5], expectedPreciseRGBA: [4.499999999999999, 143.5, 163.00000010101010101, 0.5] }, 75 | "as an array of RGB percentage values": { input: [".623%", "100.0%", "67.2587491%"], expectedRGBA: [2, 255, 172, 1] }, 76 | "as an array of RGBA percentage values": { input: [".623%", "100.0%", "67.2587491%", 0.5], expectedRGBA: [2, 255, 172, 0.5] }, 77 | "as a mixed array of integer RGB values, RGB values with decimals, and percentages": { input: [4.499999999999999, 143.5, "67.2587491%"], expectedRGBA: [4, 144, 172, 1], expectedPreciseRGBA: [4.499999999999999, 143.5, ( 67.2587491 * 2.55 ), 1] }, 78 | "as a mixed array of integer RGBA values, RGBA values with decimals, and percentages": { input: [4.499999999999999, 143.5, "67%", 0.5], expectedRGBA: [4, 144, 171, 0.5], expectedPreciseRGBA: [4.499999999999999, 143.5, ( 67 * 2.55 ), 0.5] }, 79 | 80 | "as a hash of integer RGB values": { input: { r: 4, g: 144, b: 163 }, expectedRGBA: [4, 144, 163, 1] }, 81 | "as a hash of integer RGBA values": { input: { r: 4, g: 144, b: 163, a: 0.5 }, expectedRGBA: [4, 144, 163, 0.5] }, 82 | "as a hash of RGB values with decimals": { input: { r: 4.499999999999999, g: 143.5, b: 163.00000010101010101 }, expectedRGBA: [4, 144, 163, 1], expectedPreciseRGBA: [4.499999999999999, 143.5, 163.00000010101010101, 1] }, 83 | "as a hash of RGBA values with decimals": { input: { r: 4.499999999999999, g: 143.5, b: 163.00000010101010101, a: 0.5}, expectedRGBA: [4, 144, 163, 0.5], expectedPreciseRGBA: [4.499999999999999, 143.5, 163.00000010101010101, 0.5] }, 84 | "as a hash of RGB percentage values": { input: { r: ".623%", g: "100.0%", b: "67.2587491%" }, expectedRGBA: [2, 255, 172, 1] }, 85 | "as a hash of RGBA percentage values": { input: { r: ".623%", g: "100.0%", b: "67.2587491%", a: 0.5 }, expectedRGBA: [2, 255, 172, 0.5] }, 86 | "as a mixed hash of integer RGB values, RGB values with decimals, and percentages": { input: { r: 4.499999999999999, g: 143.5, b: "67.2587491%"}, expectedRGBA: [4, 144, 172, 1], expectedPreciseRGBA: [4.499999999999999, 143.5, ( 67.2587491 * 2.55 ), 1] }, 87 | "as a mixed hash of integer RGBA values, RGBA values with decimals, and percentages": { input: { r: 4.499999999999999, g: 143.5, b: "67%", a: 0.5}, expectedRGBA: [4, 144, 171, 0.5], expectedPreciseRGBA: [4.499999999999999, 143.5, ( 67 * 2.55 ), 0.5] } 88 | }; 89 | 90 | describeWithData( inputColourScenario, function ( scenario ) { 91 | 92 | var colour; 93 | 94 | beforeEach( function () { 95 | colour = new Color( scenario.input ); 96 | } ); 97 | 98 | it( 'is recognized as a colour', function () { 99 | expect( colour.isColor() ).to.be.true; 100 | } ); 101 | 102 | it( 'is represented by the correct RGBA value', function () { 103 | expect( colour.asRgbaArray() ).to.eql( scenario.expectedRGBA ); 104 | } ); 105 | 106 | if ( scenario.expectedPreciseRGBA ) { 107 | 108 | it( 'is represented by the correct RGBA value when enabling full precision', function () { 109 | expect( colour.asRgbaArray( { precision: "max"} ) ).to.eql( scenario.expectedPreciseRGBA ); 110 | } ); 111 | 112 | } 113 | 114 | } ); 115 | 116 | } ); 117 | 118 | describe( 'Any other input is classified as not being a colour', function () { 119 | 120 | it( 'for an arbitrary, non-colour word', function () { 121 | expect( ( new Color( "foo" ) ).isColor() ).to.be.false; 122 | } ); 123 | 124 | it( 'for an rgb() string mixing percentages with non-percentage values (e.g. 0 instead of "0%")', function () { 125 | expect( ( new Color( "rgb(0, 60%, 67%)" ) ).isColor() ).to.be.false; 126 | } ); 127 | 128 | it( 'for an rgba() string mixing percentages with non-percentage values (e.g. 0 instead of "0%")', function () { 129 | expect( ( new Color( "rgba(0, 60%, 67%, .5)" ) ).isColor() ).to.be.false; 130 | } ); 131 | 132 | it( 'for an empty hash', function () { 133 | expect( ( new Color( {} ) ).isColor() ).to.be.false; 134 | } ); 135 | 136 | it( 'for an RGB hash with incomplete r, g, b properties', function () { 137 | expect( ( new Color( { r: 10, b: 10 } ) ).isColor() ).to.be.false; 138 | } ); 139 | 140 | it( 'for an RGBA hash with incomplete r, g, b properties', function () { 141 | expect( ( new Color( { r: 10, b: 10, a: 0.5 } ) ).isColor() ).to.be.false; 142 | } ); 143 | 144 | it( 'for an empty array', function () { 145 | expect( ( new Color( [] ) ).isColor() ).to.be.false; 146 | } ); 147 | 148 | it( 'for an array with arbitrary strings', function () { 149 | expect( ( new Color( [ "foo", "bar", "baz" ] ) ).isColor() ).to.be.false; 150 | } ); 151 | 152 | it( 'for an array with arbitrary percentage strings', function () { 153 | expect( ( new Color( [ "foo%", "bar%", "baz%" ] ) ).isColor() ).to.be.false; 154 | } ); 155 | 156 | it( 'for an array with a percentage above 100%', function () { 157 | expect( ( new Color( [ "10%", "100.01%", "10%" ] ) ).isColor() ).to.be.false; 158 | } ); 159 | 160 | it( 'for an array with a negative percentage', function () { 161 | expect( ( new Color( [ "-10%", "100%", "10%" ] ) ).isColor() ).to.be.false; 162 | } ); 163 | 164 | it( 'for an RGB array with a number greater than 255', function () { 165 | expect( ( new Color( [ 10, 256, 10 ] ) ).isColor() ).to.be.false; 166 | } ); 167 | 168 | it( 'for an RGB array with a negative number', function () { 169 | expect( ( new Color( [ 10, -10, 10 ] ) ).isColor() ).to.be.false; 170 | } ); 171 | 172 | it( 'for an array with an alpha channel greater than 1', function () { 173 | expect( ( new Color( [ 10, 256, 10, 1.01 ] ) ).isColor() ).to.be.false; 174 | } ); 175 | 176 | it( 'for an array with a negative alpha channel', function () { 177 | expect( ( new Color( [ 10, 256, 10, -0.01 ] ) ).isColor() ).to.be.false; 178 | } ); 179 | 180 | it( 'for an array with more than four values', function () { 181 | expect( ( new Color( [ 1, 1, 1, 1, 1 ] ) ).isColor() ).to.be.false; 182 | } ); 183 | 184 | it( 'for a boolean true', function () { 185 | expect( ( new Color( true ) ).isColor() ).to.be.false; 186 | } ); 187 | 188 | it( 'for a boolean false', function () { 189 | expect( ( new Color( false ) ).isColor() ).to.be.false; 190 | } ); 191 | 192 | it( 'for null', function () { 193 | expect( ( new Color( null ) ).isColor() ).to.be.false; 194 | } ); 195 | 196 | it( 'for undefined', function () { 197 | expect( ( new Color( undefined ) ).isColor() ).to.be.false; 198 | } ); 199 | 200 | it( 'when the colour argument is missing', function () { 201 | expect( ( new Color() ).isColor() ).to.be.false; 202 | } ); 203 | 204 | } ); 205 | 206 | } ); 207 | 208 | describe( 'Output methods', function () { 209 | 210 | var colour, 211 | 212 | scenarios = { 213 | opaque: { 214 | input: [0, 153, 169], 215 | expected: { 216 | hex: "#0099A9", 217 | rgb: "rgb(0, 153, 169)", 218 | rgba: "rgba(0, 153, 169, 1)", 219 | rgbArray: [0, 153, 169], 220 | rgbaArray: [0, 153, 169, 1], 221 | rgbPercent: "rgb(0%, 60%, 66%)", 222 | rgbPercent_precision5: "rgb(0%, 60%, 66.27451%)", 223 | rgbPercent_precisionMax: "rgb(0%, 60%, " + ( 169/2.55 ) + "%)", 224 | rgbaPercent: "rgba(0%, 60%, 66%, 1)", 225 | rgbaPercent_precision5: "rgba(0%, 60%, 66.27451%, 1)", 226 | rgbaPercent_precisionMax: "rgba(0%, 60%, " + ( 169/2.55 ) + "%, 1)", 227 | agColor: "AgColor( 0, 0.6, " + ( 169/255 ) + ", 1 )" // additional whitespace as inserted by LR 228 | } 229 | }, 230 | transparent: { 231 | input: [0, 153, 169, 0.5], 232 | expected: { 233 | rgba: "rgba(0, 153, 169, 0.5)", 234 | rgbaArray: [0, 153, 169, 0.5], 235 | rgbaPercent: "0%, 60%, 66%, 0.5", 236 | agColor: "AgColor(0, 0.6, 0.66, 0.5)" 237 | } 238 | }, 239 | noColour: { 240 | input: "foo" 241 | } 242 | }; 243 | 244 | beforeEach( function () { 245 | colour = new Color( scenarios.opaque.input ); 246 | } ); 247 | 248 | describe( 'The asHex() method', function () { 249 | 250 | it( 'returns the hex value as #rrggbb by default', function () { 251 | expect( colour.asHex() ).to.equal( scenarios.opaque.expected.hex.toLowerCase() ); 252 | } ); 253 | 254 | it( 'returns the hex value as #rrggbb with options.lowerCase', function () { 255 | expect( colour.asHex( { lowerCase: true } ) ).to.equal( scenarios.opaque.expected.hex.toLowerCase() ); 256 | } ); 257 | 258 | it( 'returns the hex value as #RRGGBB with options.upperCase', function () { 259 | expect( colour.asHex( { upperCase: true } ) ).to.equal( scenarios.opaque.expected.hex.toUpperCase() ); 260 | } ); 261 | 262 | it( 'returns the hex value as #RRGGBB with options.lowerCase = false', function () { 263 | expect( colour.asHex( { lowerCase: false } ) ).to.equal( scenarios.opaque.expected.hex.toUpperCase() ); 264 | } ); 265 | 266 | it( 'returns the hex value as rrggbb (no "#" prefix) with options.prefix = false', function () { 267 | expect( colour.asHex( { prefix: false } ) ).to.equal( scenarios.opaque.expected.hex.toLowerCase().slice( 1 ) ); 268 | } ); 269 | 270 | it( 'throws an error when the colour is transparent', function () { 271 | expect( function () { 272 | Color( scenarios.transparent.input ).asHex(); 273 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 274 | } ); 275 | 276 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 277 | expect( function () { 278 | Color( scenarios.transparent.noColour ).asHex(); 279 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 280 | } ); 281 | 282 | } ); 283 | 284 | describe( 'The asHexLC() method', function () { 285 | 286 | it( 'returns the hex value as #rrggbb', function () { 287 | expect( colour.asHexLC() ).to.equal( scenarios.opaque.expected.hex.toLowerCase() ); 288 | } ); 289 | 290 | it( 'throws an error when the colour is transparent', function () { 291 | expect( function () { 292 | Color( scenarios.transparent.input ).asHexLC(); 293 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 294 | } ); 295 | 296 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 297 | expect( function () { 298 | Color( scenarios.transparent.noColour ).asHexLC(); 299 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 300 | } ); 301 | 302 | } ); 303 | 304 | describe( 'The asHexUC() method', function () { 305 | 306 | it( 'returns the hex value as #rrggbb', function () { 307 | expect( colour.asHexUC() ).to.equal( scenarios.opaque.expected.hex.toUpperCase() ); 308 | } ); 309 | 310 | it( 'throws an error when the colour is transparent', function () { 311 | expect( function () { 312 | Color( scenarios.transparent.input ).asHexUC(); 313 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 314 | } ); 315 | 316 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 317 | expect( function () { 318 | Color( scenarios.transparent.noColour ).asHexUC(); 319 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 320 | } ); 321 | 322 | } ); 323 | 324 | describe( 'The asRgb() method', function () { 325 | 326 | it( 'returns the rgb() value', function () { 327 | expect( colour.asRgb() ).to.equal( scenarios.opaque.expected.rgb ); 328 | } ); 329 | 330 | it( 'returns the rgb() value rounded to integers', function () { 331 | var colour = new Color( "AgColor(" + [254.4999/255, 254.5/255, 254.5111/255, 1].join( ", " ) + ")" ); 332 | expect( colour.asRgb() ).to.equal( "rgb(254, 255, 255)" ); 333 | } ); 334 | 335 | it( 'throws an error when the colour is transparent', function () { 336 | expect( function () { 337 | Color( scenarios.transparent.input ).asRgb(); 338 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 339 | } ); 340 | 341 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 342 | expect( function () { 343 | Color( scenarios.transparent.noColour ).asRgb(); 344 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 345 | } ); 346 | 347 | } ); 348 | 349 | describe( 'The asRgbPercent() method', function () { 350 | 351 | it( 'returns the rgb() value with percentages (integers)', function () { 352 | expect( colour.asRgbPercent() ).to.equal( scenarios.opaque.expected.rgbPercent ); 353 | } ); 354 | 355 | it( 'returns the rgb() value with percentages in the precision specified by options.precision', function () { 356 | expect( colour.asRgbPercent( { precision: 5 } ) ).to.equal( scenarios.opaque.expected.rgbPercent_precision5 ); 357 | } ); 358 | 359 | it( 'returns the rgb() value with percentages in full precision when options.precision = "max"', function () { 360 | expect( colour.asRgbPercent( { precision: "max" } ) ).to.equal( scenarios.opaque.expected.rgbPercent_precisionMax ); 361 | } ); 362 | 363 | it( 'returns the rgb() value with percentages in full, original precision when options.precision = "max"', function () { 364 | var inputStr = "rgb(0.01%, 66.078%, 66.079%)", 365 | colour = new Color( inputStr ); 366 | 367 | // Prior to the actual main test, verifying that the almost identical percentages for G and B map to 368 | // different G and B values when rounded to RGB integers. 369 | expect( colour.asRgb() ).to.equal( "rgb(0, 168, 169)" ); 370 | 371 | // Checking that G and B are returned in their original precision, not altered by a round trip into 372 | // another format. 373 | expect( colour.asRgbPercent( { precision: "max" } ) ).to.equal( inputStr ); 374 | } ); 375 | 376 | it( 'returns the rgb() value without converting near-zero values into scientific notation when options.precision = n', function () { 377 | // NB See the previous test for the fine print. With options.precision = n, we expect to get ordinary, 378 | // non-scientific notation for any number with n fractional digits. 379 | var inputStr = "rgb(0.000000000421%, 50%, 50%)", 380 | colour = new Color( inputStr ); 381 | 382 | expect( colour.asRgbPercent( { precision: 12 } ) ).to.equal( inputStr ); 383 | } ); 384 | 385 | it( 'returns the rgb() value without converting near-zero values into scientific notation when options.precision = "max"', function () { 386 | // NB In terms of the spec, this is a bit of a grey area. Scientific notation ("1e-9%" instead of 387 | // "0.000000001%") should be OK, but the spec doesn't say so explicitly for colours. Some browsers might 388 | // bark, even though the actual display value in the browser is 0 in any case. 389 | var inputStr = "rgb(0.000000000000000001%, 50%, 50%)", // 1e-18 390 | colour = new Color( inputStr ); 391 | 392 | expect( colour.asRgbPercent( { precision: "max" } ) ).to.equal( inputStr ); 393 | } ); 394 | 395 | it( 'throws an error when the colour is transparent', function () { 396 | expect( function () { 397 | Color( scenarios.transparent.input ).asRgbPercent(); 398 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 399 | } ); 400 | 401 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 402 | expect( function () { 403 | Color( scenarios.transparent.noColour ).asRgbPercent(); 404 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 405 | } ); 406 | 407 | } ); 408 | 409 | describe( 'The asRgba() method', function () { 410 | 411 | it( 'returns the rgba() value', function () { 412 | expect( colour.asRgba() ).to.equal( scenarios.opaque.expected.rgba ); 413 | } ); 414 | 415 | it( 'returns the RGB channels of the rgba() value rounded to integers', function () { 416 | var colour = new Color( "AgColor(" + [254.4999/255, 254.5/255, 254.5111/255, 0.6789].join( ", " ) + ")" ); 417 | expect( colour.asRgba() ).to.equal( "rgba(254, 255, 255, 0.6789)" ); 418 | } ); 419 | 420 | it( 'does not throw an error when the colour is transparent', function () { 421 | expect( function () { 422 | Color( scenarios.transparent.input ).asRgba(); 423 | } ).not.to.throw(); 424 | } ); 425 | 426 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 427 | expect( function () { 428 | Color( scenarios.transparent.noColour ).asRgba(); 429 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 430 | } ); 431 | 432 | } ); 433 | 434 | describe( 'The asRgbaPercent() method', function () { 435 | 436 | it( 'returns the rgba() value with percentages (integers)', function () { 437 | expect( colour.asRgbaPercent() ).to.equal( scenarios.opaque.expected.rgbaPercent ); 438 | } ); 439 | 440 | it( 'returns the rgba() value with percentages in the precision specified by options.precision', function () { 441 | expect( colour.asRgbaPercent( { precision: 5 } ) ).to.equal( scenarios.opaque.expected.rgbaPercent_precision5 ); 442 | } ); 443 | 444 | it( 'returns the rgba() value with percentages in full precision when options.precision = "max"', function () { 445 | expect( colour.asRgbaPercent( { precision: "max" } ) ).to.equal( scenarios.opaque.expected.rgbaPercent_precisionMax ); 446 | } ); 447 | 448 | it( 'returns the rgba() value with percentages in full, original precision when options.precision = "max"', function () { 449 | var inputStr = "rgba(0.01%, 66.078%, 66.079%, 0.4)", 450 | colour = new Color( inputStr ); 451 | 452 | // Prior to the actual main test, verifying that the almost identical percentages for G and B map to 453 | // different G and B values when rounded to RGB integers. 454 | expect( colour.asRgba() ).to.equal( "rgba(0, 168, 169, 0.4)" ); 455 | 456 | // Checking that G and B are returned in their original precision, not altered by a round trip into 457 | // another format. 458 | expect( colour.asRgbaPercent( { precision: "max" } ) ).to.equal( inputStr ); 459 | } ); 460 | 461 | it( 'returns the rgba() value without converting near-zero values into scientific notation when options.precision = n', function () { 462 | // NB See the previous test for the fine print. With options.precision = n, we expect to get ordinary, 463 | // non-scientific notation for any number with n fractional digits. 464 | var inputStr = "rgba(0.000000000421%, 50%, 50%, 0.00000000000421)", 465 | colour = new Color( inputStr ); 466 | 467 | expect( colour.asRgbaPercent( { precision: 12 } ) ).to.equal( inputStr ); 468 | } ); 469 | 470 | it( 'returns the rgba() value without converting near-zero values into scientific notation when options.precision = "max"', function () { 471 | // NB In terms of the spec, this is a bit of a grey area. Scientific notation ("1e-9%" instead of 472 | // "0.000000001%") should be OK, but the spec doesn't say so explicitly for colours. Some browsers might 473 | // bark, even though the actual display value in the browser is 0 in any case. 474 | var inputStr = "rgba(0.000000000000000001%, 50%, 50%, 0.000000000000000001)", // 1e-18 475 | colour = new Color( inputStr ); 476 | 477 | expect( colour.asRgbaPercent( { precision: "max" } ) ).to.equal( inputStr ); 478 | } ); 479 | 480 | it( 'does not throw an error when the colour is transparent', function () { 481 | expect( function () { 482 | Color( scenarios.transparent.input ).asRgbaPercent(); 483 | } ).not.to.throw(); 484 | } ); 485 | 486 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 487 | expect( function () { 488 | Color( scenarios.transparent.noColour ).asRgbaPercent(); 489 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 490 | } ); 491 | 492 | } ); 493 | 494 | describe( 'The asAgColor() method', function () { 495 | 496 | // NB Lightroom inserts an additional space between values and enclosing parentheses, which is reflected in 497 | // the expected values of the tests. 498 | 499 | it( 'returns the AgColor() value with fractions', function () { 500 | expect( colour.asAgColor() ).to.equal( scenarios.opaque.expected.agColor ); 501 | } ); 502 | 503 | it( 'returns the AgColor() fractions in full, original precision', function () { 504 | var inputStr = "AgColor( 0.0001, 0.66078, 0.66079, 0.4123456789 )", 505 | colour = new Color( inputStr ); 506 | 507 | // Prior to the actual main test, verifying that the almost identical percentages for G and B map to 508 | // different G and B values when rounded to RGB integers. 509 | expect( colour.asRgba() ).to.equal( "rgba(0, 168, 169, 0.4123456789)" ); 510 | 511 | // Checking that G and B are returned in their original precision, not altered by a round trip into 512 | // another format. 513 | expect( colour.asAgColor() ).to.equal( inputStr ); 514 | } ); 515 | 516 | it( 'returns the AgColor() value without converting near-zero values into scientific notation when options.precision = "max"', function () { 517 | // NB Scientific notation ("1e-9" instead of "0.000000001") should be OK, but LR is pretty much a black 518 | // box in that regard, so better not risk it. 519 | var inputStr = "AgColor( 0.00000000000000000123, 0.5, 0.5, 0.00000000000000000123 )", // 1.23e-18 520 | colour = new Color( inputStr ); 521 | 522 | expect( colour.asAgColor() ).to.equal( inputStr ); 523 | } ); 524 | 525 | it( 'does not throw an error when the colour is transparent', function () { 526 | expect( function () { 527 | Color( scenarios.transparent.input ).asAgColor(); 528 | } ).not.to.throw(); 529 | } ); 530 | 531 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 532 | expect( function () { 533 | Color( scenarios.transparent.noColour ).asAgColor(); 534 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 535 | } ); 536 | 537 | } ); 538 | 539 | describe( 'The asRgbArray() method', function () { 540 | 541 | var preciseScenario, 542 | preciseColour; 543 | 544 | beforeEach( function () { 545 | preciseScenario = { 546 | input: [254.499999999460001, 254.5, 254.500000000150001], 547 | expected: { 548 | rounded: [254, 255, 255], 549 | precision10: [254.4999999995, 254.5, 254.5000000002], 550 | precisionMax: [254.499999999460001, 254.5, 254.500000000150001] // same as input 551 | } 552 | }; 553 | 554 | preciseColour = new Color( preciseScenario.input ); 555 | } ); 556 | 557 | it( 'returns the rgb() values', function () { 558 | expect( colour.asRgbArray() ).to.eql( scenarios.opaque.expected.rgbArray ); 559 | } ); 560 | 561 | it( 'returns the rgb() values rounded to integers', function () { 562 | expect( preciseColour.asRgbArray() ).to.eql( preciseScenario.expected.rounded ); 563 | } ); 564 | 565 | it( 'returns the rgb() values in the precision specified by options.precision', function () { 566 | expect( preciseColour.asRgbArray( { precision: 10 } ) ).to.eql( preciseScenario.expected.precision10 ); 567 | } ); 568 | 569 | it( 'returns the rgb() values in full precision when options.precision = "max"', function () { 570 | expect( preciseColour.asRgbArray( { precision: "max" } ) ).to.eql( preciseScenario.expected.precisionMax ); 571 | } ); 572 | 573 | it( 'throws an error when the colour is transparent', function () { 574 | expect( function () { 575 | Color( scenarios.transparent.input ).asRgbArray(); 576 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 577 | } ); 578 | 579 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 580 | expect( function () { 581 | Color( scenarios.transparent.noColour ).asRgbArray(); 582 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 583 | } ); 584 | 585 | } ); 586 | 587 | describe( 'The asRgbaArray() method', function () { 588 | 589 | var preciseScenario, 590 | preciseColour; 591 | 592 | beforeEach( function () { 593 | preciseScenario = { 594 | input: [254.499999999460001, 254.5, 254.500000000150001, 0.5], 595 | expected: { 596 | rounded: [254, 255, 255, 0.5], 597 | precision10: [254.4999999995, 254.5, 254.5000000002, 0.5], 598 | precisionMax: [254.499999999460001, 254.5, 254.500000000150001, 0.5] // same as input 599 | } 600 | }; 601 | 602 | preciseColour = new Color( preciseScenario.input ); 603 | } ); 604 | 605 | it( 'returns the rgba() values', function () { 606 | expect( colour.asRgbaArray() ).to.eql( scenarios.opaque.expected.rgbaArray ); 607 | } ); 608 | 609 | it( 'returns the RGB channels of the array rounded to integers', function () { 610 | expect( preciseColour.asRgbaArray() ).to.eql( preciseScenario.expected.rounded ); 611 | } ); 612 | 613 | it( 'returns the RGB channels of the array in the precision specified by options.precision', function () { 614 | expect( preciseColour.asRgbaArray( { precision: 10 } ) ).to.eql( preciseScenario.expected.precision10 ); 615 | } ); 616 | 617 | it( 'returns the RGB channels of the array in full precision when options.precision = "max"', function () { 618 | expect( preciseColour.asRgbaArray( { precision: "max" } ) ).to.eql( preciseScenario.expected.precisionMax ); 619 | } ); 620 | 621 | it( 'does not throw an error when the colour is transparent', function () { 622 | expect( function () { 623 | Color( scenarios.transparent.input ).asRgbaArray(); 624 | } ).not.to.throw(); 625 | } ); 626 | 627 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 628 | expect( function () { 629 | Color( scenarios.transparent.noColour ).asRgbaArray(); 630 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 631 | } ); 632 | 633 | } ); 634 | 635 | describe( 'The asComputed() method', function () { 636 | 637 | it( 'returns the rgb() value when the colour is opaque', function () { 638 | expect( Color( scenarios.opaque.input ).asComputed() ).to.equal( scenarios.opaque.expected.rgb ); 639 | } ); 640 | 641 | it( 'returns the channels of the rgb() value rounded to integers', function () { 642 | var colour = new Color( "AgColor(" + [254.4999/255, 254.5/255, 254.5111/255, 1].join( ", " ) + ")" ); 643 | expect( colour.asComputed() ).to.equal( "rgb(254, 255, 255)" ); 644 | } ); 645 | 646 | it( 'returns the rgba() value when the colour is transparent', function () { 647 | expect( Color( scenarios.transparent.input ).asComputed() ).to.equal( scenarios.transparent.expected.rgba ); 648 | } ); 649 | 650 | it( 'returns the RGB channels of the rgba() value rounded to integers', function () { 651 | var colour = new Color( "AgColor(" + [254.4999/255, 254.5/255, 254.5111/255, 0.6789].join( ", " ) + ")" ); 652 | expect( colour.asComputed() ).to.equal( "rgba(254, 255, 255, 0.6789)" ); 653 | } ); 654 | 655 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 656 | expect( function () { 657 | Color( scenarios.transparent.noColour ).asComputed(); 658 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 659 | } ); 660 | 661 | } ); 662 | 663 | } ); 664 | 665 | describe( 'Query methods', function () { 666 | 667 | var inputs = { 668 | opaque: [0, 153, 169], 669 | transparent: [0, 153, 169, 0.5], 670 | noColour: "foo" 671 | }; 672 | 673 | describe( 'The isColor() method', function () { 674 | 675 | it( 'returns true when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 676 | var colour = new Color( inputs.opaque ); 677 | expect( colour.isColor() ).to.be.true; 678 | } ); 679 | 680 | it( 'returns true when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 681 | var colour = new Color( inputs.transparent ); 682 | expect( colour.isColor() ).to.be.true; 683 | } ); 684 | 685 | it( 'returns false when the colour passed to the constructor has not been a valid CSS colour', function () { 686 | var colour = new Color( inputs.noColour ); 687 | expect( colour.isColor() ).to.be.false; 688 | } ); 689 | 690 | } ); 691 | 692 | describe( 'The isOpaque() method', function () { 693 | 694 | it( 'returns true when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 695 | var colour = new Color( inputs.opaque ); 696 | expect( colour.isOpaque() ).to.be.true; 697 | } ); 698 | 699 | it( 'returns false when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 700 | var colour = new Color( inputs.transparent ); 701 | expect( colour.isOpaque() ).to.be.false; 702 | } ); 703 | 704 | it( 'returns false when the colour passed to the constructor has not been a valid CSS colour', function () { 705 | var colour = new Color( inputs.noColour ); 706 | expect( colour.isOpaque() ).to.be.false; 707 | } ); 708 | 709 | } ); 710 | 711 | describe( 'The isTransparent() method', function () { 712 | 713 | it( 'returns false when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 714 | var colour = new Color( inputs.opaque ); 715 | expect( colour.isTransparent() ).to.be.false; 716 | } ); 717 | 718 | it( 'returns true when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 719 | var colour = new Color( inputs.transparent ); 720 | expect( colour.isTransparent() ).to.be.true; 721 | } ); 722 | 723 | it( 'returns false when the colour passed to the constructor has not been a valid CSS colour', function () { 724 | var colour = new Color( inputs.noColour ); 725 | expect( colour.isTransparent() ).to.be.false; 726 | } ); 727 | 728 | } ); 729 | 730 | } ); 731 | 732 | describe( 'Validation methods', function () { 733 | 734 | var inputs = { 735 | opaque: [0, 153, 169], 736 | transparent: [0, 153, 169, 0.5], 737 | noColour: "foo" 738 | }; 739 | 740 | describe( 'The ensureColor() method', function () { 741 | 742 | it( 'does not throw an error, and returns true, when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 743 | var colour = new Color( inputs.opaque ); 744 | expect( colour.ensureColor() ).to.be.true; 745 | } ); 746 | 747 | it( 'does not throw an error, and returns true, when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 748 | var colour = new Color( inputs.transparent ); 749 | expect( colour.ensureColor() ).to.be.true; 750 | } ); 751 | 752 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 753 | var colour = new Color( inputs.noColour ); 754 | expect( function () { 755 | colour.ensureColor(); 756 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 757 | } ); 758 | 759 | } ); 760 | 761 | describe( 'The ensureOpaque() method', function () { 762 | 763 | it( 'does not throw an error, and returns true, when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 764 | var colour = new Color( inputs.opaque ); 765 | expect( colour.ensureOpaque() ).to.be.true; 766 | } ); 767 | 768 | it( 'throws an error when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 769 | var colour = new Color( inputs.transparent ); 770 | expect( function () { 771 | colour.ensureOpaque(); 772 | } ).to.throw( "Color.ensureOpaque: Color is required to be opaque, but it is not" ); 773 | } ); 774 | 775 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 776 | var colour = Color( inputs.noColour ); 777 | expect( function () { 778 | colour.ensureOpaque(); 779 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 780 | } ); 781 | 782 | } ); 783 | 784 | describe( 'The ensureTransparent() method', function () { 785 | 786 | it( 'does not throw an error, and returns true, when the colour passed to the constructor has been a valid, transparent CSS colour', function () { 787 | var colour = new Color( inputs.transparent ); 788 | expect( colour.ensureTransparent() ).to.be.true; 789 | } ); 790 | 791 | it( 'throws an error when the colour passed to the constructor has been a valid, opaque CSS colour', function () { 792 | var colour = new Color( inputs.opaque ); 793 | expect( function () { 794 | colour.ensureTransparent(); 795 | } ).to.throw( "Color.ensureTransparent: Color is required to be transparent, but it is not" ); 796 | } ); 797 | 798 | it( 'throws an error when the colour passed to the constructor has not been a valid CSS colour', function () { 799 | var colour = Color( inputs.noColour ); 800 | expect( function () { 801 | colour.ensureTransparent(); 802 | } ).to.throw( "Color.ensureColor: The color object does not represent a valid color" ); 803 | } ); 804 | 805 | } ); 806 | 807 | } ); 808 | 809 | describe( 'Comparison methods', function () { 810 | 811 | var inputs = { 812 | noAlpha: { 813 | asRgb: "rgb(0, 128, 254)", 814 | asHex: "#0080FE", 815 | asPercentApprox: "rgb(0%, 50%, 99.411765%)", // precise percentages: 0%, 50.1960784314%, 99.6078431373% 816 | adjacent: { 817 | asPercent: "rgb(0%, 50%, 99.411764%)" // boundary between 253 and 254 is at 99,4117647...% 818 | }, 819 | offBy2: { 820 | above: [2, 130, 255], 821 | below: [0, 126, 252] 822 | }, 823 | offByJustMoreThanTwo: { 824 | above: [( 2.5 / 2.55 ) + "%", ( 130.5 / 2.55 ) + "%", "100%"], 825 | below: ["0%", ( 125.4999999999 / 2.55 ) + "%", ( 251.4999999999 / 2.55 ) + "%"] 826 | } 827 | }, 828 | alpha: { 829 | value: [10, 20, 30, 0.5], 830 | alphaDiff: [10, 20, 30, 0.5000000001] 831 | } 832 | }; 833 | 834 | describe( 'The equals() method', function () { 835 | 836 | it( 'accepts string input and detects equality when colours match exactly', function () { 837 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.asHex ) ).to.be.true; 838 | } ); 839 | 840 | it( 'accepts another color object and detects equality when colours match exactly', function () { 841 | var otherColour = new Color( inputs.noAlpha.asHex ); 842 | expect( Color( inputs.noAlpha.asRgb ).equals( otherColour ) ).to.be.true; 843 | } ); 844 | 845 | it( 'returns true if colours values are slightly different but map to the same RGB integers', function () { 846 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.asPercentApprox ) ).to.be.true; 847 | } ); 848 | 849 | it( 'returns false if colour values are slightly different but map to adjacent RGB values', function () { 850 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.adjacent.asPercent ) ).to.be.false; 851 | } ); 852 | 853 | it( 'returns true if the colour values map to different RGB values but only differ as much as the specified tolerance', function () { 854 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.offBy2.above, { tolerance: 2 } ) ).to.be.true; 855 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.offBy2.below, { tolerance: 2 } ) ).to.be.true; 856 | } ); 857 | 858 | it( 'returns false if the colour values map to different RGB values and differ slightly more than the specified tolerance', function () { 859 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.offByJustMoreThanTwo.above, { tolerance: 2 } ) ).to.be.false; 860 | expect( Color( inputs.noAlpha.asRgb ).equals( inputs.noAlpha.offByJustMoreThanTwo.below, { tolerance: 2 } ) ).to.be.false; 861 | } ); 862 | 863 | it( 'returns false if the RGB colour values are identical but the alpha channel differs slightly, irrespective of the specified tolerance', function () { 864 | expect( Color( inputs.alpha.value ).equals( inputs.alpha.alphaDiff, { tolerance: 255 } ) ).to.be.false; 865 | } ); 866 | 867 | it( 'returns false if the colour object is not a valid colour', function () { 868 | expect( Color( "foo" ).equals( inputs.noAlpha.asHex ) ).to.be.false; 869 | } ); 870 | 871 | it( 'returns false if the colour object is not a valid colour, when a tolerance is specified', function () { 872 | expect( Color( "foo" ).equals( inputs.noAlpha.asHex, { tolerance: 2 } ) ).to.be.false; 873 | } ); 874 | 875 | it( 'returns false if the colour argument is not a valid colour', function () { 876 | expect( Color( inputs.noAlpha.asHex ).equals( "foo" ) ).to.be.false; 877 | } ); 878 | 879 | it( 'returns false if the colour argument is not a valid colour, when a tolerance is specified', function () { 880 | expect( Color( inputs.noAlpha.asHex ).equals( "foo", { tolerance: 2 } ) ).to.be.false; 881 | } ); 882 | 883 | it( 'returns false if the colour object and the colour argument are defined by the same input value, but the value is not a valid colour', function () { 884 | expect( Color( "foo" ).equals( "foo" ) ).to.be.false; 885 | } ); 886 | 887 | } ); 888 | 889 | describe( 'The strictlyEquals() method', function () { 890 | 891 | it( 'accepts string input and detects equality when colours match exactly', function () { 892 | expect( Color( inputs.noAlpha.asRgb ).strictlyEquals( inputs.noAlpha.asHex ) ).to.be.true; 893 | } ); 894 | 895 | it( 'accepts another color object and detects equality when colours match exactly', function () { 896 | var otherColour = new Color( inputs.noAlpha.asHex ); 897 | expect( Color( inputs.noAlpha.asRgb ).strictlyEquals( otherColour ) ).to.be.true; 898 | } ); 899 | 900 | it( 'returns false if colour values are different', function () { 901 | expect( Color( inputs.noAlpha.asRgb ).strictlyEquals( inputs.noAlpha.adjacent.asPercent ) ).to.be.false; 902 | } ); 903 | 904 | it( 'returns false if colours values are slightly different but map to the same RGB integers', function () { 905 | expect( Color( inputs.noAlpha.asRgb ).strictlyEquals( inputs.noAlpha.asPercentApprox ) ).to.be.false; 906 | } ); 907 | 908 | it( 'returns false if colours values are slightly different but would be rounded to the same RGB percentages', function () { 909 | expect( Color( "rgb(0%, 50%, 100%)" ).strictlyEquals( "rgb(0.00000000000000000001%, 50.00000000000000000001%, 99.99999999999999999999%)" ) ).to.be.false; 910 | } ); 911 | 912 | it( 'returns false if the RGB colour values are identical but the alpha channel differs slightly', function () { 913 | expect( Color( inputs.alpha.value ).strictlyEquals( inputs.alpha.alphaDiff ) ).to.be.false; 914 | } ); 915 | 916 | it( 'returns false if the colour object is not a valid colour', function () { 917 | expect( Color( "foo" ).strictlyEquals( inputs.noAlpha.asHex ) ).to.be.false; 918 | } ); 919 | 920 | it( 'returns false if the colour argument is not a valid colour', function () { 921 | expect( Color( inputs.noAlpha.asHex ).strictlyEquals( "foo" ) ).to.be.false; 922 | } ); 923 | 924 | it( 'returns false if the colour object and the colour argument are defined by the same input value, but the value is not a valid colour', function () { 925 | expect( Color( "foo" ).strictlyEquals( "foo" ) ).to.be.false; 926 | } ); 927 | 928 | } ); 929 | 930 | } ); 931 | 932 | })(); --------------------------------------------------------------------------------