1/* 2 * Copyright (C) 2009, 2013 Apple Inc. All rights reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30WebInspector.Color = function(format, components) 31{ 32 this.format = format; 33 if (format === WebInspector.Color.Format.HSL || format === WebInspector.Color.Format.HSLA) 34 this._hsla = components; 35 else 36 this._rgba = components; 37 38 this.valid = !components.some(function(component) { 39 return isNaN(component); 40 }); 41} 42 43WebInspector.Color.Format = { 44 Original: "color-format-original", 45 Nickname: "color-format-nickname", 46 HEX: "color-format-hex", 47 ShortHEX: "color-format-short-hex", 48 RGB: "color-format-rgb", 49 RGBA: "color-format-rgba", 50 HSL: "color-format-hsl", 51 HSLA: "color-format-hsla" 52}; 53 54WebInspector.Color.fromString = function(colorString) 55{ 56 var value = colorString.toLowerCase().replace(/%|\s+/g, ""); 57 const transparentNicknames = ["transparent", "rgba(0,0,0,0)", "hsla(0,0,0,0)"]; 58 if (transparentNicknames.contains(value)) { 59 var color = new WebInspector.Color(WebInspector.Color.Format.Nickname, [0, 0, 0, 0]); 60 color.nickname = "transparent"; 61 color.original = colorString; 62 return color; 63 } 64 65 // Simple - #hex, rgb(), nickname, hsl() 66 var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i; 67 var match = colorString.match(simple); 68 if (match) { 69 if (match[1]) { // hex 70 var hex = match[1].toUpperCase(); 71 if (hex.length === 3) { 72 return new WebInspector.Color(WebInspector.Color.Format.ShortHEX, [ 73 parseInt(hex.charAt(0) + hex.charAt(0), 16), 74 parseInt(hex.charAt(1) + hex.charAt(1), 16), 75 parseInt(hex.charAt(2) + hex.charAt(2), 16), 76 1 77 ]); 78 } else { 79 return new WebInspector.Color(WebInspector.Color.Format.HEX, [ 80 parseInt(hex.substring(0, 2), 16), 81 parseInt(hex.substring(2, 4), 16), 82 parseInt(hex.substring(4, 6), 16), 83 1 84 ]); 85 } 86 } else if (match[2]) { // rgb 87 var rgb = match[2].split(/\s*,\s*/); 88 return new WebInspector.Color(WebInspector.Color.Format.RGB, [ 89 parseInt(rgb[0]), 90 parseInt(rgb[1]), 91 parseInt(rgb[2]), 92 1 93 ]); 94 } else if (match[3]) { // nickname 95 var nickname = match[3].toLowerCase(); 96 if (WebInspector.Color.Nicknames.hasOwnProperty(nickname)) { 97 var color = new WebInspector.Color(WebInspector.Color.Format.Nickname, WebInspector.Color.Nicknames[nickname].concat(1)); 98 color.nickname = nickname; 99 color.original = colorString; 100 return color; 101 } else 102 return null; 103 } else if (match[4]) { // hsl 104 var hsl = match[4].replace(/%/g, "").split(/\s*,\s*/); 105 return new WebInspector.Color(WebInspector.Color.Format.HSL, [ 106 parseInt(hsl[0]), 107 parseInt(hsl[1]), 108 parseInt(hsl[2]), 109 1 110 ]); 111 } 112 } 113 114 // Advanced - rgba(), hsla() 115 var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/; 116 match = colorString.match(advanced); 117 if (match) { 118 if (match[1]) { // rgba 119 var rgba = match[1].split(/\s*,\s*/); 120 return new WebInspector.Color(WebInspector.Color.Format.RGBA, [ 121 parseInt(rgba[0]), 122 parseInt(rgba[1]), 123 parseInt(rgba[2]), 124 Number.constrain(parseFloat(rgba[3]), 0, 1) 125 ]); 126 } else if (match[2]) { // hsla 127 var hsla = match[2].replace(/%/g, "").split(/\s*,\s*/); 128 return new WebInspector.Color(WebInspector.Color.Format.HSLA, [ 129 parseInt(hsla[0]), 130 parseInt(hsla[1]), 131 parseInt(hsla[2]), 132 Number.constrain(parseFloat(hsla[3]), 0, 1) 133 ]); 134 } 135 } 136 137 return null; 138} 139 140WebInspector.Color.prototype = { 141 nextFormat: function(format) 142 { 143 format = format || this.format; 144 145 switch (format) { 146 case WebInspector.Color.Format.Original: 147 return this.simple ? WebInspector.Color.Format.RGB : WebInspector.Color.Format.RGBA; 148 149 case WebInspector.Color.Format.RGB: 150 case WebInspector.Color.Format.RGBA: 151 return this.simple ? WebInspector.Color.Format.HSL : WebInspector.Color.Format.HSLA; 152 153 case WebInspector.Color.Format.HSL: 154 case WebInspector.Color.Format.HSLA: 155 if (this.nickname) 156 return WebInspector.Color.Format.Nickname; 157 if (this.simple) 158 return this._canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEX : WebInspector.Color.Format.HEX; 159 else 160 return WebInspector.Color.Format.Original; 161 162 case WebInspector.Color.Format.ShortHEX: 163 return WebInspector.Color.Format.HEX; 164 165 case WebInspector.Color.Format.HEX: 166 return WebInspector.Color.Format.Original; 167 168 case WebInspector.Color.Format.Nickname: 169 if (this.simple) 170 return this._canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEX : WebInspector.Color.Format.HEX; 171 else 172 return WebInspector.Color.Format.Original; 173 174 default: 175 console.error("Unknown color format."); 176 return null; 177 } 178 }, 179 180 get alpha() 181 { 182 return this._rgba ? this._rgba[3] : this._hsla[3]; 183 }, 184 185 get simple() 186 { 187 return this.alpha === 1; 188 }, 189 190 get rgb() 191 { 192 var rgb = this.rgba.slice(); 193 rgb.pop(); 194 return rgb; 195 }, 196 197 get hsl() 198 { 199 var hsl = this.hsla.slice(); 200 hsl.pop(); 201 return hsl; 202 }, 203 204 get rgba() 205 { 206 if (!this._rgba) 207 this._rgba = this._hslaToRGBA(this._hsla); 208 return this._rgba; 209 }, 210 211 get hsla() 212 { 213 if (!this._hsla) 214 this._hsla = this._rgbaToHSLA(this.rgba); 215 return this._hsla; 216 }, 217 218 copy: function() 219 { 220 switch (this.format) { 221 case WebInspector.Color.Format.RGB: 222 case WebInspector.Color.Format.HEX: 223 case WebInspector.Color.Format.ShortHEX: 224 case WebInspector.Color.Format.Nickname: 225 case WebInspector.Color.Format.RGBA: 226 return new WebInspector.Color(this.format, this.rgba); 227 case WebInspector.Color.Format.HSL: 228 case WebInspector.Color.Format.HSLA: 229 return new WebInspector.Color(this.format, this.hsla); 230 } 231 }, 232 233 toString: function(format) 234 { 235 if (!format) 236 format = this.format; 237 238 switch (format) { 239 case WebInspector.Color.Format.Original: 240 return this._toOriginalString(); 241 case WebInspector.Color.Format.RGB: 242 return this._toRGBString(); 243 case WebInspector.Color.Format.RGBA: 244 return this._toRGBAString(); 245 case WebInspector.Color.Format.HSL: 246 return this._toHSLString(); 247 case WebInspector.Color.Format.HSLA: 248 return this._toHSLAString(); 249 case WebInspector.Color.Format.HEX: 250 return this._toHEXString(); 251 case WebInspector.Color.Format.ShortHEX: 252 return this._toShortHEXString(); 253 case WebInspector.Color.Format.Nickname: 254 return this._toNicknameString(); 255 } 256 257 throw "invalid color format"; 258 }, 259 260 _toOriginalString: function() 261 { 262 return this.original || this._toNicknameString(); 263 }, 264 265 _toNicknameString: function() 266 { 267 if (this.nickname) 268 return this.nickname; 269 270 var rgba = this.rgba; 271 if (!this.simple) { 272 if (rgba[0] === 0 && rgba[1] === 0 && rgba[2] === 0 && rgba[3] === 0) 273 return "transparent"; 274 return this._toRGBAString(); 275 } 276 277 var nicknames = WebInspector.Color.Nicknames; 278 for (var nickname in nicknames) { 279 if (!nicknames.hasOwnProperty(nickname)) 280 continue; 281 282 var nicknameRGB = nicknames[nickname]; 283 if (nicknameRGB[0] === rgba[0] && nicknameRGB[1] === rgba[1] && nicknameRGB[2] === rgba[2]) 284 return nickname; 285 } 286 287 return this._toRGBString(); 288 }, 289 290 _toShortHEXString: function() 291 { 292 if (!this.simple) 293 return this._toRGBAString(); 294 295 var rgba = this.rgba; 296 var r = this._componentToHexValue(rgba[0]); 297 var g = this._componentToHexValue(rgba[1]); 298 var b = this._componentToHexValue(rgba[2]); 299 300 if (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) 301 return "#" + r[0] + g[0] + b[0]; 302 else 303 return "#" + r + g + b; 304 }, 305 306 _toHEXString: function() 307 { 308 if (!this.simple) 309 return this._toRGBAString(); 310 311 var rgba = this.rgba; 312 var r = this._componentToHexValue(rgba[0]); 313 var g = this._componentToHexValue(rgba[1]); 314 var b = this._componentToHexValue(rgba[2]); 315 316 return "#" + r + g + b; 317 }, 318 319 _toRGBString: function() 320 { 321 if (!this.simple) 322 return this._toRGBAString(); 323 324 var rgba = this.rgba; 325 return "rgb(" + [rgba[0], rgba[1], rgba[2]].join(", ") + ")"; 326 }, 327 328 _toRGBAString: function() 329 { 330 return "rgba(" + this.rgba.join(", ") + ")"; 331 }, 332 333 _toHSLString: function() 334 { 335 if (!this.simple) 336 return this._toHSLAString(); 337 338 var hsla = this.hsla; 339 return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; 340 }, 341 342 _toHSLAString: function() 343 { 344 var hsla = this.hsla; 345 return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")"; 346 }, 347 348 _canBeSerializedAsShortHEX: function() 349 { 350 var rgba = this.rgba; 351 352 var r = this._componentToHexValue(rgba[0]); 353 if (r[0] !== r[1]) 354 return false; 355 356 var g = this._componentToHexValue(rgba[1]); 357 if (g[0] !== g[1]) 358 return false; 359 360 var b = this._componentToHexValue(rgba[2]); 361 if (b[0] !== b[1]) 362 return false; 363 364 return true; 365 }, 366 367 _componentToNumber: function(value) 368 { 369 return Number.constrain(value, 0, 255); 370 }, 371 372 _componentToHexValue: function(value) 373 { 374 var hex = this._componentToNumber(value).toString(16); 375 if (hex.length === 1) 376 hex = "0" + hex; 377 return hex; 378 }, 379 380 _rgbToHSL: function(rgb) 381 { 382 var r = this._componentToNumber(rgb[0]) / 255; 383 var g = this._componentToNumber(rgb[1]) / 255; 384 var b = this._componentToNumber(rgb[2]) / 255; 385 var max = Math.max(r, g, b); 386 var min = Math.min(r, g, b); 387 var diff = max - min; 388 var add = max + min; 389 390 if (min === max) 391 var h = 0; 392 else if (r === max) 393 var h = ((60 * (g - b) / diff) + 360) % 360; 394 else if (g === max) 395 var h = (60 * (b - r) / diff) + 120; 396 else 397 var h = (60 * (r - g) / diff) + 240; 398 399 var l = 0.5 * add; 400 401 if (l === 0) 402 var s = 0; 403 else if (l === 1) 404 var s = 1; 405 else if (l <= 0.5) 406 var s = diff / add; 407 else 408 var s = diff / (2 - add); 409 410 h = Math.round(h); 411 s = Math.round(s * 100); 412 l = Math.round(l * 100); 413 414 return [h, s, l]; 415 }, 416 417 _hslToRGB: function(hsl) 418 { 419 var h = parseFloat(hsl[0]) / 360; 420 var s = parseFloat(hsl[1]) / 100; 421 var l = parseFloat(hsl[2]) / 100; 422 423 h *= 6; 424 var sArray = [ 425 l += s *= l < .5 ? l : 1 - l, 426 l - h % 1 * s * 2, 427 l -= s *= 2, 428 l, 429 l + h % 1 * s, 430 l + s 431 ]; 432 return [ 433 Math.round(sArray[ ~~h % 6 ] * 255), 434 Math.round(sArray[ (h|16) % 6 ] * 255), 435 Math.round(sArray[ (h|8) % 6 ] * 255) 436 ]; 437 }, 438 439 _rgbaToHSLA: function(rgba) 440 { 441 var hsl = this._rgbToHSL(rgba); 442 hsl.push(rgba[3]); 443 return hsl; 444 }, 445 446 _hslaToRGBA: function(hsla) 447 { 448 var rgba = this._hslToRGB(hsla); 449 rgba.push(hsla[3]); 450 return rgba; 451 } 452} 453 454WebInspector.Color.Nicknames = { 455 "aliceblue": [240, 248, 255], 456 "antiquewhite": [250, 235, 215], 457 "aquamarine": [127, 255, 212], 458 "azure": [240, 255, 255], 459 "beige": [245, 245, 220], 460 "bisque": [255, 228, 196], 461 "black": [0, 0, 0], 462 "blanchedalmond": [255, 235, 205], 463 "blue": [0, 0, 255], 464 "blueviolet": [138, 43, 226], 465 "brown": [165, 42, 42], 466 "burlywood": [222, 184, 135], 467 "cadetblue": [95, 158, 160], 468 "chartreuse": [127, 255, 0], 469 "chocolate": [210, 105, 30], 470 "coral": [255, 127, 80], 471 "cornflowerblue": [100, 149, 237], 472 "cornsilk": [255, 248, 220], 473 "crimson": [237, 164, 61], 474 "cyan": [0, 255, 255], 475 "darkblue": [0, 0, 139], 476 "darkcyan": [0, 139, 139], 477 "darkgoldenrod": [184, 134, 11], 478 "darkgray": [169, 169, 169], 479 "darkgreen": [0, 100, 0], 480 "darkkhaki": [189, 183, 107], 481 "darkmagenta": [139, 0, 139], 482 "darkolivegreen": [85, 107, 47], 483 "darkorange": [255, 140, 0], 484 "darkorchid": [153, 50, 204], 485 "darkred": [139, 0, 0], 486 "darksalmon": [233, 150, 122], 487 "darkseagreen": [143, 188, 143], 488 "darkslateblue": [72, 61, 139], 489 "darkslategray": [47, 79, 79], 490 "darkturquoise": [0, 206, 209], 491 "darkviolet": [148, 0, 211], 492 "deeppink": [255, 20, 147], 493 "deepskyblue": [0, 191, 255], 494 "dimgray": [105, 105, 105], 495 "dodgerblue": [30, 144, 255], 496 "firebrick": [178, 34, 34], 497 "floralwhite": [255, 250, 240], 498 "forestgreen": [34, 139, 34], 499 "gainsboro": [220, 220, 220], 500 "ghostwhite": [248, 248, 255], 501 "gold": [255, 215, 0], 502 "goldenrod": [218, 165, 32], 503 "gray": [128, 128, 128], 504 "green": [0, 128, 0], 505 "greenyellow": [173, 255, 47], 506 "honeydew": [240, 255, 240], 507 "hotpink": [255, 105, 180], 508 "indianred": [205, 92, 92], 509 "indigo": [75, 0, 130], 510 "ivory": [255, 255, 240], 511 "khaki": [240, 230, 140], 512 "lavender": [230, 230, 250], 513 "lavenderblush": [255, 240, 245], 514 "lawngreen": [124, 252, 0], 515 "lemonchiffon": [255, 250, 205], 516 "lightblue": [173, 216, 230], 517 "lightcoral": [240, 128, 128], 518 "lightcyan": [224, 255, 255], 519 "lightgoldenrodyellow": [250, 250, 210], 520 "lightgreen": [144, 238, 144], 521 "lightgrey": [211, 211, 211], 522 "lightpink": [255, 182, 193], 523 "lightsalmon": [255, 160, 122], 524 "lightseagreen": [32, 178, 170], 525 "lightskyblue": [135, 206, 250], 526 "lightslategray": [119, 136, 153], 527 "lightsteelblue": [176, 196, 222], 528 "lightyellow": [255, 255, 224], 529 "lime": [0, 255, 0], 530 "limegreen": [50, 205, 50], 531 "linen": [250, 240, 230], 532 "magenta": [255, 0, 255], 533 "maroon": [128, 0, 0], 534 "mediumaquamarine": [102, 205, 170], 535 "mediumblue": [0, 0, 205], 536 "mediumorchid": [186, 85, 211], 537 "mediumpurple": [147, 112, 219], 538 "mediumseagreen": [60, 179, 113], 539 "mediumslateblue": [123, 104, 238], 540 "mediumspringgreen": [0, 250, 154], 541 "mediumturquoise": [72, 209, 204], 542 "mediumvioletred": [199, 21, 133], 543 "midnightblue": [25, 25, 112], 544 "mintcream": [245, 255, 250], 545 "mistyrose": [255, 228, 225], 546 "moccasin": [255, 228, 181], 547 "navajowhite": [255, 222, 173], 548 "navy": [0, 0, 128], 549 "oldlace": [253, 245, 230], 550 "olive": [128, 128, 0], 551 "olivedrab": [107, 142, 35], 552 "orange": [255, 165, 0], 553 "orangered": [255, 69, 0], 554 "orchid": [218, 112, 214], 555 "palegoldenrod": [238, 232, 170], 556 "palegreen": [152, 251, 152], 557 "paleturquoise": [175, 238, 238], 558 "palevioletred": [219, 112, 147], 559 "papayawhip": [255, 239, 213], 560 "peachpuff": [255, 218, 185], 561 "peru": [205, 133, 63], 562 "pink": [255, 192, 203], 563 "plum": [221, 160, 221], 564 "powderblue": [176, 224, 230], 565 "purple": [128, 0, 128], 566 "rebeccapurple": [102, 51, 153], 567 "red": [255, 0, 0], 568 "rosybrown": [188, 143, 143], 569 "royalblue": [65, 105, 225], 570 "saddlebrown": [139, 69, 19], 571 "salmon": [250, 128, 114], 572 "sandybrown": [244, 164, 96], 573 "seagreen": [46, 139, 87], 574 "seashell": [255, 245, 238], 575 "sienna": [160, 82, 45], 576 "silver": [192, 192, 192], 577 "skyblue": [135, 206, 235], 578 "slateblue": [106, 90, 205], 579 "slategray": [112, 128, 144], 580 "snow": [255, 250, 250], 581 "springgreen": [0, 255, 127], 582 "steelblue": [70, 130, 180], 583 "tan": [210, 180, 140], 584 "teal": [0, 128, 128], 585 "thistle": [216, 191, 216], 586 "tomato": [255, 99, 71], 587 "turquoise": [64, 224, 208], 588 "violet": [238, 130, 238], 589 "wheat": [245, 222, 179], 590 "white": [255, 255, 255], 591 "whitesmoke": [245, 245, 245], 592 "yellow": [255, 255, 0], 593 "yellowgreen": [154, 205, 50] 594}; 595 596WebInspector.Color.rgb2hsv = function(r, g, b) 597{ 598 r /= 255; 599 g /= 255; 600 b /= 255; 601 602 var min = Math.min(Math.min(r, g), b); 603 var max = Math.max(Math.max(r, g), b); 604 var delta = max - min; 605 606 var v = max; 607 var s, h; 608 609 if (max === min) 610 h = 0; 611 else if (max === r) 612 h = (60 * ((g - b) / delta)) % 360; 613 else if (max === g) 614 h = 60 * ((b - r) / delta) + 120; 615 else if (max === b) 616 h = 60 * ((r - g) / delta) + 240; 617 618 if (h < 0) 619 h += 360; 620 621 // Saturation 622 if (max === 0) 623 s = 0; 624 else 625 s = 1 - (min/max); 626 627 return [h, s, v]; 628} 629 630WebInspector.Color.hsv2rgb = function(h, s, v) 631{ 632 if (s === 0) 633 return [v, v, v]; 634 635 h /= 60; 636 var i = Math.floor(h); 637 var data = [ 638 v * (1 - s), 639 v * (1 - s * (h - i)), 640 v * (1 - s * (1 - (h - i))) 641 ]; 642 var rgb; 643 644 switch (i) { 645 case 0: 646 rgb = [v, data[2], data[0]]; 647 break; 648 case 1: 649 rgb = [data[1], v, data[0]]; 650 break; 651 case 2: 652 rgb = [data[0], v, data[2]]; 653 break; 654 case 3: 655 rgb = [data[0], data[1], v]; 656 break; 657 case 4: 658 rgb = [data[2], data[0], v]; 659 break; 660 default: 661 rgb = [v, data[0], data[1]]; 662 break; 663 } 664 665 return rgb; 666} 667