├── .gitignore ├── .travis.yml ├── Gruntfile.coffee ├── LICENSE ├── README.md ├── dist ├── c0lor.js └── c0lor.min.js ├── package.json ├── src ├── LCh.coffee ├── Lab.coffee ├── RGBInt.coffee ├── XYZ.coffee ├── gamut.coffee ├── hsv.coffee ├── index.coffee ├── rgb.coffee ├── space │ ├── lab.coffee │ └── rgb.coffee ├── white.coffee └── xyY.coffee ├── tasks └── mdExtract.coffee └── test └── browser ├── jasmine ├── SpecRunner.html └── lib │ ├── jasmine-jsreporter.js │ └── jasmine │ ├── boot.js │ ├── console.js │ ├── jasmine-html.js │ ├── jasmine.css │ ├── jasmine.js │ └── jasmine_favicon.png └── src ├── LCh.coffee ├── Lab.coffee ├── README.md.spec.coffee ├── RGBInt.coffee ├── XYZ.coffee ├── console.coffee ├── gamut(node_only).coffee ├── hsv.coffee ├── indexToTest.coffee ├── matcher.coffee ├── rgb.coffee ├── space ├── lab.coffee └── rgb.coffee └── xyY.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.idea 3 | /build 4 | /*.sh 5 | /coverage 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | sudo: false 5 | before_deploy: "cd build/src" 6 | 7 | deploy: 8 | provider: npm 9 | email: hendrik@helwich.de 10 | api_key: 11 | secure: PUUs2ajF50L6HQk1n6UqGkCdooDyxrc3XdjdJ9+REnymGhJQ0/1f9xdjUVh1HDZ2lGh5WbGLiinUsB4YWYIqbT3AXpi+CpKYHB0xHxnaqs5EcL7WorarNSIY8gaZxvhBCbqkYTZFkzzhwBEg4mpQKfi9AMERPtWSujhWVN5vPuU= 12 | on: 13 | tags: true 14 | skip_cleanup: true 15 | env: 16 | global: 17 | - secure: yFiSsYp1lhq7ISOMP/ySpUm5tJdMSn+uqBhGIbg0+7rwCBHutAvVWrHkbp0G6QF6XyUZzFC2+iI/zTzmhl6FIpQEyw6Qvv30CcaYE3+6DG6Pw/NTkj7kCjwguuabKLePNknhYDYD0sN4S7EIvoY+zVMXcdanjXx0pWCtL1seCos= 18 | - secure: OEEQGxpKYqBJ1Sw7glV7HrYIYm6hp5/vsYAtPTMcEt1+Xu84qS9uxgyDA77XTe3IvdzoSrsKJT3nx9EmeKr6q7ChNuI+QwGx5oE29l97jGg/g2wkbcKHurwaxw204OrUO6yRyDC/AhsJDz5C7t1oUB86srwDokAeIIxKhJ6a+Tk= 19 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | workDir = "build" 2 | srcDir = "src" 3 | testSrcDir = "test" 4 | distDir = "dist" 5 | pkg = require "./package" 6 | 7 | 8 | module.exports = (grunt) -> 9 | 10 | browsers = [] 11 | browsers.push browser for browser in ({browserName: "iphone", version: "#{vers}"} for vers in ["5.1", "6.1", "7.1", "8.4", "9.2"]) 12 | browsers.push browser for browser in ({browserName: "android", version: "#{vers}"} for vers in ["4.0", "4.4", "5.1"]) 13 | browsers.push browser for browser in ({browserName: "safari", version: "#{vers}"} for vers in [5..9]) 14 | browsers.push browser for browser in ({platform: "XP", browserName: "opera", version: "#{vers}"} for vers in [11..12]) 15 | browsers.push browser for browser in ({platform: "XP", browserName: "googlechrome", version: "#{vers}"} for vers in [26..46] by 3) 16 | browsers.push browser for browser in ({platform: "linux", browserName: "firefox", version: "#{vers}"} for vers in [4..42] by 5) 17 | browsers.push browser for browser in ({browserName: "internet explorer", version: "#{vers}"} for vers in [6..11]) 18 | 19 | grunt.initConfig 20 | 21 | pkg: pkg 22 | 23 | clean: ["#{workDir}"] 24 | 25 | watch: 26 | files: [ 27 | "Gruntfile.*" 28 | "#{srcDir}/**/*.coffee" 29 | "#{testSrcDir}/**/*.coffee" 30 | "#{testSrcDir}/**/*.js" 31 | "#{testSrcDir}/**/*.html" 32 | ] 33 | tasks: ["default"] 34 | 35 | # Transcompile CoffeeScript to JavaScript files 36 | coffee: 37 | main: 38 | options: 39 | bare: true 40 | cwd: "#{srcDir}" 41 | expand: true 42 | src: ["**/*.coffee"] 43 | dest: "#{workDir}/#{srcDir}" 44 | ext: ".js" 45 | test: 46 | options: 47 | bare: true 48 | cwd: "#{testSrcDir}" 49 | expand: true 50 | src: ["**/*.coffee"] 51 | dest: "#{workDir}/#{testSrcDir}" 52 | ext: ".js" 53 | 54 | 55 | copy: 56 | markup: 57 | src: "*.md" 58 | dest: "#{workDir}/#{srcDir}/" 59 | jasmine: 60 | src: ["test/**/*.html", "test/**/*.js", "test/**/*.css"] 61 | dest: "#{workDir}/" 62 | 63 | browserify: 64 | dist: 65 | files: do -> 66 | files = {} 67 | files["#{distDir}/#{pkg.name}.js"] = ["#{workDir}/#{srcDir}/index.js"] 68 | files 69 | tests: 70 | files: do -> 71 | files = {} 72 | files["#{workDir}/#{testSrcDir}/browser/#{pkg.name}Spec.js"] = ["#{workDir}/#{testSrcDir}/browser/src/**/*.js"] 73 | files 74 | 75 | uglify: 76 | dist: 77 | files:do -> 78 | files = {} 79 | files["#{distDir}/#{pkg.name}.min.js"] = ["#{distDir}/#{pkg.name}.js"] 80 | files 81 | 82 | usebanner: 83 | taskName: 84 | options: 85 | position: "top" 86 | banner: "// <%= pkg.name %> v<%= pkg.version %> | (c) 2013-<%= grunt.template.today('yyyy') %> <%= pkg.author %> | <%= pkg.license %> License\n'use strict';" 87 | linebreak: false 88 | files: 89 | src: [ "#{distDir}/#{pkg.name}.min.js", "#{distDir}/#{pkg.name}.js" ] 90 | 91 | 92 | connect: 93 | server: 94 | options: 95 | base: "" 96 | port: 9999 97 | #hostname: "*" 98 | 99 | 100 | 'saucelabs-jasmine': 101 | all: 102 | options: 103 | urls: ["http://127.0.0.1:9999/build/test/browser/jasmine/SpecRunner.html"] 104 | tunnelTimeout: 5 105 | build: process.env.TRAVIS_JOB_ID 106 | concurrency: 3 107 | browsers: browsers 108 | testname: "browser black box tests" 109 | tags: ["master"] 110 | statusCheckAttempts: 450 # Each test has statusCheckAttempts * 2 seconds to complete (900s = 15 min) 111 | 112 | mdExtract: 113 | foo: 114 | src: ["README.md"] 115 | dest: "build/test/browser/src" 116 | 117 | # Loading dependencies 118 | for name of pkg.devDependencies 119 | if name != "grunt" and name != "grunt-cli" and (name.indexOf "grunt") == 0 120 | grunt.loadNpmTasks name 121 | 122 | grunt.registerTask "travis", ["clean", "coffee", "copy", "adaptPackageJson", "mdExtract", "browserify", "uglify", "usebanner", "connect", "saucelabs-jasmine"] 123 | grunt.registerTask "dev", ["clean", "coffee", "copy", "adaptPackageJson", "mdExtract", "browserify", "uglify", "usebanner", "connect", "watch"] 124 | grunt.registerTask "default", [ "dev"] 125 | 126 | grunt.loadTasks "tasks" 127 | 128 | grunt.registerTask "adaptPackageJson", -> 129 | fs = require "fs" 130 | tmpPkg = require "./package" 131 | 132 | tmpPkg.devDependencies = {} 133 | delete tmpPkg.private 134 | delete tmpPkg.scripts 135 | fs.writeFileSync "./build/src/package.json", JSON.stringify tmpPkg, null, 2 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Hendrik Helwich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | c0lor [![.](https://badge.fury.io/js/c0lor.png)](http://badge.fury.io/js/c0lor) [![.](https://travis-ci.org/hhelwich/c0lor.png?branch=master)](https://travis-ci.org/hhelwich/c0lor) [![.](https://coveralls.io/repos/hhelwich/c0lor/badge.png)](https://coveralls.io/r/hhelwich/c0lor) 2 | ===== 3 | 4 | A color space conversion library to use in the browser or node.js. It currently contains: 5 | 6 | * RGB, HSV color space 7 | * XYZ, xyY color space 8 | * Lab, LCh color space 9 | 10 | 11 | Browser 12 | ------- 13 | 14 | To use the library in the browser, you need to include [this](https://raw.github.com/hhelwich/c0lor/master/dist/c0lor.min.js) JavaScript file: 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | It exports the global `c0lor` object. You can create an alias variable if you like: 21 | 22 | ```javascript 23 | var C = c0lor; 24 | ``` 25 | 26 | The following browsers are tested: 27 | 28 | [![Browser Test Status](https://saucelabs.com/browser-matrix/hhelwich-c0lor.svg)](https://saucelabs.com/u/hhelwich-c0lor) 29 | 30 | 31 | node.js 32 | ------- 33 | 34 | You can install this package with: 35 | 36 | ``` 37 | npm install c0lor 38 | ``` 39 | 40 | You can load the global module: 41 | 42 | ```javascript 43 | var C = require('c0lor'); 44 | ``` 45 | 46 | You can also require directly on submodules 47 | (can e.g. be useful with [browserify](https://github.com/substack/node-browserify)): 48 | 49 | ```javascript 50 | var rgb = require('c0lor/rgb'); // same as: require('c0lor').rgb; 51 | ``` 52 | 53 | 54 | Examples 55 | ======== 56 | 57 | Color Creation 58 | -------------- 59 | 60 | ```javascript 61 | var cyan_rgb, red_RGB, yellow_RGB, magenta_hsv, 62 | green_XYZ, orange_xyY, purple_Lab, blue_LCh; 63 | 64 | // create RGB colors: 65 | cyan_rgb = C.rgb(0, 1, 1); 66 | red_RGB = C.RGB(255, 0, 0); 67 | yellow_RGB = C.RGB().hex('FFFF00'); 68 | 69 | // create HSV color: 70 | magenta_hsv = C.hsv(0.8, 1, 1); 71 | 72 | // create XYZ and xyY color: 73 | green_XYZ = C.XYZ(0.4, 0.7, 0.2); 74 | orange_xyY = C.xyY(0.5, 0.4, 0.3); 75 | 76 | // create Lab and LCh color: 77 | purple_Lab = C.Lab(31, 24, -22); 78 | blue_LCh = C.LCh(30, 56, -1); 79 | ``` 80 | 81 | Simple Conversions 82 | ------------------ 83 | 84 | ```javascript 85 | var cyan_RGB, red_hexStr, yellow_rgb, magenta_rgb, yellow_hsv, 86 | green_xyY, orange_XYZ, purple_LCh, blue_Lab; 87 | 88 | // convert between RGB color spaces: 89 | cyan_RGB = cyan_rgb.RGB(); 90 | red_hexStr = red_RGB.hex(); 91 | yellow_rgb = yellow_RGB.rgb(); 92 | 93 | // convert between hsv and rgb color space: 94 | magenta_rgb = magenta_hsv.rgb(); 95 | yellow_hsv = yellow_rgb.hsv(); 96 | 97 | // convert between XYZ and xyY color space: 98 | green_xyY = green_XYZ.xyY(); 99 | orange_XYZ = orange_xyY.XYZ(); 100 | 101 | // convert between Lab and LCh color space: 102 | purple_LCh = purple_Lab.LCh(); 103 | blue_Lab = blue_LCh.Lab(); 104 | ``` 105 | 106 | Instead of creating a new color, you can also pass an existing color which will be used to store the result: 107 | 108 | ```javascript 109 | // convert hsv to rgb color: 110 | hsvColor.rgb(rgbColor); 111 | ``` 112 | 113 | rgb ↔ XYZ Conversion 114 | -------------------- 115 | 116 | To convert between rgb and XYZ color spaces you need to pick one of the predefined rgb color space definitions or create 117 | one by yourself. 118 | The following rgb color spaces are included: 119 | 120 | Adobe-98, Adobe-RGB, CIE-RGB, ColorMatch, EBU-Monitor, ECI-RGB, HDTV, Kodak-DC, NTSC-53, PAL-SECAM, sRGB, WideGamut 121 | 122 | Use predefined rgb color spaces to convert colors: 123 | 124 | ```javascript 125 | var sRGB, adobe98, cyan_XYZ; 126 | 127 | // create alias to rgb color spaces: 128 | sRGB = C.space.rgb.sRGB; 129 | adobe98 = C.space.rgb['Adobe-98']; 130 | 131 | // convert color from sRGB to XYZ color space: 132 | cyan_XYZ = sRGB.XYZ(cyan_rgb); 133 | // convert color from XYZ to Adobe-98 color space: 134 | cyan_rgb = adobe98.rgb(cyan_XYZ); 135 | ``` 136 | 137 | Instead of creating a new color, you can also pass an existing color which will be used to store the result: 138 | 139 | ```javascript 140 | // convert color from XYZ to Adobe-98 color space: 141 | adobe98.rgb(cyan_XYZ, cyan_rgb); 142 | ``` 143 | 144 | You can create your own rgb color space if you like: 145 | 146 | ```javascript 147 | var myRed, myGreen, myBlue, myRgbSpace; 148 | 149 | myRed = C.xyY(0.6, 0.3); 150 | myGreen = C.xyY(0.2, 0.7); 151 | myBlue = C.xyY(0.1, 0.1); 152 | 153 | myRgbSpace = C.space.rgb(myRed, myGreen, myBlue, C.white.D65, 2.1); 154 | ``` 155 | 156 | Instead of giving a gamma value you can also give a gamma function and its inverse function: 157 | 158 | ```javascript 159 | myRgbSpace = C.space.rgb(myRed, myGreen, myBlue, C.white.D65, function (x) { 160 | return Math.pow(x, 2.1); 161 | }, function (x) { 162 | return Math.pow(x, 1 / 2.1); 163 | }); 164 | ``` 165 | 166 | The following white points (in XYZ color space) are included: 167 | 168 | A, B, C, D50, D55, D65, D75, E, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12 169 | 170 | 171 | Lab ↔ XYZ Conversion 172 | -------------------- 173 | 174 | Convert between Lab and XYZ color space: 175 | 176 | ```javascript 177 | var lab50, purple_XYZ, green_Lab; 178 | 179 | // create Lab color space with D50 white point 180 | labD50 = C.space.lab(C.white.D50); 181 | 182 | // convert color from Lab to XYZ color space: 183 | purple_XYZ = labD50.XYZ(purple_Lab); 184 | // convert color from XYZ to Lab color space: 185 | green_Lab = labD50.Lab(green_XYZ); 186 | ``` 187 | 188 | Instead of creating a new color, you can also pass an existing color which will be used to store the result: 189 | 190 | ```javascript 191 | // convert color from XYZ to Lab color space: 192 | labD50.Lab(green_XYZ, green_Lab); 193 | ``` 194 | -------------------------------------------------------------------------------- /dist/c0lor.js: -------------------------------------------------------------------------------- 1 | // c0lor v0.1.1 | (c) 2013-2021 Hendrik Helwich | MIT License 2 | 'use strict';(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o e3) { 347 | return pow(w, 1 / 3); 348 | } else { 349 | return foo * w + N; 350 | } 351 | }; 352 | 353 | fInv = function(w) { 354 | if (w > e) { 355 | return pow(w, 3); 356 | } else { 357 | return (w - N) / foo; 358 | } 359 | }; 360 | 361 | fDeriv = function(w) { 362 | if (w > e3) { 363 | return 1 / (3 * (pow(w, 2 / 3))); 364 | } else { 365 | return foo; 366 | } 367 | }; 368 | 369 | labCsPrototype = { 370 | Lab: function(XYZ, T) { 371 | var l; 372 | if (T == null) { 373 | T = Lab(); 374 | } 375 | l = f(XYZ.Y / this.white.Y); 376 | T.L = 116 * l - 16; 377 | T.a = 500 * ((f(XYZ.X / this.white.X)) - l); 378 | T.b = 200 * (l - (f(XYZ.Z / this.white.Z))); 379 | return T; 380 | }, 381 | XYZ: function(Lab, T) { 382 | var fy; 383 | if (T == null) { 384 | T = xyz(); 385 | } 386 | fy = (Lab.L + 16) / 116; 387 | T.X = (fInv(fy + Lab.a / 500)) * this.white.X; 388 | T.Y = (fInv(fy)) * this.white.Y; 389 | T.Z = (fInv(fy - Lab.b / 200)) * this.white.Z; 390 | return T; 391 | }, 392 | LabderivL: function(Y) { 393 | return 116 * (fDeriv(Y / this.white.Y)) / this.white.Y; 394 | } 395 | }; 396 | 397 | module.exports = O((function(white) { 398 | this.white = white; 399 | }), labCsPrototype); 400 | 401 | },{"../Lab":2,"../XYZ":4,"ut1l/create/object":16}],9:[function(require,module,exports){ 402 | var M, O, XYZ, createSpace, gammaSRgb, gammaSRgbInv, lazyInitRgbBase, lu, pow, rgbSpaceConstructor, rgbSpacePrototype, white, xyY; 403 | 404 | O = require("ut1l/create/object"); 405 | 406 | M = require("m4th/matrix"); 407 | 408 | lu = require("m4th/lu"); 409 | 410 | white = require("../white"); 411 | 412 | xyY = require("../xyY"); 413 | 414 | XYZ = require("../XYZ"); 415 | 416 | pow = Math.pow; 417 | 418 | gammaSRgb = function(x) { 419 | if (x <= 0.04045) { 420 | return x / 12.92; 421 | } else { 422 | return pow((x + 0.055) / 1.055, 2.4); 423 | } 424 | }; 425 | 426 | gammaSRgbInv = function(x) { 427 | if (x <= 0.0031308) { 428 | return x * 12.92; 429 | } else { 430 | return 1.055 * (pow(x, 1 / 2.4)) - 0.055; 431 | } 432 | }; 433 | 434 | lazyInitRgbBase = function() { 435 | var bxyz, bxyzLU, w; 436 | bxyz = M([this.red.x, this.green.x, this.blue.x, this.red.y, this.green.y, this.blue.y, 1 - this.red.x - this.red.y, 1 - this.green.x - this.green.y, 1 - this.blue.x - this.blue.y]); 437 | bxyzLU = lu(bxyz); 438 | w = M(3, [this.white.X, this.white.Y, this.white.Z]); 439 | bxyzLU.solve(w, w); 440 | bxyz = bxyz.mult(M.diag(w.array)); 441 | this.base = bxyz.array; 442 | this.baseInv = (lu(bxyz)).getInverse().array; 443 | delete this.XYZ; 444 | delete this.rgb; 445 | }; 446 | 447 | rgbSpaceConstructor = function(red, green, blue, white1, gamma, gammaInv) { 448 | this.red = red; 449 | this.green = green; 450 | this.blue = blue; 451 | this.white = white1; 452 | if (typeof gamma === "function") { 453 | this.gamma = gamma; 454 | this.gammaInv = gammaInv; 455 | } else { 456 | this.g = gamma; 457 | this.gInv = 1 / gamma; 458 | } 459 | this.rgb = function() { 460 | lazyInitRgbBase.call(this); 461 | return this.rgb.apply(this, arguments); 462 | }; 463 | this.XYZ = function() { 464 | lazyInitRgbBase.call(this); 465 | return this.XYZ.apply(this, arguments); 466 | }; 467 | }; 468 | 469 | rgbSpacePrototype = { 470 | gamma: function(x) { 471 | return pow(x, this.g); 472 | }, 473 | gammaInv: function(x) { 474 | return pow(x, this.gInv); 475 | }, 476 | rgb: function(XYZ, T) { 477 | var a; 478 | if (T == null) { 479 | T = require("../rgb")(); 480 | } 481 | a = this.baseInv; 482 | T.r = this.gammaInv(a[0] * XYZ.X + a[1] * XYZ.Y + a[2] * XYZ.Z); 483 | T.g = this.gammaInv(a[3] * XYZ.X + a[4] * XYZ.Y + a[5] * XYZ.Z); 484 | T.b = this.gammaInv(a[6] * XYZ.X + a[7] * XYZ.Y + a[8] * XYZ.Z); 485 | return T; 486 | }, 487 | XYZ: function(Rgb, T) { 488 | var a, gb, gg, gr; 489 | if (T == null) { 490 | T = XYZ(); 491 | } 492 | a = this.base; 493 | gr = this.gamma(Rgb.r); 494 | gg = this.gamma(Rgb.g); 495 | gb = this.gamma(Rgb.b); 496 | T.X = a[0] * gr + a[1] * gg + a[2] * gb; 497 | T.Y = a[3] * gr + a[4] * gg + a[5] * gb; 498 | T.Z = a[6] * gr + a[7] * gg + a[8] * gb; 499 | return T; 500 | } 501 | }; 502 | 503 | createSpace = O(rgbSpaceConstructor, rgbSpacePrototype); 504 | 505 | createSpace["Adobe-98"] = createSpace(xyY(0.6400, 0.3300), xyY(0.2100, 0.7100), xyY(0.1500, 0.0600), white.D65, 2.2); 506 | 507 | createSpace["Adobe-RGB"] = createSpace(xyY(0.6250, 0.3400), xyY(0.2800, 0.5950), xyY(0.1550, 0.0700), white.D65, 1.8); 508 | 509 | createSpace["CIE-RGB"] = createSpace(xyY(0.7350, 0.2650), xyY(0.2740, 0.7170), xyY(0.1670, 0.0090), white.E, 1); 510 | 511 | createSpace["ColorMatch"] = createSpace(xyY(0.6300, 0.3400), xyY(0.2950, 0.6050), xyY(0.1500, 0.0750), white.D50, 1.8); 512 | 513 | createSpace["EBU-Monitor"] = createSpace(xyY(0.6314, 0.3391), xyY(0.2809, 0.5971), xyY(0.1487, 0.0645), white.D50, 1.9); 514 | 515 | createSpace["ECI-RGB"] = createSpace(xyY(0.6700, 0.3300), xyY(0.2100, 0.7100), xyY(0.1400, 0.0800), white.D50, 1.8); 516 | 517 | createSpace["HDTV"] = createSpace(xyY(0.6400, 0.3300), xyY(0.2900, 0.6000), xyY(0.1500, 0.0600), white.D65, 2.2); 518 | 519 | createSpace["Kodak-DC"] = createSpace(xyY(0.6492, 0.3314), xyY(0.3219, 0.5997), xyY(0.1548, 0.0646), white.D50, 2.22); 520 | 521 | createSpace["NTSC-53"] = createSpace(xyY(0.6700, 0.3300), xyY(0.2100, 0.7100), xyY(0.1400, 0.0800), white.C, 2.2); 522 | 523 | createSpace["PAL-SECAM"] = createSpace(xyY(0.6400, 0.3300), xyY(0.2900, 0.6000), xyY(0.1500, 0.0600), white.D65, 2.2); 524 | 525 | createSpace["sRGB"] = createSpace(xyY(0.6400, 0.3300), xyY(0.3000, 0.6000), xyY(0.1500, 0.0600), white.D65, gammaSRgb, gammaSRgbInv); 526 | 527 | createSpace["WideGamut"] = createSpace(xyY(0.7347, 0.2653), xyY(0.1152, 0.8264), xyY(0.1566, 0.0177), white.D50, 2.2); 528 | 529 | module.exports = createSpace; 530 | 531 | },{"../XYZ":4,"../rgb":7,"../white":10,"../xyY":11,"m4th/lu":12,"m4th/matrix":13,"ut1l/create/object":16}],10:[function(require,module,exports){ 532 | var xy, xyY; 533 | 534 | xyY = require("./xyY"); 535 | 536 | xy = function(x, y) { 537 | return (xyY(x, y, 1)).XYZ(); 538 | }; 539 | 540 | module.exports = { 541 | A: xy(0.44757, 0.40745), 542 | B: xy(0.34842, 0.35161), 543 | C: xy(0.31006, 0.31616), 544 | D50: xy(0.34567, 0.35850), 545 | D55: xy(0.33242, 0.34743), 546 | D65: xy(0.31271, 0.32902), 547 | D75: xy(0.29902, 0.31485), 548 | E: xy(1 / 3, 1 / 3), 549 | F1: xy(0.31310, 0.33727), 550 | F2: xy(0.37208, 0.37529), 551 | F3: xy(0.40910, 0.39430), 552 | F4: xy(0.44018, 0.40329), 553 | F5: xy(0.31379, 0.34531), 554 | F6: xy(0.37790, 0.38835), 555 | F7: xy(0.31292, 0.32933), 556 | F8: xy(0.34588, 0.35875), 557 | F9: xy(0.37417, 0.37281), 558 | F10: xy(0.34609, 0.35986), 559 | F11: xy(0.38052, 0.37713), 560 | F12: xy(0.43695, 0.40441) 561 | }; 562 | 563 | },{"./xyY":11}],11:[function(require,module,exports){ 564 | var O, xyyPrototype; 565 | 566 | O = require("ut1l/create/object"); 567 | 568 | xyyPrototype = { 569 | XYZ: function(T) { 570 | if (T == null) { 571 | T = require("./XYZ")(); 572 | } 573 | T.X = this.x * this.Y / this.y; 574 | T.Y = this.Y; 575 | T.Z = (1 - this.x - this.y) * this.Y / this.y; 576 | return T; 577 | }, 578 | isDefined: function() { 579 | return (this.x != null) && (this.y != null) && (this.Y != null); 580 | }, 581 | toString: function() { 582 | return "x=" + this.x + ", y=" + this.y + ", Y=" + this.Y; 583 | } 584 | }; 585 | 586 | module.exports = O((function(x, y, Y) { 587 | this.x = x; 588 | this.y = y; 589 | this.Y = Y; 590 | }), xyyPrototype); 591 | 592 | },{"./XYZ":4,"ut1l/create/object":16}],12:[function(require,module,exports){ 593 | var M, T, creator, fail, luDecompConstructor, luDecompPrototype; 594 | 595 | M = require("./matrix"); 596 | 597 | creator = require("ut1l/create/object"); 598 | 599 | T = require("ut1l/create/throwable"); 600 | 601 | fail = T("MatrixException"); 602 | 603 | 604 | /* 605 | A very basic LU decomposition implementation without pivoting. Decomposition is done in place. Given buffer must 606 | be square and regular. The values of L below the diagonal are stored. The ones on the diagonal and the zeros 607 | above the diagonal are not stored. The values of U on and above the diagonal are stored. The zero values below 608 | the diagonal are not stored. 609 | */ 610 | 611 | luDecompConstructor = function(A, T) { 612 | var i, j, k, _i, _j, _k, _l, _m, _ref, _ref1, _ref2, _ref3; 613 | if (T == null) { 614 | T = A.clone(); 615 | } 616 | for (i = _i = 0, _ref = T.columns; _i < _ref; i = _i += 1) { 617 | for (j = _j = i, _ref1 = T.columns; _j < _ref1; j = _j += 1) { 618 | for (k = _k = 0; _k < i; k = _k += 1) { 619 | T.set(i, j, (T.get(i, j)) - (T.get(i, k)) * (T.get(k, j))); 620 | } 621 | } 622 | for (j = _l = _ref2 = i + 1, _ref3 = T.columns; _l < _ref3; j = _l += 1) { 623 | for (k = _m = 0; _m < i; k = _m += 1) { 624 | T.set(j, i, (T.get(j, i)) - (T.get(j, k)) * (T.get(k, i))); 625 | } 626 | T.set(j, i, (T.get(j, i)) / (T.get(i, i))); 627 | } 628 | } 629 | this.lu = T; 630 | }; 631 | 632 | luDecompPrototype = { 633 | 634 | /* Calculate X = A^-1 * B in place or not in place */ 635 | solve: function(B, T) { 636 | var A, i, j, k, _i, _j, _k, _l, _m, _n, _o, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; 637 | if (T == null) { 638 | T = B.clone(); 639 | } 640 | A = this.lu; 641 | if (B.rows !== A.columns || !B.isSize(T)) { 642 | fail("unmatching matrix dimension"); 643 | } 644 | for (k = _i = 0, _ref = A.columns; _i < _ref; k = _i += 1) { 645 | for (i = _j = _ref1 = k + 1, _ref2 = A.columns; _j < _ref2; i = _j += 1) { 646 | for (j = _k = 0, _ref3 = T.columns; _k < _ref3; j = _k += 1) { 647 | T.set(i, j, (T.get(i, j)) - (T.get(k, j)) * (A.get(i, k))); 648 | } 649 | } 650 | } 651 | for (k = _l = _ref4 = A.columns - 1; _l >= 0; k = _l += -1) { 652 | for (j = _m = 0, _ref5 = T.columns; _m < _ref5; j = _m += 1) { 653 | T.set(k, j, (T.get(k, j)) / (A.get(k, k))); 654 | } 655 | for (i = _n = 0; _n < k; i = _n += 1) { 656 | for (j = _o = 0, _ref6 = T.columns; _o < _ref6; j = _o += 1) { 657 | T.set(i, j, (T.get(i, j)) - (T.get(k, j)) * (A.get(i, k))); 658 | } 659 | } 660 | } 661 | return T; 662 | }, 663 | getInverse: function() { 664 | var I; 665 | I = M.I(this.lu.columns); 666 | return this.solve(I, I); 667 | } 668 | }; 669 | 670 | module.exports = creator(luDecompConstructor, luDecompPrototype); 671 | 672 | },{"./matrix":13,"ut1l/create/object":14,"ut1l/create/throwable":15}],13:[function(require,module,exports){ 673 | var T, add, ceil, createMatrix, creator, each, eachDiagonal, eachInRow, fail, failUnmatchingDimensions, floor, id, isNumber, makeReduce, matrixConstructor, matrixProto, matrixStatic, min, minus, sqrt; 674 | 675 | creator = require("ut1l/create/object"); 676 | 677 | T = require("ut1l/create/throwable"); 678 | 679 | fail = T("MatrixException"); 680 | 681 | failUnmatchingDimensions = function() { 682 | return fail("invalid dimension"); 683 | }; 684 | 685 | floor = Math.floor, ceil = Math.ceil, sqrt = Math.sqrt, min = Math.min; 686 | 687 | id = function(x) { 688 | return x; 689 | }; 690 | 691 | add = function(a, b) { 692 | return a + b; 693 | }; 694 | 695 | minus = function(a, b) { 696 | return a - b; 697 | }; 698 | 699 | isNumber = function(n) { 700 | return typeof n === "number"; 701 | }; 702 | 703 | matrixConstructor = function(arrayOrRows, arrayOrColumns, arrayOpt) { 704 | var array, cols, rows; 705 | if (!isNumber(arrayOrRows)) { 706 | array = arrayOrRows; 707 | } else { 708 | rows = arrayOrRows; 709 | if (!isNumber(arrayOrColumns)) { 710 | array = arrayOrColumns; 711 | } else { 712 | cols = arrayOrColumns; 713 | array = arrayOpt; 714 | } 715 | } 716 | if (rows == null) { 717 | rows = ceil(sqrt(array.length)); 718 | } 719 | if (cols == null) { 720 | cols = rows === 0 ? 0 : array != null ? ceil(array.length / rows) : rows; 721 | } 722 | if (array == null) { 723 | array = []; 724 | } 725 | this.columns = cols; 726 | this.rows = rows; 727 | this.array = array; 728 | }; 729 | 730 | matrixStatic = { 731 | I: function(rows, columns) { 732 | var i, _i, _ref; 733 | if (columns == null) { 734 | columns = rows; 735 | } 736 | T = createMatrix(rows, columns); 737 | T.fill(0, T); 738 | for (i = _i = 0, _ref = min(rows, columns); _i < _ref; i = _i += 1) { 739 | T.set(i, i, 1); 740 | } 741 | return T; 742 | }, 743 | diag: function(x, T) { 744 | if (T == null) { 745 | T = createMatrix(x.length, x.length); 746 | } else if (!T.isSize(x.length)) { 747 | failUnmatchingDimensions(); 748 | } 749 | T.each(function(val, r, c) { 750 | return T.set(r, c, r === c ? x[r] : 0); 751 | }); 752 | return T; 753 | } 754 | }; 755 | 756 | eachInRow = function(row, handler) { 757 | var j, _i, _ref; 758 | for (j = _i = 0, _ref = this.columns; _i < _ref; j = _i += 1) { 759 | handler.call(this, this.get(row, j), row, j); 760 | } 761 | return this; 762 | }; 763 | 764 | each = function(handler) { 765 | var i, _i, _ref; 766 | for (i = _i = 0, _ref = this.rows; _i < _ref; i = _i += 1) { 767 | eachInRow.call(this, i, handler); 768 | } 769 | return this; 770 | }; 771 | 772 | eachDiagonal = function(handler) { 773 | var ij, _i, _ref; 774 | for (ij = _i = 0, _ref = min(this.rows, this.columns); _i < _ref; ij = _i += 1) { 775 | handler.call(this, this.get(ij, ij), ij, ij); 776 | } 777 | return this; 778 | }; 779 | 780 | makeReduce = function(eachFunc) { 781 | return function(callback, initialValue) { 782 | var value; 783 | value = initialValue; 784 | eachFunc.call(this, function(val, i, j) { 785 | if (value != null) { 786 | value = callback.call(this, value, val, i, j); 787 | } else { 788 | value = val; 789 | } 790 | }); 791 | return value; 792 | }; 793 | }; 794 | 795 | matrixProto = { 796 | get: function(row, col) { 797 | if (col == null) { 798 | col = 0; 799 | } 800 | return this.array[row * this.columns + col]; 801 | }, 802 | set: function(row, col, val) { 803 | this.array[row * this.columns + col] = val; 804 | return this; 805 | }, 806 | isSize: function(rowsOrM, columns) { 807 | if (isNumber(rowsOrM)) { 808 | if (columns == null) { 809 | columns = rowsOrM; 810 | } 811 | return this.rows === rowsOrM && this.columns === columns; 812 | } else { 813 | return this.isSize(rowsOrM.rows, rowsOrM.columns); 814 | } 815 | }, 816 | isSquare: function() { 817 | return this.rows === this.columns; 818 | }, 819 | each: each, 820 | eachDiagonal: eachDiagonal, 821 | reduce: makeReduce(each), 822 | reduceDiagonal: makeReduce(eachDiagonal), 823 | reduceRows: function(callback, initialValue) { 824 | var i, j, rdcRows, val, value, _i, _j, _ref, _ref1; 825 | rdcRows = []; 826 | for (i = _i = 0, _ref = this.rows; _i < _ref; i = _i += 1) { 827 | value = initialValue; 828 | for (j = _j = 0, _ref1 = this.columns; _j < _ref1; j = _j += 1) { 829 | val = this.get(i, j); 830 | if (value != null) { 831 | value = callback.call(this, value, val, i, j); 832 | } else { 833 | value = val; 834 | } 835 | } 836 | rdcRows.push(value); 837 | } 838 | return rdcRows; 839 | }, 840 | map: function() { 841 | var args, elements, func, l, n; 842 | args = arguments; 843 | n = args.length - 1; 844 | if (args[n] === void 0) { 845 | n -= 1; 846 | } 847 | if (typeof args[n] !== "function") { 848 | T = args[n--]; 849 | if (!this.isSize(T)) { 850 | failUnmatchingDimensions(); 851 | } 852 | } else { 853 | T = createMatrix(this.rows, this.columns); 854 | } 855 | func = args[n]; 856 | l = T.rows * T.columns; 857 | elements = []; 858 | T.each((function(_this) { 859 | return function(val, i, j) { 860 | var k, _i; 861 | elements[0] = _this.get(i, j); 862 | for (k = _i = 0; _i < n; k = _i += 1) { 863 | elements[k + 1] = args[k].get(i, j); 864 | } 865 | elements[++k] = i; 866 | elements[++k] = j; 867 | return T.set(i, j, func.apply(_this, elements)); 868 | }; 869 | })(this)); 870 | return T; 871 | }, 872 | clone: function(T) { 873 | return this.map(id, T); 874 | }, 875 | fill: function(s, T) { 876 | return this.map((function() { 877 | return s; 878 | }), T); 879 | }, 880 | times: function(s, T) { 881 | return this.map((function(x) { 882 | return s * x; 883 | }), T); 884 | }, 885 | add: function(B, T) { 886 | return this.map(B, add, T); 887 | }, 888 | minus: function(B, T) { 889 | return this.map(B, minus, T); 890 | }, 891 | transp: function(T) { 892 | var B, i, j, _i, _j, _ref, _ref1, _ref2; 893 | if (T == null) { 894 | T = createMatrix(this.columns, this.rows); 895 | } 896 | if (T === this) { 897 | B = this.clone(); 898 | _ref = [this.columns, this.rows], this.rows = _ref[0], this.columns = _ref[1]; 899 | return B.transp(this); 900 | } else { 901 | if (this.rows !== T.columns || this.columns !== T.rows) { 902 | failUnmatchingDimensions(); 903 | } 904 | for (i = _i = 0, _ref1 = this.columns; _i < _ref1; i = _i += 1) { 905 | for (j = _j = 0, _ref2 = this.rows; _j < _ref2; j = _j += 1) { 906 | T.set(i, j, this.get(j, i)); 907 | } 908 | } 909 | return T; 910 | } 911 | }, 912 | mult: function(B, T) { 913 | var i, j, k, _i, _j, _k, _ref, _ref1, _ref2; 914 | if (T == null) { 915 | T = createMatrix(this.rows, B.columns); 916 | } 917 | if (this.columns !== B.rows || T.rows !== this.rows || T.columns !== B.columns) { 918 | failUnmatchingDimensions(); 919 | } 920 | T.fill(0, T); 921 | for (i = _i = 0, _ref = this.rows; _i < _ref; i = _i += 1) { 922 | for (j = _j = 0, _ref1 = B.columns; _j < _ref1; j = _j += 1) { 923 | for (k = _k = 0, _ref2 = this.columns; _k < _ref2; k = _k += 1) { 924 | T.array[i * T.columns + j] += (this.get(i, k)) * (B.get(k, j)); 925 | } 926 | } 927 | } 928 | return T; 929 | }, 930 | toString: (function() { 931 | var concatEntries; 932 | concatEntries = function(x, y) { 933 | return x + " " + y; 934 | }; 935 | return function() { 936 | return (this.reduceRows(concatEntries)).join("\n"); 937 | }; 938 | })() 939 | }; 940 | 941 | module.exports = createMatrix = creator(matrixStatic, matrixConstructor, matrixProto); 942 | 943 | },{"ut1l/create/object":14,"ut1l/create/throwable":15}],14:[function(require,module,exports){ 944 | var createBuilder; 945 | 946 | createBuilder = function(extend, constructor, prototype) { 947 | var F, f, key, value; 948 | if (typeof extend === "function") { 949 | prototype = constructor; 950 | constructor = extend; 951 | extend = null; 952 | } else if ((constructor == null) && (prototype == null)) { 953 | prototype = extend; 954 | extend = null; 955 | } 956 | F = constructor != null ? function(args) { 957 | var ret; 958 | ret = constructor.apply(this, args); 959 | if (ret !== void 0) { 960 | return ret; 961 | } else { 962 | return this; 963 | } 964 | } : function() {}; 965 | if (prototype == null) { 966 | prototype = {}; 967 | } 968 | F.prototype = prototype; 969 | f = function() { 970 | return new F(arguments); 971 | }; 972 | f.prototype = prototype; 973 | for (key in extend) { 974 | value = extend[key]; 975 | f[key] = value; 976 | } 977 | return f; 978 | }; 979 | 980 | module.exports = createBuilder; 981 | 982 | },{}],15:[function(require,module,exports){ 983 | var O, createCreateThrowable, createTopThrowable, throwableConstr, throwableProto; 984 | 985 | O = require("./object"); 986 | 987 | throwableProto = { 988 | name: "Error", 989 | toString: function() { 990 | if (this.message != null) { 991 | return "" + this.name + ": " + this.message; 992 | } else { 993 | return this.name; 994 | } 995 | } 996 | }; 997 | 998 | createTopThrowable = O(throwableProto); 999 | 1000 | throwableConstr = function(message) { 1001 | var e; 1002 | this.message = message; 1003 | e = Error.call(this, message); 1004 | this.stack = e.stack; 1005 | }; 1006 | 1007 | createCreateThrowable = function(name, parent) { 1008 | var proto; 1009 | if (parent == null) { 1010 | parent = createTopThrowable; 1011 | } 1012 | proto = parent(); 1013 | if (name != null) { 1014 | proto.name = name; 1015 | } 1016 | return O(throwableConstr, proto); 1017 | }; 1018 | 1019 | createCreateThrowable.c4tch = function() { 1020 | var action, arg, args, idx, onError, throwables, _i, _len; 1021 | args = arguments; 1022 | throwables = []; 1023 | for (idx = _i = 0, _len = args.length; _i < _len; idx = ++_i) { 1024 | arg = args[idx]; 1025 | if (arg.prototype instanceof createTopThrowable) { 1026 | throwables.push(arg); 1027 | } else { 1028 | break; 1029 | } 1030 | } 1031 | if (throwables.length === 0) { 1032 | throwables.push(createTopThrowable); 1033 | } 1034 | action = args[idx]; 1035 | onError = args[idx + 1]; 1036 | return function() { 1037 | var e, t, _j, _len1; 1038 | try { 1039 | return action.apply(this, arguments); 1040 | } catch (_error) { 1041 | e = _error; 1042 | for (_j = 0, _len1 = throwables.length; _j < _len1; _j++) { 1043 | t = throwables[_j]; 1044 | if (e instanceof t) { 1045 | return (onError != null ? onError.call(this, e) : void 0); 1046 | } 1047 | } 1048 | throw e; 1049 | } 1050 | }; 1051 | }; 1052 | 1053 | module.exports = createCreateThrowable; 1054 | 1055 | },{"./object":14}],16:[function(require,module,exports){ 1056 | var addProto, createBuilder, defineProperty; 1057 | 1058 | defineProperty = Object.defineProperty; 1059 | 1060 | addProto = defineProperty !== void 0 ? function(func, proto) { 1061 | func.prototype = proto; 1062 | defineProperty(func, "prototype", { 1063 | enumerable: false 1064 | }); 1065 | } : function(func, proto) { 1066 | func.prototype = proto; 1067 | }; 1068 | 1069 | createBuilder = function(extend, constructor, prototype) { 1070 | var F, f, key, value; 1071 | if (typeof extend === "function") { 1072 | prototype = constructor; 1073 | constructor = extend; 1074 | extend = null; 1075 | } else if ((constructor == null) && (prototype == null)) { 1076 | prototype = extend; 1077 | extend = null; 1078 | } 1079 | F = constructor != null ? function(args) { 1080 | var ret; 1081 | ret = constructor.apply(this, args); 1082 | if (ret !== void 0) { 1083 | return ret; 1084 | } else { 1085 | return this; 1086 | } 1087 | } : function() {}; 1088 | if (prototype == null) { 1089 | prototype = {}; 1090 | } 1091 | F.prototype = prototype; 1092 | f = function() { 1093 | return new F(arguments); 1094 | }; 1095 | addProto(f, prototype); 1096 | for (key in extend) { 1097 | value = extend[key]; 1098 | f[key] = value; 1099 | } 1100 | return f; 1101 | }; 1102 | 1103 | module.exports = createBuilder; 1104 | 1105 | },{}]},{},[6]); 1106 | -------------------------------------------------------------------------------- /dist/c0lor.min.js: -------------------------------------------------------------------------------- 1 | // c0lor v0.1.1 | (c) 2013-2021 Hendrik Helwich | MIT License 2 | 'use strict';!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gh?n(a,1/3):l*a+e},k=function(a){return a>g?n(a,3):(a-e)/l},j=function(a){return a>h?1/(3*n(a,2/3)):l},m={Lab:function(a,b){var c;return null==b&&(b=d()),c=i(a.Y/this.white.Y),b.L=116*c-16,b.a=500*(i(a.X/this.white.X)-c),b.b=200*(c-i(a.Z/this.white.Z)),b},XYZ:function(a,b){var c;return null==b&&(b=o()),c=(a.L+16)/116,b.X=k(c+a.a/500)*this.white.X,b.Y=k(c)*this.white.Y,b.Z=k(c-a.b/200)*this.white.Z,b},LabderivL:function(a){return 116*j(a/this.white.Y)/this.white.Y}},b.exports=f(function(a){this.white=a},m)},{"../Lab":2,"../XYZ":4,"ut1l/create/object":16}],9:[function(a,b,c){var d,e,f,g,h,i,j,k,l,m,n,o,p;e=a("ut1l/create/object"),d=a("m4th/matrix"),k=a("m4th/lu"),o=a("../white"),p=a("../xyY"),f=a("../XYZ"),l=Math.pow,h=function(a){return a<=.04045?a/12.92:l((a+.055)/1.055,2.4)},i=function(a){return a<=.0031308?12.92*a:1.055*l(a,1/2.4)-.055},j=function(){var a,b,c;a=d([this.red.x,this.green.x,this.blue.x,this.red.y,this.green.y,this.blue.y,1-this.red.x-this.red.y,1-this.green.x-this.green.y,1-this.blue.x-this.blue.y]),b=k(a),c=d(3,[this.white.X,this.white.Y,this.white.Z]),b.solve(c,c),a=a.mult(d.diag(c.array)),this.base=a.array,this.baseInv=k(a).getInverse().array,delete this.XYZ,delete this.rgb},m=function(a,b,c,d,e,f){this.red=a,this.green=b,this.blue=c,this.white=d,"function"==typeof e?(this.gamma=e,this.gammaInv=f):(this.g=e,this.gInv=1/e),this.rgb=function(){return j.call(this),this.rgb.apply(this,arguments)},this.XYZ=function(){return j.call(this),this.XYZ.apply(this,arguments)}},n={gamma:function(a){return l(a,this.g)},gammaInv:function(a){return l(a,this.gInv)},rgb:function(b,c){var d;return null==c&&(c=a("../rgb")()),d=this.baseInv,c.r=this.gammaInv(d[0]*b.X+d[1]*b.Y+d[2]*b.Z),c.g=this.gammaInv(d[3]*b.X+d[4]*b.Y+d[5]*b.Z),c.b=this.gammaInv(d[6]*b.X+d[7]*b.Y+d[8]*b.Z),c},XYZ:function(a,b){var c,d,e,g;return null==b&&(b=f()),c=this.base,g=this.gamma(a.r),e=this.gamma(a.g),d=this.gamma(a.b),b.X=c[0]*g+c[1]*e+c[2]*d,b.Y=c[3]*g+c[4]*e+c[5]*d,b.Z=c[6]*g+c[7]*e+c[8]*d,b}},g=e(m,n),g["Adobe-98"]=g(p(.64,.33),p(.21,.71),p(.15,.06),o.D65,2.2),g["Adobe-RGB"]=g(p(.625,.34),p(.28,.595),p(.155,.07),o.D65,1.8),g["CIE-RGB"]=g(p(.735,.265),p(.274,.717),p(.167,.009),o.E,1),g.ColorMatch=g(p(.63,.34),p(.295,.605),p(.15,.075),o.D50,1.8),g["EBU-Monitor"]=g(p(.6314,.3391),p(.2809,.5971),p(.1487,.0645),o.D50,1.9),g["ECI-RGB"]=g(p(.67,.33),p(.21,.71),p(.14,.08),o.D50,1.8),g.HDTV=g(p(.64,.33),p(.29,.6),p(.15,.06),o.D65,2.2),g["Kodak-DC"]=g(p(.6492,.3314),p(.3219,.5997),p(.1548,.0646),o.D50,2.22),g["NTSC-53"]=g(p(.67,.33),p(.21,.71),p(.14,.08),o.C,2.2),g["PAL-SECAM"]=g(p(.64,.33),p(.29,.6),p(.15,.06),o.D65,2.2),g.sRGB=g(p(.64,.33),p(.3,.6),p(.15,.06),o.D65,h,i),g.WideGamut=g(p(.7347,.2653),p(.1152,.8264),p(.1566,.0177),o.D50,2.2),b.exports=g},{"../XYZ":4,"../rgb":7,"../white":10,"../xyY":11,"m4th/lu":12,"m4th/matrix":13,"ut1l/create/object":16}],10:[function(a,b,c){var d,e;e=a("./xyY"),d=function(a,b){return e(a,b,1).XYZ()},b.exports={A:d(.44757,.40745),B:d(.34842,.35161),C:d(.31006,.31616),D50:d(.34567,.3585),D55:d(.33242,.34743),D65:d(.31271,.32902),D75:d(.29902,.31485),E:d(1/3,1/3),F1:d(.3131,.33727),F2:d(.37208,.37529),F3:d(.4091,.3943),F4:d(.44018,.40329),F5:d(.31379,.34531),F6:d(.3779,.38835),F7:d(.31292,.32933),F8:d(.34588,.35875),F9:d(.37417,.37281),F10:d(.34609,.35986),F11:d(.38052,.37713),F12:d(.43695,.40441)}},{"./xyY":11}],11:[function(a,b,c){var d,e;d=a("ut1l/create/object"),e={XYZ:function(b){return null==b&&(b=a("./XYZ")()),b.X=this.x*this.Y/this.y,b.Y=this.Y,b.Z=(1-this.x-this.y)*this.Y/this.y,b},isDefined:function(){return null!=this.x&&null!=this.y&&null!=this.Y},toString:function(){return"x="+this.x+", y="+this.y+", Y="+this.Y}},b.exports=d(function(a,b,c){this.x=a,this.y=b,this.Y=c},e)},{"./XYZ":4,"ut1l/create/object":16}],12:[function(a,b,c){var d,e,f,g,h,i;d=a("./matrix"),f=a("ut1l/create/object"),e=a("ut1l/create/throwable"),g=e("MatrixException"),h=function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n;for(null==b&&(b=a.clone()),c=f=0,k=b.columns;f=0;f=k+=-1){for(e=l=0,t=b.columns;l 13 | T.L = @L 14 | T.a = @C * cos @h 15 | T.b = @C * sin @h 16 | T 17 | 18 | # Returns a human readable string serialization of the color. 19 | toString: -> 20 | "L=#{@L}, C=#{@C}, h=#{@h}" 21 | 22 | 23 | # Public API 24 | # ---------- 25 | 26 | module.exports = O ((@L, @C, @h) ->), lchPrototype -------------------------------------------------------------------------------- /src/Lab.coffee: -------------------------------------------------------------------------------- 1 | # Imports / Shortcuts 2 | # ------------------- 3 | 4 | O = require "ut1l/create/object" 5 | 6 | sqrt = Math.sqrt 7 | atan2 = Math.atan2 8 | 9 | 10 | # Lab color prototype 11 | # ------------------- 12 | 13 | labPrototype = 14 | 15 | # Converts color from Lab to LCh color space. 16 | LCh: (T = do require "./LCh") -> 17 | T.L = @L # lightness 18 | T.C = sqrt @a * @a + @b * @b # chroma 19 | T.h = atan2 @b, @a # hue 20 | T 21 | 22 | # Returns a human readable string serialization of the color. 23 | toString: -> 24 | "L=#{@L}, a=#{@a}, b=#{@b}" 25 | 26 | 27 | 28 | # Public API 29 | # ---------- 30 | 31 | module.exports = O ((@L, @a, @b) ->), labPrototype -------------------------------------------------------------------------------- /src/RGBInt.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | O = require "ut1l/create/object" 4 | 5 | 6 | fromByte = (b) -> 7 | if b == undefined then b else b / 255 8 | 9 | to2Hex = (b) -> 10 | hex = do (b.toString 16).toUpperCase 11 | if hex.length == 1 then "0" + hex else hex 12 | 13 | from2Hex = (str) -> 14 | parseInt str, 16 15 | 16 | isValid = (n) -> 17 | 0 <= n <= 255 and n % 1 == 0 18 | 19 | # RGB 24 bit prototype 20 | # -------------------- 21 | 22 | rgb24Prototype = 23 | 24 | rgb: (T = do require "./rgb") -> 25 | T.r = fromByte @R 26 | T.g = fromByte @G 27 | T.b = fromByte @B 28 | T 29 | 30 | hex: (str) -> 31 | if str? 32 | @R = from2Hex str.substring 0, 2 33 | @G = from2Hex str.substring 2, 4 34 | @B = from2Hex str.substring 4, 6 35 | @ 36 | else 37 | if do @isDefined then (to2Hex @R) + (to2Hex @G) + (to2Hex @B) else undefined 38 | 39 | isDefined: -> 40 | @R? and @G? and @B? 41 | 42 | isValid: -> 43 | @isDefined() and (isValid @R) and (isValid @G) and (isValid @B) 44 | 45 | toString: -> 46 | "R=#{@R}, G=#{@G}, B=#{@B}" 47 | 48 | 49 | 50 | # Public api 51 | # ---------- 52 | 53 | module.exports = O ((@R, @G, @B) ->), rgb24Prototype -------------------------------------------------------------------------------- /src/XYZ.coffee: -------------------------------------------------------------------------------- 1 | # XYZ and xyY color space implementation. 2 | 3 | # Imports 4 | # ------- 5 | 6 | O = require "ut1l/create/object" 7 | 8 | # XYZ prototype 9 | # ------------- 10 | 11 | xyzPrototype = 12 | 13 | # Convert the color from *XYZ* to *xyY* color space. Give optional *xyY* color `T` to store the result. 14 | xyY: (T = do require "./xyY") -> 15 | T.x = @X / (@X + @Y + @Z) 16 | T.y = @Y / (@X + @Y + @Z) 17 | T.Y = @Y 18 | T 19 | 20 | # Returns `true` if all components of the color are not `null` and not `undefined`. 21 | isDefined: -> 22 | @X? and @Y? and @Z? 23 | 24 | # Returns a human readable string serialization of the color. 25 | toString: -> 26 | "X=#{@X}, Y=#{@Y}, Z=#{@Z}" 27 | 28 | 29 | 30 | 31 | # Public API 32 | # ---------- 33 | 34 | module.exports = O ((@X, @Y, @Z) ->), xyzPrototype 35 | -------------------------------------------------------------------------------- /src/gamut.coffee: -------------------------------------------------------------------------------- 1 | #{info, warn, fail} = require "ut1l/log" 2 | pow = Math.pow 3 | rgbM = require "./rgb" 4 | xyzM = require "./XYZ" 5 | 6 | ### 7 | solveCubic = (a0, a, b, c) -> # see: http://de.wikipedia.org/wiki/Cardanische_Formeln 8 | # normalize 9 | a /= a0 10 | b /= a0 11 | c /= a0 12 | # substitute x=z-a/3 => z^3 + p*z + q = 0 13 | p = b - a*a / 3 14 | q = 2*(pow a, 3)/27 - a*b/3 + c 15 | # calculate dicriminat 16 | D = (pow p, 3)/27 + q*q/4 17 | if D > 0 18 | D = Math.sqrt D 19 | ru = -q/2 + D 20 | rv = -q/2 - D 21 | 22 | if ru < 0 23 | ru = -(pow -ru, 1/3) 24 | else 25 | ru = pow ru, 1/3 26 | 27 | if rv < 0 28 | rv = -(pow -rv, 1/3) 29 | else 30 | rv = pow rv, 1/3 31 | 32 | X = ru + rv 33 | 34 | X -= a/3 35 | else if D < 0 36 | ppp = (pow p,3) / 27 37 | ppp = Math.abs ppp 38 | ppp = 2 * Math.pow ppp , 1/2 39 | argu = -q/ppp 40 | phi = Math.acos argu 41 | phi = phi/3 42 | # alert(phi); 43 | pp = Math.abs p/3 44 | pp = 2*pow pp, 1/2 45 | X = pp * Math.cos phi 46 | 47 | X -= a/3 48 | else 49 | fail "D = 0 is not implemented" 50 | 51 | 52 | 53 | #console.log "solution x = "+X 54 | #console.log "test: x^3 + a*x^2 + b*x + c = " + ((pow X, 3) + a*X*X + b*X + c) 55 | 56 | info "expect to be 0: " + (X*X*X + a*X*X + b*X + c) 57 | X 58 | 59 | 60 | checkSolution = (C, a, b, fy, rgb, nbase, mr, mg, mb) -> 61 | if C >= 0 62 | X = pow fy + a * C, 3 63 | Z = pow fy + b * C, 3 64 | if not rgb.r? 65 | rgb.r = nbase[0] * X + nbase[2] * Z + mr 66 | else info "expected #{rgb.r} == "+ (nbase[0] * X + nbase[2] * Z + mr) 67 | if not rgb.g? 68 | rgb.g = nbase[3] * X + nbase[5] * Z + mg 69 | else info "expected #{rgb.g} == "+ (nbase[3] * X + nbase[5] * Z + mg) 70 | if not rgb.b? 71 | rgb.b = nbase[6] * X + nbase[8] * Z + mb 72 | else info "expected #{rgb.b} == "+ (nbase[6] * X + nbase[8] * Z + mb ) 73 | if rgb.isValid() 74 | return true 75 | rgb.r = rgb.g = rgb.b = null 76 | return false 77 | 78 | gammaInv = (rgb, rgbCs) -> 79 | rgb.r = rgbCs.gammaInv rgb.r 80 | rgb.g = rgbCs.gammaInv rgb.g 81 | rgb.b = rgbCs.gammaInv rgb.b 82 | rgb 83 | ### 84 | 85 | class GamutMapping 86 | 87 | constructor: (@rgbCs, @labCs) -> 88 | ### 89 | rgbCs.init() # make sure rgb base is initialized 90 | # store rgb inverse base normalised with lab white point 91 | a = rgbCs.baseInv 92 | @nbase = [ 93 | a[0] * @labCs.white.X, a[1] * @labCs.white.Y, a[2] * @labCs.white.Z 94 | a[3] * @labCs.white.X, a[4] * @labCs.white.Y, a[5] * @labCs.white.Z 95 | a[6] * @labCs.white.X, a[7] * @labCs.white.Y, a[8] * @labCs.white.Z 96 | ] 97 | ### 98 | 99 | 100 | LChMaxC: (LCh, rgb = rgbM()) -> 101 | # naive binary search implementation 102 | # TODO: manage correct direct calculation 103 | LCh.C = 0 104 | step = 110 105 | validC = null 106 | for n in [0..50] by 1 107 | lab = LCh.Lab lab 108 | xyz = @labCs.XYZ lab, xyz 109 | @rgbCs.rgb xyz, rgb 110 | if rgb.isValid() 111 | validC = LCh.C 112 | LCh.C += step 113 | else 114 | LCh.C -= step 115 | step /= 2 116 | LCh.C = validC 117 | if validC? # solution found 118 | @rgbCs.rgb (@labCs.XYZ (LCh.Lab lab), xyz), rgb 119 | else 120 | null 121 | 122 | 123 | 124 | ### 125 | 126 | LChMaxCExp: (LCh, rgb = rgbM.rgb()) -> 127 | 128 | 129 | xyz = xyzM.XYZ() # temporary variable 130 | 131 | # valid (r, 0, 0) with given luminance possible? 132 | r = (pow 16 + LCh.L, 3) / (@labCs.white.Y * 1560896 * @rgbCs.base[3]) # 1560896 = 116^3 133 | if 0 <= r <= 1 # valid rgb element ? 134 | r = @rgbCs.gammaInv r # linear rgb -> nonlinear rgb 135 | _rgb = rgbM.rgb r, 0, 0 136 | # map to LCh to get hue 137 | @rgbCs.toXYZ _rgb, xyz 138 | lab = @labCs.fromXYZ xyz, lab 139 | lch = lab.LCh lch 140 | 141 | 142 | # valid (0, g, 0) with given luminance possible? 143 | g = (pow 16 + LCh.L, 3) / (@labCs.white.Y * 1560896 * @rgbCs.base[4]) # 1560896 = 116^3 144 | if 0 <= g <= 1 # valid rgb element ? 145 | g = @rgbCs.gammaInv g # linear rgb -> nonlinear rgb 146 | _rgb = rgbM.rgb 0, g, 0 147 | # map to LCh to get hue 148 | @rgbCs.toXYZ _rgb, xyz 149 | lab = @labCs.fromXYZ xyz, lab 150 | lch = lab.LCh lch 151 | 152 | 153 | 154 | # valid (0, 0, b) with given luminance possible? 155 | b = (pow 16 + LCh.L, 3) / (@labCs.white.Y * 1560896 * @rgbCs.base[5]) # 1560896 = 116^3 156 | if 0 <= b <= 1 # valid rgb element ? 157 | b = @rgbCs.gammaInv b # linear rgb -> nonlinear rgb 158 | _rgb = rgbM.rgb 0, 0, b 159 | # map to LCh to get hue 160 | @rgbCs.toXYZ _rgb, xyz 161 | lab = @labCs.fromXYZ xyz, lab 162 | lch = lab.LCh lch 163 | else # valid (r, 0, 1) with given luminance possible? 164 | r = ((pow (16 + LCh.L) / 116, 3) * @labCs.white.Y - @rgbCs.base[5]) / @rgbCs.base[3] 165 | 166 | #lchTest = (@labCs.fromXYZ @rgbCs.toXYZ rgbTest).LCh() 167 | 168 | #lchTest = (@labCs.fromXYZ @rgbCs.toXYZ rgbTest.set 0, 1, 0).LCh() 169 | 170 | #lchTest = (@labCs.fromXYZ @rgbCs.toXYZ rgbTest.set 0, 0, 1).LCh() 171 | 172 | # L and h fixed. maximum C is wanted so that the LCh color is a valid in current rgb color space 173 | # calculate a,b for chroma 1. Now we need to find the factor for a and b (the chroma) 174 | a = Math.cos LCh.h 175 | b = Math.sin LCh.h 176 | # to XYZ (for now ignore linear case) 177 | a /= 500 178 | b /= -200 179 | # calculate some constant values 180 | fy = (LCh.L + 16) / 116 181 | Y = pow(fy, 3) 182 | mr = @nbase[1] * Y # maximum red value for X / Z params 183 | mg = @nbase[4] * Y # maximum green value for X / Z params 184 | mb = @nbase[7] * Y # maximum blue value for X / Z params 185 | #X = pow(fy + a, 3) 186 | #Z = pow(fy + b, 3) 187 | # to linear rgb (maximum value 1) 188 | 189 | 190 | #mr = a0 * X + a2 * Z #red 191 | #mg = a3 * X + a5 * Z #green 192 | #mb = a6 * X + a8 * Z #blue 193 | 194 | 195 | bxr = @nbase[0] 196 | bzr = @nbase[2] 197 | bxg = @nbase[3] 198 | bzg = @nbase[5] 199 | bxb = @nbase[6] 200 | bzb = @nbase[8] 201 | 202 | a2 = a*a 203 | a3 = a2*a 204 | b2 = b*b 205 | b3 = b2*b 206 | fy_3 = 3*fy 207 | fy2_3 = fy_3*fy 208 | fy3 = pow fy, 3 209 | 210 | lr = solveCubic bxr*a3 + bzr*b3, fy_3*(bxr*a2 + bzr*b2), fy2_3*(bxr*a + bzr*b), fy3*(bxr+bzr)+mr-1 211 | 212 | rgb.r = 1 213 | rgb.g = rgb.b = null 214 | if (checkSolution lr, a, b, fy, rgb, @nbase, mr, mg, mb) 215 | return gammaInv rgb, @rgbCs 216 | 217 | lg = solveCubic bxg*a3 + bzg*b3, fy_3*(bxg*a2 + bzg*b2), fy2_3*(bxg*a + bzg*b), fy3*(bxg + bzg) + mg-1 218 | 219 | rgb.g = 1 220 | if (checkSolution lg, a, b, fy, rgb, @nbase, mr, mg, mb) 221 | return gammaInv rgb, @rgbCs 222 | 223 | 224 | 225 | lb = solveCubic bxb*a3 + bzb*b3, fy_3*(bxb*a2 + bzb*b2), fy2_3*(bxb*a + bzb*b), fy3*(bxb+bzb)+mb-1 226 | rgb.b = 1 227 | if (checkSolution lb, a, b, fy, rgb, @nbase, mr, mg, mb) 228 | return gammaInv rgb, @rgbCs 229 | 230 | 231 | lr0 = solveCubic bxr*a3 + bzr*b3, fy_3*(bxr*a2 + bzr*b), fy2_3*(bxr*a + bzr*b), fy3*(bxr+bzr)+mr 232 | 233 | rgb.r = 0 234 | if (checkSolution lr0, a, b, fy, rgb, @nbase, mr, mg, mb) 235 | return gammaInv rgb, @rgbCs 236 | 237 | lg0 = solveCubic bxg*a3 + bzg*b3, fy_3*(bxg*a2 + bzg*b), fy2_3*(bxg*a + bzg*b), fy3*(bxg+bzg)+mg 238 | 239 | rgb.g = 0 240 | if (checkSolution lg0, a, b, fy, rgb, @nbase, mr, mg, mb) 241 | return gammaInv rgb, @rgbCs 242 | 243 | 244 | lb0 = solveCubic bxb*a3 + bzb*b3, fy_3*(bxb*a2 + bzb*b2), fy2_3*(bxb*a + bzb*b), fy3*(bxb+bzb)+mb 245 | rgb.b = 0 246 | if (checkSolution lb0, a, b, fy, rgb, @nbase, mr, mg, mb) 247 | return gammaInv rgb, @rgbCs 248 | 249 | 250 | 251 | 252 | 253 | ### 254 | 255 | 256 | 257 | module.exports = (rgbCs, labCs) -> 258 | new GamutMapping rgbCs, labCs 259 | -------------------------------------------------------------------------------- /src/hsv.coffee: -------------------------------------------------------------------------------- 1 | # see: http://en.wikipedia.org/wiki/HSL_and_HSV 2 | 3 | # Imports / Shortcuts 4 | # ------------------- 5 | 6 | O = require "ut1l/create/object" 7 | 8 | floor = Math.floor 9 | 10 | 11 | # HSV color prototype 12 | # ------------------- 13 | 14 | hsvPrototype = 15 | 16 | rgb: (T = do require "./rgb") -> 17 | if @s == 0 # simplification 18 | T.set @v, @v, @v 19 | else 20 | h = (@h - floor @h) * 6 # 0 <= h < 6 21 | f = h - floor h # 0 <= f < 1 22 | p = @v * (1 - @s) 23 | q = @v * (1 - @s * f) 24 | t = @v * (1 - @s * (1 - f)) 25 | switch floor h 26 | when 0 then T.set @v, t, p 27 | when 1 then T.set q, @v, p 28 | when 2 then T.set p, @v, t 29 | when 3 then T.set p, q, @v 30 | when 4 then T.set t, p, @v 31 | when 5 then T.set @v, p, q 32 | 33 | set: (@h, @s, @v) -> 34 | @ 35 | 36 | toString: -> 37 | "h=#{@h}, s=#{@s}, v=#{@v}" 38 | 39 | 40 | # Extend RGB module 41 | # ----------------- 42 | 43 | hsv = createHsv = O ((@h, @s, @v) ->), hsvPrototype 44 | 45 | (require "./rgb").extendRgb (rgb) -> 46 | 47 | rgb.hsv = (T = do createHsv) -> 48 | max = Math.max @r, @g, @b 49 | min = Math.min @r, @g, @b 50 | T.v = max 51 | d = max - min 52 | T.s = if max != 0 then d / max else 0 53 | if T.s == 0 54 | T.h = 0 55 | else 56 | switch max 57 | when @r 58 | T.h = if @g < @b then 6 else 0 59 | T.h += (@g - @b) / d 60 | when @g 61 | T.h = 2 + (@b - @r) / d 62 | else # blue 63 | T.h = 4 + (@r - @g) / d 64 | T.h /= 6 65 | T 66 | 67 | 68 | 69 | # Public API 70 | # ---------- 71 | 72 | module.exports = hsv -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | module.exports = index = 2 | rgb: require "./rgb" 3 | RGB: require "./RGBInt" 4 | hsv: require "./hsv" 5 | Lab: require "./Lab" 6 | LCh: require "./LCh" 7 | XYZ: require "./XYZ" 8 | xyY: require "./xyY" 9 | white: require "./white" 10 | #gamut: require "./gamut" 11 | space: 12 | rgb: require "./space/rgb" 13 | lab: require "./space/lab" 14 | 15 | # create global index if in browser 16 | if window? 17 | window.c0lor = index 18 | -------------------------------------------------------------------------------- /src/rgb.coffee: -------------------------------------------------------------------------------- 1 | # Imports / Shortcuts 2 | # ------------------- 3 | 4 | 5 | O = require "ut1l/create/object" 6 | 7 | round = Math.round 8 | 9 | # Helpers 10 | # ------- 11 | 12 | 13 | toByte = (d) -> 14 | round d * 255 if d? 15 | 16 | validRgbEl = (x) -> 17 | (isFinite x) and 0 <= x <= 1 18 | 19 | # RGB prototype 20 | # ------------- 21 | 22 | rgbPrototype = 23 | 24 | RGB: (T = do require "./RGBInt") -> 25 | T.R = toByte @r 26 | T.G = toByte @g 27 | T.B = toByte @b 28 | T 29 | 30 | set: (@r, @g, @b) -> 31 | @ 32 | 33 | isDefined: -> 34 | @r? and @g? and @b? 35 | 36 | isValid: -> 37 | do @isDefined and (validRgbEl @r) and (validRgbEl @g) and (validRgbEl @b) 38 | 39 | toString: -> 40 | "r=#{@r}, g=#{@g}, b=#{@b}" 41 | 42 | 43 | 44 | # Public api 45 | # ---------- 46 | 47 | module.exports = O (extendRgb: (f) -> f rgbPrototype), ((@r, @g, @b) ->), rgbPrototype -------------------------------------------------------------------------------- /src/space/lab.coffee: -------------------------------------------------------------------------------- 1 | O = require "ut1l/create/object" 2 | 3 | xyz = require "../XYZ" 4 | Lab = require "../Lab" 5 | 6 | pow = Math.pow 7 | 8 | # Helpers 9 | # ------- 10 | 11 | N = 4 / 29 # = 16 / 116 12 | e3 = 216 / 24389 # = e^3 13 | foo = 841 / 108 # = (1 / 116) * (24389 / 27) = 1/3 * (29/6)^2 14 | e = 6 / 29 # = e3^(1/3) 15 | 16 | f = (w) -> 17 | if w > e3 18 | pow w, 1/3 19 | else 20 | foo * w + N 21 | 22 | fInv = (w) -> 23 | if w > e 24 | pow w, 3 25 | else 26 | (w - N) / foo 27 | 28 | # Derivative of `f`. 29 | fDeriv = (w) -> 30 | if w > e3 31 | 1 / (3 * (pow w, 2/3)) 32 | else 33 | foo 34 | 35 | # Lab color space prototype 36 | # ------------------------- 37 | 38 | labCsPrototype = 39 | 40 | # Converts color `XYZ` from *XYZ* to this *Lab* color space. 41 | Lab: (XYZ, T = do Lab) -> 42 | l = f XYZ.Y / @white.Y 43 | T.L = 116 * l - 16 44 | T.a = 500 * ((f XYZ.X / @white.X) - l) 45 | T.b = 200 * (l - (f XYZ.Z / @white.Z)) 46 | T 47 | 48 | XYZ: (Lab, T = do xyz) -> 49 | fy = (Lab.L + 16) / 116 50 | T.X = (fInv fy + Lab.a / 500) * @white.X 51 | T.Y = (fInv fy) * @white.Y 52 | T.Z = (fInv fy - Lab.b / 200) * @white.Z 53 | T 54 | 55 | LabderivL: (Y) -> 56 | 116 * (fDeriv Y / @white.Y) / @white.Y 57 | 58 | 59 | 60 | # Public API 61 | # ---------- 62 | 63 | module.exports = O ((@white) ->), labCsPrototype -------------------------------------------------------------------------------- /src/space/rgb.coffee: -------------------------------------------------------------------------------- 1 | # Imports / Shortcuts 2 | # ------------------- 3 | 4 | 5 | O = require "ut1l/create/object" 6 | 7 | 8 | M = require "m4th/matrix" 9 | lu = require "m4th/lu" 10 | 11 | 12 | white = require "../white" 13 | xyY = require "../xyY" 14 | 15 | XYZ = require "../XYZ" 16 | 17 | pow = Math.pow 18 | 19 | # Helpers 20 | # ------- 21 | 22 | 23 | gammaSRgb = (x) -> 24 | if x <= 0.04045 25 | x / 12.92; 26 | else 27 | pow (x + 0.055) / 1.055, 2.4 28 | 29 | gammaSRgbInv = (x) -> 30 | if x <= 0.0031308 31 | x * 12.92 32 | else 33 | 1.055 * (pow x, 1 / 2.4) - 0.055 34 | 35 | 36 | # RGB space constructor 37 | # --------------------- 38 | 39 | lazyInitRgbBase = -> 40 | # create xyz base (luminance is unknown => need to multiply each column by a scalar) 41 | bxyz = M [ 42 | @red.x , @green.x , @blue.x 43 | @red.y, @green.y, @blue.y 44 | 1 - @red.x - @red.y, 1 - @green.x - @green.y, 1 - @blue.x - @blue.y 45 | ] 46 | # calculate LU decomposition of xyz base 47 | bxyzLU = lu bxyz 48 | w = M 3, [ @white.X, @white.Y, @white.Z ] 49 | # get the needed scales or the columns of bxyz (sum of the columns of the base must be the white point) 50 | bxyzLU.solve w, w # calculate in place 51 | # scale bxyz to get the wanted XYZ base (sum of columns is white point) 52 | bxyz = bxyz.mult M.diag w.array 53 | @base = bxyz.array 54 | @baseInv = (lu bxyz).getInverse().array 55 | 56 | delete @XYZ 57 | delete @rgb 58 | return 59 | 60 | rgbSpaceConstructor = (@red, @green, @blue, @white, gamma, gammaInv) -> 61 | if typeof gamma == "function" 62 | @gamma = gamma 63 | @gammaInv = gammaInv 64 | else 65 | @g = gamma 66 | @gInv = 1 / gamma 67 | # Lazy definition of following two methods. 68 | @rgb = -> 69 | lazyInitRgbBase.call @ 70 | @rgb.apply @, arguments 71 | @XYZ = -> 72 | lazyInitRgbBase.call @ 73 | @XYZ.apply @, arguments 74 | return 75 | 76 | 77 | 78 | # RGB space prototype 79 | # ------------------- 80 | 81 | rgbSpacePrototype = 82 | 83 | 84 | gamma: (x) -> 85 | pow(x, @g) 86 | 87 | gammaInv: (x) -> 88 | pow(x, @gInv) 89 | 90 | rgb: (XYZ, T = do require "../rgb") -> 91 | a = @baseInv 92 | T.r = @gammaInv a[0] * XYZ.X + a[1] * XYZ.Y + a[2] * XYZ.Z 93 | T.g = @gammaInv a[3] * XYZ.X + a[4] * XYZ.Y + a[5] * XYZ.Z 94 | T.b = @gammaInv a[6] * XYZ.X + a[7] * XYZ.Y + a[8] * XYZ.Z 95 | T 96 | 97 | XYZ: (Rgb, T = XYZ()) -> 98 | a = @base 99 | gr = @gamma Rgb.r 100 | gg = @gamma Rgb.g 101 | gb = @gamma Rgb.b 102 | T.X = a[0] * gr + a[1] * gg + a[2] * gb 103 | T.Y = a[3] * gr + a[4] * gg + a[5] * gb 104 | T.Z = a[6] * gr + a[7] * gg + a[8] * gb 105 | T 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | # Public api 114 | # ---------- 115 | 116 | 117 | 118 | createSpace = O rgbSpaceConstructor, rgbSpacePrototype 119 | 120 | createSpace["Adobe-98"] = createSpace (xyY 0.6400, 0.3300), (xyY 0.2100, 0.7100), (xyY 0.1500, 0.0600), white.D65, 2.2 121 | createSpace["Adobe-RGB"] = createSpace (xyY 0.6250, 0.3400), (xyY 0.2800, 0.5950), (xyY 0.1550, 0.0700), white.D65, 1.8 122 | createSpace["CIE-RGB"] = createSpace (xyY 0.7350, 0.2650), (xyY 0.2740, 0.7170), (xyY 0.1670, 0.0090), white.E , 1 123 | createSpace["ColorMatch"] = createSpace (xyY 0.6300, 0.3400), (xyY 0.2950, 0.6050), (xyY 0.1500, 0.0750), white.D50, 1.8 124 | createSpace["EBU-Monitor"] = createSpace (xyY 0.6314, 0.3391), (xyY 0.2809, 0.5971), (xyY 0.1487, 0.0645), white.D50, 1.9 125 | createSpace["ECI-RGB"] = createSpace (xyY 0.6700, 0.3300), (xyY 0.2100, 0.7100), (xyY 0.1400, 0.0800), white.D50, 1.8 126 | createSpace["HDTV"] = createSpace (xyY 0.6400, 0.3300), (xyY 0.2900, 0.6000), (xyY 0.1500, 0.0600), white.D65, 2.2 127 | createSpace["Kodak-DC"] = createSpace (xyY 0.6492, 0.3314), (xyY 0.3219, 0.5997), (xyY 0.1548, 0.0646), white.D50, 2.22 128 | createSpace["NTSC-53"] = createSpace (xyY 0.6700, 0.3300), (xyY 0.2100, 0.7100), (xyY 0.1400, 0.0800), white.C , 2.2 129 | createSpace["PAL-SECAM"] = createSpace (xyY 0.6400, 0.3300), (xyY 0.2900, 0.6000), (xyY 0.1500, 0.0600), white.D65, 2.2 130 | createSpace["sRGB"] = createSpace (xyY 0.6400, 0.3300), (xyY 0.3000, 0.6000), (xyY 0.1500, 0.0600), white.D65, gammaSRgb, gammaSRgbInv 131 | createSpace["WideGamut"] = createSpace (xyY 0.7347, 0.2653), (xyY 0.1152, 0.8264), (xyY 0.1566, 0.0177), white.D50, 2.2 132 | 133 | 134 | module.exports = createSpace 135 | -------------------------------------------------------------------------------- /src/white.coffee: -------------------------------------------------------------------------------- 1 | # Map of white points in *XYZ* color space. 2 | 3 | # Imports 4 | # ------- 5 | 6 | # *xyY* color constructor. 7 | xyY = require "./xyY" 8 | 9 | 10 | # Helpers 11 | # ------- 12 | 13 | # Create *XYZ* color with luminance *1* from chromaticity. 14 | xy = (x, y) -> do (xyY x, y, 1).XYZ 15 | 16 | 17 | # Public API 18 | # ---------- 19 | 20 | # Export Map of white points. 21 | module.exports = 22 | A: xy 0.44757, 0.40745 23 | B: xy 0.34842, 0.35161 24 | C: xy 0.31006, 0.31616 25 | D50: xy 0.34567, 0.35850 26 | D55: xy 0.33242, 0.34743 27 | D65: xy 0.31271, 0.32902 28 | D75: xy 0.29902, 0.31485 29 | E: xy 1 / 3, 1 / 3 30 | F1: xy 0.31310, 0.33727 31 | F2: xy 0.37208, 0.37529 32 | F3: xy 0.40910, 0.39430 33 | F4: xy 0.44018, 0.40329 34 | F5: xy 0.31379, 0.34531 35 | F6: xy 0.37790, 0.38835 36 | F7: xy 0.31292, 0.32933 37 | F8: xy 0.34588, 0.35875 38 | F9: xy 0.37417, 0.37281 39 | F10: xy 0.34609, 0.35986 40 | F11: xy 0.38052, 0.37713 41 | F12: xy 0.43695, 0.40441 42 | 43 | -------------------------------------------------------------------------------- /src/xyY.coffee: -------------------------------------------------------------------------------- 1 | # XYZ and xyY color space implementation. 2 | 3 | # Imports 4 | # ------- 5 | 6 | O = require "ut1l/create/object" 7 | 8 | # xyY prototype 9 | # ------------- 10 | 11 | xyyPrototype = 12 | 13 | # Convert color from *xyY* to *XYZ* color space. Give optional *XYZ* color `T` to store the result. 14 | XYZ: (T = do require "./XYZ") -> 15 | T.X = @x * @Y / @y 16 | T.Y = @Y 17 | T.Z = (1 - @x - @y) * @Y / @y 18 | T 19 | 20 | # Returns `true` if all components of the color are not `null` and not `undefined`. 21 | isDefined: -> 22 | @x? and @y? and @Y? 23 | 24 | # Returns a human readable string serialization of the color. 25 | toString: -> 26 | "x=#{@x}, y=#{@y}, Y=#{@Y}" 27 | 28 | 29 | 30 | 31 | # Public API 32 | # ---------- 33 | 34 | module.exports = O ((@x, @y, @Y) ->), xyyPrototype -------------------------------------------------------------------------------- /tasks/mdExtract.coffee: -------------------------------------------------------------------------------- 1 | esprima = require "esprima" 2 | 3 | separator = "\n// ---------------------------------------------------------------------------------------------------------------------\n" 4 | 5 | header = (text) -> 6 | pos = Math.floor (separator.length - text.length) / 2 7 | (separator.substring 0, pos) + text + (separator.substring pos + text.length) 8 | 9 | extractVars = (node, vars, declaration) -> 10 | switch node.type 11 | when "Program" 12 | for child in node.body 13 | extractVars child, vars 14 | when "VariableDeclaration" 15 | for dec in node.declarations 16 | extractVars dec, vars 17 | when "VariableDeclarator" 18 | extractVars node.id, vars, true 19 | if node.init? 20 | extractVars node.init, vars 21 | when "MemberExpression" 22 | extractVars node.object, vars 23 | when "Identifier" 24 | vars[node.name] = vars[node.name] == true || declaration == true 25 | when "CallExpression" 26 | extractVars node.callee, vars 27 | for arg in node.arguments 28 | extractVars arg, vars 29 | when "ExpressionStatement" 30 | extractVars node.expression, vars 31 | when "AssignmentExpression" 32 | extractVars node.left, vars 33 | extractVars node.right, vars 34 | when "BinaryExpression" 35 | extractVars node.left, vars 36 | extractVars node.right, vars 37 | when "ForStatement" 38 | extractVars node.init, vars 39 | extractVars node.test, vars 40 | extractVars node.update, vars 41 | extractVars node.body, vars 42 | 43 | 44 | getVars = (ast) -> 45 | vars = {} 46 | extractVars ast, vars 47 | vars 48 | 49 | module.exports = (grunt) -> 50 | 51 | grunt.registerMultiTask "mdExtract", -> 52 | 53 | files = @files.slice() 54 | 55 | 56 | for file in files 57 | 58 | destDir = file.dest 59 | 60 | sfiles = file.src.filter (filepath) -> 61 | # Warn on and remove invalid source files (if nonull was set). 62 | fileExists = grunt.file.exists filepath 63 | if not fileExists 64 | grunt.log.warn "Source file '#{filepath}' not found." 65 | fileExists 66 | 67 | rexp = /```javascript([^`]*)/g 68 | 69 | for sfile in sfiles # iterate source files 70 | 71 | outJs = "module.exports = [\n" 72 | 73 | str = grunt.file.read sfile 74 | 75 | # extract snippets array 76 | snippets = while (myArray = (rexp.exec str)) != null 77 | myArray[1] 78 | 79 | 80 | snippets = snippets.map (snippet, i) -> 81 | snippet = snippet.replace "require", "_require" 82 | 83 | vars = getVars esprima.parse snippet 84 | allVars = Object.keys vars 85 | 86 | undeclared = [] 87 | for v of vars 88 | if not vars[v] 89 | undeclared.push v 90 | 91 | out = "function(#{if allVars.length > 0 then "_" else ""}){" 92 | 93 | # declare undeclared variables 94 | if undeclared.length > 0 95 | out += "var #{undeclared.join ","};" 96 | # initialize variables 97 | if allVars.length > 0 98 | out += "if(_!=null){" 99 | out += (allVars.map (v) -> 100 | "#{v}=_.#{v};").join "" 101 | out += "}" 102 | 103 | out += header " Snippet #{i} " 104 | 105 | out += snippet 106 | 107 | out += separator 108 | out += "return{#{allVars.map (v) -> "#{v}:#{v}"}};}" 109 | 110 | outJs += snippets.join "," 111 | 112 | 113 | outJs += "];\n" 114 | 115 | grunt.file.write "#{destDir}/#{sfile}.js", outJs 116 | -------------------------------------------------------------------------------- /test/browser/jasmine/SpecRunner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner v2.0.0 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine-jsreporter.js: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of the Jasmine JSReporter project from Ivan De Marino. 3 | 4 | Copyright (C) 2011-2014 Ivan De Marino 5 | Copyright (C) 2014 Alex Treppass 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of the nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL IVAN DE MARINO BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | (function (jasmine) { 31 | 32 | if (!jasmine) { 33 | throw new Error("[Jasmine JSReporter] 'Jasmine' library not found"); 34 | } 35 | 36 | // ------------------------------------------------------------------------ 37 | // Jasmine JSReporter for Jasmine 1.x 38 | // ------------------------------------------------------------------------ 39 | 40 | /** 41 | * Calculate elapsed time, in Seconds. 42 | * @param startMs Start time in Milliseconds 43 | * @param finishMs Finish time in Milliseconds 44 | * @return Elapsed time in Seconds */ 45 | function elapsedSec (startMs, finishMs) { 46 | return (finishMs - startMs) / 1000; 47 | } 48 | 49 | /** 50 | * Round an amount to the given number of Digits. 51 | * If no number of digits is given, than '2' is assumed. 52 | * @param amount Amount to round 53 | * @param numOfDecDigits Number of Digits to round to. Default value is '2'. 54 | * @return Rounded amount */ 55 | function round (amount, numOfDecDigits) { 56 | numOfDecDigits = numOfDecDigits || 2; 57 | return Math.round(amount * Math.pow(10, numOfDecDigits)) / Math.pow(10, numOfDecDigits); 58 | } 59 | 60 | /** 61 | * Create a new array which contains only the failed items. 62 | * @param items Items which will be filtered 63 | * @returns {Array} of failed items */ 64 | function failures (items) { 65 | var fs = [], i, v; 66 | for (i = 0; i < items.length; i += 1) { 67 | v = items[i]; 68 | if (!v.passed_) { 69 | fs.push(v); 70 | } 71 | } 72 | return fs; 73 | } 74 | 75 | /** 76 | * Collect information about a Suite, recursively, and return a JSON result. 77 | * @param suite The Jasmine Suite to get data from 78 | */ 79 | function getSuiteData (suite) { 80 | var suiteData = { 81 | description : suite.description, 82 | durationSec : 0, 83 | specs: [], 84 | suites: [], 85 | passed: true 86 | }, 87 | specs = suite.specs(), 88 | suites = suite.suites(), 89 | i, ilen; 90 | 91 | // Loop over all the Suite's Specs 92 | for (i = 0, ilen = specs.length; i < ilen; ++i) { 93 | suiteData.specs[i] = { 94 | description : specs[i].description, 95 | durationSec : specs[i].durationSec, 96 | passed : specs[i].results().passedCount === specs[i].results().totalCount, 97 | skipped : specs[i].results().skipped, 98 | passedCount : specs[i].results().passedCount, 99 | failedCount : specs[i].results().failedCount, 100 | totalCount : specs[i].results().totalCount, 101 | failures: failures(specs[i].results().getItems()) 102 | }; 103 | suiteData.passed = !suiteData.specs[i].passed ? false : suiteData.passed; 104 | suiteData.durationSec += suiteData.specs[i].durationSec; 105 | } 106 | 107 | // Loop over all the Suite's sub-Suites 108 | for (i = 0, ilen = suites.length; i < ilen; ++i) { 109 | suiteData.suites[i] = getSuiteData(suites[i]); //< recursive population 110 | suiteData.passed = !suiteData.suites[i].passed ? false : suiteData.passed; 111 | suiteData.durationSec += suiteData.suites[i].durationSec; 112 | } 113 | 114 | // Rounding duration numbers to 3 decimal digits 115 | suiteData.durationSec = round(suiteData.durationSec, 4); 116 | 117 | return suiteData; 118 | } 119 | 120 | var JSReporter = function () { 121 | }; 122 | 123 | JSReporter.prototype = { 124 | reportRunnerStarting: function (runner) { 125 | // Nothing to do 126 | }, 127 | 128 | reportSpecStarting: function (spec) { 129 | // Start timing this spec 130 | spec.startedAt = new Date(); 131 | }, 132 | 133 | reportSpecResults: function (spec) { 134 | // Finish timing this spec and calculate duration/delta (in sec) 135 | spec.finishedAt = new Date(); 136 | // If the spec was skipped, reportSpecStarting is never called and spec.startedAt is undefined 137 | spec.durationSec = spec.startedAt ? elapsedSec(spec.startedAt.getTime(), spec.finishedAt.getTime()) : 0; 138 | }, 139 | 140 | reportSuiteResults: function (suite) { 141 | // Nothing to do 142 | }, 143 | 144 | reportRunnerResults: function (runner) { 145 | var suites = runner.suites(), 146 | i, j, ilen; 147 | 148 | // Attach results to the "jasmine" object to make those results easy to scrap/find 149 | jasmine.runnerResults = { 150 | suites: [], 151 | durationSec : 0, 152 | passed : true 153 | }; 154 | 155 | // Loop over all the Suites 156 | for (i = 0, ilen = suites.length, j = 0; i < ilen; ++i) { 157 | if (suites[i].parentSuite === null) { 158 | jasmine.runnerResults.suites[j] = getSuiteData(suites[i]); 159 | // If 1 suite fails, the whole runner fails 160 | jasmine.runnerResults.passed = !jasmine.runnerResults.suites[j].passed ? false : jasmine.runnerResults.passed; 161 | // Add up all the durations 162 | jasmine.runnerResults.durationSec += jasmine.runnerResults.suites[j].durationSec; 163 | j++; 164 | } 165 | } 166 | 167 | // Decorate the 'jasmine' object with getters 168 | jasmine.getJSReport = function () { 169 | if (jasmine.runnerResults) { 170 | return jasmine.runnerResults; 171 | } 172 | return null; 173 | }; 174 | jasmine.getJSReportAsString = function () { 175 | return JSON.stringify(jasmine.getJSReport()); 176 | }; 177 | } 178 | }; 179 | 180 | // export public 181 | jasmine.JSReporter = JSReporter; 182 | 183 | 184 | // ------------------------------------------------------------------------ 185 | // Jasmine JSReporter for Jasmine 2.0 186 | // ------------------------------------------------------------------------ 187 | 188 | /* 189 | Simple timer implementation 190 | */ 191 | var Timer = function () {}; 192 | 193 | Timer.prototype.start = function () { 194 | this.startTime = new Date().getTime(); 195 | return this; 196 | }; 197 | 198 | Timer.prototype.elapsed = function () { 199 | if (this.startTime == null) { 200 | return -1; 201 | } 202 | return new Date().getTime() - this.startTime; 203 | }; 204 | 205 | /* 206 | Utility methods 207 | */ 208 | var _extend = function (obj1, obj2) { 209 | for (var prop in obj2) { 210 | obj1[prop] = obj2[prop]; 211 | } 212 | return obj1; 213 | }; 214 | var _clone = function (obj) { 215 | if (obj !== Object(obj)) { 216 | return obj; 217 | } 218 | return _extend({}, obj); 219 | }; 220 | 221 | jasmine.JSReporter2 = function () { 222 | this.specs = {}; 223 | this.suites = {}; 224 | this.rootSuites = []; 225 | this.suiteStack = []; 226 | 227 | // export methods under jasmine namespace 228 | jasmine.getJSReport = this.getJSReport; 229 | jasmine.getJSReportAsString = this.getJSReportAsString; 230 | }; 231 | 232 | var JSR = jasmine.JSReporter2.prototype; 233 | 234 | // Reporter API methods 235 | // -------------------- 236 | 237 | JSR.suiteStarted = function (suite) { 238 | suite = this._cacheSuite(suite); 239 | // build up suite tree as we go 240 | suite.specs = []; 241 | suite.suites = []; 242 | suite.passed = true; 243 | suite.parentId = this.suiteStack.slice(this.suiteStack.length -1)[0]; 244 | if (suite.parentId) { 245 | this.suites[suite.parentId].suites.push(suite); 246 | } else { 247 | this.rootSuites.push(suite.id); 248 | } 249 | this.suiteStack.push(suite.id); 250 | suite.timer = new Timer().start(); 251 | }; 252 | 253 | JSR.suiteDone = function (suite) { 254 | suite = this._cacheSuite(suite); 255 | suite.duration = suite.timer.elapsed(); 256 | suite.durationSec = suite.duration / 1000; 257 | this.suiteStack.pop(); 258 | 259 | // maintain parent suite state 260 | var parent = this.suites[suite.parentId]; 261 | if (parent) { 262 | parent.passed = parent.passed && suite.passed; 263 | } 264 | 265 | // keep report representation clean 266 | delete suite.timer; 267 | delete suite.id; 268 | delete suite.parentId; 269 | delete suite.fullName; 270 | }; 271 | 272 | JSR.specStarted = function (spec) { 273 | spec = this._cacheSpec(spec); 274 | spec.timer = new Timer().start(); 275 | // build up suites->spec tree as we go 276 | spec.suiteId = this.suiteStack.slice(this.suiteStack.length -1)[0]; 277 | this.suites[spec.suiteId].specs.push(spec); 278 | }; 279 | 280 | JSR.specDone = function (spec) { 281 | spec = this._cacheSpec(spec); 282 | 283 | spec.duration = spec.timer.elapsed(); 284 | spec.durationSec = spec.duration / 1000; 285 | 286 | spec.skipped = spec.status === 'pending'; 287 | spec.passed = spec.skipped || spec.status === 'passed'; 288 | 289 | // totalCount and passedCount will be populated if/when jasmine#575 gets accepted 290 | spec.totalCount = spec.totalExpectations || 0; 291 | spec.passedCount = spec.passedExpectations ? spec.passedExpectations.length : 0; 292 | 293 | spec.failedCount = spec.failedExpectations.length; 294 | spec.failures = []; 295 | 296 | for (var i = 0, j = spec.failedExpectations.length; i < j; i++) { 297 | var fail = spec.failedExpectations[i]; 298 | spec.failures.push({ 299 | type: 'expect', 300 | expected: fail.expected, 301 | passed: false, 302 | message: fail.message, 303 | matcherName: fail.matcherName, 304 | trace: { 305 | stack: fail.stack 306 | } 307 | }); 308 | } 309 | 310 | // maintain parent suite state 311 | var parent = this.suites[spec.suiteId]; 312 | if (spec.failed) { 313 | parent.failingSpecs.push(spec); 314 | } 315 | parent.passed = parent.passed && spec.passed; 316 | 317 | // keep report representation clean 318 | delete spec.timer; 319 | delete spec.totalExpectations; 320 | delete spec.passedExpectations; 321 | delete spec.suiteId; 322 | delete spec.fullName; 323 | delete spec.id; 324 | delete spec.status; 325 | delete spec.failedExpectations; 326 | }; 327 | 328 | JSR.jasmineDone = function () { 329 | this._buildReport(); 330 | }; 331 | 332 | JSR.getJSReport = function () { 333 | if (jasmine.jsReport) { 334 | return jasmine.jsReport; 335 | } 336 | }; 337 | 338 | JSR.getJSReportAsString = function () { 339 | if (jasmine.jsReport) { 340 | return JSON.stringify(jasmine.jsReport); 341 | } 342 | }; 343 | 344 | // Private methods 345 | // --------------- 346 | 347 | JSR._haveSpec = function (spec) { 348 | return this.specs[spec.id] != null; 349 | }; 350 | 351 | JSR._cacheSpec = function (spec) { 352 | var existing = this.specs[spec.id]; 353 | if (existing == null) { 354 | existing = this.specs[spec.id] = _clone(spec); 355 | } else { 356 | _extend(existing, spec); 357 | } 358 | return existing; 359 | }; 360 | 361 | JSR._haveSuite = function (suite) { 362 | return this.suites[suite.id] != null; 363 | }; 364 | 365 | JSR._cacheSuite = function (suite) { 366 | var existing = this.suites[suite.id]; 367 | if (existing == null) { 368 | existing = this.suites[suite.id] = _clone(suite); 369 | } else { 370 | _extend(existing, suite); 371 | } 372 | return existing; 373 | }; 374 | 375 | // workaround for sauce labs bug. 376 | // TODO: remove after they fixed the bug 377 | function thereCanBeOnlyOne(suite) { 378 | if (suite.passed) { // get first passing test 379 | if (suite.specs.length > 0) { 380 | suite.specs = [suite.specs[0]]; 381 | suite.suites = []; 382 | return true; 383 | } else { 384 | thereCanBeOnlyOne(suite.suites[0]); 385 | suite.suites = [suite.suites[0]]; 386 | } 387 | } else { 388 | var failedSuites = []; 389 | for (var i = 0; i < suite.suites.length; i += 1) { 390 | var s = suite.suites[i]; 391 | if (!s.passed) { 392 | failedSuites.push(s); 393 | thereCanBeOnlyOne(s); 394 | } 395 | } 396 | suite.suites = failedSuites; 397 | var failedSpecs = []; 398 | for (i = 0; i < suite.specs.length; i += 1) { 399 | var spec = suite.specs[i]; 400 | if (!spec.passed) { 401 | failedSpecs.push(spec); 402 | } 403 | } 404 | suite.specs = failedSpecs; 405 | } 406 | } 407 | 408 | // workaround for sauce labs bug. 409 | // TODO: remove after they fixed the bug 410 | function countPassed(suite) { 411 | for (var i = 0; i < suite.specs.length; i += 1) { 412 | var spec = suite.specs[i]; 413 | if (spec.passed) { 414 | if (spec.passedCount === 0) { 415 | spec.passedCount = 1; 416 | } 417 | 418 | if (spec.totalCount === 0) { 419 | spec.totalCount = 1; 420 | } 421 | } 422 | } 423 | for (i = 0; i < suite.suites.length; i += 1) { 424 | countPassed(suite.suites[i]); 425 | } 426 | } 427 | 428 | JSR._buildReport = function () { 429 | var overallDuration = 0; 430 | var overallPassed = true; 431 | var overallSuites = []; 432 | 433 | for (var i = 0, j = this.rootSuites.length; i < j; i++) { 434 | var suite = this.suites[this.rootSuites[i]]; 435 | thereCanBeOnlyOne(suite); 436 | countPassed(suite); 437 | overallDuration += suite.duration; 438 | overallPassed = overallPassed && suite.passed; 439 | overallSuites.push(suite); 440 | } 441 | 442 | jasmine.jsReport = { 443 | passed: overallPassed, 444 | durationSec: overallDuration / 1000, 445 | suites: overallSuites 446 | }; 447 | }; 448 | 449 | })(jasmine); -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine/boot.js: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = { 36 | describe: function(description, specDefinitions) { 37 | return env.describe(description, specDefinitions); 38 | }, 39 | 40 | xdescribe: function(description, specDefinitions) { 41 | return env.xdescribe(description, specDefinitions); 42 | }, 43 | 44 | it: function(desc, func) { 45 | return env.it(desc, func); 46 | }, 47 | 48 | xit: function(desc, func) { 49 | return env.xit(desc, func); 50 | }, 51 | 52 | beforeEach: function(beforeEachFunction) { 53 | return env.beforeEach(beforeEachFunction); 54 | }, 55 | 56 | afterEach: function(afterEachFunction) { 57 | return env.afterEach(afterEachFunction); 58 | }, 59 | 60 | expect: function(actual) { 61 | return env.expect(actual); 62 | }, 63 | 64 | pending: function() { 65 | return env.pending(); 66 | }, 67 | 68 | spyOn: function(obj, methodName) { 69 | return env.spyOn(obj, methodName); 70 | }, 71 | 72 | jsApiReporter: new jasmine.JsApiReporter({ 73 | timer: new jasmine.Timer() 74 | }) 75 | }; 76 | 77 | /** 78 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 79 | */ 80 | if (typeof window == "undefined" && typeof exports == "object") { 81 | extend(exports, jasmineInterface); 82 | } else { 83 | extend(window, jasmineInterface); 84 | } 85 | 86 | /** 87 | * Expose the interface for adding custom equality testers. 88 | */ 89 | jasmine.addCustomEqualityTester = function(tester) { 90 | env.addCustomEqualityTester(tester); 91 | }; 92 | 93 | /** 94 | * Expose the interface for adding custom expectation matchers 95 | */ 96 | jasmine.addMatchers = function(matchers) { 97 | return env.addMatchers(matchers); 98 | }; 99 | 100 | /** 101 | * Expose the mock interface for the JavaScript timeout functions 102 | */ 103 | jasmine.clock = function() { 104 | return env.clock; 105 | }; 106 | 107 | /** 108 | * ## Runner Parameters 109 | * 110 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 111 | */ 112 | 113 | var queryString = new jasmine.QueryString({ 114 | getWindowLocation: function() { return window.location; } 115 | }); 116 | 117 | var catchingExceptions = queryString.getParam("catch"); 118 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 119 | 120 | /** 121 | * ## Reporters 122 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 123 | */ 124 | var htmlReporter = new jasmine.HtmlReporter({ 125 | env: env, 126 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); }, 127 | getContainer: function() { return document.body; }, 128 | createElement: function(type) { return document.createElement(type); }, 129 | createTextNode: function(child) { return document.createTextNode(child); }, 130 | timer: new jasmine.Timer() 131 | }); 132 | 133 | /** 134 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 135 | */ 136 | env.addReporter(jasmineInterface.jsApiReporter); 137 | env.addReporter(htmlReporter); 138 | 139 | /** 140 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 141 | */ 142 | var specFilter = new jasmine.HtmlSpecFilter({ 143 | filterString: function() { return queryString.getParam("spec"); } 144 | }); 145 | 146 | env.specFilter = function(spec) { 147 | return specFilter.matches(spec.getFullName()); 148 | }; 149 | 150 | /** 151 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack. 152 | */ 153 | window.setTimeout = window.setTimeout; 154 | window.setInterval = window.setInterval; 155 | window.clearTimeout = window.clearTimeout; 156 | window.clearInterval = window.clearInterval; 157 | 158 | /** 159 | * ## Execution 160 | * 161 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded. 162 | */ 163 | var currentWindowOnload = window.onload; 164 | 165 | window.onload = function() { 166 | if (currentWindowOnload) { 167 | currentWindowOnload(); 168 | } 169 | htmlReporter.initialize(); 170 | env.execute(); 171 | }; 172 | 173 | /** 174 | * Helper function for readability above. 175 | */ 176 | function extend(destination, source) { 177 | for (var property in source) destination[property] = source[property]; 178 | return destination; 179 | } 180 | 181 | }()); 182 | -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2013 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | function getJasmineRequireObj() { 24 | if (typeof module !== "undefined" && module.exports) { 25 | return exports; 26 | } else { 27 | window.jasmineRequire = window.jasmineRequire || {}; 28 | return window.jasmineRequire; 29 | } 30 | } 31 | 32 | getJasmineRequireObj().console = function(jRequire, j$) { 33 | j$.ConsoleReporter = jRequire.ConsoleReporter(); 34 | }; 35 | 36 | getJasmineRequireObj().ConsoleReporter = function() { 37 | 38 | var noopTimer = { 39 | start: function(){}, 40 | elapsed: function(){ return 0; } 41 | }; 42 | 43 | function ConsoleReporter(options) { 44 | var print = options.print, 45 | showColors = options.showColors || false, 46 | onComplete = options.onComplete || function() {}, 47 | timer = options.timer || noopTimer, 48 | specCount, 49 | failureCount, 50 | failedSpecs = [], 51 | pendingCount, 52 | ansi = { 53 | green: '\x1B[32m', 54 | red: '\x1B[31m', 55 | yellow: '\x1B[33m', 56 | none: '\x1B[0m' 57 | }; 58 | 59 | this.jasmineStarted = function() { 60 | specCount = 0; 61 | failureCount = 0; 62 | pendingCount = 0; 63 | print("Started"); 64 | printNewline(); 65 | timer.start(); 66 | }; 67 | 68 | this.jasmineDone = function() { 69 | printNewline(); 70 | for (var i = 0; i < failedSpecs.length; i++) { 71 | specFailureDetails(failedSpecs[i]); 72 | } 73 | 74 | printNewline(); 75 | var specCounts = specCount + " " + plural("spec", specCount) + ", " + 76 | failureCount + " " + plural("failure", failureCount); 77 | 78 | if (pendingCount) { 79 | specCounts += ", " + pendingCount + " pending " + plural("spec", pendingCount); 80 | } 81 | 82 | print(specCounts); 83 | 84 | printNewline(); 85 | var seconds = timer.elapsed() / 1000; 86 | print("Finished in " + seconds + " " + plural("second", seconds)); 87 | 88 | printNewline(); 89 | 90 | onComplete(failureCount === 0); 91 | }; 92 | 93 | this.specDone = function(result) { 94 | specCount++; 95 | 96 | if (result.status == "pending") { 97 | pendingCount++; 98 | print(colored("yellow", "*")); 99 | return; 100 | } 101 | 102 | if (result.status == "passed") { 103 | print(colored("green", '.')); 104 | return; 105 | } 106 | 107 | if (result.status == "failed") { 108 | failureCount++; 109 | failedSpecs.push(result); 110 | print(colored("red", 'F')); 111 | } 112 | }; 113 | 114 | return this; 115 | 116 | function printNewline() { 117 | print("\n"); 118 | } 119 | 120 | function colored(color, str) { 121 | return showColors ? (ansi[color] + str + ansi.none) : str; 122 | } 123 | 124 | function plural(str, count) { 125 | return count == 1 ? str : str + "s"; 126 | } 127 | 128 | function repeat(thing, times) { 129 | var arr = []; 130 | for (var i = 0; i < times; i++) { 131 | arr.push(thing); 132 | } 133 | return arr; 134 | } 135 | 136 | function indent(str, spaces) { 137 | var lines = (str || '').split("\n"); 138 | var newArr = []; 139 | for (var i = 0; i < lines.length; i++) { 140 | newArr.push(repeat(" ", spaces).join("") + lines[i]); 141 | } 142 | return newArr.join("\n"); 143 | } 144 | 145 | function specFailureDetails(result) { 146 | printNewline(); 147 | print(result.fullName); 148 | 149 | for (var i = 0; i < result.failedExpectations.length; i++) { 150 | var failedExpectation = result.failedExpectations[i]; 151 | printNewline(); 152 | print(indent(failedExpectation.stack, 2)); 153 | } 154 | 155 | printNewline(); 156 | } 157 | } 158 | 159 | return ConsoleReporter; 160 | }; 161 | -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2013 Pivotal Labs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | jasmineRequire.html = function(j$) { 24 | j$.ResultsNode = jasmineRequire.ResultsNode(); 25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); 26 | j$.QueryString = jasmineRequire.QueryString(); 27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); 28 | }; 29 | 30 | jasmineRequire.HtmlReporter = function(j$) { 31 | 32 | var noopTimer = { 33 | start: function() {}, 34 | elapsed: function() { return 0; } 35 | }; 36 | 37 | function HtmlReporter(options) { 38 | var env = options.env || {}, 39 | getContainer = options.getContainer, 40 | createElement = options.createElement, 41 | createTextNode = options.createTextNode, 42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, 43 | timer = options.timer || noopTimer, 44 | results = [], 45 | specsExecuted = 0, 46 | failureCount = 0, 47 | pendingSpecCount = 0, 48 | htmlReporterMain, 49 | symbols; 50 | 51 | this.initialize = function() { 52 | htmlReporterMain = createDom("div", {className: "html-reporter"}, 53 | createDom("div", {className: "banner"}, 54 | createDom("span", {className: "title"}, "Jasmine"), 55 | createDom("span", {className: "version"}, j$.version) 56 | ), 57 | createDom("ul", {className: "symbol-summary"}), 58 | createDom("div", {className: "alert"}), 59 | createDom("div", {className: "results"}, 60 | createDom("div", {className: "failures"}) 61 | ) 62 | ); 63 | getContainer().appendChild(htmlReporterMain); 64 | 65 | symbols = find(".symbol-summary"); 66 | }; 67 | 68 | var totalSpecsDefined; 69 | this.jasmineStarted = function(options) { 70 | totalSpecsDefined = options.totalSpecsDefined || 0; 71 | timer.start(); 72 | }; 73 | 74 | var summary = createDom("div", {className: "summary"}); 75 | 76 | var topResults = new j$.ResultsNode({}, "", null), 77 | currentParent = topResults; 78 | 79 | this.suiteStarted = function(result) { 80 | currentParent.addChild(result, "suite"); 81 | currentParent = currentParent.last(); 82 | }; 83 | 84 | this.suiteDone = function(result) { 85 | if (currentParent == topResults) { 86 | return; 87 | } 88 | 89 | currentParent = currentParent.parent; 90 | }; 91 | 92 | this.specStarted = function(result) { 93 | currentParent.addChild(result, "spec"); 94 | }; 95 | 96 | var failures = []; 97 | this.specDone = function(result) { 98 | if (result.status != "disabled") { 99 | specsExecuted++; 100 | } 101 | 102 | symbols.appendChild(createDom("li", { 103 | className: result.status, 104 | id: "spec_" + result.id, 105 | title: result.fullName 106 | } 107 | )); 108 | 109 | if (result.status == "failed") { 110 | failureCount++; 111 | 112 | var failure = 113 | createDom("div", {className: "spec-detail failed"}, 114 | createDom("div", {className: "description"}, 115 | createDom("a", {title: result.fullName, href: specHref(result)}, result.fullName) 116 | ), 117 | createDom("div", {className: "messages"}) 118 | ); 119 | var messages = failure.childNodes[1]; 120 | 121 | for (var i = 0; i < result.failedExpectations.length; i++) { 122 | var expectation = result.failedExpectations[i]; 123 | messages.appendChild(createDom("div", {className: "result-message"}, expectation.message)); 124 | messages.appendChild(createDom("div", {className: "stack-trace"}, expectation.stack)); 125 | } 126 | 127 | failures.push(failure); 128 | } 129 | 130 | if (result.status == "pending") { 131 | pendingSpecCount++; 132 | } 133 | }; 134 | 135 | this.jasmineDone = function() { 136 | var banner = find(".banner"); 137 | banner.appendChild(createDom("span", {className: "duration"}, "finished in " + timer.elapsed() / 1000 + "s")); 138 | 139 | var alert = find(".alert"); 140 | 141 | alert.appendChild(createDom("span", { className: "exceptions" }, 142 | createDom("label", { className: "label", 'for': "raise-exceptions" }, "raise exceptions"), 143 | createDom("input", { 144 | className: "raise", 145 | id: "raise-exceptions", 146 | type: "checkbox" 147 | }) 148 | )); 149 | var checkbox = find("input"); 150 | 151 | checkbox.checked = !env.catchingExceptions(); 152 | checkbox.onclick = onRaiseExceptionsClick; 153 | 154 | if (specsExecuted < totalSpecsDefined) { 155 | var skippedMessage = "Ran " + specsExecuted + " of " + totalSpecsDefined + " specs - run all"; 156 | alert.appendChild( 157 | createDom("span", {className: "bar skipped"}, 158 | createDom("a", {href: "?", title: "Run all specs"}, skippedMessage) 159 | ) 160 | ); 161 | } 162 | var statusBarMessage = "" + pluralize("spec", specsExecuted) + ", " + pluralize("failure", failureCount); 163 | if (pendingSpecCount) { statusBarMessage += ", " + pluralize("pending spec", pendingSpecCount); } 164 | 165 | var statusBarClassName = "bar " + ((failureCount > 0) ? "failed" : "passed"); 166 | alert.appendChild(createDom("span", {className: statusBarClassName}, statusBarMessage)); 167 | 168 | var results = find(".results"); 169 | results.appendChild(summary); 170 | 171 | summaryList(topResults, summary); 172 | 173 | function summaryList(resultsTree, domParent) { 174 | var specListNode; 175 | for (var i = 0; i < resultsTree.children.length; i++) { 176 | var resultNode = resultsTree.children[i]; 177 | if (resultNode.type == "suite") { 178 | var suiteListNode = createDom("ul", {className: "suite", id: "suite-" + resultNode.result.id}, 179 | createDom("li", {className: "suite-detail"}, 180 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) 181 | ) 182 | ); 183 | 184 | summaryList(resultNode, suiteListNode); 185 | domParent.appendChild(suiteListNode); 186 | } 187 | if (resultNode.type == "spec") { 188 | if (domParent.getAttribute("class") != "specs") { 189 | specListNode = createDom("ul", {className: "specs"}); 190 | domParent.appendChild(specListNode); 191 | } 192 | specListNode.appendChild( 193 | createDom("li", { 194 | className: resultNode.result.status, 195 | id: "spec-" + resultNode.result.id 196 | }, 197 | createDom("a", {href: specHref(resultNode.result)}, resultNode.result.description) 198 | ) 199 | ); 200 | } 201 | } 202 | } 203 | 204 | if (failures.length) { 205 | alert.appendChild( 206 | createDom('span', {className: "menu bar spec-list"}, 207 | createDom("span", {}, "Spec List | "), 208 | createDom('a', {className: "failures-menu", href: "#"}, "Failures"))); 209 | alert.appendChild( 210 | createDom('span', {className: "menu bar failure-list"}, 211 | createDom('a', {className: "spec-list-menu", href: "#"}, "Spec List"), 212 | createDom("span", {}, " | Failures "))); 213 | 214 | find(".failures-menu").onclick = function() { 215 | setMenuModeTo('failure-list'); 216 | }; 217 | find(".spec-list-menu").onclick = function() { 218 | setMenuModeTo('spec-list'); 219 | }; 220 | 221 | setMenuModeTo('failure-list'); 222 | 223 | var failureNode = find(".failures"); 224 | for (var i = 0; i < failures.length; i++) { 225 | failureNode.appendChild(failures[i]); 226 | } 227 | } 228 | }; 229 | 230 | return this; 231 | 232 | function find(selector) { 233 | var container = getContainer(), cls, elem, i; 234 | if (typeof container.querySelector !== "function") { // IE6 compatibility 235 | if (selector.charAt(0) === ".") { 236 | cls = selector.substring(1); 237 | elem = container.getElementsByTagName("*"); 238 | for (i = 0; i < elem.length; i += 1) { 239 | if ((" " + elem[i].className + " ").indexOf(" " + cls + " ") !== -1) { 240 | return elem[i]; 241 | } 242 | } 243 | } else { 244 | return container.getElementsByTagName(selector); 245 | } 246 | } else { // IE7+ 247 | return container.querySelector(selector); 248 | } 249 | } 250 | 251 | function createDom(type, attrs, childrenVarArgs) { 252 | var el = createElement(type); 253 | 254 | for (var i = 2; i < arguments.length; i++) { 255 | var child = arguments[i]; 256 | 257 | if (typeof child === 'string') { 258 | el.appendChild(createTextNode(child)); 259 | } else { 260 | if (child) { 261 | el.appendChild(child); 262 | } 263 | } 264 | } 265 | 266 | for (var attr in attrs) { 267 | if (attr == "className") { 268 | el[attr] = attrs[attr]; 269 | } else { 270 | el.setAttribute(attr, attrs[attr]); 271 | } 272 | } 273 | 274 | return el; 275 | } 276 | 277 | function pluralize(singular, count) { 278 | var word = (count == 1 ? singular : singular + "s"); 279 | 280 | return "" + count + " " + word; 281 | } 282 | 283 | function specHref(result) { 284 | return "?spec=" + encodeURIComponent(result.fullName); 285 | } 286 | 287 | function setMenuModeTo(mode) { 288 | htmlReporterMain.setAttribute("class", "html-reporter " + mode); 289 | } 290 | } 291 | 292 | return HtmlReporter; 293 | }; 294 | 295 | jasmineRequire.HtmlSpecFilter = function() { 296 | function HtmlSpecFilter(options) { 297 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 298 | var filterPattern = new RegExp(filterString); 299 | 300 | this.matches = function(specName) { 301 | return filterPattern.test(specName); 302 | }; 303 | } 304 | 305 | return HtmlSpecFilter; 306 | }; 307 | 308 | jasmineRequire.ResultsNode = function() { 309 | function ResultsNode(result, type, parent) { 310 | this.result = result; 311 | this.type = type; 312 | this.parent = parent; 313 | 314 | this.children = []; 315 | 316 | this.addChild = function(result, type) { 317 | this.children.push(new ResultsNode(result, type, this)); 318 | }; 319 | 320 | this.last = function() { 321 | return this.children[this.children.length - 1]; 322 | }; 323 | } 324 | 325 | return ResultsNode; 326 | }; 327 | 328 | jasmineRequire.QueryString = function() { 329 | function QueryString(options) { 330 | 331 | this.setParam = function(key, value) { 332 | var paramMap = queryStringToParamMap(); 333 | paramMap[key] = value; 334 | options.getWindowLocation().search = toQueryString(paramMap); 335 | }; 336 | 337 | this.getParam = function(key) { 338 | return queryStringToParamMap()[key]; 339 | }; 340 | 341 | return this; 342 | 343 | function toQueryString(paramMap) { 344 | var qStrPairs = []; 345 | for (var prop in paramMap) { 346 | qStrPairs.push(encodeURIComponent(prop) + "=" + encodeURIComponent(paramMap[prop])); 347 | } 348 | return "?" + qStrPairs.join('&'); 349 | } 350 | 351 | function queryStringToParamMap() { 352 | var paramStr = options.getWindowLocation().search.substring(1), 353 | params = [], 354 | paramMap = {}; 355 | 356 | if (paramStr.length > 0) { 357 | params = paramStr.split('&'); 358 | for (var i = 0; i < params.length; i++) { 359 | var p = params[i].split('='); 360 | var value = decodeURIComponent(p[1]); 361 | if (value === "true" || value === "false") { 362 | value = JSON.parse(value); 363 | } 364 | paramMap[decodeURIComponent(p[0])] = value; 365 | } 366 | } 367 | 368 | return paramMap; 369 | } 370 | 371 | } 372 | 373 | return QueryString; 374 | }; 375 | -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | .html-reporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | .html-reporter a { text-decoration: none; } 5 | .html-reporter a:hover { text-decoration: underline; } 6 | .html-reporter p, .html-reporter h1, .html-reporter h2, .html-reporter h3, .html-reporter h4, .html-reporter h5, .html-reporter h6 { margin: 0; line-height: 14px; } 7 | .html-reporter .banner, .html-reporter .symbol-summary, .html-reporter .summary, .html-reporter .result-message, .html-reporter .spec .description, .html-reporter .spec-detail .description, .html-reporter .alert .bar, .html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; } 8 | .html-reporter .banner .version { margin-left: 14px; } 9 | .html-reporter #jasmine_content { position: fixed; right: 100%; } 10 | .html-reporter .version { color: #aaaaaa; } 11 | .html-reporter .banner { margin-top: 14px; } 12 | .html-reporter .duration { color: #aaaaaa; float: right; } 13 | .html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; } 14 | .html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; } 15 | .html-reporter .symbol-summary li.passed { font-size: 14px; } 16 | .html-reporter .symbol-summary li.passed:before { color: #5e7d00; content: "\02022"; } 17 | .html-reporter .symbol-summary li.failed { line-height: 9px; } 18 | .html-reporter .symbol-summary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 19 | .html-reporter .symbol-summary li.disabled { font-size: 14px; } 20 | .html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; } 21 | .html-reporter .symbol-summary li.pending { line-height: 17px; } 22 | .html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; } 23 | .html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 24 | .html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 25 | .html-reporter .bar.failed { background-color: #b03911; } 26 | .html-reporter .bar.passed { background-color: #a6b779; } 27 | .html-reporter .bar.skipped { background-color: #bababa; } 28 | .html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; } 29 | .html-reporter .bar.menu a { color: #333333; } 30 | .html-reporter .bar a { color: white; } 31 | .html-reporter.spec-list .bar.menu.failure-list, .html-reporter.spec-list .results .failures { display: none; } 32 | .html-reporter.failure-list .bar.menu.spec-list, .html-reporter.failure-list .summary { display: none; } 33 | .html-reporter .running-alert { background-color: #666666; } 34 | .html-reporter .results { margin-top: 14px; } 35 | .html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | .html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | .html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | .html-reporter.showDetails .summary { display: none; } 39 | .html-reporter.showDetails #details { display: block; } 40 | .html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | .html-reporter .summary { margin-top: 14px; } 42 | .html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; } 43 | .html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; } 44 | .html-reporter .summary li.passed a { color: #5e7d00; } 45 | .html-reporter .summary li.failed a { color: #b03911; } 46 | .html-reporter .summary li.pending a { color: #ba9d37; } 47 | .html-reporter .description + .suite { margin-top: 0; } 48 | .html-reporter .suite { margin-top: 14px; } 49 | .html-reporter .suite a { color: #333333; } 50 | .html-reporter .failures .spec-detail { margin-bottom: 28px; } 51 | .html-reporter .failures .spec-detail .description { background-color: #b03911; } 52 | .html-reporter .failures .spec-detail .description a { color: white; } 53 | .html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; } 54 | .html-reporter .result-message span.result { display: block; } 55 | .html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 56 | -------------------------------------------------------------------------------- /test/browser/jasmine/lib/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hhelwich/c0lor/ac637c672ad08b17d6b2bbee8edc401e0aabe8f6/test/browser/jasmine/lib/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /test/browser/src/LCh.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "LCh module", -> 5 | 6 | Lab1 = Lab2 = LCh1 = LCh2 = null 7 | 8 | beforeEach -> 9 | 10 | # same color in Lab color space with D50 reference white (http://www.brucelindbloom.com/) 11 | Lab1 = C.Lab 51.8372, -57.4865, -25.7804 12 | Lab2 = C.Lab 1.8066, -7.3832, -2.5470 13 | 14 | # reference values generated here: http://www.brucelindbloom.com/ 15 | toRad = (a) -> 16 | (if a > 180 then a - 360 else a) * Math.PI / 180 17 | LCh1 = C.LCh 51.8372, 63.0026, toRad 204.1543 18 | LCh2 = C.LCh 1.8066, 7.8102, toRad 199.0330 19 | 20 | jasmine.addMatchers require "./matcher" 21 | 22 | 23 | describe "LCh.Lab() function", -> 24 | 25 | it "maps correctly to Lab", -> 26 | 27 | (expect LCh1.Lab()).toAllBeCloseTo Lab1, 0.0001 28 | (expect LCh2.Lab()).toAllBeCloseTo Lab2, 0.0001 29 | 30 | 31 | describe "LCh.toString()", -> 32 | 33 | it "creates informative output", -> 34 | 35 | (expect "#{C.LCh 51.8372, 63.0026, -2.7}").toBe "L=51.8372, C=63.0026, h=-2.7" 36 | -------------------------------------------------------------------------------- /test/browser/src/Lab.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "Lab Color", -> 5 | 6 | Lab1 = Lab2 = LCh1 = LCh2 = null 7 | 8 | beforeEach -> 9 | 10 | # same color in Lab color space with D50 reference white (http://www.brucelindbloom.com/) 11 | Lab1 = C.Lab 51.8372, -57.4865, -25.7804 12 | Lab2 = C.Lab 1.8066, -7.3832, -2.5470 13 | 14 | # reference values generated here: http://www.brucelindbloom.com/ 15 | toRad = (a) -> 16 | (if a > 180 then a - 360 else a) * Math.PI / 180 17 | LCh1 = C.LCh 51.8372, 63.0026, toRad 204.1543 18 | LCh2 = C.LCh 1.8066, 7.8102, toRad 199.0330 19 | 20 | jasmine.addMatchers require "./matcher" 21 | 22 | 23 | describe "Lab.LCh() function", -> 24 | 25 | it "maps correctly to LCh", -> 26 | 27 | (expect Lab1.LCh()).toAllBeCloseTo LCh1, 4 28 | (expect Lab2.LCh()).toAllBeCloseTo LCh2, 4 29 | 30 | 31 | describe "Lab.toString()", -> 32 | 33 | it "creates informative output", -> 34 | 35 | (expect "#{Lab1}").toBe "L=51.8372, a=-57.4865, b=-25.7804" 36 | -------------------------------------------------------------------------------- /test/browser/src/README.md.spec.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | snippets = require "./README.md" 4 | 5 | describe "README.md", -> 6 | 7 | beforeEach -> 8 | jasmine.addMatchers require "./matcher" 9 | 10 | 11 | describe "snippet 3", -> 12 | 13 | it "creates expected colors", -> 14 | # run snippet 15 | _ = snippets[3] C: C 16 | # verify 17 | (expect _.cyan_rgb).toEqual 18 | r: 0 19 | g: 1 20 | b: 1 21 | (expect _.red_RGB).toEqual 22 | R: 255 23 | G: 0 24 | B: 0 25 | (expect _.yellow_RGB).toEqual 26 | R: 255 27 | G: 255 28 | B: 0 29 | 30 | (expect _.magenta_hsv).toEqual 31 | h: 0.8 32 | s: 1 33 | v: 1 34 | 35 | (expect _.green_XYZ).toEqual 36 | X: 0.4 37 | Y: 0.7 38 | Z: 0.2 39 | (expect _.orange_xyY).toEqual 40 | x: 0.5 41 | y: 0.4 42 | Y: 0.3 43 | 44 | (expect _.purple_Lab).toEqual 45 | L: 31 46 | a: 24 47 | b: -22 48 | (expect _.blue_LCh).toEqual 49 | L: 30 50 | C: 56 51 | h: -1 52 | 53 | 54 | describe "snippet 4", -> 55 | 56 | it "converts colors correctly", -> 57 | 58 | # run snippets 3, 4 59 | _ = snippets[4] snippets[3] C: C 60 | 61 | # verify 62 | (expect _.cyan_RGB).toEqual 63 | R: 0 64 | G: 255 65 | B: 255 66 | (expect _.red_hexStr).toBe "FF0000" 67 | (expect _.yellow_rgb).toEqual 68 | r: 1 69 | g: 1 70 | b: 0 71 | 72 | (expect _.magenta_rgb).toEqual 73 | r: 0.8000000000000007 74 | g: 0 75 | b: 1 76 | (expect _.yellow_hsv).toEqual 77 | h: 0.16666666666666666 78 | s: 1 79 | v: 1 80 | 81 | (expect _.green_xyY).toEqual 82 | x: 0.3076923076923077 83 | y: 0.5384615384615384 84 | Y: 0.7 85 | (expect _.orange_XYZ).toEqual 86 | X: 0.37499999999999994 87 | Y: 0.3 88 | Z: 0.07499999999999997 89 | 90 | (expect _.purple_LCh).toEqual 91 | L: 31 92 | C: 32.55764119219941 93 | h: -0.7419472680059175 94 | (expect _.blue_Lab).toAllBeCloseTo 95 | L: 30 96 | a: 30.256929128615827 97 | b: -47.1223751492422 98 | , 13 99 | 100 | 101 | describe "snippet 5", -> 102 | 103 | it "converts color correctly", -> 104 | 105 | rgbColor = C.rgb() 106 | # run snippet 107 | _ = snippets[5] 108 | rgbColor: rgbColor 109 | hsvColor: C.hsv 0.8, 1, 1 110 | 111 | # verify 112 | (expect _.rgbColor).toBe rgbColor # in place? 113 | (expect _.rgbColor).toEqual 114 | r: 0.8000000000000007 115 | g: 0 116 | b: 1 117 | -------------------------------------------------------------------------------- /test/browser/src/RGBInt.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "RGB 24 bit color", -> 5 | 6 | XYZ2 = XYZ3 = XYZ2_s = XYZ3_s = null 7 | rgb1 = rgb2 = rgb3 = rgb4 = null 8 | RGB1 = RGB2 = RGB3 = RGB4 = null 9 | rgbCs1 = rgbCs2 = null 10 | 11 | beforeEach -> 12 | 13 | rgb1 = C.rgb 0, undefined, 0 14 | rgb2 = C.rgb 1, 1, 1 15 | rgb3 = C.rgb 0.5, 0.3, 0.2 16 | rgb4 = C.rgb 1.4, 1.00001, -0.1 17 | 18 | RGB1 = C.RGB 0, undefined, 0 19 | RGB2 = C.RGB 255, 255, 255 20 | RGB3 = C.RGB 128, 77, 51 21 | RGB4 = C.RGB undefined, 255, undefined 22 | 23 | rgbCs1 = C.space.rgb["Adobe-98"] 24 | # Adobe-98 / http://www.brucelindbloom.com 25 | XYZ2 = C.XYZ 0.95047, 1 , 1.08883 26 | XYZ3 = C.XYZ 0.1441 , 0.111282, 0.039618 27 | 28 | rgbCs2 = C.space.rgb.sRGB 29 | # sRGB / http://www.brucelindbloom.com 30 | XYZ2_s = C.XYZ 0.95047 , 1 , 1.08883 31 | XYZ3_s = C.XYZ 0.120444, 0.100287, 0.044327 32 | 33 | jasmine.addMatchers require "./matcher" 34 | 35 | 36 | describe "RGB.rgb()", -> 37 | 38 | it "RGB.rgb() maps correctly from 3 bit rgb", -> 39 | 40 | (expect RGB1.rgb()).toEqual rgb1 41 | (expect RGB2.rgb()).toEqual rgb2 42 | (expect RGB3.rgb()).toAllBeCloseTo rgb3, 2 43 | (expect RGB4.rgb()).toEqual C.rgb undefined, 1, undefined 44 | 45 | it "RGB -> rgb -> RGB = Id", -> 46 | 47 | (expect RGB1.rgb().RGB()).toEqual RGB1 48 | (expect RGB2.rgb().RGB()).toEqual RGB2 49 | (expect RGB3.rgb().RGB()).toEqual RGB3 50 | (expect RGB4.rgb().RGB()).toEqual RGB4 51 | 52 | 53 | describe "RGB.hex()", -> 54 | 55 | it "creates hex string", -> 56 | 57 | (expect RGB1.hex()).toBeUndefined() 58 | (expect RGB2.hex()).toBe "FFFFFF" 59 | (expect RGB3.hex()).toBe "804D33" 60 | (expect RGB4.hex()).toBeUndefined() 61 | 62 | it "parses hex string", -> 63 | 64 | (expect RGB1.hex "FFFFFF").toEqual RGB2 65 | (expect C.RGB().hex "804D33").toEqual RGB3 66 | 67 | 68 | describe "RGB.toString()", -> 69 | 70 | it "creates informative output", -> 71 | 72 | (expect "#{RGB3}").toBe "R=128, G=77, B=51" 73 | 74 | 75 | describe "RGB.isDefined()", -> 76 | 77 | it "is true if all components are defined", -> 78 | 79 | (expect RGB1.isDefined()).toBe false 80 | (expect RGB2.isDefined()).toBe true 81 | (expect RGB3.isDefined()).toBe true 82 | (expect RGB4.isDefined()).toBe false 83 | 84 | 85 | describe "RGB.isValid()", -> 86 | 87 | it "is true if all components are defined and in the desired range", -> 88 | 89 | (expect RGB1.isValid()).toBe false # not defined 90 | (expect RGB2.isValid()).toBe true 91 | (expect RGB3.isValid()).toBe true 92 | (expect RGB4.isValid()).toBe false # not defined 93 | # out of range 94 | (expect (C.RGB -1, 0, 0).isValid()).toBe false 95 | (expect (C.RGB 0, -1, 0).isValid()).toBe false 96 | (expect (C.RGB 0, 0, -1).isValid()).toBe false 97 | (expect (C.RGB 256, 0, 0).isValid()).toBe false 98 | # rational number 99 | (expect (C.RGB 0, 0.1, 0).isValid()).toBe false 100 | -------------------------------------------------------------------------------- /test/browser/src/XYZ.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "XYZ Colorspace module", -> 5 | 6 | XYZ = null 7 | xyY = null 8 | 9 | beforeEach -> 10 | 11 | XYZ = C.XYZ 0.1, 0.2, 0.3 12 | # same color in xyY color space 13 | xyY = C.xyY 1/6, 1/3, 0.2 14 | 15 | jasmine.addMatchers require "./matcher" 16 | 17 | 18 | describe "XYZ constructors", -> 19 | 20 | it "creates a new expected XYZ object", -> 21 | 22 | (expect XYZ.X).toBe 0.1 23 | (expect XYZ.Y).toBe 0.2 24 | (expect XYZ.Z).toBe 0.3 25 | 26 | 27 | describe "XYZ <-> xyY", -> 28 | 29 | it "XYZ maps correctly to xyY", -> 30 | 31 | (expect XYZ.xyY()).toEqual xyY 32 | 33 | it "can map XYZ to xyY in place", -> 34 | 35 | x = C.xyY 0.1, 0.1, 0.1 36 | y = XYZ.xyY x 37 | (expect y).toEqual xyY 38 | (expect y).toBe x 39 | 40 | 41 | describe "XYZ.isDefined()", -> 42 | 43 | it "is true if all components are defined", -> 44 | 45 | (expect C.XYZ().isDefined()).toBe false 46 | (expect (C.XYZ 1).isDefined()).toBe false 47 | (expect (C.XYZ 0.1, 0.2, 0.3).isDefined()).toBe true 48 | 49 | 50 | describe "XYZ.toString()", -> 51 | 52 | it "creates informative output", -> 53 | 54 | (expect "#{XYZ}").toBe "X=0.1, Y=0.2, Z=0.3" 55 | -------------------------------------------------------------------------------- /test/browser/src/console.coffee: -------------------------------------------------------------------------------- 1 | # create console to log test debug output 2 | 3 | charsToReplace = 4 | "&": "&" 5 | "<": "<" 6 | ">": ">" 7 | 8 | replaceChar = (tag) -> 9 | charsToReplace[tag] || tag; 10 | 11 | safe_tags_replace = (str) -> 12 | str.replace /[&<>]/g, replaceChar 13 | 14 | log = 15 | if document? # in browser => log to page 16 | console = document.getElementById "console" 17 | (message) -> 18 | console.innerHTML = console.innerHTML + "* " + (safe_tags_replace message) + "
"; 19 | else if console? # not in browser but console object available? 20 | console.log 21 | else 22 | -> # no console output 23 | 24 | log "Initialized console" 25 | 26 | module.exports = log 27 | -------------------------------------------------------------------------------- /test/browser/src/gamut(node_only).coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | if C.BROWSER 5 | return 6 | 7 | C_gamut = require.call null, "../../src/gamut" 8 | 9 | describe "Gamut Mapping module", -> 10 | 11 | labCs = rgbCs = gamut = null 12 | 13 | beforeEach -> 14 | 15 | labCs = C.space.lab C.white.D50 16 | rgbCs = C.space.rgb["Adobe-98"] 17 | gamut = C_gamut rgbCs, labCs 18 | 19 | jasmine.addMatchers require "./matcher" 20 | 21 | 22 | describe "LChMaxC() function", -> 23 | 24 | it "maximizes chroma in rgb / lab space combination", -> 25 | 26 | # minimal luminance 27 | lch1 = C.LCh 0, null, 1 28 | rgb1 = gamut.LChMaxC lch1 29 | (expect lch1).toEqual C.LCh 0, 0, 1 30 | (expect rgb1).toEqual C.rgb 0, 0, 0 31 | 32 | # impossible luminance for current color space combination 33 | lch1 = C.LCh 100, null, 1 34 | rgb1 = gamut.LChMaxC lch1 35 | (expect lch1).toEqual C.LCh 100, null, 1 36 | (expect rgb1).toEqual null 37 | 38 | # check some valid combinations 39 | for L in [10..90] by 40 40 | for h in [0..360] by 30 41 | h *= Math.PI / 180 42 | lch1 = C.LCh L, null, h 43 | rgb1 = gamut.LChMaxC lch1 44 | 45 | # L, h unchanged; C set to valid value 46 | (expect lch1.L).toBe L 47 | (expect lch1.h).toBe h 48 | (expect lch1.C).not.toBe null 49 | (expect 0 <= lch1.C).toBe true 50 | # returned rgb is valid 51 | (expect rgb1.isValid()).toBe true 52 | # rgb color equals set LCh color 53 | lab1 = labCs.Lab rgbCs.XYZ rgb1 54 | (expect lab1).toAllBeCloseTo lch1.Lab(), 12 55 | 56 | # check chroma is maximal 57 | lch1_ = lab1.LCh() 58 | lch1_.C += 0.0001 59 | rgbOut = rgbCs.rgb labCs.XYZ lch1_.Lab() 60 | (expect rgbOut.isValid()).toBe false 61 | -------------------------------------------------------------------------------- /test/browser/src/hsv.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "Hsv module", -> 5 | 6 | hsvBlack = hsvRed = hsvYellow = hsvBrown = hsvWhite = hsvGreen = hsvDarkGreen = hsvCyan = hsvBlue = hsvMagenta = null 7 | rgbBlack = rgbRed = rgbYellow = rgbBrown = rgbWhite = rgbGreen = rgbDarkGreen = rgbCyan = rgbBlue = rgbMagenta = null 8 | 9 | beforeEach -> 10 | 11 | hsvBlack = C.hsv 0, 0, 0 # any value for h & s 12 | hsvRed = C.hsv 0, 1, 1 13 | hsvYellow = C.hsv 60/360, 1, 1 14 | hsvBrown = C.hsv 24/360, .75, .36 15 | hsvWhite = C.hsv 0, 0, 1 # any value for h 16 | hsvGreen = C.hsv 120/360, 1, 1 17 | hsvDarkGreen = C.hsv 120/360, 1, 0.5 18 | hsvCyan = C.hsv 180/360, 1, 1 19 | hsvBlue = C.hsv 240/360, 1, 1 20 | hsvMagenta = C.hsv 300/360, 1, 1 21 | 22 | rgbBlack = C.rgb 0, 0, 0 23 | rgbRed = C.rgb 1, 0, 0 24 | rgbYellow = C.rgb 1, 1, 0 25 | rgbBrown = C.rgb .36, .198, .09 26 | rgbWhite = C.rgb 1, 1, 1 27 | rgbGreen = C.rgb 0, 1, 0 28 | rgbDarkGreen = C.rgb 0, .5, 0 29 | rgbCyan = C.rgb 0, 1, 1 30 | rgbBlue = C.rgb 0, 0, 1 31 | rgbMagenta = C.rgb 1, 0, 1 32 | 33 | jasmine.addMatchers require "./matcher" 34 | 35 | 36 | describe "hsv constructor", -> 37 | 38 | it "stores given args", -> 39 | (expect hsvBrown.h).toBe 24/360 40 | (expect hsvBrown.s).toBe .75 41 | (expect hsvBrown.v).toBe .36 42 | 43 | 44 | describe "hsv.rgb()", -> 45 | 46 | it "maps correctly to rgb", -> 47 | 48 | (expect hsvBlack.rgb()).toEqual rgbBlack 49 | (expect hsvRed.rgb()).toEqual rgbRed 50 | (expect hsvYellow.rgb()).toEqual rgbYellow 51 | (expect hsvBrown.rgb()).toEqual rgbBrown 52 | (expect hsvWhite.rgb()).toEqual rgbWhite 53 | (expect hsvGreen.rgb()).toEqual rgbGreen 54 | (expect hsvDarkGreen.rgb()).toEqual rgbDarkGreen 55 | (expect hsvCyan.rgb()).toEqual rgbCyan 56 | (expect hsvBlue.rgb()).toEqual rgbBlue 57 | (expect hsvMagenta.rgb()).toEqual rgbMagenta 58 | 59 | 60 | describe "rgb.hsv()", -> 61 | 62 | it "maps correctly to hsv", -> 63 | 64 | (expect rgbBlack.hsv()).toEqual hsvBlack 65 | (expect rgbRed.hsv()).toEqual hsvRed 66 | (expect rgbYellow.hsv()).toEqual hsvYellow 67 | (expect rgbBrown.hsv()).toAllBeCloseTo hsvBrown, 15 68 | (expect rgbWhite.hsv()).toEqual hsvWhite 69 | (expect rgbGreen.hsv()).toEqual hsvGreen 70 | (expect rgbDarkGreen.hsv()).toEqual hsvDarkGreen 71 | (expect rgbCyan.hsv()).toEqual hsvCyan 72 | (expect rgbBlue.hsv()).toEqual hsvBlue 73 | (expect rgbMagenta.hsv()).toEqual hsvMagenta 74 | 75 | 76 | describe "hsv.set()", -> 77 | 78 | it "sets the given values", -> 79 | 80 | hsv1 = C.hsv 0.1, 0.2, 0.3 81 | hsv2 = hsv1.set 0.4, 0.5, 0.6 82 | (expect hsv2).toBe hsv1 # return obj for chaining 83 | (expect hsv1).toEqual C.hsv 0.4, 0.5, 0.6 # values changed 84 | -------------------------------------------------------------------------------- /test/browser/src/indexToTest.coffee: -------------------------------------------------------------------------------- 1 | # Always returns the index object anyway if tested in browser or node. Also does call require so browserify does not 2 | # follow this import. 3 | 4 | index = if (inBrowser = c0lor?) then c0lor else require.call null, "../../src/index" 5 | 6 | index.BROWSER = inBrowser 7 | 8 | module.exports = index -------------------------------------------------------------------------------- /test/browser/src/matcher.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | 3 | # like toBeCloseTo of jasmine but works on objects and verifies all number properties 4 | toAllBeCloseTo: (util, customEqualityTesters) -> 5 | compare: (actual, expected, precision) -> 6 | if precision != 0 7 | precision = precision || 2 8 | precision = (Math.pow 10, -precision) / 2 9 | for own key, value of actual 10 | if typeof value == "number" 11 | valueExpected = expected[key] 12 | if typeof valueExpected != "number" 13 | return pass: false 14 | delta = Math.abs value - valueExpected 15 | if delta >= precision 16 | return pass: false 17 | pass: true -------------------------------------------------------------------------------- /test/browser/src/rgb.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "Rgb module", -> 5 | 6 | XYZ2 = XYZ3 = XYZ2_s = XYZ3_s = null 7 | rgb1 = rgb2 = rgb3 = rgb4 = null 8 | RGB1 = RGB2 = RGB3 = RGB4 = null 9 | rgbCs1 = rgbCs2 = null 10 | 11 | beforeEach -> 12 | 13 | rgb1 = C.rgb 0, undefined, 0 14 | rgb2 = C.rgb 1, 1, 1 15 | rgb3 = C.rgb 0.5, 0.3, 0.2 16 | rgb4 = C.rgb 1.4, 1.00001, -0.1 17 | 18 | RGB1 = C.RGB 0, undefined, 0 19 | RGB2 = C.RGB 255, 255, 255 20 | RGB3 = C.RGB 128, 77, 51 21 | RGB4 = C.RGB 357, 255, -25 22 | 23 | rgbCs1 = C.space.rgb["Adobe-98"] 24 | # Adobe-98 / http://www.brucelindbloom.com 25 | XYZ2 = C.XYZ 0.95047, 1 , 1.08883 26 | XYZ3 = C.XYZ 0.1441 , 0.111282, 0.039618 27 | 28 | rgbCs2 = C.space.rgb.sRGB 29 | # sRGB / http://www.brucelindbloom.com 30 | XYZ2_s = C.XYZ 0.95047 , 1 , 1.08883 31 | XYZ3_s = C.XYZ 0.120444, 0.100287, 0.044327 32 | 33 | jasmine.addMatchers require "./matcher" 34 | 35 | 36 | describe "rgb.RGB(), RGB.rgb()", -> 37 | 38 | it "rgb.RGB() maps correctly to 3 bit rgb", -> 39 | 40 | (expect rgb1.RGB()).toEqual RGB1 41 | (expect rgb2.RGB()).toEqual RGB2 42 | (expect rgb3.RGB()).toEqual RGB3 43 | (expect rgb4.RGB()).toEqual RGB4 44 | 45 | 46 | describe "rgb.toString()", -> 47 | 48 | it "creates informative output", -> 49 | 50 | (expect "#{rgb3}").toBe "r=0.5, g=0.3, b=0.2" 51 | 52 | 53 | describe "rgb.isDefined()", -> 54 | 55 | it "is true if all components are defined", -> 56 | 57 | (expect rgb1.isDefined()).toBe false 58 | (expect rgb2.isDefined()).toBe true 59 | (expect rgb3.isDefined()).toBe true 60 | (expect rgb4.isDefined()).toBe true 61 | 62 | 63 | describe "rgb.isValid()", -> 64 | 65 | it "is true if all components are defined and between 0..1", -> 66 | 67 | # ok 68 | (expect (C.rgb 0, 0, 0).isValid()).toBe true 69 | (expect (C.rgb -0, -0, -0).isValid()).toBe true 70 | (expect (C.rgb 1, 1, 1).isValid()).toBe true 71 | (expect (C.rgb 0.1, 0.2, 0.3).isValid()).toBe true 72 | # not ok 73 | (expect (C.rgb 0, -0.001, 0).isValid()).toBe false 74 | (expect (C.rgb 0, 0, 1.0001).isValid()).toBe false 75 | (expect (C.rgb undefined, 0, 1).isValid()).toBe false 76 | (expect (C.rgb 0, null, 0).isValid()).toBe false 77 | (expect (C.rgb 0, Number.POSITIVE_INFINITY, 0).isValid()).toBe false 78 | (expect (C.rgb 0, Number.NaN, 0).isValid()).toBe false 79 | (expect (C.rgb 0, Number.NEGATIVE_INFINITY, 0).isValid()).toBe false 80 | 81 | 82 | describe "rgb.set()", -> 83 | 84 | it "sets the given values", -> 85 | 86 | rgb_ = C.rgb 0.1, 0.2, 0.3 87 | rgb2 = rgb_.set 0.4, 0.5, 0.6 88 | (expect rgb2).toBe rgb_ # return obj for chaining 89 | (expect rgb_).toEqual C.rgb 0.4, 0.5, 0.6 # values changed 90 | -------------------------------------------------------------------------------- /test/browser/src/space/lab.coffee: -------------------------------------------------------------------------------- 1 | C = require "../indexToTest" 2 | 3 | 4 | describe "Lab Colorspace", -> 5 | 6 | XYZ1 = XYZ2 = Lab1 = Lab2 = LCh1 = LCh2 = labCs = null 7 | 8 | beforeEach -> 9 | 10 | labCs = C.space.lab C.white.D50 11 | XYZ1 = C.XYZ 0.1, 0.2, 0.3 12 | XYZ2 = C.XYZ 0.0001, 0.002, 0.003 13 | # same color in Lab color space with D50 reference white (http://www.brucelindbloom.com/) 14 | Lab1 = C.Lab 51.8372, -57.4865, -25.7804 15 | Lab2 = C.Lab 1.8066, -7.3832, -2.5470 16 | 17 | # reference values generated here: http://www.brucelindbloom.com/ 18 | toRad = (a) -> 19 | (if a > 180 then a - 360 else a) * Math.PI / 180 20 | LCh1 = C.LCh 51.8372, 63.0026, toRad 204.1543 21 | LCh2 = C.LCh 1.8066, 7.8102, toRad 199.0330 22 | 23 | jasmine.addMatchers require "../matcher" 24 | 25 | 26 | describe "fromXYZ() function", -> 27 | 28 | it "maps correctly to XYZ", -> 29 | 30 | expect(labCs.Lab XYZ1).toAllBeCloseTo Lab1, 2 31 | expect(labCs.Lab labCs.XYZ Lab1).toAllBeCloseTo Lab1, 13 32 | expect(labCs.Lab XYZ2).toAllBeCloseTo Lab2, 3 33 | expect(labCs.Lab labCs.XYZ Lab2).toAllBeCloseTo Lab2, 13 34 | 35 | 36 | describe "toXYZ() function", -> 37 | 38 | it "maps correctly to Lab", -> 39 | 40 | expect(labCs.XYZ Lab1).toAllBeCloseTo XYZ1, 4 41 | expect(labCs.XYZ labCs.Lab XYZ1).toAllBeCloseTo XYZ1, 15 42 | expect(labCs.XYZ Lab2).toAllBeCloseTo XYZ2, 6 43 | expect(labCs.XYZ labCs.Lab XYZ2).toAllBeCloseTo XYZ2, 17 44 | 45 | 46 | describe "fromXYZderivL()", -> 47 | 48 | it "seems to linear approximate the function at some random point", -> 49 | 50 | f = (Y) -> (labCs.Lab C.XYZ null, Y, null).L 51 | f_ = (Y) -> labCs.LabderivL Y 52 | Y = 0.3 # examine around some point Y 53 | t1 = (x) -> (f Y) + (f_ Y) * (x - Y) # taylor"s theorem 54 | (expect t1 Y).toBe f Y 55 | (expect t1 Y + 0.001).toBeCloseTo (f Y + 0.001), 3 56 | (expect t1 Y - 0.001).toBeCloseTo (f Y - 0.001), 3 57 | -------------------------------------------------------------------------------- /test/browser/src/space/rgb.coffee: -------------------------------------------------------------------------------- 1 | C = require "../indexToTest" 2 | 3 | 4 | describe "Rgb Colorspace module", -> 5 | 6 | XYZ2 = XYZ3 = XYZ2_s = XYZ3_s = null 7 | rgb1 = rgb2 = rgb3 = rgb4 = null 8 | RGB1 = RGB2 = RGB3 = RGB4 = null 9 | rgbCs1 = rgbCs2 = null 10 | 11 | beforeEach -> 12 | 13 | rgb1 = C.rgb 0, undefined, 0 14 | rgb2 = C.rgb 1, 1, 1 15 | rgb3 = C.rgb 0.5, 0.3, 0.2 16 | rgb4 = C.rgb 1.4, 1.00001, -0.1 17 | 18 | RGB1 = C.RGB 0, undefined, 0 19 | RGB2 = C.RGB 255, 255, 255 20 | RGB3 = C.RGB 128, 77, 51 21 | RGB4 = C.RGB undefined, 255, undefined 22 | 23 | rgbCs1 = C.space.rgb["Adobe-98"] 24 | # Adobe-98 / http://www.brucelindbloom.com 25 | XYZ2 = C.XYZ 0.95047, 1 , 1.08883 26 | XYZ3 = C.XYZ 0.1441 , 0.111282, 0.039618 27 | 28 | rgbCs2 = C.space.rgb.sRGB 29 | # sRGB / http://www.brucelindbloom.com 30 | XYZ2_s = C.XYZ 0.95047 , 1 , 1.08883 31 | XYZ3_s = C.XYZ 0.120444, 0.100287, 0.044327 32 | 33 | jasmine.addMatchers require "../matcher" 34 | 35 | 36 | describe "toXYZ()", -> 37 | 38 | it "maps to XYZ correctly", -> 39 | 40 | (expect rgbCs1.XYZ rgb2).toAllBeCloseTo XYZ2, 3 41 | (expect rgbCs1.XYZ rgb3).toAllBeCloseTo XYZ3, 3 42 | 43 | (expect rgbCs2.XYZ rgb2).toAllBeCloseTo XYZ2_s, 3 44 | (expect rgbCs2.XYZ rgb3).toAllBeCloseTo XYZ3_s, 4 45 | 46 | (expect rgbCs1.XYZ rgbCs1.rgb XYZ2).toAllBeCloseTo XYZ2, 15 47 | (expect rgbCs1.XYZ rgbCs1.rgb XYZ3).toAllBeCloseTo XYZ3, 16 48 | 49 | (expect rgbCs2.XYZ rgbCs2.rgb XYZ2_s).toAllBeCloseTo XYZ2_s, 15 50 | (expect rgbCs2.XYZ rgbCs2.rgb XYZ3_s).toAllBeCloseTo XYZ3_s, 15 51 | 52 | 53 | describe "fromXYZ()", -> 54 | 55 | it "maps to XYZ correctly", -> 56 | 57 | (expect rgbCs1.rgb XYZ2).toAllBeCloseTo rgb2, 4 58 | (expect rgbCs1.rgb XYZ3).toAllBeCloseTo rgb3, 4 59 | 60 | (expect rgbCs2.rgb XYZ2_s).toAllBeCloseTo rgb2, 3 61 | (expect rgbCs2.rgb XYZ3_s).toAllBeCloseTo rgb3, 4 62 | 63 | (expect rgbCs1.rgb rgbCs1.XYZ rgb2).toEqual rgb2 64 | (expect rgbCs1.rgb rgbCs1.XYZ rgb3).toEqual rgb3 65 | 66 | (expect rgbCs2.rgb rgbCs2.XYZ rgb2).toAllBeCloseTo rgb2, 15 67 | (expect rgbCs2.rgb rgbCs2.XYZ rgb3).toAllBeCloseTo rgb3, 15 68 | 69 | 70 | describe "color space constructor", -> 71 | 72 | it "create a new (identity) color space", -> 73 | 74 | cs = C.space.rgb (C.xyY 1, 0), (C.xyY 0, 1), (C.xyY 0, 0), (C.XYZ 1, 1, 1), 1 75 | (expect cs.XYZ C.rgb 0, 0, 0).toEqual C.XYZ 0, 0, 0 76 | (expect cs.XYZ C.rgb 1, 0, 0).toEqual C.XYZ 1, 0, 0 77 | (expect cs.XYZ C.rgb 0, 1, 0).toEqual C.XYZ 0, 1, 0 78 | (expect cs.XYZ C.rgb 0, 0, 1).toEqual C.XYZ 0, 0, 1 79 | (expect cs.XYZ C.rgb 1, 1, 1).toEqual C.XYZ 1, 1, 1 80 | (expect cs.XYZ C.rgb 0.1, 0.2, 0.5).toEqual C.XYZ 0.1, 0.2, 0.5 81 | (expect cs.rgb C.XYZ 0.1, 0.2, 0.5).toEqual C.rgb 0.1, 0.2, 0.5 82 | 83 | 84 | describe "Predefined color spaces", -> 85 | 86 | it "have the expected range in Lab colorspace", -> 87 | 88 | min = C.Lab 0, 0, 0 89 | max = C.Lab 100, 0, 0 90 | 91 | # rgb test colors 92 | rgbTest = [ 93 | C.rgb 0, 0, 0 94 | C.rgb 1, 0, 0 95 | C.rgb 0, 1, 0 96 | C.rgb 0, 0, 1 97 | C.rgb 1, 0, 1 98 | C.rgb 0, 1, 1 99 | C.rgb 1, 1, 0 100 | C.rgb 1, 1, 1 101 | ] 102 | 103 | for own rgbSpaceName, rgbSpace of C.space.rgb 104 | if rgbSpaceName == "prototype" # on ios 4.3 / 10.6 the prototype property gets iterable. why? 105 | continue 106 | for rgbCol in rgbTest 107 | xyzCol = rgbSpace.XYZ rgbCol 108 | for whiteName, white of C.white 109 | labColor = (C.space.lab white).Lab xyzCol 110 | min.L = Math.min labColor.L, min.L 111 | min.a = Math.min labColor.a, min.a 112 | min.b = Math.min labColor.b, min.b 113 | max.L = Math.max labColor.L, max.L 114 | max.a = Math.max labColor.a, max.a 115 | max.b = Math.max labColor.b, max.b 116 | 117 | (expect min).toEqual C.Lab 0, -223.4241602806217, -237.05355418094157 118 | (expect max).toEqual C.Lab 100, 191.62605068015958, 158.7312579450673 119 | -------------------------------------------------------------------------------- /test/browser/src/xyY.coffee: -------------------------------------------------------------------------------- 1 | C = require "./indexToTest" 2 | 3 | 4 | describe "xyY Colorspace module", -> 5 | 6 | XYZ = null 7 | xyY = null 8 | 9 | beforeEach -> 10 | 11 | XYZ = C.XYZ 0.1, 0.2, 0.3 12 | # same color in xyY color space 13 | xyY = C.xyY 1/6, 1/3, 0.2 14 | 15 | jasmine.addMatchers require "./matcher" 16 | 17 | 18 | describe "xyY constructors", -> 19 | 20 | it "creates a new expected xyY object", -> 21 | 22 | (expect xyY.x).toBe 1/6 23 | (expect xyY.y).toBe 1/3 24 | (expect xyY.Y).toBe 0.2 25 | 26 | 27 | describe "XYZ <-> xyY", -> 28 | 29 | it "xyY maps correctly to XYZ", -> 30 | 31 | (expect xyY.XYZ()).toAllBeCloseTo XYZ, 15 32 | 33 | it "can map xyY to XYZ in place", -> 34 | 35 | x = C.XYZ 0.4, 0.4, 0.4 36 | y = xyY.XYZ x 37 | (expect y).toAllBeCloseTo XYZ, 15 38 | (expect y).toBe x 39 | 40 | 41 | describe "xyY.isDefined()", -> 42 | 43 | it "is true if all components are defined", -> 44 | 45 | (expect C.xyY().isDefined()).toBe false 46 | (expect (C.xyY 0.1).isDefined()).toBe false 47 | (expect (C.xyY 0.1, 0.2, 0.3).isDefined()).toBe true 48 | 49 | 50 | describe "xyY.toString()", -> 51 | 52 | it "creates informative output", -> 53 | 54 | (expect "#{C.xyY 0.2, 0.3, 0.4}").toBe "x=0.2, y=0.3, Y=0.4" 55 | --------------------------------------------------------------------------------