1 /* 2 * Copyright (C) 2009 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 Computer, 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 30 /** 31 * @param {!Array.<number>} rgba 32 * @param {string=} format 33 * @param {string=} originalText 34 * @constructor 35 */ 36 WebInspector.Color = function(rgba, format, originalText) 37 { 38 this._rgba = rgba; 39 this._originalText = originalText || null; 40 this._format = format || null; 41 if (typeof this._rgba[3] === "undefined") 42 this._rgba[3] = 1; 43 for (var i = 0; i < 4; ++i) { 44 if (this._rgba[i] < 0) 45 this._rgba[i] = 0; 46 if (this._rgba[i] > 1) 47 this._rgba[i] = 1; 48 } 49 } 50 51 /** 52 * @param {string} text 53 * @return {?WebInspector.Color} 54 */ 55 WebInspector.Color.parse = function(text) 56 { 57 // Simple - #hex, rgb(), nickname, hsl() 58 var value = text.toLowerCase().replace(/\s+/g, ""); 59 var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i; 60 var match = value.match(simple); 61 if (match) { 62 if (match[1]) { // hex 63 var hex = match[1].toUpperCase(); 64 var format; 65 if (hex.length === 3) { 66 format = WebInspector.Color.Format.ShortHEX; 67 hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); 68 } else 69 format = WebInspector.Color.Format.HEX; 70 var r = parseInt(hex.substring(0,2), 16); 71 var g = parseInt(hex.substring(2,4), 16); 72 var b = parseInt(hex.substring(4,6), 16); 73 return new WebInspector.Color([r / 255, g / 255, b / 255, 1], format, text); 74 } 75 76 if (match[2]) { // rgb 77 var rgbString = match[2].split(/\s*,\s*/); 78 var rgba = [ WebInspector.Color._parseRgbNumeric(rgbString[0]), 79 WebInspector.Color._parseRgbNumeric(rgbString[1]), 80 WebInspector.Color._parseRgbNumeric(rgbString[2]), 1 ]; 81 return new WebInspector.Color(rgba, WebInspector.Color.Format.RGB, text); 82 } 83 84 if (match[3]) { // nickname 85 var nickname = match[3].toLowerCase(); 86 if (nickname in WebInspector.Color.Nicknames) { 87 var rgba = WebInspector.Color.Nicknames[nickname]; 88 var color = WebInspector.Color.fromRGBA(rgba); 89 color._format = WebInspector.Color.Format.Nickname; 90 color._originalText = nickname; 91 return color; 92 } 93 return null; 94 } 95 96 if (match[4]) { // hsl 97 var hslString = match[4].replace(/%/g, "").split(/\s*,\s*/); 98 var hsla = [ WebInspector.Color._parseHueNumeric(hslString[0]), 99 WebInspector.Color._parseSatLightNumeric(hslString[1]), 100 WebInspector.Color._parseSatLightNumeric(hslString[2]), 1 ]; 101 var rgba = WebInspector.Color._hsl2rgb(hsla); 102 return new WebInspector.Color(rgba, WebInspector.Color.Format.HSL, text); 103 } 104 105 return null; 106 } 107 108 // Advanced - rgba(), hsla() 109 var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/; 110 match = value.match(advanced); 111 if (match) { 112 if (match[1]) { // rgba 113 var rgbaString = match[1].split(/\s*,\s*/); 114 var rgba = [ WebInspector.Color._parseRgbNumeric(rgbaString[0]), 115 WebInspector.Color._parseRgbNumeric(rgbaString[1]), 116 WebInspector.Color._parseRgbNumeric(rgbaString[2]), 117 WebInspector.Color._parseAlphaNumeric(rgbaString[3]) ]; 118 return new WebInspector.Color(rgba, WebInspector.Color.Format.RGBA, text); 119 } 120 121 if (match[2]) { // hsla 122 var hslaString = match[2].replace(/%/g, "").split(/\s*,\s*/); 123 var hsla = [ WebInspector.Color._parseHueNumeric(hslaString[0]), 124 WebInspector.Color._parseSatLightNumeric(hslaString[1]), 125 WebInspector.Color._parseSatLightNumeric(hslaString[2]), 126 WebInspector.Color._parseAlphaNumeric(hslaString[3]) ]; 127 var rgba = WebInspector.Color._hsl2rgb(hsla); 128 return new WebInspector.Color(rgba, WebInspector.Color.Format.HSLA, text); 129 } 130 } 131 132 return null; 133 } 134 135 /** 136 * @param {!Array.<number>} rgba 137 * @return {!WebInspector.Color} 138 */ 139 WebInspector.Color.fromRGBA = function(rgba) 140 { 141 return new WebInspector.Color([rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3]]); 142 } 143 144 /** 145 * @param {!Array.<number>} hsva 146 * @return {!WebInspector.Color} 147 */ 148 WebInspector.Color.fromHSVA = function(hsva) 149 { 150 var h = hsva[0]; 151 var s = hsva[1]; 152 var v = hsva[2]; 153 154 var t = (2 - s) * v; 155 if (v === 0 || s === 0) 156 s = 0; 157 else 158 s *= v / (t < 1 ? t : 2 - t); 159 var hsla = [h, s, t / 2, hsva[3]]; 160 161 return new WebInspector.Color(WebInspector.Color._hsl2rgb(hsla), WebInspector.Color.Format.HSLA); 162 } 163 164 WebInspector.Color.prototype = { 165 /** 166 * @return {?string} 167 */ 168 format: function() 169 { 170 return this._format; 171 }, 172 173 /** 174 * @return {!Array.<number>} HSLA with components within [0..1] 175 */ 176 hsla: function() 177 { 178 if (this._hsla) 179 return this._hsla; 180 var r = this._rgba[0]; 181 var g = this._rgba[1]; 182 var b = this._rgba[2]; 183 var max = Math.max(r, g, b); 184 var min = Math.min(r, g, b); 185 var diff = max - min; 186 var add = max + min; 187 188 if (min === max) 189 var h = 0; 190 else if (r === max) 191 var h = ((1/6 * (g - b) / diff) + 1) % 1; 192 else if (g === max) 193 var h = (1/6 * (b - r) / diff) + 1/3; 194 else 195 var h = (1/6 * (r - g) / diff) + 2/3; 196 197 var l = 0.5 * add; 198 199 if (l === 0) 200 var s = 0; 201 else if (l === 1) 202 var s = 1; 203 else if (l <= 0.5) 204 var s = diff / add; 205 else 206 var s = diff / (2 - add); 207 208 this._hsla = [h, s, l, this._rgba[3]]; 209 return this._hsla; 210 }, 211 212 /** 213 * @return {!Array.<number>} HSVA with components within [0..1] 214 */ 215 hsva: function() 216 { 217 var hsla = this.hsla(); 218 var h = hsla[0]; 219 var s = hsla[1]; 220 var l = hsla[2]; 221 222 s *= l < 0.5 ? l : 1 - l; 223 return [h, s !== 0 ? 2 * s / (l + s) : 0, (l + s), hsla[3]]; 224 }, 225 226 /** 227 * @return {boolean} 228 */ 229 hasAlpha: function() 230 { 231 return this._rgba[3] !== 1; 232 }, 233 234 /** 235 * @return {boolean} 236 */ 237 canBeShortHex: function() 238 { 239 if (this.hasAlpha()) 240 return false; 241 for (var i = 0; i < 3; ++i) { 242 var c = Math.round(this._rgba[i] * 255); 243 if (c % 17) 244 return false; 245 } 246 return true; 247 }, 248 249 /** 250 * @return {?string} 251 */ 252 toString: function(format) 253 { 254 if (!format) 255 format = this._format; 256 257 /** 258 * @param {number} value 259 * @return {number} 260 */ 261 function toRgbValue(value) 262 { 263 return Math.round(value * 255); 264 } 265 266 /** 267 * @param {number} value 268 * @return {string} 269 */ 270 function toHexValue(value) 271 { 272 var hex = Math.round(value * 255).toString(16); 273 return hex.length === 1 ? "0" + hex : hex; 274 } 275 276 /** 277 * @param {number} value 278 * @return {string} 279 */ 280 function toShortHexValue(value) 281 { 282 return (Math.round(value * 255) / 17).toString(16); 283 } 284 285 switch (format) { 286 case WebInspector.Color.Format.Original: 287 return this._originalText; 288 case WebInspector.Color.Format.RGB: 289 if (this.hasAlpha()) 290 return null; 291 return String.sprintf("rgb(%d, %d, %d)", toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2])); 292 case WebInspector.Color.Format.RGBA: 293 return String.sprintf("rgba(%d, %d, %d, %f)", toRgbValue(this._rgba[0]), toRgbValue(this._rgba[1]), toRgbValue(this._rgba[2]), this._rgba[3]); 294 case WebInspector.Color.Format.HSL: 295 if (this.hasAlpha()) 296 return null; 297 var hsl = this.hsla(); 298 return String.sprintf("hsl(%d, %d%, %d%)", Math.round(hsl[0] * 360), Math.round(hsl[1] * 100), Math.round(hsl[2] * 100)); 299 case WebInspector.Color.Format.HSLA: 300 var hsla = this.hsla(); 301 return String.sprintf("hsla(%d, %d%, %d%, %f)", Math.round(hsla[0] * 360), Math.round(hsla[1] * 100), Math.round(hsla[2] * 100), hsla[3]); 302 case WebInspector.Color.Format.HEX: 303 if (this.hasAlpha()) 304 return null; 305 return String.sprintf("#%s%s%s", toHexValue(this._rgba[0]), toHexValue(this._rgba[1]), toHexValue(this._rgba[2])).toUpperCase(); 306 case WebInspector.Color.Format.ShortHEX: 307 if (!this.canBeShortHex()) 308 return null; 309 return String.sprintf("#%s%s%s", toShortHexValue(this._rgba[0]), toShortHexValue(this._rgba[1]), toShortHexValue(this._rgba[2])).toUpperCase(); 310 case WebInspector.Color.Format.Nickname: 311 return this.nickname(); 312 } 313 314 return this._originalText; 315 }, 316 317 /** 318 * @return {!Array.<number>} 319 */ 320 _canonicalRGBA: function() 321 { 322 var rgba = new Array(3); 323 for (var i = 0; i < 3; ++i) 324 rgba[i] = Math.round(this._rgba[i] * 255); 325 if (this._rgba[3] !== 1) 326 rgba.push(this._rgba[3]); 327 return rgba; 328 }, 329 330 /** 331 * @return {?string} nickname 332 */ 333 nickname: function() 334 { 335 if (!WebInspector.Color._rgbaToNickname) { 336 WebInspector.Color._rgbaToNickname = {}; 337 for (var nickname in WebInspector.Color.Nicknames) { 338 var rgba = WebInspector.Color.Nicknames[nickname]; 339 WebInspector.Color._rgbaToNickname[rgba] = nickname; 340 } 341 } 342 343 return WebInspector.Color._rgbaToNickname[this._canonicalRGBA()] || null; 344 }, 345 346 /** 347 * @return {!DOMAgent.RGBA} 348 */ 349 toProtocolRGBA: function() 350 { 351 var rgba = this._canonicalRGBA(); 352 var result = { r: rgba[0], g: rgba[1], b: rgba[2] }; 353 if (rgba[3] !== 1) 354 result.a = rgba[3]; 355 return result; 356 }, 357 358 /** 359 * @return {!WebInspector.Color} 360 */ 361 invert: function() 362 { 363 var rgba = []; 364 rgba[0] = 1 - this._rgba[0]; 365 rgba[1] = 1 - this._rgba[1]; 366 rgba[2] = 1 - this._rgba[2]; 367 rgba[3] = this._rgba[3]; 368 return new WebInspector.Color(rgba); 369 }, 370 371 /** 372 * @param {number} alpha 373 * @return {!WebInspector.Color} 374 */ 375 setAlpha: function(alpha) 376 { 377 var rgba = this._rgba.slice(); 378 rgba[3] = alpha; 379 return new WebInspector.Color(rgba); 380 } 381 } 382 383 /** 384 * @param {string} value 385 * return {number} 386 */ 387 WebInspector.Color._parseRgbNumeric = function(value) 388 { 389 var parsed = parseInt(value, 10); 390 if (value.indexOf("%") !== -1) 391 parsed /= 100; 392 else 393 parsed /= 255; 394 return parsed; 395 } 396 397 /** 398 * @param {string} value 399 * return {number} 400 */ 401 WebInspector.Color._parseHueNumeric = function(value) 402 { 403 return isNaN(value) ? 0 : (parseFloat(value) / 360) % 1; 404 } 405 406 /** 407 * @param {string} value 408 * return {number} 409 */ 410 WebInspector.Color._parseSatLightNumeric = function(value) 411 { 412 return parseFloat(value) / 100; 413 } 414 415 /** 416 * @param {string} value 417 * return {number} 418 */ 419 WebInspector.Color._parseAlphaNumeric = function(value) 420 { 421 return isNaN(value) ? 0 : parseFloat(value); 422 } 423 424 /** 425 * @param {!Array.<number>} hsl 426 * @return {!Array.<number>} 427 */ 428 WebInspector.Color._hsl2rgb = function(hsl) 429 { 430 var h = hsl[0]; 431 var s = hsl[1]; 432 var l = hsl[2]; 433 434 function hue2rgb(p, q, h) 435 { 436 if (h < 0) 437 h += 1; 438 else if (h > 1) 439 h -= 1; 440 441 if ((h * 6) < 1) 442 return p + (q - p) * h * 6; 443 else if ((h * 2) < 1) 444 return q; 445 else if ((h * 3) < 2) 446 return p + (q - p) * ((2 / 3) - h) * 6; 447 else 448 return p; 449 } 450 451 if (s < 0) 452 s = 0; 453 454 if (l <= 0.5) 455 var q = l * (1 + s); 456 else 457 var q = l + s - (l * s); 458 459 var p = 2 * l - q; 460 461 var tr = h + (1 / 3); 462 var tg = h; 463 var tb = h - (1 / 3); 464 465 var r = hue2rgb(p, q, tr); 466 var g = hue2rgb(p, q, tg); 467 var b = hue2rgb(p, q, tb); 468 return [r, g, b, hsl[3]]; 469 } 470 471 WebInspector.Color.Nicknames = { 472 "aliceblue": [240,248,255], 473 "antiquewhite": [250,235,215], 474 "aquamarine": [127,255,212], 475 "azure": [240,255,255], 476 "beige": [245,245,220], 477 "bisque": [255,228,196], 478 "black": [0,0,0], 479 "blanchedalmond": [255,235,205], 480 "blue": [0,0,255], 481 "blueviolet": [138,43,226], 482 "brown": [165,42,42], 483 "burlywood": [222,184,135], 484 "cadetblue": [95,158,160], 485 "chartreuse": [127,255,0], 486 "chocolate": [210,105,30], 487 "coral": [255,127,80], 488 "cornflowerblue": [100,149,237], 489 "cornsilk": [255,248,220], 490 "crimson": [237,20,61], 491 "cyan": [0,255,255], 492 "darkblue": [0,0,139], 493 "darkcyan": [0,139,139], 494 "darkgoldenrod": [184,134,11], 495 "darkgray": [169,169,169], 496 "darkgreen": [0,100,0], 497 "darkkhaki": [189,183,107], 498 "darkmagenta": [139,0,139], 499 "darkolivegreen": [85,107,47], 500 "darkorange": [255,140,0], 501 "darkorchid": [153,50,204], 502 "darkred": [139,0,0], 503 "darksalmon": [233,150,122], 504 "darkseagreen": [143,188,143], 505 "darkslateblue": [72,61,139], 506 "darkslategray": [47,79,79], 507 "darkturquoise": [0,206,209], 508 "darkviolet": [148,0,211], 509 "deeppink": [255,20,147], 510 "deepskyblue": [0,191,255], 511 "dimgray": [105,105,105], 512 "dodgerblue": [30,144,255], 513 "firebrick": [178,34,34], 514 "floralwhite": [255,250,240], 515 "forestgreen": [34,139,34], 516 "gainsboro": [220,220,220], 517 "ghostwhite": [248,248,255], 518 "gold": [255,215,0], 519 "goldenrod": [218,165,32], 520 "gray": [128,128,128], 521 "green": [0,128,0], 522 "greenyellow": [173,255,47], 523 "honeydew": [240,255,240], 524 "hotpink": [255,105,180], 525 "indianred": [205,92,92], 526 "indigo": [75,0,130], 527 "ivory": [255,255,240], 528 "khaki": [240,230,140], 529 "lavender": [230,230,250], 530 "lavenderblush": [255,240,245], 531 "lawngreen": [124,252,0], 532 "lemonchiffon": [255,250,205], 533 "lightblue": [173,216,230], 534 "lightcoral": [240,128,128], 535 "lightcyan": [224,255,255], 536 "lightgoldenrodyellow":[250,250,210], 537 "lightgreen": [144,238,144], 538 "lightgrey": [211,211,211], 539 "lightpink": [255,182,193], 540 "lightsalmon": [255,160,122], 541 "lightseagreen": [32,178,170], 542 "lightskyblue": [135,206,250], 543 "lightslategray": [119,136,153], 544 "lightsteelblue": [176,196,222], 545 "lightyellow": [255,255,224], 546 "lime": [0,255,0], 547 "limegreen": [50,205,50], 548 "linen": [250,240,230], 549 "magenta": [255,0,255], 550 "maroon": [128,0,0], 551 "mediumaquamarine": [102,205,170], 552 "mediumblue": [0,0,205], 553 "mediumorchid": [186,85,211], 554 "mediumpurple": [147,112,219], 555 "mediumseagreen": [60,179,113], 556 "mediumslateblue": [123,104,238], 557 "mediumspringgreen": [0,250,154], 558 "mediumturquoise": [72,209,204], 559 "mediumvioletred": [199,21,133], 560 "midnightblue": [25,25,112], 561 "mintcream": [245,255,250], 562 "mistyrose": [255,228,225], 563 "moccasin": [255,228,181], 564 "navajowhite": [255,222,173], 565 "navy": [0,0,128], 566 "oldlace": [253,245,230], 567 "olive": [128,128,0], 568 "olivedrab": [107,142,35], 569 "orange": [255,165,0], 570 "orangered": [255,69,0], 571 "orchid": [218,112,214], 572 "palegoldenrod": [238,232,170], 573 "palegreen": [152,251,152], 574 "paleturquoise": [175,238,238], 575 "palevioletred": [219,112,147], 576 "papayawhip": [255,239,213], 577 "peachpuff": [255,218,185], 578 "peru": [205,133,63], 579 "pink": [255,192,203], 580 "plum": [221,160,221], 581 "powderblue": [176,224,230], 582 "purple": [128,0,128], 583 "red": [255,0,0], 584 "rosybrown": [188,143,143], 585 "royalblue": [65,105,225], 586 "saddlebrown": [139,69,19], 587 "salmon": [250,128,114], 588 "sandybrown": [244,164,96], 589 "seagreen": [46,139,87], 590 "seashell": [255,245,238], 591 "sienna": [160,82,45], 592 "silver": [192,192,192], 593 "skyblue": [135,206,235], 594 "slateblue": [106,90,205], 595 "slategray": [112,128,144], 596 "snow": [255,250,250], 597 "springgreen": [0,255,127], 598 "steelblue": [70,130,180], 599 "tan": [210,180,140], 600 "teal": [0,128,128], 601 "thistle": [216,191,216], 602 "tomato": [255,99,71], 603 "turquoise": [64,224,208], 604 "violet": [238,130,238], 605 "wheat": [245,222,179], 606 "white": [255,255,255], 607 "whitesmoke": [245,245,245], 608 "yellow": [255,255,0], 609 "yellowgreen": [154,205,50], 610 "transparent": [0, 0, 0, 0], 611 }; 612 613 WebInspector.Color.PageHighlight = { 614 Content: WebInspector.Color.fromRGBA([111, 168, 220, .66]), 615 ContentLight: WebInspector.Color.fromRGBA([111, 168, 220, .5]), 616 ContentOutline: WebInspector.Color.fromRGBA([9, 83, 148]), 617 Padding: WebInspector.Color.fromRGBA([147, 196, 125, .55]), 618 PaddingLight: WebInspector.Color.fromRGBA([147, 196, 125, .4]), 619 Border: WebInspector.Color.fromRGBA([255, 229, 153, .66]), 620 BorderLight: WebInspector.Color.fromRGBA([255, 229, 153, .5]), 621 Margin: WebInspector.Color.fromRGBA([246, 178, 107, .66]), 622 MarginLight: WebInspector.Color.fromRGBA([246, 178, 107, .5]), 623 EventTarget: WebInspector.Color.fromRGBA([255, 196, 196, .66]) 624 } 625 626 WebInspector.Color.Format = { 627 Original: "original", 628 Nickname: "nickname", 629 HEX: "hex", 630 ShortHEX: "shorthex", 631 RGB: "rgb", 632 RGBA: "rgba", 633 HSL: "hsl", 634 HSLA: "hsla" 635 } 636