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