├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist └── deltae.global.min.js ├── gulpfile.js ├── package.json ├── src ├── dE00.js ├── dE76.js ├── dE94.js └── index.js └── tests └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /nbproject/ 3 | /jsdoc/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeltaE - Quantify Color Difference 2 | 3 | Check out the following GitHub Pages respository for information on this library. 4 | 5 | http://zschuessler.github.io/DeltaE/ 6 | 7 | ## About Delta E 8 | 9 | This package gives access to three color difference algorithms. 10 | These algorithms represent the hard work of the [International Commission on Illumination (CIE).](http://www.cie.co.at/) 11 | 12 | 13 | Historically, each iterative algorithm has been used in print 14 | and textile industries to maintain consistency in machine calibration. 15 | These days, far more interesting use cases arise with media processing. 16 | 17 | ## Install It 18 | 19 | DeltaE comes in flavors of npm and Bower. 20 | 21 | // install via npm 22 | npm install delta-e 23 | 24 | // install via Bower 25 | bower install delta-e 26 | 27 | ### Use It 28 | // For npm: use require 29 | var DeltaE = require('delta-e'); 30 | 31 | // For Bower: include the script. DeltaE global now accessible. 32 | 33 | 34 | // Create two test LAB color objects to compare! 35 | var color1 = {L: 36, A: 60, B: 41}; 36 | var color2 = {L: 100, A: 40, B: 90}; 37 | 38 | // 1976 formula 39 | console.log(DeltaE.getDeltaE76(color1, color2)); 40 | 41 | // 1994 formula 42 | console.log(DeltaE.getDeltaE94(color1, color2)); 43 | 44 | // 2000 formula 45 | console.log(DeltaE.getDeltaE00(color1, color2)); 46 | 47 | ## Tests 48 | 49 | A simple Mocha test is setup to test the accuracy of each dE algorithm, for both 50 | npm and Bower versions. 51 | 52 | $ cd tests/ 53 | $ mocha main.js 54 | 55 | ## Gulp Tasks 56 | 57 | ### build 58 | 59 | Builds both the Bower and npm version from source. 60 | 61 | $ gulp build 62 | 63 | ### jsdoc 64 | 65 | Generates full documentation in the /jsdoc/ folder. 66 | 67 | $ gulp jsdoc 68 | 69 | ## Licensing 70 | 71 | This is Unlicensed. Do what you want. Just don't sue me, and we is cool. 72 | 73 | > This is free and unencumbered software released into the public domain. 74 | > 75 | > Anyone is free to copy, modify, publish, use, compile, sell, or 76 | distribute this software, either in source code form or as a compiled 77 | binary, for any purpose, commercial or non-commercial, and by any 78 | means. 79 | > 80 | > In jurisdictions that recognize copyright laws, the author or authors 81 | of this software dedicate any and all copyright interest in the 82 | software to the public domain. We make this dedication for the benefit 83 | of the public at large and to the detriment of our heirs and 84 | successors. We intend this dedication to be an overt act of 85 | relinquishment in perpetuity of all present and future rights to this 86 | software under copyright law. 87 | > 88 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 91 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 92 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 93 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 94 | OTHER DEALINGS IN THE SOFTWARE. 95 | > 96 | > For more information, please refer to 97 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delta-e", 3 | "version": "0.0.7", 4 | "homepage": "https://github.com/zschuessler/DeltaE", 5 | "authors": [ 6 | "Zachary Schuessler " 7 | ], 8 | "description": "Color difference algorithms..in JavaScript", 9 | "main": "dist/deltae.global.min.js", 10 | "moduleType": [ 11 | "globals" 12 | ], 13 | "keywords": [ 14 | "deltae", 15 | "delta", 16 | "e" 17 | ], 18 | "license": "Unlicensed" 19 | } 20 | -------------------------------------------------------------------------------- /dist/deltae.global.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i;i="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,i.DeltaE=t()}}(function(){return function t(i,e,s){function h(a,n){if(!e[a]){if(!i[a]){var o="function"==typeof require&&require;if(!n&&o)return o(a,!0);if(r)return r(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var m=e[a]={exports:{}};i[a][0].call(m.exports,function(t){var e=i[a][1][t];return h(e?e:t)},m,m.exports,t,i,e,s)}return e[a].exports}for(var r="function"==typeof require&&require,a=0;a180?(this.hPrime1+this.hPrime2+360)/2:(this.hPrime1+this.hPrime2)/2},s.prototype.getDeltahPrime=function(){var t=Math.abs;return 0===this.C1||0===this.C2?0:t(this.hPrime1-this.hPrime2)<=180?this.hPrime2-this.hPrime1:this.hPrime2<=this.hPrime1?this.hPrime2-this.hPrime1+360:this.hPrime2-this.hPrime1-360},s.prototype.gethPrime1=function(){return this._gethPrimeFn(this.x1.B,this.aPrime1)},s.prototype.gethPrime2=function(){return this._gethPrimeFn(this.x2.B,this.aPrime2)},s.prototype._gethPrimeFn=function(t,i){var e;return 0===t&&0===i?0:(e=this.radiansToDegrees(Math.atan2(t,i)),e>=0?e:e+360)},s.prototype.radiansToDegrees=function(t){return t*(180/Math.PI)},s.prototype.degreesToRadians=function(t){return t*(Math.PI/180)},i.exports=s},{}],3:[function(t,i,e){"use strict";function s(t,i){this.x1=t,this.x2=i}s.prototype.getDeltaE=function(){var t=this.x1,i=this.x2;return Math.sqrt(Math.pow(i.L-t.L,2)+Math.pow(i.A-t.A,2)+Math.pow(i.B-t.B,2))},i.exports=s},{}],4:[function(t,i,e){"use strict";function s(t,i,e){this.x1=t,this.x2=i,this.weights=e||{},this.weights.lightness=this.weights.lightness||1,this.weights.chroma=this.weights.chroma||1,this.weights.hue=this.weights.hue||1,1===this.weights.lightness?(this.weights.K1=.045,this.weights.K2=.015):(this.weights.K1=.048,this.weights.K2=.014)}s.prototype.getDeltaE=function(){var t=this.x1,i=this.x2,e=Math.sqrt,s=Math.pow;return e(s(this.calculateL(t,i),2)+s(this.calculateA(t,i),2)+s(this.calculateB(t,i),2))},s.prototype.calculateL=function(t,i){return(t.L-i.L)/this.weights.lightness},s.prototype.calculateA=function(t,i){var e=Math.sqrt,s=Math.pow,h=e(s(t.A,2)+s(t.B,2)),r=e(s(i.A,2)+s(i.B,2)),a=h-r,n=1+this.weights.K1*h;return a/(this.weights.chroma*n)},s.prototype.calculateB=function(t,i){var e=Math.sqrt,s=Math.pow,h=e(s(t.A,2)+s(t.B,2)),r=e(s(i.A,2)+s(i.B,2)),a=h-r,n=t.A-i.A,o=t.B-i.B,u=e(s(n,2)+s(o,2)-s(a,2))||0,h=e(s(t.A,2)+s(t.B,2)),m=1+this.weights.K2*h;return u/m},i.exports=s},{}]},{},[1])(1)}); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('browserify'); 3 | var source = require('vinyl-source-stream'); 4 | var wrap = require('gulp-wrap-exports'); 5 | var streamify = require('gulp-streamify'); 6 | var uglify = require('gulp-uglify'); 7 | var buffer = require('vinyl-buffer'); 8 | var jsdoc = require('gulp-jsdoc'); 9 | 10 | // Build all distribution files from source 11 | gulp.task('build', function() { 12 | // global export version 13 | browserify('./src/index.js',{ 14 | standalone: 'DeltaE' 15 | }) 16 | .bundle() 17 | .pipe(source('deltae.global.min.js')) 18 | .pipe(buffer()) 19 | .pipe(uglify()) 20 | .pipe(gulp.dest('./dist/')); 21 | }); 22 | 23 | // Generate JSDocs 24 | gulp.task('jsdoc', function() { 25 | gulp 26 | .src(['./src/*.js', './README.md']) 27 | .pipe(jsdoc('./jsdoc', { 28 | path: 'ink-docstrap' 29 | })); 30 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delta-e", 3 | "version": "0.0.8", 4 | "description": "CIE color difference algorithms in JavaScript.", 5 | "private": false, 6 | "main": "./src/index.js", 7 | "keywords": [ 8 | "cie", 9 | "cie94", 10 | "cie76", 11 | "color difference", 12 | "color algorithm", 13 | "delta e" 14 | ], 15 | "maintainers": [ 16 | { 17 | "name": "Zachary Schuessler", 18 | "email": "zlschuessler@gmail.com", 19 | "web": "http://zaclee.net" 20 | } 21 | ], 22 | "licenses": [ 23 | { 24 | "name": "Unlicense", 25 | "url": "http://unlicense.org/" 26 | } 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/zschuessler/DeltaE.git" 31 | }, 32 | "devDependencies": { 33 | "browserify": "^9.0.3", 34 | "gulp": "^3.8.11", 35 | "gulp-browserify": "^0.5.0", 36 | "gulp-concat": "^2.5.2", 37 | "gulp-jshint": "^1.8.5", 38 | "gulp-rename": "^1.2.0", 39 | "gulp-sourcemaps": "^1.5.1", 40 | "gulp-streamify": "0.0.5", 41 | "gulp-uglify": "^1.1.0", 42 | "gulp-wrap-exports": "^0.3.0", 43 | "gulp-wrap-umd": "^0.2.1", 44 | "vinyl-source-stream": "^1.1.0", 45 | "gulp-jsdoc": "~0.1.4", 46 | "vinyl-buffer": "~1.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/dE00.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @class dE00 5 | * @classdesc 6 | * The CIE2000 color difference algorithm. 7 | * http://en.wikipedia.org/wiki/Color_difference#CIEDE2000 8 | * @constructs dE00 9 | * @memberOf DeltaE 10 | * @property {object} x1 The LAB color configuration object. 11 | * @property {number} x1.L The lightness value, on scale of 0-100. 12 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 13 | * @property {number} x1.B The hue value, on scale of -128 to 128. 14 | * @property {object} x2 The LAB color configuration object. 15 | * @property {number} x2.L The lightness value, on scale of 0-100. 16 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 17 | * @property {number} x2.B The hue value, on scale of -128 to 128. 18 | * @property {object} weights The weights configuration object. 19 | * @property {number} weights.lightness A weight factor to apply to lightness. 20 | * @property {number} weights.chroma A weight factor to apply to chroma. 21 | * @property {number} weights.hue A weight factor to apply to hue. 22 | * @example 23 | * var deltaE = new dE00( 24 | * {L:50, A:50, B:50}, 25 | * {L:100, A:50, B:50}, 26 | * ); 27 | * console.log(deltaE.getDeltaE()); 28 | */ 29 | function dE00(x1, x2, weights) { 30 | var sqrt = Math.sqrt; 31 | var pow = Math.pow; 32 | 33 | this.x1 = x1; 34 | this.x2 = x2; 35 | 36 | this.weights = weights || {}; 37 | this.ksubL = this.weights.lightness || 1; 38 | this.ksubC = this.weights.chroma || 1; 39 | this.ksubH = this.weights.hue || 1; 40 | 41 | // Delta L Prime 42 | this.deltaLPrime = x2.L - x1.L; 43 | 44 | // L Bar 45 | this.LBar = (x1.L + x2.L) / 2; 46 | 47 | // C1 & C2 48 | this.C1 = sqrt(pow(x1.A, 2) + pow(x1.B, 2)); 49 | this.C2 = sqrt(pow(x2.A, 2) + pow(x2.B, 2)); 50 | 51 | // C Bar 52 | this.CBar = (this.C1 + this.C2) / 2; 53 | 54 | // A Prime 1 55 | this.aPrime1 = x1.A + 56 | (x1.A / 2) * 57 | (1 - sqrt( 58 | pow(this.CBar, 7) / 59 | (pow(this.CBar, 7) + pow(25, 7)) 60 | )); 61 | 62 | // A Prime 2 63 | this.aPrime2 = x2.A + 64 | (x2.A / 2) * 65 | (1 - sqrt( 66 | pow(this.CBar, 7) / 67 | (pow(this.CBar, 7) + pow(25, 7)) 68 | )); 69 | 70 | // C Prime 1 71 | this.CPrime1 = sqrt( 72 | pow(this.aPrime1, 2) + 73 | pow(x1.B, 2) 74 | ); 75 | 76 | // C Prime 2 77 | this.CPrime2 = sqrt( 78 | pow(this.aPrime2, 2) + 79 | pow(x2.B, 2) 80 | ); 81 | 82 | // C Bar Prime 83 | this.CBarPrime = (this.CPrime1 + this.CPrime2) / 2; 84 | 85 | // Delta C Prime 86 | this.deltaCPrime = this.CPrime2 - this.CPrime1; 87 | 88 | // S sub L 89 | this.SsubL = 1 + ( 90 | (0.015 * pow(this.LBar - 50, 2)) / 91 | sqrt(20 + pow(this.LBar - 50, 2)) 92 | ); 93 | 94 | // S sub C 95 | this.SsubC = 1 + 0.045 * this.CBarPrime; 96 | 97 | /** 98 | * Properties set in getDeltaE method, for access to convenience functions 99 | */ 100 | // h Prime 1 101 | this.hPrime1 = 0; 102 | 103 | // h Prime 2 104 | this.hPrime2 = 0; 105 | 106 | // Delta h Prime 107 | this.deltahPrime = 0; 108 | 109 | // Delta H Prime 110 | this.deltaHPrime = 0; 111 | 112 | // H Bar Prime 113 | this.HBarPrime = 0; 114 | 115 | // T 116 | this.T = 0; 117 | 118 | // S sub H 119 | this.SsubH = 0; 120 | 121 | // R sub T 122 | this.RsubT = 0; 123 | } 124 | 125 | /** 126 | * Returns the deltaE value. 127 | * @method 128 | * @returns {number} 129 | */ 130 | dE00.prototype.getDeltaE = function() { 131 | var sqrt = Math.sqrt; 132 | var sin = Math.sin; 133 | var pow = Math.pow; 134 | 135 | // h Prime 1 136 | this.hPrime1 = this.gethPrime1(); 137 | 138 | // h Prime 2 139 | this.hPrime2 = this.gethPrime2(); 140 | 141 | // Delta h Prime 142 | this.deltahPrime = this.getDeltahPrime(); 143 | 144 | // Delta H Prime 145 | this.deltaHPrime = 2 * sqrt(this.CPrime1 * this.CPrime2) * sin(this.degreesToRadians(this.deltahPrime) / 2); 146 | 147 | // H Bar Prime 148 | this.HBarPrime = this.getHBarPrime(); 149 | 150 | // T 151 | this.T = this.getT(); 152 | 153 | // S sub H 154 | this.SsubH = 1 + 0.015 * this.CBarPrime * this.T; 155 | 156 | // R sub T 157 | this.RsubT = this.getRsubT(); 158 | 159 | // Put it all together! 160 | var lightness = this.deltaLPrime / (this.ksubL * this.SsubL); 161 | var chroma = this.deltaCPrime / (this.ksubC * this.SsubC); 162 | var hue = this.deltaHPrime / (this.ksubH * this.SsubH); 163 | 164 | return sqrt( 165 | pow(lightness, 2) + 166 | pow(chroma, 2) + 167 | pow(hue, 2) + 168 | this.RsubT * chroma * hue 169 | ); 170 | }; 171 | 172 | /** 173 | * Returns the RT variable calculation. 174 | * @method 175 | * @returns {number} 176 | */ 177 | dE00.prototype.getRsubT = function() { 178 | var sin = Math.sin; 179 | var sqrt = Math.sqrt; 180 | var pow = Math.pow; 181 | var exp = Math.exp; 182 | 183 | return -2 * 184 | sqrt( 185 | pow(this.CBarPrime, 7) / 186 | (pow(this.CBarPrime, 7) + pow(25, 7)) 187 | ) * 188 | sin(this.degreesToRadians( 189 | 60 * 190 | exp( 191 | -( 192 | pow( 193 | (this.HBarPrime - 275) / 25, 2 194 | ) 195 | ) 196 | ) 197 | )); 198 | }; 199 | 200 | /** 201 | * Returns the T variable calculation. 202 | * @method 203 | * @returns {number} 204 | */ 205 | dE00.prototype.getT = function() { 206 | var cos = Math.cos; 207 | 208 | return 1 - 209 | 0.17 * cos(this.degreesToRadians(this.HBarPrime - 30)) + 210 | 0.24 * cos(this.degreesToRadians(2 * this.HBarPrime)) + 211 | 0.32 * cos(this.degreesToRadians(3 * this.HBarPrime + 6)) - 212 | 0.20 * cos(this.degreesToRadians(4 * this.HBarPrime - 63)); 213 | }; 214 | 215 | /** 216 | * Returns the H Bar Prime variable calculation. 217 | * @method 218 | * @returns {number} 219 | */ 220 | dE00.prototype.getHBarPrime= function() { 221 | var abs = Math.abs; 222 | 223 | if (abs(this.hPrime1 - this.hPrime2) > 180) { 224 | return (this.hPrime1 + this.hPrime2 + 360) / 2 225 | } 226 | 227 | return (this.hPrime1 + this.hPrime2) / 2 228 | }; 229 | 230 | /** 231 | * Returns the Delta h Prime variable calculation. 232 | * @method 233 | * @returns {number} 234 | */ 235 | dE00.prototype.getDeltahPrime = function() { 236 | var abs = Math.abs; 237 | 238 | // When either C′1 or C′2 is zero, then Δh′ is irrelevant and may be set to 239 | // zero. 240 | if (0 === this.C1 || 0 === this.C2) { 241 | return 0; 242 | } 243 | 244 | if (abs(this.hPrime1 - this.hPrime2) <= 180) { 245 | return this.hPrime2 - this.hPrime1; 246 | } 247 | 248 | if (this.hPrime2 <= this.hPrime1) { 249 | return this.hPrime2 - this.hPrime1 + 360; 250 | } else { 251 | return this.hPrime2 - this.hPrime1 - 360; 252 | } 253 | }; 254 | 255 | /** 256 | * Returns the h Prime 1 variable calculation. 257 | * @method 258 | * @returns {number} 259 | */ 260 | dE00.prototype.gethPrime1 = function() { 261 | return this._gethPrimeFn(this.x1.B, this.aPrime1); 262 | }; 263 | 264 | /** 265 | * Returns the h Prime 2 variable calculation. 266 | * @method 267 | * @returns {number} 268 | */ 269 | dE00.prototype.gethPrime2 = function() { 270 | return this._gethPrimeFn(this.x2.B, this.aPrime2); 271 | }; 272 | 273 | /** 274 | * A helper function to calculate the h Prime 1 and h Prime 2 values. 275 | * @method 276 | * @private 277 | * @returns {number} 278 | */ 279 | dE00.prototype._gethPrimeFn = function(x, y) { 280 | var hueAngle; 281 | 282 | if (x === 0 && y === 0) { 283 | return 0; 284 | } 285 | 286 | hueAngle = this.radiansToDegrees(Math.atan2(x, y)); 287 | 288 | if (hueAngle >= 0) { 289 | return hueAngle; 290 | } else { 291 | return hueAngle + 360; 292 | } 293 | }; 294 | 295 | /** 296 | * Gives the radian equivalent of a specified degree angle. 297 | * @method 298 | * @returns {number} 299 | */ 300 | dE00.prototype.radiansToDegrees = function(radians) { 301 | return radians * (180 / Math.PI); 302 | }; 303 | 304 | /** 305 | * Gives the degree equivalent of a specified radian. 306 | * @method 307 | * @returns {number} 308 | */ 309 | dE00.prototype.degreesToRadians = function(degrees) { 310 | return degrees * (Math.PI / 180); 311 | }; 312 | 313 | module.exports = dE00; 314 | -------------------------------------------------------------------------------- /src/dE76.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @class dE76 5 | * @classdesc 6 | * The CIE76 color difference algorithm: a simple euclidean distance calculation. 7 | * http://en.wikipedia.org/wiki/Color_difference#CIE76 8 | * @constructs dE76 9 | * @memberOf DeltaE 10 | * @property {object} x1 The LAB color configuration object. 11 | * @property {number} x1.L The lightness value, on scale of 0-100. 12 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 13 | * @property {number} x1.B The hue value, on scale of -128 to 128. 14 | * @property {object} x2 The LAB color configuration object. 15 | * @property {number} x2.L The lightness value, on scale of 0-100. 16 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 17 | * @property {number} x2.B The hue value, on scale of -128 to 128. 18 | * @example 19 | * var deltaE = new dE76( 20 | * {L:50, A:50, B:50}, 21 | * {L:100, A:50, B:50}, 22 | * ); 23 | * console.log(deltaE.getDeltaE()); 24 | */ 25 | function dE76(x1, x2) { 26 | this.x1 = x1; 27 | this.x2 = x2; 28 | } 29 | 30 | /** 31 | * Returns the dE76 value. 32 | * @method 33 | * @returns {number} 34 | */ 35 | dE76.prototype.getDeltaE = function() { 36 | var x1 = this.x1; 37 | var x2 = this.x2; 38 | 39 | return Math.sqrt( 40 | Math.pow(x2.L - x1.L, 2) + 41 | Math.pow(x2.A - x1.A, 2) + 42 | Math.pow(x2.B - x1.B, 2) 43 | ); 44 | }; 45 | 46 | module.exports = dE76; -------------------------------------------------------------------------------- /src/dE94.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @class dE94 5 | * @classdesc 6 | * The CIE94 algorithm: an iteration of the CIE76 algorithm. 7 | * http://en.wikipedia.org/wiki/Color_difference#CIE94 8 | * @constructs dE94 9 | * @memberOf DeltaE 10 | * @property {object} x1 The LAB color configuration object. 11 | * @property {number} x1.L The lightness value, on scale of 0-100. 12 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 13 | * @property {number} x1.B The hue value, on scale of -128 to 128. 14 | * @property {object} x2 The LAB color configuration object. 15 | * @property {number} x2.L The lightness value, on scale of 0-100. 16 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 17 | * @property {number} x2.B The hue value, on scale of -128 to 128. 18 | * @property {object} weights The weights configuration object. 19 | * @property {number} weights.lightness A weight factor to apply to lightness. 20 | * @property {number} weights.chroma A weight factor to apply to chroma. 21 | * @property {number} weights.hue A weight factor to apply to hue. 22 | * @example 23 | * var deltaE = new dE94( 24 | * {L:50, A:50, B:50}, 25 | * {L:100, A:50, B:50}, 26 | * ); 27 | * console.log(deltaE.getDeltaE()); 28 | */ 29 | function dE94(x1, x2, weights) { 30 | this.x1 = x1; 31 | this.x2 = x2; 32 | 33 | this.weights = weights || {}; 34 | this.weights.lightness = this.weights.lightness || 1; 35 | this.weights.chroma = this.weights.chroma || 1; 36 | this.weights.hue = this.weights.hue || 1; 37 | 38 | if (1 === this.weights.lightness) { 39 | this.weights.K1 = 0.045; 40 | this.weights.K2 = 0.015; 41 | } else { 42 | this.weights.K1 = 0.048; 43 | this.weights.K2 = 0.014; 44 | } 45 | } 46 | 47 | /** 48 | * Returns the dE94 value. 49 | * @method 50 | * @returns {number} 51 | */ 52 | dE94.prototype.getDeltaE = function() { 53 | var x1 = this.x1; 54 | var x2 = this.x2; 55 | var sqrt = Math.sqrt; 56 | var pow = Math.pow; 57 | 58 | return sqrt( 59 | pow(this.calculateL(x1, x2), 2) + 60 | pow(this.calculateA(x1, x2), 2) + 61 | pow(this.calculateB(x1, x2), 2) 62 | ); 63 | }; 64 | 65 | /** 66 | * Calculates the lightness value. 67 | * @method 68 | * @returns {number} 69 | */ 70 | dE94.prototype.calculateL = function(x1, x2) { 71 | return (x1.L - x2.L) / this.weights.lightness; 72 | }; 73 | 74 | /** 75 | * Calculates the chroma value. 76 | * @method 77 | * @returns {number} 78 | */ 79 | dE94.prototype.calculateA = function(x1, x2) { 80 | var sqrt = Math.sqrt; 81 | var pow = Math.pow; 82 | 83 | //top 84 | var c1 = sqrt(pow(x1.A, 2) + pow(x1.B, 2)); 85 | var c2 = sqrt(pow(x2.A, 2) + pow(x2.B, 2)); 86 | var cab = c1 - c2; 87 | 88 | // bottom 89 | var sc = 1 + (this.weights.K1 * c1); 90 | 91 | return cab / (this.weights.chroma * sc); 92 | }; 93 | 94 | /** 95 | * Calculates the hue value. 96 | * @method 97 | * @returns {number} 98 | */ 99 | dE94.prototype.calculateB = function(x1, x2) { 100 | var sqrt = Math.sqrt; 101 | var pow = Math.pow; 102 | 103 | // cab 104 | var c1 = sqrt(pow(x1.A, 2) + pow(x1.B, 2)); 105 | var c2 = sqrt(pow(x2.A, 2) + pow(x2.B, 2)); 106 | var cab = c1 - c2; 107 | 108 | // top 109 | var a = x1.A - x2.A; 110 | var b = x1.B - x2.B; 111 | var hab = sqrt( 112 | pow(a, 2) + 113 | pow(b, 2) - 114 | pow(cab, 2) 115 | ) || 0; 116 | 117 | // bottom 118 | var c1 = sqrt(pow(x1.A, 2) + pow(x1.B, 2)); 119 | var sh = 1 + (this.weights.K2 * c1); 120 | 121 | return hab / sh; 122 | }; 123 | 124 | module.exports = dE94; 125 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @class DeltaE 5 | * @classdesc 6 | * A package of dE76, dE94, and dE00 algorithms. 7 | * @constructs DeltaE 8 | * @example 9 | * var DeltaE = new DeltaE(); 10 | * var labColor1 = {L: 50, A: 50, B: 50}; 11 | * var labColor2 = {L: 20, A: 20, B: 20}; 12 | * 13 | * DeltaE.getDeltaE94(labColor1, labColor2); 14 | */ 15 | var dE76 = require('./dE76'); 16 | var dE94 = require('./dE94'); 17 | var dE00 = require('./dE00'); 18 | 19 | function DeltaE() {} 20 | 21 | /** 22 | * The CIE76 color difference algorithm: a simple euclidean distance calculation. 23 | * http://en.wikipedia.org/wiki/Color_difference#CIE76 24 | * @property {object} x1 The LAB color configuration object. 25 | * @property {number} x1.L The lightness value, on scale of 0-100. 26 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 27 | * @property {number} x1.B The hue value, on scale of -128 to 128. 28 | * @property {object} x2 The LAB color configuration object. 29 | * @property {number} x2.L The lightness value, on scale of 0-100. 30 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 31 | * @property {number} x2.B The hue value, on scale of -128 to 128. 32 | * @returns {number} The computed dE76 value. 33 | * @example 34 | * var labColor1 = {L: 50, A: 50, B: 50}; 35 | * var labColor2 = {L: 20, A: 20, B: 20}; 36 | * 37 | * DeltaE.getDeltaE76(labColor1, labColor2); 38 | */ 39 | DeltaE.prototype.getDeltaE76 = function(lab1, lab2) { 40 | var deltaE = new dE76(lab1, lab2); 41 | return deltaE.getDeltaE(); 42 | }; 43 | 44 | /** 45 | * The CIE94 algorithm: an iteration of the CIE76 algorithm. 46 | * http://en.wikipedia.org/wiki/Color_difference#CIE94 47 | * @property {object} x1 The LAB color configuration object. 48 | * @property {number} x1.L The lightness value, on scale of 0-100. 49 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 50 | * @property {number} x1.B The hue value, on scale of -128 to 128. 51 | * @property {object} x2 The LAB color configuration object. 52 | * @property {number} x2.L The lightness value, on scale of 0-100. 53 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 54 | * @property {number} x2.B The hue value, on scale of -128 to 128. 55 | * @property {object} weights The weights configuration object. 56 | * @property {number} weights.lightness A weight factor to apply to lightness. 57 | * @property {number} weights.chroma A weight factor to apply to chroma. 58 | * @property {number} weights.hue A weight factor to apply to hue. 59 | * @returns {number} The computed dE94 value. 60 | * @example 61 | * var labColor1 = {L: 50, A: 50, B: 50}; 62 | * var labColor2 = {L: 20, A: 20, B: 20}; 63 | * 64 | * DeltaE.getDeltaE94(labColor1, labColor2); 65 | */ 66 | DeltaE.prototype.getDeltaE94 = function(lab1, lab2) { 67 | var deltaE = new dE94(lab1, lab2); 68 | return deltaE.getDeltaE(); 69 | }; 70 | 71 | /** 72 | * The CIE2000 color difference algorithm. 73 | * http://en.wikipedia.org/wiki/Color_difference#CIEDE2000 74 | * @property {object} x1 The LAB color configuration object. 75 | * @property {number} x1.L The lightness value, on scale of 0-100. 76 | * @property {number} x1.A The chroma value, on scale of -128 to 128. 77 | * @property {number} x1.B The hue value, on scale of -128 to 128. 78 | * @property {object} x2 The LAB color configuration object. 79 | * @property {number} x2.L The lightness value, on scale of 0-100. 80 | * @property {number} x2.A The chroma value, on scale of -128 to 128. 81 | * @property {number} x2.B The hue value, on scale of -128 to 128. 82 | * @property {object} weights The weights configuration object. 83 | * @property {number} weights.lightness A weight factor to apply to lightness. 84 | * @property {number} weights.chroma A weight factor to apply to chroma. 85 | * @property {number} weights.hue A weight factor to apply to hue. 86 | * @returns {number} The computed dE00 value. 87 | * @example 88 | * var labColor1 = {L: 50, A: 50, B: 50}; 89 | * var labColor2 = {L: 20, A: 20, B: 20}; 90 | * 91 | * DeltaE.getDeltaE00(labColor1, labColor2); 92 | */ 93 | DeltaE.prototype.getDeltaE00 = function(lab1, lab2) { 94 | var deltaE = new dE00(lab1, lab2); 95 | return deltaE.getDeltaE(); 96 | }; 97 | 98 | module.exports = new DeltaE; -------------------------------------------------------------------------------- /tests/main.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const DeltaE_Global = require('../dist/deltae.global.min'); 3 | const DeltaE_CommonJS = require('../src/index'); 4 | 5 | /** 6 | * Simple Mocha tests to verify dE values for each formula. 7 | * 8 | * Run them over CLI: 9 | * $ mocha main.js 10 | */ 11 | 12 | const color1 = { 13 | L: 36, 14 | A: 60, 15 | B: 41, 16 | }; 17 | 18 | const color2 = { 19 | L: 55, 20 | A: 66, 21 | B: 77, 22 | }; 23 | 24 | const floatingPointColor1 = { 25 | L: 53.23288178584245, 26 | A: 80.10930952982204, 27 | B: 67.22006831026425 28 | }; 29 | 30 | const floatingPointColor2 = { 31 | L: 50.9588099835815, 32 | A: 77.47798295202801, 33 | B: 65.01211079141827 34 | }; 35 | 36 | 37 | function round(n) { 38 | return Math.round(n * 10000) / 10000; 39 | } 40 | 41 | function format(c) { 42 | return { 43 | L: c[0], 44 | A: c[1], 45 | B: c[2], 46 | }; 47 | } 48 | 49 | function assertDeltaE00(expected, c1, c2) { 50 | assert.equal(round(expected), round(DeltaE_Global.getDeltaE00(format(c1), format(c2)))); 51 | assert.equal(round(expected), round(DeltaE_Global.getDeltaE00(format(c2), format(c1)))); 52 | } 53 | 54 | describe('deltaE', () => { 55 | /** 56 | * CIE76 algorithm 57 | */ 58 | describe('dE76', () => { 59 | /** 60 | * http://colormine.org/delta-e-calculator 61 | */ 62 | it('Return DeltaE', (done) => { 63 | const resultGlobal = DeltaE_Global.getDeltaE76(color1, color2); 64 | const resultCommonJS = DeltaE_CommonJS.getDeltaE76(color1, color2); 65 | const correctDeltaE = 41.14608122288197; 66 | 67 | assert.equal(resultCommonJS, correctDeltaE); 68 | assert.equal(resultGlobal, correctDeltaE); 69 | done(); 70 | }); 71 | }); 72 | 73 | /** 74 | * CIE94 algorithm 75 | */ 76 | describe('dE94', () => { 77 | /** 78 | * http://colormine.org/delta-e-calculator/cie94 79 | */ 80 | it('Return DeltaE', (done) => { 81 | const resultGlobal = DeltaE_Global.getDeltaE94(color1, color2); 82 | const resultCommonJS = DeltaE_CommonJS.getDeltaE94(color1, color2); 83 | const correctDeltaE = 22.849281934529994; 84 | 85 | assert.equal(resultCommonJS, correctDeltaE); 86 | assert.equal(resultGlobal, correctDeltaE); 87 | done(); 88 | }); 89 | 90 | it('Handles Floating Point Error', (done) => { 91 | const resultGlobal = DeltaE_Global.getDeltaE94(floatingPointColor1, floatingPointColor2); 92 | const resultCommonJS = DeltaE_CommonJS.getDeltaE94(floatingPointColor1, floatingPointColor2); 93 | const correctDeltaE = 2.3524048718867823; 94 | 95 | assert.equal(resultCommonJS, correctDeltaE); 96 | assert.equal(resultGlobal, correctDeltaE); 97 | done(); 98 | }) 99 | }); 100 | 101 | /** 102 | * CIE2000 algorithm 103 | */ 104 | describe('dE00', () => { 105 | /** 106 | * http://colormine.org/delta-e-calculator/cie2000 107 | */ 108 | it('Return DeltaE', (done) => { 109 | const resultGlobal = DeltaE_Global.getDeltaE00(color1, color2); 110 | const resultCommonJS = DeltaE_CommonJS.getDeltaE00(color1, color2); 111 | const correctDeltaE = 22.3945069524179; 112 | 113 | assert.equal(resultCommonJS, correctDeltaE); 114 | assert.equal(resultGlobal, correctDeltaE); 115 | done(); 116 | }); 117 | 118 | /** 119 | * Cases taken from the paper "The CIEDE2000 Color-Difference Formula: 120 | * Implementation Notes, Supplementary Test Data, and Mathematical Observations" 121 | * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal. 122 | */ 123 | it('0.0 difference', () => { 124 | assertDeltaE00(0.0, [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]); 125 | assertDeltaE00(0.0, [99.5, 0.005, -0.010], [99.5, 0.005, -0.010]); 126 | }); 127 | it('100.0 difference', () => { 128 | assertDeltaE00(100.0, [100, 0.005, -0.010], [0.0, 0.0, 0.0]); 129 | }); 130 | it('True chroma difference (#1)', () => { 131 | assertDeltaE00(2.0425, [50.0000, 132 | 2.6772, -79.7751, 133 | ], [50.0000, 134 | 0.0000, -82.7485, 135 | ]); 136 | }); 137 | it('True chroma difference (#2)', () => { 138 | assertDeltaE00(2.8615, [50.0000, 139 | 3.1571, -77.2803, 140 | ], [50.0000, 141 | 0.0000, -82.7485, 142 | ]); 143 | }); 144 | it('True chroma difference (#3)', () => { 145 | assertDeltaE00(3.4412, [50.0000, 146 | 2.8361, -74.0200, 147 | ], [50.0000, 148 | 0.0000, -82.7485, 149 | ]); 150 | }); 151 | it('True hue difference (#4)', () => { 152 | assertDeltaE00(1.0000, [50.0000, -1.3802, -84.2814], [50.0000, 153 | 0.0000, -82.7485, 154 | ]); 155 | }); 156 | it('True hue difference (#5)', () => { 157 | assertDeltaE00(1.0000, [50.0000, -1.1848, -84.8006], [50.0000, 158 | 0.0000, -82.7485, 159 | ]); 160 | }); 161 | it('True hue difference (#6)', () => { 162 | assertDeltaE00(1.0000, [50.0000, -0.9009, -85.5211], [50.0000, 163 | 0.0000, -82.7485, 164 | ]); 165 | }); 166 | it('Arctangent computation (#7)', () => { 167 | assertDeltaE00(2.3669, [50.0000, 168 | 0.0000, 169 | 0.0000, 170 | ], [50.0000, -1.0000, 171 | 2.0000, 172 | ]); 173 | }); 174 | it('Arctangent computation (#8)', () => { 175 | assertDeltaE00(2.3669, [50.0000, -1.0000, 176 | 2.0000, 177 | ], [50.0000, 178 | 0.0000, 179 | 0.0000, 180 | ]); 181 | }); 182 | it('Arctangent computation (#9)', () => { 183 | assertDeltaE00(7.1792, [50.0000, 184 | 2.4900, -0.0010, 185 | ], [50.0000, -2.4900, 186 | 0.0009, 187 | ]); 188 | }); 189 | it('Arctangent computation (#10)', () => { 190 | assertDeltaE00(7.1792, [50.0000, 191 | 2.4900, -0.0010, 192 | ], [50.0000, -2.4900, 193 | 0.0010, 194 | ]); 195 | }); 196 | it('Arctangent computation (#11)', () => { 197 | assertDeltaE00(7.2195, [50.0000, 198 | 2.4900, -0.0010, 199 | ], [50.0000, -2.4900, 200 | 0.0011, 201 | ]); 202 | }); 203 | it('Arctangent computation (#12)', () => { 204 | assertDeltaE00(7.2195, [50.0000, 205 | 2.4900, -0.0010, 206 | ], [50.0000, -2.4900, 207 | 0.0012, 208 | ]); 209 | }); 210 | it('Arctangent computation (#13)', () => { 211 | assertDeltaE00(4.8045, [50.0000, -0.0010, 212 | 2.4900, 213 | ], [50.0000, 214 | 0.0009, -2.4900, 215 | ]); 216 | }); 217 | it('Arctangent computation (#14)', () => { 218 | assertDeltaE00(4.8045, [50.0000, -0.0010, 219 | 2.4900, 220 | ], [50.0000, 221 | 0.0010, -2.4900, 222 | ]); 223 | }); 224 | it('Arctangent computation (#15)', () => { 225 | assertDeltaE00(4.7461, [50.0000, -0.0010, 226 | 2.4900, 227 | ], [50.0000, 228 | 0.0011, -2.4900, 229 | ]); 230 | }); 231 | it('Arctangent computation (#16)', () => { 232 | assertDeltaE00(4.3065, [50.0000, 233 | 2.5000, 234 | 0.0000, 235 | ], [50.0000, 236 | 0.0000, -2.5000, 237 | ]); 238 | }); 239 | it('Large color differences (#17)', () => { 240 | assertDeltaE00(27.1492, [50.0000, 241 | 2.5000, 242 | 0.0000, 243 | ], [73.0000, 244 | 25.0000, -18.0000, 245 | ]); 246 | }); 247 | it('Large color differences (#18)', () => { 248 | assertDeltaE00(22.8977, [50.0000, 249 | 2.5000, 250 | 0.0000, 251 | ], [61.0000, -5.0000, 252 | 29.0000, 253 | ]); 254 | }); 255 | it('Large color differences (#19)', () => { 256 | assertDeltaE00(31.9030, [50.0000, 257 | 2.5000, 258 | 0.0000, 259 | ], [56.0000, -27.0000, -3.0000]); 260 | }); 261 | it('Large color differences (#20)', () => { 262 | assertDeltaE00(19.4535, [50.0000, 263 | 2.5000, 264 | 0.0000, 265 | ], [58.0000, 266 | 24.0000, 267 | 15.0000, 268 | ]); 269 | }); 270 | it('CIE technical report (#21)', () => { 271 | assertDeltaE00(1.0000, [50.0000, 272 | 2.5000, 273 | 0.0000, 274 | ], [50.0000, 275 | 3.1736, 276 | 0.5854, 277 | ]); 278 | }); 279 | it('CIE technical report (#22)', () => { 280 | assertDeltaE00(1.0000, [50.0000, 281 | 2.5000, 282 | 0.0000, 283 | ], [50.0000, 284 | 3.2972, 285 | 0.0000, 286 | ]); 287 | }); 288 | it('CIE technical report (#23)', () => { 289 | assertDeltaE00(1.0000, [50.0000, 290 | 2.5000, 291 | 0.0000, 292 | ], [50.0000, 293 | 1.8634, 294 | 0.5757, 295 | ]); 296 | }); 297 | it('CIE technical report (#24)', () => { 298 | assertDeltaE00(1.0000, [50.0000, 299 | 2.5000, 300 | 0.0000, 301 | ], [50.0000, 302 | 3.2592, 303 | 0.3350, 304 | ]); 305 | }); 306 | it('CIE technical report (#25)', () => { 307 | assertDeltaE00(1.2644, [60.2574, -34.0099, 308 | 36.2677, 309 | ], [60.4626, -34.1751, 310 | 39.4387, 311 | ]); 312 | }); 313 | it('CIE technical report (#26)', () => { 314 | assertDeltaE00(1.2630, [63.0109, -31.0961, -5.8663], [62.8187, -29.7946, -4.0864]); 315 | }); 316 | it('CIE technical report (#27)', () => { 317 | assertDeltaE00(1.8731, [61.2901, 318 | 3.7196, -5.3901, 319 | ], [61.4292, 320 | 2.2480, -4.9620, 321 | ]); 322 | }); 323 | it('CIE technical report (#28)', () => { 324 | assertDeltaE00(1.8645, [35.0831, -44.1164, 325 | 3.7933, 326 | ], [35.0232, -40.0716, 327 | 1.5901, 328 | ]); 329 | }); 330 | it('CIE technical report (#29)', () => { 331 | assertDeltaE00(2.0373, [22.7233, 332 | 20.0904, -46.6940, 333 | ], [23.0331, 334 | 14.9730, -42.5619, 335 | ]); 336 | }); 337 | it('CIE technical report (#30)', () => { 338 | assertDeltaE00(1.4146, [36.4612, 339 | 47.8580, 340 | 18.3852, 341 | ], [36.2715, 342 | 50.5065, 343 | 21.2231, 344 | ]); 345 | }); 346 | it('CIE technical report (#31)', () => { 347 | assertDeltaE00(1.4441, [90.8027, -2.0831, 348 | 1.4410, 349 | ], [91.1528, -1.6435, 350 | 0.0447, 351 | ]); 352 | }); 353 | it('CIE technical report (#32)', () => { 354 | assertDeltaE00(1.5381, [90.9257, -0.5406, -0.9208], [88.6381, -0.8985, -0.7239]); 355 | }); 356 | it('CIE technical report (#33)', () => { 357 | assertDeltaE00(0.6377, [6.7747, -0.2908, -2.4247], [5.8714, -0.0985, -2.2286]); 358 | }); 359 | it('CIE technical report (#34)', () => { 360 | assertDeltaE00(0.9082, [2.0776, 361 | 0.0795, -1.1350, 362 | ], [0.9033, -0.0636, -0.5514]); 363 | }); 364 | }); 365 | }); 366 | --------------------------------------------------------------------------------