1<?php 2 3namespace LesserPHP\Utils; 4 5use LesserPHP\Constants; 6 7/** 8 * Color handling utilities 9 */ 10class Color 11{ 12 /** 13 * coerce a value for use in color operation 14 * returns null if the value can't be used in color operations 15 */ 16 public static function coerceColor(array $value): ?array 17 { 18 switch ($value[0]) { 19 case 'color': 20 return $value; 21 case 'raw_color': 22 $c = ['color', 0, 0, 0]; 23 $colorStr = substr($value[1], 1); 24 $num = hexdec($colorStr); 25 $width = strlen($colorStr) == 3 ? 16 : 256; 26 27 for ($i = 3; $i > 0; $i--) { // 3 2 1 28 $t = $num % $width; 29 $num /= $width; 30 31 $c[$i] = $t * (256 / $width) + $t * floor(16 / $width); 32 } 33 34 return $c; 35 case 'keyword': 36 $name = $value[1]; 37 if (isset(Constants::CSS_COLORS[$name])) { 38 $rgba = explode(',', Constants::CSS_COLORS[$name]); 39 40 if (isset($rgba[3])) 41 return ['color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]]; 42 43 return ['color', $rgba[0], $rgba[1], $rgba[2]]; 44 } 45 return null; 46 } 47 return null; 48 } 49 50 /** 51 * Calculate the perceptual brightness of a color object 52 */ 53 public static function toLuma(array $color): float 54 { 55 [, $r, $g, $b] = Color::coerceColor($color); 56 57 $r = $r / 255; 58 $g = $g / 255; 59 $b = $b / 255; 60 61 $r = ($r <= 0.03928) ? $r / 12.92 : (($r + 0.055) / 1.055) ** 2.4; 62 $g = ($g <= 0.03928) ? $g / 12.92 : (($g + 0.055) / 1.055) ** 2.4; 63 $b = ($b <= 0.03928) ? $b / 12.92 : (($b + 0.055) / 1.055) ** 2.4; 64 65 return (0.2126 * $r) + (0.7152 * $g) + (0.0722 * $b); 66 } 67 68 /** 69 * Convert a color to HSL color space 70 */ 71 public static function toHSL(array $color): array 72 { 73 if ($color[0] == 'hsl') return $color; 74 75 $r = $color[1] / 255; 76 $g = $color[2] / 255; 77 $b = $color[3] / 255; 78 79 $min = min($r, $g, $b); 80 $max = max($r, $g, $b); 81 82 $L = ($min + $max) / 2; 83 if ($min == $max) { 84 $S = $H = 0; 85 } else { 86 if ($L < 0.5) { 87 $S = ($max - $min) / ($max + $min); 88 } else { 89 $S = ($max - $min) / (2.0 - $max - $min); 90 } 91 92 if ($r == $max) { 93 $H = ($g - $b) / ($max - $min); 94 } elseif ($g == $max) { 95 $H = 2.0 + ($b - $r) / ($max - $min); 96 } elseif ($b == $max) { 97 $H = 4.0 + ($r - $g) / ($max - $min); 98 } else { 99 $H = 0; 100 } 101 } 102 103 $out = [ 104 'hsl', 105 ($H < 0 ? $H + 6 : $H) * 60, 106 $S * 100, 107 $L * 100, 108 ]; 109 110 if (count($color) > 4) $out[] = $color[4]; // copy alpha 111 return $out; 112 } 113 114 115 /** 116 * Converts a hsl array into a color value in rgb. 117 * Expects H to be in range of 0 to 360, S and L in 0 to 100 118 */ 119 public static function toRGB(array $color): array 120 { 121 if ($color[0] == 'color') return $color; 122 123 $H = $color[1] / 360; 124 $S = $color[2] / 100; 125 $L = $color[3] / 100; 126 127 if ($S == 0) { 128 $r = $g = $b = $L; 129 } else { 130 $temp2 = $L < 0.5 ? 131 $L * (1.0 + $S) : 132 $L + $S - $L * $S; 133 134 $temp1 = 2.0 * $L - $temp2; 135 136 $r = self::calculateRGBComponent($H + 1 / 3, $temp1, $temp2); 137 $g = self::calculateRGBComponent($H, $temp1, $temp2); 138 $b = self::calculateRGBComponent($H - 1 / 3, $temp1, $temp2); 139 } 140 141 // $out = array('color', round($r*255), round($g*255), round($b*255)); 142 $out = ['color', $r * 255, $g * 255, $b * 255]; 143 if (count($color) > 4) $out[] = $color[4]; // copy alpha 144 return $out; 145 } 146 147 148 /** 149 * make sure a color's components don't go out of bounds 150 */ 151 public static function fixColor(array $c): array 152 { 153 foreach (range(1, 3) as $i) { 154 if ($c[$i] < 0) $c[$i] = 0; 155 if ($c[$i] > 255) $c[$i] = 255; 156 } 157 158 return $c; 159 } 160 161 /** 162 * Helper function for the HSL to RGB conversion process. 163 * 164 * This function normalizes the input component of the HSL color and determines the RGB 165 * value based on the HSL values. 166 * 167 * @param float $comp The component of the HSL color to be normalized and converted. 168 * @param float $temp1 The first temporary variable used in the conversion process 169 * @param float $temp2 The second temporary variable used in the conversion process 170 * 171 * @return float The calculated RGB value as percentage of the maximum value (255) 172 */ 173 protected static function calculateRGBComponent(float $comp, float $temp1, float $temp2): float 174 { 175 // Normalize the component value to be within the range [0, 1] 176 if ($comp < 0) $comp += 1.0; 177 elseif ($comp > 1) $comp -= 1.0; 178 179 // Determine the return value based on the value of the component 180 if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; 181 if (2 * $comp < 1) return $temp2; 182 if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6; 183 184 // Fallback return value, represents the case where the saturation of the color is zero 185 return $temp1; 186 } 187} 188