You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Color.php 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. <?php
  2. /**
  3. * Author: Arlo Carreon <http://arlocarreon.com>
  4. * Info: http://mexitek.github.io/phpColors/
  5. * License: http://arlo.mit-license.org/
  6. */
  7. namespace Mexitek\PHPColors;
  8. use Exception;
  9. /**
  10. * A color utility that helps manipulate HEX colors
  11. */
  12. class Color
  13. {
  14. /**
  15. * @var string
  16. */
  17. private $_hex;
  18. /**
  19. * @var array
  20. */
  21. private $_hsl;
  22. /**
  23. * @var array
  24. */
  25. private $_rgb;
  26. /**
  27. * Auto darkens/lightens by 10% for sexily-subtle gradients.
  28. * Set this to FALSE to adjust automatic shade to be between given color
  29. * and black (for darken) or white (for lighten)
  30. */
  31. public const DEFAULT_ADJUST = 10;
  32. /**
  33. * Instantiates the class with a HEX value
  34. * @param string $hex
  35. * @throws Exception
  36. */
  37. public function __construct(string $hex)
  38. {
  39. $color = self::sanitizeHex($hex);
  40. $this->_hex = $color;
  41. $this->_hsl = self::hexToHsl($color);
  42. $this->_rgb = self::hexToRgb($color);
  43. }
  44. /**
  45. * Given a HEX string returns a HSL array equivalent.
  46. * @param string $color
  47. * @return array HSL associative array
  48. * @throws Exception
  49. */
  50. public static function hexToHsl(string $color): array
  51. {
  52. // Sanity check
  53. $color = self::sanitizeHex($color);
  54. // Convert HEX to DEC
  55. $R = hexdec($color[0] . $color[1]);
  56. $G = hexdec($color[2] . $color[3]);
  57. $B = hexdec($color[4] . $color[5]);
  58. $HSL = array();
  59. $var_R = ($R / 255);
  60. $var_G = ($G / 255);
  61. $var_B = ($B / 255);
  62. $var_Min = min($var_R, $var_G, $var_B);
  63. $var_Max = max($var_R, $var_G, $var_B);
  64. $del_Max = $var_Max - $var_Min;
  65. $L = ($var_Max + $var_Min) / 2;
  66. if ($del_Max == 0) {
  67. $H = 0;
  68. $S = 0;
  69. } else {
  70. if ($L < 0.5) {
  71. $S = $del_Max / ($var_Max + $var_Min);
  72. } else {
  73. $S = $del_Max / (2 - $var_Max - $var_Min);
  74. }
  75. $del_R = ((($var_Max - $var_R) / 6) + ($del_Max / 2)) / $del_Max;
  76. $del_G = ((($var_Max - $var_G) / 6) + ($del_Max / 2)) / $del_Max;
  77. $del_B = ((($var_Max - $var_B) / 6) + ($del_Max / 2)) / $del_Max;
  78. if ($var_R == $var_Max) {
  79. $H = $del_B - $del_G;
  80. } elseif ($var_G == $var_Max) {
  81. $H = (1 / 3) + $del_R - $del_B;
  82. } elseif ($var_B == $var_Max) {
  83. $H = (2 / 3) + $del_G - $del_R;
  84. }
  85. if ($H < 0) {
  86. $H++;
  87. }
  88. if ($H > 1) {
  89. $H--;
  90. }
  91. }
  92. $HSL['H'] = ($H * 360);
  93. $HSL['S'] = $S;
  94. $HSL['L'] = $L;
  95. return $HSL;
  96. }
  97. /**
  98. * Given a HSL associative array returns the equivalent HEX string
  99. * @param array $hsl
  100. * @return string HEX string
  101. * @throws Exception "Bad HSL Array"
  102. */
  103. public static function hslToHex(array $hsl = array()): string
  104. {
  105. // Make sure it's HSL
  106. if (empty($hsl) || !isset($hsl["H"], $hsl["S"], $hsl["L"])) {
  107. throw new Exception("Param was not an HSL array");
  108. }
  109. list($H, $S, $L) = array($hsl['H'] / 360, $hsl['S'], $hsl['L']);
  110. if ($S == 0) {
  111. $r = $L * 255;
  112. $g = $L * 255;
  113. $b = $L * 255;
  114. } else {
  115. if ($L < 0.5) {
  116. $var_2 = $L * (1 + $S);
  117. } else {
  118. $var_2 = ($L + $S) - ($S * $L);
  119. }
  120. $var_1 = 2 * $L - $var_2;
  121. $r = 255 * self::hueToRgb($var_1, $var_2, $H + (1 / 3));
  122. $g = 255 * self::hueToRgb($var_1, $var_2, $H);
  123. $b = 255 * self::hueToRgb($var_1, $var_2, $H - (1 / 3));
  124. }
  125. // Convert to hex
  126. $r = dechex(round($r));
  127. $g = dechex(round($g));
  128. $b = dechex(round($b));
  129. // Make sure we get 2 digits for decimals
  130. $r = (strlen("" . $r) === 1) ? "0" . $r : $r;
  131. $g = (strlen("" . $g) === 1) ? "0" . $g : $g;
  132. $b = (strlen("" . $b) === 1) ? "0" . $b : $b;
  133. return $r . $g . $b;
  134. }
  135. /**
  136. * Given a HEX string returns a RGB array equivalent.
  137. * @param string $color
  138. * @return array RGB associative array
  139. * @throws Exception
  140. */
  141. public static function hexToRgb(string $color): array
  142. {
  143. // Sanity check
  144. $color = self::sanitizeHex($color);
  145. // Convert HEX to DEC
  146. $R = hexdec($color[0] . $color[1]);
  147. $G = hexdec($color[2] . $color[3]);
  148. $B = hexdec($color[4] . $color[5]);
  149. $RGB['R'] = $R;
  150. $RGB['G'] = $G;
  151. $RGB['B'] = $B;
  152. return $RGB;
  153. }
  154. /**
  155. * Given an RGB associative array returns the equivalent HEX string
  156. * @param array $rgb
  157. * @return string Hex string
  158. * @throws Exception "Bad RGB Array"
  159. */
  160. public static function rgbToHex(array $rgb = array()): string
  161. {
  162. // Make sure it's RGB
  163. if (empty($rgb) || !isset($rgb["R"], $rgb["G"], $rgb["B"])) {
  164. throw new Exception("Param was not an RGB array");
  165. }
  166. // https://github.com/mexitek/phpColors/issues/25#issuecomment-88354815
  167. // Convert RGB to HEX
  168. $hex[0] = str_pad(dechex((int)$rgb['R']), 2, '0', STR_PAD_LEFT);
  169. $hex[1] = str_pad(dechex((int)$rgb['G']), 2, '0', STR_PAD_LEFT);
  170. $hex[2] = str_pad(dechex((int)$rgb['B']), 2, '0', STR_PAD_LEFT);
  171. // Make sure that 2 digits are allocated to each color.
  172. $hex[0] = (strlen($hex[0]) === 1) ? '0' . $hex[0] : $hex[0];
  173. $hex[1] = (strlen($hex[1]) === 1) ? '0' . $hex[1] : $hex[1];
  174. $hex[2] = (strlen($hex[2]) === 1) ? '0' . $hex[2] : $hex[2];
  175. return implode('', $hex);
  176. }
  177. /**
  178. * Given an RGB associative array, returns CSS string output.
  179. * @param array $rgb
  180. * @return string rgb(r,g,b) string
  181. * @throws Exception "Bad RGB Array"
  182. */
  183. public static function rgbToString(array $rgb = array()): string
  184. {
  185. // Make sure it's RGB
  186. if (empty($rgb) || !isset($rgb["R"], $rgb["G"], $rgb["B"])) {
  187. throw new Exception("Param was not an RGB array");
  188. }
  189. return 'rgb(' .
  190. $rgb['R'] . ', ' .
  191. $rgb['G'] . ', ' .
  192. $rgb['B'] . ')';
  193. }
  194. /**
  195. * Given a standard color name, return hex code
  196. *
  197. * @param string $color_name
  198. * @return string
  199. */
  200. public static function nameToHex(string $color_name): string
  201. {
  202. $colors = array(
  203. 'aliceblue' => 'F0F8FF',
  204. 'antiquewhite' => 'FAEBD7',
  205. 'aqua' => '00FFFF',
  206. 'aquamarine' => '7FFFD4',
  207. 'azure' => 'F0FFFF',
  208. 'beige' => 'F5F5DC',
  209. 'bisque' => 'FFE4C4',
  210. 'black' => '000000',
  211. 'blanchedalmond' => 'FFEBCD',
  212. 'blue' => '0000FF',
  213. 'blueviolet' => '8A2BE2',
  214. 'brown' => 'A52A2A',
  215. 'burlywood' => 'DEB887',
  216. 'cadetblue' => '5F9EA0',
  217. 'chartreuse' => '7FFF00',
  218. 'chocolate' => 'D2691E',
  219. 'coral' => 'FF7F50',
  220. 'cornflowerblue' => '6495ED',
  221. 'cornsilk' => 'FFF8DC',
  222. 'crimson' => 'DC143C',
  223. 'cyan' => '00FFFF',
  224. 'darkblue' => '00008B',
  225. 'darkcyan' => '008B8B',
  226. 'darkgoldenrod' => 'B8860B',
  227. 'darkgray' => 'A9A9A9',
  228. 'darkgreen' => '006400',
  229. 'darkgrey' => 'A9A9A9',
  230. 'darkkhaki' => 'BDB76B',
  231. 'darkmagenta' => '8B008B',
  232. 'darkolivegreen' => '556B2F',
  233. 'darkorange' => 'FF8C00',
  234. 'darkorchid' => '9932CC',
  235. 'darkred' => '8B0000',
  236. 'darksalmon' => 'E9967A',
  237. 'darkseagreen' => '8FBC8F',
  238. 'darkslateblue' => '483D8B',
  239. 'darkslategray' => '2F4F4F',
  240. 'darkslategrey' => '2F4F4F',
  241. 'darkturquoise' => '00CED1',
  242. 'darkviolet' => '9400D3',
  243. 'deeppink' => 'FF1493',
  244. 'deepskyblue' => '00BFFF',
  245. 'dimgray' => '696969',
  246. 'dimgrey' => '696969',
  247. 'dodgerblue' => '1E90FF',
  248. 'firebrick' => 'B22222',
  249. 'floralwhite' => 'FFFAF0',
  250. 'forestgreen' => '228B22',
  251. 'fuchsia' => 'FF00FF',
  252. 'gainsboro' => 'DCDCDC',
  253. 'ghostwhite' => 'F8F8FF',
  254. 'gold' => 'FFD700',
  255. 'goldenrod' => 'DAA520',
  256. 'gray' => '808080',
  257. 'green' => '008000',
  258. 'greenyellow' => 'ADFF2F',
  259. 'grey' => '808080',
  260. 'honeydew' => 'F0FFF0',
  261. 'hotpink' => 'FF69B4',
  262. 'indianred' => 'CD5C5C',
  263. 'indigo' => '4B0082',
  264. 'ivory' => 'FFFFF0',
  265. 'khaki' => 'F0E68C',
  266. 'lavender' => 'E6E6FA',
  267. 'lavenderblush' => 'FFF0F5',
  268. 'lawngreen' => '7CFC00',
  269. 'lemonchiffon' => 'FFFACD',
  270. 'lightblue' => 'ADD8E6',
  271. 'lightcoral' => 'F08080',
  272. 'lightcyan' => 'E0FFFF',
  273. 'lightgoldenrodyellow' => 'FAFAD2',
  274. 'lightgray' => 'D3D3D3',
  275. 'lightgreen' => '90EE90',
  276. 'lightgrey' => 'D3D3D3',
  277. 'lightpink' => 'FFB6C1',
  278. 'lightsalmon' => 'FFA07A',
  279. 'lightseagreen' => '20B2AA',
  280. 'lightskyblue' => '87CEFA',
  281. 'lightslategray' => '778899',
  282. 'lightslategrey' => '778899',
  283. 'lightsteelblue' => 'B0C4DE',
  284. 'lightyellow' => 'FFFFE0',
  285. 'lime' => '00FF00',
  286. 'limegreen' => '32CD32',
  287. 'linen' => 'FAF0E6',
  288. 'magenta' => 'FF00FF',
  289. 'maroon' => '800000',
  290. 'mediumaquamarine' => '66CDAA',
  291. 'mediumblue' => '0000CD',
  292. 'mediumorchid' => 'BA55D3',
  293. 'mediumpurple' => '9370D0',
  294. 'mediumseagreen' => '3CB371',
  295. 'mediumslateblue' => '7B68EE',
  296. 'mediumspringgreen' => '00FA9A',
  297. 'mediumturquoise' => '48D1CC',
  298. 'mediumvioletred' => 'C71585',
  299. 'midnightblue' => '191970',
  300. 'mintcream' => 'F5FFFA',
  301. 'mistyrose' => 'FFE4E1',
  302. 'moccasin' => 'FFE4B5',
  303. 'navajowhite' => 'FFDEAD',
  304. 'navy' => '000080',
  305. 'oldlace' => 'FDF5E6',
  306. 'olive' => '808000',
  307. 'olivedrab' => '6B8E23',
  308. 'orange' => 'FFA500',
  309. 'orangered' => 'FF4500',
  310. 'orchid' => 'DA70D6',
  311. 'palegoldenrod' => 'EEE8AA',
  312. 'palegreen' => '98FB98',
  313. 'paleturquoise' => 'AFEEEE',
  314. 'palevioletred' => 'DB7093',
  315. 'papayawhip' => 'FFEFD5',
  316. 'peachpuff' => 'FFDAB9',
  317. 'peru' => 'CD853F',
  318. 'pink' => 'FFC0CB',
  319. 'plum' => 'DDA0DD',
  320. 'powderblue' => 'B0E0E6',
  321. 'purple' => '800080',
  322. 'red' => 'FF0000',
  323. 'rosybrown' => 'BC8F8F',
  324. 'royalblue' => '4169E1',
  325. 'saddlebrown' => '8B4513',
  326. 'salmon' => 'FA8072',
  327. 'sandybrown' => 'F4A460',
  328. 'seagreen' => '2E8B57',
  329. 'seashell' => 'FFF5EE',
  330. 'sienna' => 'A0522D',
  331. 'silver' => 'C0C0C0',
  332. 'skyblue' => '87CEEB',
  333. 'slateblue' => '6A5ACD',
  334. 'slategray' => '708090',
  335. 'slategrey' => '708090',
  336. 'snow' => 'FFFAFA',
  337. 'springgreen' => '00FF7F',
  338. 'steelblue' => '4682B4',
  339. 'tan' => 'D2B48C',
  340. 'teal' => '008080',
  341. 'thistle' => 'D8BFD8',
  342. 'tomato' => 'FF6347',
  343. 'turquoise' => '40E0D0',
  344. 'violet' => 'EE82EE',
  345. 'wheat' => 'F5DEB3',
  346. 'white' => 'FFFFFF',
  347. 'whitesmoke' => 'F5F5F5',
  348. 'yellow' => 'FFFF00',
  349. 'yellowgreen' => '9ACD32'
  350. );
  351. $color_name = strtolower($color_name);
  352. if (isset($colors[$color_name])) {
  353. return '#' . $colors[$color_name];
  354. }
  355. return $color_name;
  356. }
  357. /**
  358. * Given a HEX value, returns a darker color. If no desired amount provided, then the color halfway between
  359. * given HEX and black will be returned.
  360. * @param int $amount
  361. * @return string Darker HEX value
  362. * @throws Exception
  363. */
  364. public function darken(int $amount = self::DEFAULT_ADJUST): string
  365. {
  366. // Darken
  367. $darkerHSL = $this->darkenHsl($this->_hsl, $amount);
  368. // Return as HEX
  369. return self::hslToHex($darkerHSL);
  370. }
  371. /**
  372. * Given a HEX value, returns a lighter color. If no desired amount provided, then the color halfway between
  373. * given HEX and white will be returned.
  374. * @param int $amount
  375. * @return string Lighter HEX value
  376. * @throws Exception
  377. */
  378. public function lighten(int $amount = self::DEFAULT_ADJUST): string
  379. {
  380. // Lighten
  381. $lighterHSL = $this->lightenHsl($this->_hsl, $amount);
  382. // Return as HEX
  383. return self::hslToHex($lighterHSL);
  384. }
  385. /**
  386. * Given a HEX value, returns a mixed color. If no desired amount provided, then the color mixed by this ratio
  387. * @param string $hex2 Secondary HEX value to mix with
  388. * @param int $amount = -100..0..+100
  389. * @return string mixed HEX value
  390. * @throws Exception
  391. */
  392. public function mix(string $hex2, int $amount = 0): string
  393. {
  394. $rgb2 = self::hexToRgb($hex2);
  395. $mixed = $this->mixRgb($this->_rgb, $rgb2, $amount);
  396. // Return as HEX
  397. return self::rgbToHex($mixed);
  398. }
  399. /**
  400. * Creates an array with two shades that can be used to make a gradient
  401. * @param int $amount Optional percentage amount you want your contrast color
  402. * @return array An array with a 'light' and 'dark' index
  403. * @throws Exception
  404. */
  405. public function makeGradient(int $amount = self::DEFAULT_ADJUST): array
  406. {
  407. // Decide which color needs to be made
  408. if ($this->isLight()) {
  409. $lightColor = $this->_hex;
  410. $darkColor = $this->darken($amount);
  411. } else {
  412. $lightColor = $this->lighten($amount);
  413. $darkColor = $this->_hex;
  414. }
  415. // Return our gradient array
  416. return array("light" => $lightColor, "dark" => $darkColor);
  417. }
  418. /**
  419. * Returns whether or not given color is considered "light"
  420. * @param string|bool $color
  421. * @param int $lighterThan
  422. * @return boolean
  423. */
  424. public function isLight($color = false, int $lighterThan = 130): bool
  425. {
  426. // Get our color
  427. $color = ($color) ? $color : $this->_hex;
  428. // Calculate straight from rbg
  429. $r = hexdec($color[0] . $color[1]);
  430. $g = hexdec($color[2] . $color[3]);
  431. $b = hexdec($color[4] . $color[5]);
  432. return (($r * 299 + $g * 587 + $b * 114) / 1000 > $lighterThan);
  433. }
  434. /**
  435. * Returns whether or not a given color is considered "dark"
  436. * @param string|bool $color
  437. * @param int $darkerThan
  438. * @return boolean
  439. */
  440. public function isDark($color = false, int $darkerThan = 130): bool
  441. {
  442. // Get our color
  443. $color = ($color) ? $color : $this->_hex;
  444. // Calculate straight from rbg
  445. $r = hexdec($color[0] . $color[1]);
  446. $g = hexdec($color[2] . $color[3]);
  447. $b = hexdec($color[4] . $color[5]);
  448. return (($r * 299 + $g * 587 + $b * 114) / 1000 <= $darkerThan);
  449. }
  450. /**
  451. * Returns the complimentary color
  452. * @return string Complementary hex color
  453. * @throws Exception
  454. */
  455. public function complementary(): string
  456. {
  457. // Get our HSL
  458. $hsl = $this->_hsl;
  459. // Adjust Hue 180 degrees
  460. $hsl['H'] += ($hsl['H'] > 180) ? -180 : 180;
  461. // Return the new value in HEX
  462. return self::hslToHex($hsl);
  463. }
  464. /**
  465. * Returns the HSL array of your color
  466. */
  467. public function getHsl(): array
  468. {
  469. return $this->_hsl;
  470. }
  471. /**
  472. * Returns your original color
  473. */
  474. public function getHex(): string
  475. {
  476. return $this->_hex;
  477. }
  478. /**
  479. * Returns the RGB array of your color
  480. */
  481. public function getRgb(): array
  482. {
  483. return $this->_rgb;
  484. }
  485. /**
  486. * Returns the cross browser CSS3 gradient
  487. * @param int $amount Optional: percentage amount to light/darken the gradient
  488. * @param boolean $vintageBrowsers Optional: include vendor prefixes for browsers that almost died out already
  489. * @param string $prefix Optional: prefix for every lines
  490. * @param string $suffix Optional: suffix for every lines
  491. * @return string CSS3 gradient for chrome, safari, firefox, opera and IE10
  492. * @throws Exception
  493. * @link http://caniuse.com/css-gradients Resource for the browser support
  494. */
  495. public function getCssGradient($amount = self::DEFAULT_ADJUST, $vintageBrowsers = false, $suffix = "", $prefix = ""): string
  496. {
  497. // Get the recommended gradient
  498. $g = $this->makeGradient($amount);
  499. $css = "";
  500. /* fallback/image non-cover color */
  501. $css .= "{$prefix}background-color: #" . $this->_hex . ";{$suffix}";
  502. /* IE Browsers */
  503. $css .= "{$prefix}filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#" . $g['light'] . "', endColorstr='#" . $g['dark'] . "');{$suffix}";
  504. /* Safari 4+, Chrome 1-9 */
  505. if ($vintageBrowsers) {
  506. $css .= "{$prefix}background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#" . $g['light'] . "), to(#" . $g['dark'] . "));{$suffix}";
  507. }
  508. /* Safari 5.1+, Mobile Safari, Chrome 10+ */
  509. $css .= "{$prefix}background-image: -webkit-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}";
  510. if ($vintageBrowsers) {
  511. /* Firefox 3.6+ */
  512. $css .= "{$prefix}background-image: -moz-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}";
  513. /* Opera 11.10+ */
  514. $css .= "{$prefix}background-image: -o-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}";
  515. }
  516. /* Unprefixed version (standards): FF 16+, IE10+, Chrome 26+, Safari 7+, Opera 12.1+ */
  517. $css .= "{$prefix}background-image: linear-gradient(to bottom, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}";
  518. // Return our CSS
  519. return $css;
  520. }
  521. /**
  522. * Darkens a given HSL array
  523. * @param array $hsl
  524. * @param int $amount
  525. * @return array $hsl
  526. */
  527. private function darkenHsl(array $hsl, int $amount = self::DEFAULT_ADJUST): array
  528. {
  529. // Check if we were provided a number
  530. if ($amount) {
  531. $hsl['L'] = ($hsl['L'] * 100) - $amount;
  532. $hsl['L'] = ($hsl['L'] < 0) ? 0 : $hsl['L'] / 100;
  533. } else {
  534. // We need to find out how much to darken
  535. $hsl['L'] /= 2;
  536. }
  537. return $hsl;
  538. }
  539. /**
  540. * Lightens a given HSL array
  541. * @param array $hsl
  542. * @param int $amount
  543. * @return array
  544. */
  545. private function lightenHsl(array $hsl, int $amount = self::DEFAULT_ADJUST): array
  546. {
  547. // Check if we were provided a number
  548. if ($amount) {
  549. $hsl['L'] = ($hsl['L'] * 100) + $amount;
  550. $hsl['L'] = ($hsl['L'] > 100) ? 1 : $hsl['L'] / 100;
  551. } else {
  552. // We need to find out how much to lighten
  553. $hsl['L'] += (1 - $hsl['L']) / 2;
  554. }
  555. return $hsl;
  556. }
  557. /**
  558. * Mix two RGB colors and return the resulting RGB color
  559. * ported from http://phpxref.pagelines.com/nav.html?includes/class.colors.php.source.html
  560. * @param array $rgb1
  561. * @param array $rgb2
  562. * @param int $amount ranged -100..0..+100
  563. * @return array
  564. */
  565. private function mixRgb(array $rgb1, array $rgb2, int $amount = 0): array
  566. {
  567. $r1 = ($amount + 100) / 100;
  568. $r2 = 2 - $r1;
  569. $rmix = (($rgb1['R'] * $r1) + ($rgb2['R'] * $r2)) / 2;
  570. $gmix = (($rgb1['G'] * $r1) + ($rgb2['G'] * $r2)) / 2;
  571. $bmix = (($rgb1['B'] * $r1) + ($rgb2['B'] * $r2)) / 2;
  572. return array('R' => $rmix, 'G' => $gmix, 'B' => $bmix);
  573. }
  574. /**
  575. * Given a Hue, returns corresponding RGB value
  576. * @param float $v1
  577. * @param float $v2
  578. * @param float $vH
  579. * @return float
  580. */
  581. private static function hueToRgb(float $v1, float $v2, float $vH): float
  582. {
  583. if ($vH < 0) {
  584. ++$vH;
  585. }
  586. if ($vH > 1) {
  587. --$vH;
  588. }
  589. if ((6 * $vH) < 1) {
  590. return ($v1 + ($v2 - $v1) * 6 * $vH);
  591. }
  592. if ((2 * $vH) < 1) {
  593. return $v2;
  594. }
  595. if ((3 * $vH) < 2) {
  596. return ($v1 + ($v2 - $v1) * ((2 / 3) - $vH) * 6);
  597. }
  598. return $v1;
  599. }
  600. /**
  601. * Checks the HEX string for correct formatting and converts short format to long
  602. * @param string $hex
  603. * @return string
  604. * @throws Exception
  605. */
  606. private static function sanitizeHex(string $hex): string
  607. {
  608. // Strip # sign if it is present
  609. $color = str_replace("#", "", $hex);
  610. // Validate hex string
  611. if (!preg_match('/^[a-fA-F0-9]+$/', $color)) {
  612. throw new Exception("HEX color does not match format");
  613. }
  614. // Make sure it's 6 digits
  615. if (strlen($color) === 3) {
  616. $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
  617. } elseif (strlen($color) !== 6) {
  618. throw new Exception("HEX color needs to be 6 or 3 digits long");
  619. }
  620. return $color;
  621. }
  622. /**
  623. * Converts object into its string representation
  624. * @return string
  625. */
  626. public function __toString()
  627. {
  628. return "#" . $this->getHex();
  629. }
  630. /**
  631. * @param string $name
  632. * @return mixed|null
  633. */
  634. public function __get(string $name)
  635. {
  636. switch (strtolower($name)) {
  637. case 'red':
  638. case 'r':
  639. return $this->_rgb["R"];
  640. case 'green':
  641. case 'g':
  642. return $this->_rgb["G"];
  643. case 'blue':
  644. case 'b':
  645. return $this->_rgb["B"];
  646. case 'hue':
  647. case 'h':
  648. return $this->_hsl["H"];
  649. case 'saturation':
  650. case 's':
  651. return $this->_hsl["S"];
  652. case 'lightness':
  653. case 'l':
  654. return $this->_hsl["L"];
  655. }
  656. $trace = debug_backtrace();
  657. trigger_error(
  658. 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
  659. E_USER_NOTICE
  660. );
  661. return null;
  662. }
  663. /**
  664. * @param string $name
  665. * @param mixed $value
  666. * @throws Exception
  667. */
  668. public function __set(string $name, $value)
  669. {
  670. switch (strtolower($name)) {
  671. case 'red':
  672. case 'r':
  673. $this->_rgb["R"] = $value;
  674. $this->_hex = self::rgbToHex($this->_rgb);
  675. $this->_hsl = self::hexToHsl($this->_hex);
  676. break;
  677. case 'green':
  678. case 'g':
  679. $this->_rgb["G"] = $value;
  680. $this->_hex = self::rgbToHex($this->_rgb);
  681. $this->_hsl = self::hexToHsl($this->_hex);
  682. break;
  683. case 'blue':
  684. case 'b':
  685. $this->_rgb["B"] = $value;
  686. $this->_hex = self::rgbToHex($this->_rgb);
  687. $this->_hsl = self::hexToHsl($this->_hex);
  688. break;
  689. case 'hue':
  690. case 'h':
  691. $this->_hsl["H"] = $value;
  692. $this->_hex = self::hslToHex($this->_hsl);
  693. $this->_rgb = self::hexToRgb($this->_hex);
  694. break;
  695. case 'saturation':
  696. case 's':
  697. $this->_hsl["S"] = $value;
  698. $this->_hex = self::hslToHex($this->_hsl);
  699. $this->_rgb = self::hexToRgb($this->_hex);
  700. break;
  701. case 'lightness':
  702. case 'light':
  703. case 'l':
  704. $this->_hsl["L"] = $value;
  705. $this->_hex = self::hslToHex($this->_hsl);
  706. $this->_rgb = self::hexToRgb($this->_hex);
  707. break;
  708. }
  709. }
  710. }