1: | <?php |
2: | |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | |
16: | |
17: | |
18: | |
19: | |
20: | |
21: | |
22: | |
23: | |
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: | |
185: | |
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: | |
301: | if (isset(self::$color_map[$value])) { |
302: | if (!$this->setValuesFromHex(self::$color_map[$value])) { |
303: | return; |
304: | } |
305: | |
306: | $variant = self::COLOR_NAME; |
307: | } elseif ('#' === $value[0]) { |
308: | $variant = $this->setValuesFromHex(\substr($value, 1)); |
309: | |
310: | if (!$variant) { |
311: | return; |
312: | } |
313: | } else { |
314: | $variant = $this->setValuesFromFunction($value); |
315: | |
316: | if (!$variant) { |
317: | return; |
318: | } |
319: | } |
320: | |
321: | |
322: | if ($this->r > 0xFF || $this->g > 0xFF || $this->b > 0xFF || $this->a > 1) { |
323: | $this->variant = null; |
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: | |
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: | |
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; |
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: | |
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: | |
465: | |
466: | |
467: | |
468: | |
469: | |
470: | |
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: | |
498: | |
499: | |
500: | |
501: | |
502: | |
503: | |
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: | |
553: | |
554: | |
555: | |
556: | |
557: | |
558: | |
559: | |
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: | |