Color.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. /**
  3. * Color
  4. *
  5. * @package Less
  6. * @subpackage tree
  7. */
  8. class Less_Tree_Color extends Less_Tree {
  9. public $rgb;
  10. public $alpha;
  11. public $isTransparentKeyword;
  12. public $type = 'Color';
  13. public function __construct( $rgb, $a = 1, $isTransparentKeyword = null ) {
  14. if ( $isTransparentKeyword ) {
  15. $this->rgb = $rgb;
  16. $this->alpha = $a;
  17. $this->isTransparentKeyword = true;
  18. return;
  19. }
  20. $this->rgb = array();
  21. if ( is_array( $rgb ) ) {
  22. $this->rgb = $rgb;
  23. } else if ( strlen( $rgb ) == 6 ) {
  24. foreach ( str_split( $rgb, 2 ) as $c ) {
  25. $this->rgb[] = hexdec( $c );
  26. }
  27. } else {
  28. foreach ( str_split( $rgb, 1 ) as $c ) {
  29. $this->rgb[] = hexdec( $c.$c );
  30. }
  31. }
  32. $this->alpha = is_numeric( $a ) ? $a : 1;
  33. }
  34. public function compile() {
  35. return $this;
  36. }
  37. public function luma() {
  38. $r = $this->rgb[0] / 255;
  39. $g = $this->rgb[1] / 255;
  40. $b = $this->rgb[2] / 255;
  41. $r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( ( $r + 0.055 ) / 1.055 ), 2.4 );
  42. $g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( ( $g + 0.055 ) / 1.055 ), 2.4 );
  43. $b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( ( $b + 0.055 ) / 1.055 ), 2.4 );
  44. return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
  45. }
  46. /**
  47. * @see Less_Tree::genCSS
  48. */
  49. public function genCSS( $output ) {
  50. $output->add( $this->toCSS() );
  51. }
  52. public function toCSS( $doNotCompress = false ) {
  53. $compress = Less_Parser::$options['compress'] && !$doNotCompress;
  54. $alpha = Less_Functions::fround( $this->alpha );
  55. //
  56. // If we have some transparency, the only way to represent it
  57. // is via `rgba`. Otherwise, we use the hex representation,
  58. // which has better compatibility with older browsers.
  59. // Values are capped between `0` and `255`, rounded and zero-padded.
  60. //
  61. if ( $alpha < 1 ) {
  62. if ( ( $alpha === 0 || $alpha === 0.0 ) && isset( $this->isTransparentKeyword ) && $this->isTransparentKeyword ) {
  63. return 'transparent';
  64. }
  65. $values = array();
  66. foreach ( $this->rgb as $c ) {
  67. $values[] = Less_Functions::clamp( round( $c ), 255 );
  68. }
  69. $values[] = $alpha;
  70. $glue = ( $compress ? ',' : ', ' );
  71. return "rgba(" . implode( $glue, $values ) . ")";
  72. } else {
  73. $color = $this->toRGB();
  74. if ( $compress ) {
  75. // Convert color to short format
  76. if ( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6] ) {
  77. $color = '#'.$color[1] . $color[3] . $color[5];
  78. }
  79. }
  80. return $color;
  81. }
  82. }
  83. //
  84. // Operations have to be done per-channel, if not,
  85. // channels will spill onto each other. Once we have
  86. // our result, in the form of an integer triplet,
  87. // we create a new Color node to hold the result.
  88. //
  89. /**
  90. * @param string $op
  91. */
  92. public function operate( $op, $other ) {
  93. $rgb = array();
  94. $alpha = $this->alpha * ( 1 - $other->alpha ) + $other->alpha;
  95. for ( $c = 0; $c < 3; $c++ ) {
  96. $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c] );
  97. }
  98. return new Less_Tree_Color( $rgb, $alpha );
  99. }
  100. public function toRGB() {
  101. return $this->toHex( $this->rgb );
  102. }
  103. public function toHSL() {
  104. $r = $this->rgb[0] / 255;
  105. $g = $this->rgb[1] / 255;
  106. $b = $this->rgb[2] / 255;
  107. $a = $this->alpha;
  108. $max = max( $r, $g, $b );
  109. $min = min( $r, $g, $b );
  110. $l = ( $max + $min ) / 2;
  111. $d = $max - $min;
  112. $h = $s = 0;
  113. if ( $max !== $min ) {
  114. $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
  115. switch ( $max ) {
  116. case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
  117. break;
  118. case $g: $h = ( $b - $r ) / $d + 2;
  119. break;
  120. case $b: $h = ( $r - $g ) / $d + 4;
  121. break;
  122. }
  123. $h /= 6;
  124. }
  125. return array( 'h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a );
  126. }
  127. // Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
  128. public function toHSV() {
  129. $r = $this->rgb[0] / 255;
  130. $g = $this->rgb[1] / 255;
  131. $b = $this->rgb[2] / 255;
  132. $a = $this->alpha;
  133. $max = max( $r, $g, $b );
  134. $min = min( $r, $g, $b );
  135. $v = $max;
  136. $d = $max - $min;
  137. if ( $max === 0 ) {
  138. $s = 0;
  139. } else {
  140. $s = $d / $max;
  141. }
  142. $h = 0;
  143. if ( $max !== $min ) {
  144. switch ( $max ) {
  145. case $r: $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
  146. break;
  147. case $g: $h = ( $b - $r ) / $d + 2;
  148. break;
  149. case $b: $h = ( $r - $g ) / $d + 4;
  150. break;
  151. }
  152. $h /= 6;
  153. }
  154. return array( 'h' => $h * 360, 's' => $s, 'v' => $v, 'a' => $a );
  155. }
  156. public function toARGB() {
  157. $argb = array_merge( (array)Less_Parser::round( $this->alpha * 255 ), $this->rgb );
  158. return $this->toHex( $argb );
  159. }
  160. public function compare( $x ) {
  161. if ( !property_exists( $x, 'rgb' ) ) {
  162. return -1;
  163. }
  164. return ( $x->rgb[0] === $this->rgb[0] &&
  165. $x->rgb[1] === $this->rgb[1] &&
  166. $x->rgb[2] === $this->rgb[2] &&
  167. $x->alpha === $this->alpha ) ? 0 : -1;
  168. }
  169. public function toHex( $v ) {
  170. $ret = '#';
  171. foreach ( $v as $c ) {
  172. $c = Less_Functions::clamp( Less_Parser::round( $c ), 255 );
  173. if ( $c < 16 ) {
  174. $ret .= '0';
  175. }
  176. $ret .= dechex( $c );
  177. }
  178. return $ret;
  179. }
  180. /**
  181. * @param string $keyword
  182. */
  183. public static function fromKeyword( $keyword ) {
  184. $keyword = strtolower( $keyword );
  185. if ( Less_Colors::hasOwnProperty( $keyword ) ) {
  186. // detect named color
  187. return new Less_Tree_Color( substr( Less_Colors::color( $keyword ), 1 ) );
  188. }
  189. if ( $keyword === 'transparent' ) {
  190. return new Less_Tree_Color( array( 0, 0, 0 ), 0, true );
  191. }
  192. }
  193. }