color.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <?php
  2. namespace Pen;
  3. /* phpcs:ignoreFile */
  4. /**
  5. * A color utility that helps manipulate HEX colors
  6. *
  7. * Author: Arlo Carreon <http://arlocarreon.com>
  8. * Info: http://mexitek.github.io/phpColors/
  9. * License: http://arlo.mit-license.org/
  10. */
  11. class Color {
  12. private $_hex;
  13. private $_hsl;
  14. private $_rgb;
  15. /**
  16. * Auto darkens/lightens by 10% for sexily-subtle gradients.
  17. * Set this to FALSE to adjust automatic shade to be between given color
  18. * and black (for darken) or white (for lighten)
  19. */
  20. const DEFAULT_ADJUST = 10;
  21. /**
  22. * Instantiates the class with a HEX value
  23. * @param string $hex
  24. * @throws Exception "Bad color format"
  25. */
  26. function __construct( $hex ) {
  27. // Strip # sign is present
  28. $color = str_replace( '#', '', $hex );
  29. // Make sure it's 6 digits
  30. if ( strlen( $color ) === 3 ) {
  31. $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
  32. } elseif ( strlen( $color ) != 6 ) {
  33. throw new Exception( esc_html__( 'HEX color needs to be 6 or 3 digits long', 'pen' ) );
  34. }
  35. $this->_hsl = self::hexToHsl( $color );
  36. $this->_hex = $color;
  37. $this->_rgb = self::hexToRgb( $color );
  38. }
  39. // ====================
  40. // = Public Interface =
  41. // ====================
  42. /**
  43. * Given a HEX string returns a HSL array equivalent.
  44. * @param string $color
  45. * @return array HSL associative array
  46. */
  47. public static function hexToHsl( $color ) {
  48. // Sanity check
  49. $color = self::_checkHex( $color );
  50. // Convert HEX to DEC
  51. $R = hexdec( $color[0] . $color[1] );
  52. $G = hexdec( $color[2] . $color[3] );
  53. $B = hexdec( $color[4] . $color[5] );
  54. $HSL = array();
  55. $var_R = ( $R / 255 );
  56. $var_G = ( $G / 255 );
  57. $var_B = ( $B / 255 );
  58. $var_Min = min( $var_R, $var_G, $var_B );
  59. $var_Max = max( $var_R, $var_G, $var_B );
  60. $del_Max = $var_Max - $var_Min;
  61. $L = ( $var_Max + $var_Min ) / 2;
  62. if ( $del_Max == 0 ) {
  63. $H = 0;
  64. $S = 0;
  65. } else {
  66. if ( $L < 0.5 ) {
  67. $S = $del_Max / ( $var_Max + $var_Min );
  68. } else {
  69. $S = $del_Max / ( 2 - $var_Max - $var_Min );
  70. }
  71. $del_R = ( ( ( $var_Max - $var_R ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
  72. $del_G = ( ( ( $var_Max - $var_G ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
  73. $del_B = ( ( ( $var_Max - $var_B ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
  74. if ( $var_R == $var_Max ) {
  75. $H = $del_B - $del_G;
  76. } elseif ( $var_G == $var_Max ) {
  77. $H = ( 1 / 3 ) + $del_R - $del_B;
  78. } elseif ( $var_B == $var_Max ) {
  79. $H = ( 2 / 3 ) + $del_G - $del_R;
  80. }
  81. if ( $H < 0 ) {
  82. $H++;
  83. }
  84. if ( $H > 1 ) {
  85. $H--;
  86. }
  87. }
  88. $HSL['H'] = ( $H * 360 );
  89. $HSL['S'] = $S;
  90. $HSL['L'] = $L;
  91. return $HSL;
  92. }
  93. /**
  94. * Given a HSL associative array returns the equivalent HEX string
  95. * @param array $hsl
  96. * @return string HEX string
  97. * @throws Exception "Bad HSL Array"
  98. */
  99. public static function hslToHex( $hsl = array() ) {
  100. // Make sure it's HSL
  101. if ( empty( $hsl ) || ! isset( $hsl['H'] ) || ! isset( $hsl['S'] ) || ! isset( $hsl['L'] ) ) {
  102. throw new Exception( esc_html__( 'Parameter was not an HSL array', 'pen' ) );
  103. }
  104. list($H,$S,$L) = array( $hsl['H'] / 360, $hsl['S'], $hsl['L'] );
  105. if ( $S == 0 ) {
  106. $r = $L * 255;
  107. $g = $L * 255;
  108. $b = $L * 255;
  109. } else {
  110. if ( $L < 0.5 ) {
  111. $var_2 = $L * ( 1 + $S );
  112. } else {
  113. $var_2 = ( $L + $S ) - ( $S * $L );
  114. }
  115. $var_1 = 2 * $L - $var_2;
  116. $r = round( 255 * self::_huetorgb( $var_1, $var_2, $H + ( 1 / 3 ) ) );
  117. $g = round( 255 * self::_huetorgb( $var_1, $var_2, $H ) );
  118. $b = round( 255 * self::_huetorgb( $var_1, $var_2, $H - ( 1 / 3 ) ) );
  119. }
  120. // Convert to hex
  121. $r = dechex( $r );
  122. $g = dechex( $g );
  123. $b = dechex( $b );
  124. // Make sure we get 2 digits for decimals
  125. $r = ( strlen( '' . $r ) === 1 ) ? '0' . $r : $r;
  126. $g = ( strlen( '' . $g ) === 1 ) ? '0' . $g : $g;
  127. $b = ( strlen( '' . $b ) === 1 ) ? '0' . $b : $b;
  128. return $r . $g . $b;
  129. }
  130. /**
  131. * Given a HEX string returns a RGB array equivalent.
  132. * @param string $color
  133. * @return array RGB associative array
  134. */
  135. public static function hexToRgb( $color ) {
  136. // Sanity check
  137. $color = self::_checkHex( $color );
  138. // Convert HEX to DEC
  139. $R = hexdec( $color[0] . $color[1] );
  140. $G = hexdec( $color[2] . $color[3] );
  141. $B = hexdec( $color[4] . $color[5] );
  142. $RGB['R'] = $R;
  143. $RGB['G'] = $G;
  144. $RGB['B'] = $B;
  145. return $RGB;
  146. }
  147. /**
  148. * Given an RGB associative array returns the equivalent HEX string
  149. * @param array $rgb
  150. * @return string RGB string
  151. * @throws Exception "Bad RGB Array"
  152. */
  153. public static function rgbToHex( $rgb = array() ) {
  154. // Make sure it's RGB
  155. if ( empty( $rgb ) || ! isset( $rgb['R'] ) || ! isset( $rgb['G'] ) || ! isset( $rgb['B'] ) ) {
  156. throw new Exception( esc_html__( 'Parameter was not an RGB array', 'pen' ) );
  157. }
  158. // https://github.com/mexitek/phpColors/issues/25#issuecomment-88354815
  159. // Convert RGB to HEX
  160. $hex[0] = str_pad( dechex( $rgb['R'] ), 2, '0', STR_PAD_LEFT );
  161. $hex[1] = str_pad( dechex( $rgb['G'] ), 2, '0', STR_PAD_LEFT );
  162. $hex[2] = str_pad( dechex( $rgb['B'] ), 2, '0', STR_PAD_LEFT );
  163. return implode( '', $hex );
  164. }
  165. /**
  166. * Given a HEX value, returns a darker color. If no desired amount provided, then the color halfway between
  167. * given HEX and black will be returned.
  168. * @param int $amount
  169. * @return string Darker HEX value
  170. */
  171. public function darken( $amount = self::DEFAULT_ADJUST ) {
  172. // Darken
  173. $darkerHSL = $this->_darken( $this->_hsl, $amount );
  174. // Return as HEX
  175. return self::hslToHex( $darkerHSL );
  176. }
  177. /**
  178. * Given a HEX value, returns a lighter color. If no desired amount provided, then the color halfway between
  179. * given HEX and white will be returned.
  180. * @param int $amount
  181. * @return string Lighter HEX value
  182. */
  183. public function lighten( $amount = self::DEFAULT_ADJUST ) {
  184. // Lighten
  185. $lighterHSL = $this->_lighten( $this->_hsl, $amount );
  186. // Return as HEX
  187. return self::hslToHex( $lighterHSL );
  188. }
  189. /**
  190. * Given a HEX value, returns a mixed color. If no desired amount provided, then the color mixed by this ratio
  191. * @param string $hex2 Secondary HEX value to mix with
  192. * @param int $amount = -100..0..+100
  193. * @return string mixed HEX value
  194. */
  195. public function mix( $hex2, $amount = 0 ) {
  196. $rgb2 = self::hexToRgb( $hex2 );
  197. $mixed = $this->_mix( $this->_rgb, $rgb2, $amount );
  198. // Return as HEX
  199. return self::rgbToHex( $mixed );
  200. }
  201. /**
  202. * Creates an array with two shades that can be used to make a gradient
  203. * @param int $amount Optional percentage amount you want your contrast color
  204. * @return array An array with a 'light' and 'dark' index
  205. */
  206. public function makeGradient( $amount = self::DEFAULT_ADJUST ) {
  207. // Decide which color needs to be made
  208. if ( $this->isLight() ) {
  209. $lightColor = $this->_hex;
  210. $darkColor = $this->darken( $amount );
  211. } else {
  212. $lightColor = $this->lighten( $amount );
  213. $darkColor = $this->_hex;
  214. }
  215. // Return our gradient array
  216. return array(
  217. 'light' => $lightColor,
  218. 'dark' => $darkColor,
  219. );
  220. }
  221. /**
  222. * Returns whether or not given color is considered "light"
  223. * @param string|Boolean $color
  224. * @param int $lighterThan
  225. * @return boolean
  226. */
  227. public function isLight( $color = false, $lighterThan = 130 ) {
  228. // Get our color
  229. $color = ( $color ) ? $color : $this->_hex;
  230. // Calculate straight from rbg
  231. $r = hexdec( $color[0] . $color[1] );
  232. $g = hexdec( $color[2] . $color[3] );
  233. $b = hexdec( $color[4] . $color[5] );
  234. return ( ( $r * 299 + $g * 587 + $b * 114 ) / 1000 > $lighterThan );
  235. }
  236. /**
  237. * Returns whether or not a given color is considered "dark"
  238. * @param string|Boolean $color
  239. * @param int $darkerThan
  240. * @return boolean
  241. */
  242. public function isDark( $color = false, $darkerThan = 130 ) {
  243. // Get our color
  244. $color = ( $color ) ? $color : $this->_hex;
  245. // Calculate straight from rbg
  246. $r = hexdec( $color[0] . $color[1] );
  247. $g = hexdec( $color[2] . $color[3] );
  248. $b = hexdec( $color[4] . $color[5] );
  249. return ( ( $r * 299 + $g * 587 + $b * 114 ) / 1000 <= $darkerThan );
  250. }
  251. /**
  252. * Returns the complimentary color
  253. * @return string Complementary hex color
  254. *
  255. */
  256. public function complementary() {
  257. // Get our HSL
  258. $hsl = $this->_hsl;
  259. // Adjust Hue 180 degrees
  260. $hsl['H'] += ( $hsl['H'] > 180 ) ? -180 : 180;
  261. // Return the new value in HEX
  262. return self::hslToHex( $hsl );
  263. }
  264. /**
  265. * Returns your color's HSL array
  266. */
  267. public function getHsl() {
  268. return $this->_hsl;
  269. }
  270. /**
  271. * Returns your original color
  272. */
  273. public function getHex() {
  274. return $this->_hex;
  275. }
  276. /**
  277. * Returns your color's RGB array
  278. */
  279. public function getRgb() {
  280. return $this->_rgb;
  281. }
  282. /**
  283. * Returns the cross browser CSS3 gradient
  284. * @param int $amount Optional: percentage amount to light/darken the gradient
  285. * @param boolean $vintageBrowsers Optional: include vendor prefixes for browsers that almost died out already
  286. * @param string $prefix Optional: prefix for every lines
  287. * @param string $suffix Optional: suffix for every lines
  288. * @link http://caniuse.com/css-gradients Resource for the browser support
  289. * @return string CSS3 gradient for chrome, safari, firefox, opera and IE10
  290. */
  291. public function getCssGradient( $amount = self::DEFAULT_ADJUST, $vintageBrowsers = false, $suffix = '', $prefix = '' ) {
  292. // Get the recommended gradient
  293. $g = $this->makeGradient( $amount );
  294. $css = '';
  295. /* fallback/image non-cover color */
  296. $css .= "{$prefix}background-color: #" . $this->_hex . ";{$suffix}";
  297. /* IE Browsers */
  298. $css .= "{$prefix}filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#" . $g['light'] . "', endColorstr='#" . $g['dark'] . "');{$suffix}";
  299. /* Safari 4+, Chrome 1-9 */
  300. if ( $vintageBrowsers ) {
  301. $css .= "{$prefix}background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#" . $g['light'] . '), to(#' . $g['dark'] . "));{$suffix}";
  302. }
  303. /* Safari 5.1+, Mobile Safari, Chrome 10+ */
  304. $css .= "{$prefix}background-image: -webkit-linear-gradient(top, #" . $g['light'] . ', #' . $g['dark'] . ");{$suffix}";
  305. /* Firefox 3.6+ */
  306. if ( $vintageBrowsers ) {
  307. $css .= "{$prefix}background-image: -moz-linear-gradient(top, #" . $g['light'] . ', #' . $g['dark'] . ");{$suffix}";
  308. }
  309. /* Opera 11.10+ */
  310. if ( $vintageBrowsers ) {
  311. $css .= "{$prefix}background-image: -o-linear-gradient(top, #" . $g['light'] . ', #' . $g['dark'] . ");{$suffix}";
  312. }
  313. /* Unprefixed version (standards): FF 16+, IE10+, Chrome 26+, Safari 7+, Opera 12.1+ */
  314. $css .= "{$prefix}background-image: linear-gradient(to bottom, #" . $g['light'] . ', #' . $g['dark'] . ");{$suffix}";
  315. // Return our CSS
  316. return $css;
  317. }
  318. // ===========================
  319. // = Private Functions Below =
  320. // ===========================
  321. /**
  322. * Darkens a given HSL array
  323. * @param array $hsl
  324. * @param int $amount
  325. * @return array $hsl
  326. */
  327. private function _darken( $hsl, $amount = self::DEFAULT_ADJUST ) {
  328. // Check if we were provided a number
  329. if ( $amount ) {
  330. $hsl['L'] = ( $hsl['L'] * 100 ) - $amount;
  331. $hsl['L'] = ( $hsl['L'] < 0 ) ? 0 : $hsl['L'] / 100;
  332. } else {
  333. // We need to find out how much to darken
  334. $hsl['L'] = $hsl['L'] / 2;
  335. }
  336. return $hsl;
  337. }
  338. /**
  339. * Lightens a given HSL array
  340. * @param array $hsl
  341. * @param int $amount
  342. * @return array $hsl
  343. */
  344. private function _lighten( $hsl, $amount = self::DEFAULT_ADJUST ) {
  345. // Check if we were provided a number
  346. if ( $amount ) {
  347. $hsl['L'] = ( $hsl['L'] * 100 ) + $amount;
  348. $hsl['L'] = ( $hsl['L'] > 100 ) ? 1 : $hsl['L'] / 100;
  349. } else {
  350. // We need to find out how much to lighten
  351. $hsl['L'] += ( 1 - $hsl['L'] ) / 2;
  352. }
  353. return $hsl;
  354. }
  355. /**
  356. * Mix 2 rgb colors and return an rgb color
  357. * @param array $rgb1
  358. * @param array $rgb2
  359. * @param int $amount ranged -100..0..+100
  360. * @return array $rgb
  361. *
  362. * ported from http://phpxref.pagelines.com/nav.html?includes/class.colors.php.source.html
  363. */
  364. private function _mix( $rgb1, $rgb2, $amount = 0 ) {
  365. $r1 = ( $amount + 100 ) / 100;
  366. $r2 = 2 - $r1;
  367. $rmix = ( ( $rgb1['R'] * $r1 ) + ( $rgb2['R'] * $r2 ) ) / 2;
  368. $gmix = ( ( $rgb1['G'] * $r1 ) + ( $rgb2['G'] * $r2 ) ) / 2;
  369. $bmix = ( ( $rgb1['B'] * $r1 ) + ( $rgb2['B'] * $r2 ) ) / 2;
  370. return array(
  371. 'R' => $rmix,
  372. 'G' => $gmix,
  373. 'B' => $bmix,
  374. );
  375. }
  376. /**
  377. * Given a Hue, returns corresponding RGB value
  378. * @param int $v1
  379. * @param int $v2
  380. * @param int $vH
  381. * @return int
  382. */
  383. private static function _huetorgb( $v1, $v2, $vH ) {
  384. if ( $vH < 0 ) {
  385. $vH += 1;
  386. }
  387. if ( $vH > 1 ) {
  388. $vH -= 1;
  389. }
  390. if ( ( 6 * $vH ) < 1 ) {
  391. return ( $v1 + ( $v2 - $v1 ) * 6 * $vH );
  392. }
  393. if ( ( 2 * $vH ) < 1 ) {
  394. return $v2;
  395. }
  396. if ( ( 3 * $vH ) < 2 ) {
  397. return ( $v1 + ( $v2 - $v1 ) * ( ( 2 / 3 ) - $vH ) * 6 );
  398. }
  399. return $v1;
  400. }
  401. /**
  402. * You need to check if you were given a good hex string
  403. * @param string $hex
  404. * @return string Color
  405. * @throws Exception "Bad color format"
  406. */
  407. private static function _checkHex( $hex ) {
  408. // Strip # sign is present
  409. $color = str_replace( '#', '', $hex );
  410. // Make sure it's 6 digits
  411. if ( strlen( $color ) == 3 ) {
  412. $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2];
  413. } elseif ( strlen( $color ) != 6 ) {
  414. throw new Exception( esc_html__( 'HEX color needs to be 6 or 3 digits long', 'pen' ) );
  415. }
  416. return $color;
  417. }
  418. /**
  419. * Converts object into its string representation
  420. * @return string Color
  421. */
  422. public function __toString() {
  423. return '#' . $this->getHex();
  424. }
  425. }