Passed
Push — master ( ba645d...9eba8c )
by Michael
07:09 queued 43s
created

ColorRepresentation   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 545
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 383
c 1
b 0
f 0
dl 0
loc 545
rs 2
wmc 97

9 Methods

Rating   Name   Duplication   Size   Complexity  
A hslToRgb() 0 21 5
A __construct() 0 6 1
D getColor() 0 62 21
A hueToRgb() 0 14 6
B rgbToHsl() 0 43 8
B hasAlpha() 0 18 9
B setValuesFromHex() 0 44 10
F setValuesFromFunction() 0 82 27
B setValues() 0 33 10

How to fix   Complexity   

Complex Class

Complex classes like ColorRepresentation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ColorRepresentation, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2013 Jonathan Vollebregt ([email protected]), Rokas Šleinius ([email protected])
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
9
 * this software and associated documentation files (the "Software"), to deal in
10
 * the Software without restriction, including without limitation the rights to
11
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12
 * the Software, and to permit persons to whom the Software is furnished to do so,
13
 * subject to the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be included in all
16
 * copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
 */
25
26
namespace Kint\Object\Representation;
27
28
use InvalidArgumentException;
29
30
class ColorRepresentation extends Representation
31
{
32
    const COLOR_NAME = 1;
33
    const COLOR_HEX_3 = 2;
34
    const COLOR_HEX_6 = 3;
35
    const COLOR_RGB = 4;
36
    const COLOR_RGBA = 5;
37
    const COLOR_HSL = 6;
38
    const COLOR_HSLA = 7;
39
    const COLOR_HEX_4 = 8;
40
    const COLOR_HEX_8 = 9;
41
42
    public static $color_map = array(
43
        'aliceblue' => 'f0f8ff',
44
        'antiquewhite' => 'faebd7',
45
        'aqua' => '00ffff',
46
        'aquamarine' => '7fffd4',
47
        'azure' => 'f0ffff',
48
        'beige' => 'f5f5dc',
49
        'bisque' => 'ffe4c4',
50
        'black' => '000000',
51
        'blanchedalmond' => 'ffebcd',
52
        'blue' => '0000ff',
53
        'blueviolet' => '8a2be2',
54
        'brown' => 'a52a2a',
55
        'burlywood' => 'deb887',
56
        'cadetblue' => '5f9ea0',
57
        'chartreuse' => '7fff00',
58
        'chocolate' => 'd2691e',
59
        'coral' => 'ff7f50',
60
        'cornflowerblue' => '6495ed',
61
        'cornsilk' => 'fff8dc',
62
        'crimson' => 'dc143c',
63
        'cyan' => '00ffff',
64
        'darkblue' => '00008b',
65
        'darkcyan' => '008b8b',
66
        'darkgoldenrod' => 'b8860b',
67
        'darkgray' => 'a9a9a9',
68
        'darkgreen' => '006400',
69
        'darkgrey' => 'a9a9a9',
70
        'darkkhaki' => 'bdb76b',
71
        'darkmagenta' => '8b008b',
72
        'darkolivegreen' => '556b2f',
73
        'darkorange' => 'ff8c00',
74
        'darkorchid' => '9932cc',
75
        'darkred' => '8b0000',
76
        'darksalmon' => 'e9967a',
77
        'darkseagreen' => '8fbc8f',
78
        'darkslateblue' => '483d8b',
79
        'darkslategray' => '2f4f4f',
80
        'darkslategrey' => '2f4f4f',
81
        'darkturquoise' => '00ced1',
82
        'darkviolet' => '9400d3',
83
        'deeppink' => 'ff1493',
84
        'deepskyblue' => '00bfff',
85
        'dimgray' => '696969',
86
        'dimgrey' => '696969',
87
        'dodgerblue' => '1e90ff',
88
        'firebrick' => 'b22222',
89
        'floralwhite' => 'fffaf0',
90
        'forestgreen' => '228b22',
91
        'fuchsia' => 'ff00ff',
92
        'gainsboro' => 'dcdcdc',
93
        'ghostwhite' => 'f8f8ff',
94
        'gold' => 'ffd700',
95
        'goldenrod' => 'daa520',
96
        'gray' => '808080',
97
        'green' => '008000',
98
        'greenyellow' => 'adff2f',
99
        'grey' => '808080',
100
        'honeydew' => 'f0fff0',
101
        'hotpink' => 'ff69b4',
102
        'indianred' => 'cd5c5c',
103
        'indigo' => '4b0082',
104
        'ivory' => 'fffff0',
105
        'khaki' => 'f0e68c',
106
        'lavender' => 'e6e6fa',
107
        'lavenderblush' => 'fff0f5',
108
        'lawngreen' => '7cfc00',
109
        'lemonchiffon' => 'fffacd',
110
        'lightblue' => 'add8e6',
111
        'lightcoral' => 'f08080',
112
        'lightcyan' => 'e0ffff',
113
        'lightgoldenrodyellow' => 'fafad2',
114
        'lightgray' => 'd3d3d3',
115
        'lightgreen' => '90ee90',
116
        'lightgrey' => 'd3d3d3',
117
        'lightpink' => 'ffb6c1',
118
        'lightsalmon' => 'ffa07a',
119
        'lightseagreen' => '20b2aa',
120
        'lightskyblue' => '87cefa',
121
        'lightslategray' => '778899',
122
        'lightslategrey' => '778899',
123
        'lightsteelblue' => 'b0c4de',
124
        'lightyellow' => 'ffffe0',
125
        'lime' => '00ff00',
126
        'limegreen' => '32cd32',
127
        'linen' => 'faf0e6',
128
        'magenta' => 'ff00ff',
129
        'maroon' => '800000',
130
        'mediumaquamarine' => '66cdaa',
131
        'mediumblue' => '0000cd',
132
        'mediumorchid' => 'ba55d3',
133
        'mediumpurple' => '9370db',
134
        'mediumseagreen' => '3cb371',
135
        'mediumslateblue' => '7b68ee',
136
        'mediumspringgreen' => '00fa9a',
137
        'mediumturquoise' => '48d1cc',
138
        'mediumvioletred' => 'c71585',
139
        'midnightblue' => '191970',
140
        'mintcream' => 'f5fffa',
141
        'mistyrose' => 'ffe4e1',
142
        'moccasin' => 'ffe4b5',
143
        'navajowhite' => 'ffdead',
144
        'navy' => '000080',
145
        'oldlace' => 'fdf5e6',
146
        'olive' => '808000',
147
        'olivedrab' => '6b8e23',
148
        'orange' => 'ffa500',
149
        'orangered' => 'ff4500',
150
        'orchid' => 'da70d6',
151
        'palegoldenrod' => 'eee8aa',
152
        'palegreen' => '98fb98',
153
        'paleturquoise' => 'afeeee',
154
        'palevioletred' => 'db7093',
155
        'papayawhip' => 'ffefd5',
156
        'peachpuff' => 'ffdab9',
157
        'peru' => 'cd853f',
158
        'pink' => 'ffc0cb',
159
        'plum' => 'dda0dd',
160
        'powderblue' => 'b0e0e6',
161
        'purple' => '800080',
162
        'rebeccapurple' => '663399',
163
        'red' => 'ff0000',
164
        'rosybrown' => 'bc8f8f',
165
        'royalblue' => '4169e1',
166
        'saddlebrown' => '8b4513',
167
        'salmon' => 'fa8072',
168
        'sandybrown' => 'f4a460',
169
        'seagreen' => '2e8b57',
170
        'seashell' => 'fff5ee',
171
        'sienna' => 'a0522d',
172
        'silver' => 'c0c0c0',
173
        'skyblue' => '87ceeb',
174
        'slateblue' => '6a5acd',
175
        'slategray' => '708090',
176
        'slategrey' => '708090',
177
        'snow' => 'fffafa',
178
        'springgreen' => '00ff7f',
179
        'steelblue' => '4682b4',
180
        'tan' => 'd2b48c',
181
        'teal' => '008080',
182
        'thistle' => 'd8bfd8',
183
        'tomato' => 'ff6347',
184
        // To quote MDN:
185
        // "Technically, transparent is a shortcut for rgba(0,0,0,0)."
186
        'transparent' => '00000000',
187
        'turquoise' => '40e0d0',
188
        'violet' => 'ee82ee',
189
        'wheat' => 'f5deb3',
190
        'white' => 'ffffff',
191
        'whitesmoke' => 'f5f5f5',
192
        'yellow' => 'ffff00',
193
        'yellowgreen' => '9acd32',
194
    );
195
196
    public $r = 0;
197
    public $g = 0;
198
    public $b = 0;
199
    public $a = 1.0;
200
    public $variant;
201
    public $implicit_label = true;
202
    public $hints = array('color');
203
204
    public function __construct($value)
205
    {
206
        parent::__construct('Color');
207
208
        $this->contents = $value;
209
        $this->setValues($value);
210
    }
211
212
    public function getColor($variant = null)
213
    {
214
        if (!$variant) {
215
            $variant = $this->variant;
216
        }
217
218
        switch ($variant) {
219
            case self::COLOR_NAME:
220
                $hex = \sprintf('%02x%02x%02x', $this->r, $this->g, $this->b);
221
                $hex_alpha = \sprintf('%02x%02x%02x%02x', $this->r, $this->g, $this->b, \round($this->a * 0xFF));
222
223
                return \array_search($hex, self::$color_map, true) ?: \array_search($hex_alpha, self::$color_map, true);
224
           case self::COLOR_HEX_3:
225
                if (0 === $this->r % 0x11 && 0 === $this->g % 0x11 && 0 === $this->b % 0x11) {
226
                    return \sprintf(
227
                        '#%1X%1X%1X',
228
                        \round($this->r / 0x11),
229
                        \round($this->g / 0x11),
230
                        \round($this->b / 0x11)
231
                    );
232
                }
233
234
                return false;
235
            case self::COLOR_HEX_6:
236
                return \sprintf('#%02X%02X%02X', $this->r, $this->g, $this->b);
237
            case self::COLOR_RGB:
238
                if (1.0 === $this->a) {
239
                    return \sprintf('rgb(%d, %d, %d)', $this->r, $this->g, $this->b);
240
                }
241
242
                return \sprintf('rgb(%d, %d, %d, %s)', $this->r, $this->g, $this->b, \round($this->a, 4));
243
            case self::COLOR_RGBA:
244
                return \sprintf('rgba(%d, %d, %d, %s)', $this->r, $this->g, $this->b, \round($this->a, 4));
245
            case self::COLOR_HSL:
246
                $val = self::rgbToHsl($this->r, $this->g, $this->b);
247
                if (1.0 === $this->a) {
248
                    return \vsprintf('hsl(%d, %d%%, %d%%)', $val);
249
                }
250
251
                return \sprintf('hsl(%d, %d%%, %d%%, %s)', $val[0], $val[1], $val[2], \round($this->a, 4));
252
            case self::COLOR_HSLA:
253
                $val = self::rgbToHsl($this->r, $this->g, $this->b);
254
255
                return \sprintf('hsla(%d, %d%%, %d%%, %s)', $val[0], $val[1], $val[2], \round($this->a, 4));
256
            case self::COLOR_HEX_4:
257
                if (0 === $this->r % 0x11 && 0 === $this->g % 0x11 && 0 === $this->b % 0x11 && 0 === ($this->a * 255) % 0x11) {
258
                    return \sprintf(
259
                        '#%1X%1X%1X%1X',
260
                        \round($this->r / 0x11),
261
                        \round($this->g / 0x11),
262
                        \round($this->b / 0x11),
263
                        \round($this->a * 0xF)
264
                    );
265
                }
266
267
                return false;
268
269
            case self::COLOR_HEX_8:
270
                return \sprintf('#%02X%02X%02X%02X', $this->r, $this->g, $this->b, \round($this->a * 0xFF));
271
        }
272
273
        return false;
274
    }
275
276
    public function hasAlpha($variant = null)
277
    {
278
        if (null === $variant) {
279
            $variant = $this->variant;
280
        }
281
282
        switch ($variant) {
283
            case self::COLOR_NAME:
284
            case self::COLOR_RGB:
285
            case self::COLOR_HSL:
286
                return \abs($this->a - 1) >= 0.0001;
287
            case self::COLOR_RGBA:
288
            case self::COLOR_HSLA:
289
            case self::COLOR_HEX_4:
290
            case self::COLOR_HEX_8:
291
                return true;
292
            default:
293
                return false;
294
        }
295
    }
296
297
    protected function setValues($value)
298
    {
299
        $value = \strtolower(\trim($value));
300
        // Find out which variant of color input it is
301
        if (isset(self::$color_map[$value])) {
302
            if (!$this->setValuesFromHex(self::$color_map[$value])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->setValuesFromHex(self::color_map[$value]) of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
303
                return;
304
            }
305
306
            $variant = self::COLOR_NAME;
307
        } elseif ('#' === $value[0]) {
308
            $variant = $this->setValuesFromHex(\substr($value, 1));
309
310
            if (!$variant) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $variant of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
311
                return;
312
            }
313
        } else {
314
            $variant = $this->setValuesFromFunction($value);
315
316
            if (!$variant) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $variant of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
317
                return;
318
            }
319
        }
320
321
        // If something has gone horribly wrong
322
        if ($this->r > 0xFF || $this->g > 0xFF || $this->b > 0xFF || $this->a > 1) {
323
            $this->variant = null; // @codeCoverageIgnore
324
        } else {
325
            $this->variant = $variant;
326
            $this->r = (int) $this->r;
327
            $this->g = (int) $this->g;
328
            $this->b = (int) $this->b;
329
            $this->a = (float) $this->a;
330
        }
331
    }
332
333
    protected function setValuesFromHex($hex)
334
    {
335
        if (!\ctype_xdigit($hex)) {
336
            return null;
337
        }
338
339
        switch (\strlen($hex)) {
340
            case 3:
341
                $variant = self::COLOR_HEX_3;
342
                break;
343
            case 6:
344
                $variant = self::COLOR_HEX_6;
345
                break;
346
            case 4:
347
                $variant = self::COLOR_HEX_4;
348
                break;
349
            case 8:
350
                $variant = self::COLOR_HEX_8;
351
                break;
352
            default:
353
                return null;
354
        }
355
356
        switch ($variant) {
357
            case self::COLOR_HEX_4:
358
                $this->a = \hexdec($hex[3]) / 0xF;
359
                // no break
360
            case self::COLOR_HEX_3:
361
                $this->r = \hexdec($hex[0]) * 0x11;
362
                $this->g = \hexdec($hex[1]) * 0x11;
363
                $this->b = \hexdec($hex[2]) * 0x11;
364
                break;
365
            case self::COLOR_HEX_8:
366
                $this->a = \hexdec(\substr($hex, 6, 2)) / 0xFF;
367
                // no break
368
            case self::COLOR_HEX_6:
369
                $hex = \str_split($hex, 2);
370
                $this->r = \hexdec($hex[0]);
371
                $this->g = \hexdec($hex[1]);
372
                $this->b = \hexdec($hex[2]);
373
                break;
374
        }
375
376
        return $variant;
377
    }
378
379
    protected function setValuesFromFunction($value)
380
    {
381
        if (!\preg_match('/^((?:rgb|hsl)a?)\\s*\\(([0-9\\.%,\\s\\/\\-]+)\\)$/i', $value, $match)) {
382
            return null;
383
        }
384
385
        switch (\strtolower($match[1])) {
386
            case 'rgb':
387
                $variant = self::COLOR_RGB;
388
                break;
389
            case 'rgba':
390
                $variant = self::COLOR_RGBA;
391
                break;
392
            case 'hsl':
393
                $variant = self::COLOR_HSL;
394
                break;
395
            case 'hsla':
396
                $variant = self::COLOR_HSLA;
397
                break;
398
            default:
399
                return null; // @codeCoverageIgnore
400
        }
401
402
        $params = \preg_replace('/[,\\s\\/]+/', ',', \trim($match[2]));
403
        $params = \explode(',', $params);
404
        $params = \array_map('trim', $params);
405
406
        if (\count($params) < 3 || \count($params) > 4) {
407
            return null;
408
        }
409
410
        foreach ($params as $i => &$color) {
411
            if (false !== \strpos($color, '%')) {
412
                $color = (float) \str_replace('%', '', $color);
413
414
                if (3 === $i) {
415
                    $color = $color / 100;
416
                } elseif (\in_array($variant, array(self::COLOR_RGB, self::COLOR_RGBA), true)) {
417
                    $color = \round($color / 100 * 0xFF);
418
                }
419
            }
420
421
            $color = (float) $color;
422
423
            if (0 === $i && \in_array($variant, array(self::COLOR_HSL, self::COLOR_HSLA), true)) {
424
                $color = ($color % 360 + 360) % 360;
425
            }
426
        }
427
428
        /** @var float[] Psalm bug workaround */
429
        $params = \array_map('floatval', $params);
430
431
        switch ($variant) {
432
            case self::COLOR_RGBA:
433
            case self::COLOR_RGB:
434
                if (\min($params) < 0 || \max($params) > 0xFF) {
435
                    return null;
436
                }
437
                break;
438
            case self::COLOR_HSLA:
439
            case self::COLOR_HSL:
440
                if (\min($params) < 0 || $params[0] > 360 || \max($params[1], $params[2]) > 100) {
441
                    return null;
442
                }
443
                break;
444
        }
445
446
        if (4 === \count($params)) {
447
            if ($params[3] > 1) {
448
                return null;
449
            }
450
451
            $this->a = $params[3];
452
        }
453
454
        if (self::COLOR_HSLA === $variant || self::COLOR_HSL === $variant) {
455
            $params = self::hslToRgb($params[0], $params[1], $params[2]);
456
        }
457
458
        list($this->r, $this->g, $this->b) = $params;
459
460
        return $variant;
461
    }
462
463
    /**
464
     * Turns HSL color to RGB. Black magic.
465
     *
466
     * @param float $h Hue
467
     * @param float $s Saturation
468
     * @param float $l Lightness
469
     *
470
     * @return int[] RGB array
471
     */
472
    public static function hslToRgb($h, $s, $l)
473
    {
474
        if (\min($h, $s, $l) < 0) {
475
            throw new InvalidArgumentException('The parameters for hslToRgb should be no less than 0');
476
        }
477
478
        if ($h > 360 || \max($s, $l) > 100) {
479
            throw new InvalidArgumentException('The parameters for hslToRgb should be no more than 360, 100, and 100 respectively');
480
        }
481
482
        $h /= 360;
483
        $s /= 100;
484
        $l /= 100;
485
486
        $m2 = ($l <= 0.5) ? $l * ($s + 1) : $l + $s - $l * $s;
487
        $m1 = $l * 2 - $m2;
488
489
        return array(
490
            (int) \round(self::hueToRgb($m1, $m2, $h + 1 / 3) * 0xFF),
491
            (int) \round(self::hueToRgb($m1, $m2, $h) * 0xFF),
492
            (int) \round(self::hueToRgb($m1, $m2, $h - 1 / 3) * 0xFF),
493
        );
494
    }
495
496
    /**
497
     * Converts RGB to HSL. Color inversion of previous black magic is white magic?
498
     *
499
     * @param float|int $red   Red
500
     * @param float|int $green Green
501
     * @param float|int $blue  Blue
502
     *
503
     * @return float[] HSL array
504
     */
505
    public static function rgbToHsl($red, $green, $blue)
506
    {
507
        if (\min($red, $green, $blue) < 0) {
508
            throw new InvalidArgumentException('The parameters for rgbToHsl should be no less than 0');
509
        }
510
511
        if (\max($red, $green, $blue) > 0xFF) {
512
            throw new InvalidArgumentException('The parameters for rgbToHsl should be no more than 255');
513
        }
514
515
        $clrMin = \min($red, $green, $blue);
516
        $clrMax = \max($red, $green, $blue);
517
        $deltaMax = $clrMax - $clrMin;
518
519
        $L = ($clrMax + $clrMin) / 510;
520
521
        if (0 == $deltaMax) {
522
            $H = 0;
523
            $S = 0;
524
        } else {
525
            if (0.5 > $L) {
526
                $S = $deltaMax / ($clrMax + $clrMin);
527
            } else {
528
                $S = $deltaMax / (510 - $clrMax - $clrMin);
529
            }
530
531
            if ($clrMax === $red) {
532
                $H = ($green - $blue) / (6.0 * $deltaMax);
533
534
                if (0 > $H) {
535
                    $H += 1.0;
536
                }
537
            } elseif ($clrMax === $green) {
538
                $H = 1 / 3 + ($blue - $red) / (6.0 * $deltaMax);
539
            } else {
540
                $H = 2 / 3 + ($red - $green) / (6.0 * $deltaMax);
541
            }
542
        }
543
544
        return array(
545
            (float) ($H * 360 % 360),
546
            (float) ($S * 100),
547
            (float) ($L * 100),
548
        );
549
    }
550
551
    /**
552
     * Helper function for hslToRgb. Even blacker magic.
553
     *
554
     *
555
     * @param float $m1
556
     * @param float $m2
557
     * @param float $hue
558
     *
559
     * @return float Color value
560
     */
561
    private static function hueToRgb($m1, $m2, $hue)
562
    {
563
        $hue = ($hue < 0) ? $hue + 1 : (($hue > 1) ? $hue - 1 : $hue);
564
        if ($hue * 6 < 1) {
565
            return $m1 + ($m2 - $m1) * $hue * 6;
566
        }
567
        if ($hue * 2 < 1) {
568
            return $m2;
569
        }
570
        if ($hue * 3 < 2) {
571
            return $m1 + ($m2 - $m1) * (2 / 3 - $hue) * 6;
572
        }
573
574
        return $m1;
575
    }
576
}
577