| 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: | |