| 1 |
<?php |
| 2 |
/** |
| 3 |
* Copyright (C) 2020-2022 Graham Breach |
| 4 |
* |
| 5 |
* This program is free software: you can redistribute it and/or modify |
| 6 |
* it under the terms of the GNU Lesser General Public License as published by |
| 7 |
* the Free Software Foundation, either version 3 of the License, or |
| 8 |
* (at your option) any later version. |
| 9 |
* |
| 10 |
* This program is distributed in the hope that it will be useful, |
| 11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 |
* GNU Lesser General Public License for more details. |
| 14 |
* |
| 15 |
* You should have received a copy of the GNU Lesser General Public License |
| 16 |
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 |
*/ |
| 18 |
/** |
| 19 |
* For more information, please contact <graham@goat1000.com> |
| 20 |
*/ |
| 21 |
|
| 22 |
namespace Goat1000\SVGGraph; |
| 23 |
|
| 24 |
/** |
| 25 |
* Class for colour manipulation |
| 26 |
*/ |
| 27 |
class RGBColour { |
| 28 |
private $r = 0; |
| 29 |
private $g = 0; |
| 30 |
private $b = 0; |
| 31 |
private $a = 1.0; |
| 32 |
|
| 33 |
const KEYWORDS = [ |
| 34 |
'aliceblue' => '#f0f8ff', |
| 35 |
'antiquewhite' => '#faebd7', |
| 36 |
'aqua' => '#00ffff', |
| 37 |
'aquamarine' => '#7fffd4', |
| 38 |
'azure' => '#f0ffff', |
| 39 |
'beige' => '#f5f5dc', |
| 40 |
'bisque' => '#ffe4c4', |
| 41 |
'black' => '#000000', |
| 42 |
'blanchedalmond' => '#ffebcd', |
| 43 |
'blue' => '#0000ff', |
| 44 |
'blueviolet' => '#8a2be2', |
| 45 |
'brown' => '#a52a2a', |
| 46 |
'burlywood' => '#deb887', |
| 47 |
'cadetblue' => '#5f9ea0', |
| 48 |
'chartreuse' => '#7fff00', |
| 49 |
'chocolate' => '#d2691e', |
| 50 |
'coral' => '#ff7f50', |
| 51 |
'cornflowerblue' => '#6495ed', |
| 52 |
'cornsilk' => '#fff8dc', |
| 53 |
'crimson' => '#dc143c', |
| 54 |
'cyan' => '#00ffff', |
| 55 |
'darkblue' => '#00008b', |
| 56 |
'darkcyan' => '#008b8b', |
| 57 |
'darkgoldenrod' => '#b8860b', |
| 58 |
'darkgray' => '#a9a9a9', |
| 59 |
'darkgreen' => '#006400', |
| 60 |
'darkgrey' => '#a9a9a9', |
| 61 |
'darkkhaki' => '#bdb76b', |
| 62 |
'darkmagenta' => '#8b008b', |
| 63 |
'darkolivegreen' => '#556b2f', |
| 64 |
'darkorange' => '#ff8c00', |
| 65 |
'darkorchid' => '#9932cc', |
| 66 |
'darkred' => '#8b0000', |
| 67 |
'darksalmon' => '#e9967a', |
| 68 |
'darkseagreen' => '#8fbc8f', |
| 69 |
'darkslateblue' => '#483d8b', |
| 70 |
'darkslategray' => '#2f4f4f', |
| 71 |
'darkslategrey' => '#2f4f4f', |
| 72 |
'darkturquoise' => '#00ced1', |
| 73 |
'darkviolet' => '#9400d3', |
| 74 |
'deeppink' => '#ff1493', |
| 75 |
'deepskyblue' => '#00bfff', |
| 76 |
'dimgray' => '#696969', |
| 77 |
'dimgrey' => '#696969', |
| 78 |
'dodgerblue' => '#1e90ff', |
| 79 |
'firebrick' => '#b22222', |
| 80 |
'floralwhite' => '#fffaf0', |
| 81 |
'forestgreen' => '#228b22', |
| 82 |
'fuchsia' => '#ff00ff', |
| 83 |
'gainsboro' => '#dcdcdc', |
| 84 |
'ghostwhite' => '#f8f8ff', |
| 85 |
'gold' => '#ffd700', |
| 86 |
'goldenrod' => '#daa520', |
| 87 |
'gray' => '#808080', |
| 88 |
'green' => '#008000', |
| 89 |
'greenyellow' => '#adff2f', |
| 90 |
'grey' => '#808080', |
| 91 |
'honeydew' => '#f0fff0', |
| 92 |
'hotpink' => '#ff69b4', |
| 93 |
'indianred' => '#cd5c5c', |
| 94 |
'indigo' => '#4b0082', |
| 95 |
'ivory' => '#fffff0', |
| 96 |
'khaki' => '#f0e68c', |
| 97 |
'lavender' => '#e6e6fa', |
| 98 |
'lavenderblush' => '#fff0f5', |
| 99 |
'lawngreen' => '#7cfc00', |
| 100 |
'lemonchiffon' => '#fffacd', |
| 101 |
'lightblue' => '#add8e6', |
| 102 |
'lightcoral' => '#f08080', |
| 103 |
'lightcyan' => '#e0ffff', |
| 104 |
'lightgoldenrodyellow' => '#fafad2', |
| 105 |
'lightgray' => '#d3d3d3', |
| 106 |
'lightgreen' => '#90ee90', |
| 107 |
'lightgrey' => '#d3d3d3', |
| 108 |
'lightpink' => '#ffb6c1', |
| 109 |
'lightsalmon' => '#ffa07a', |
| 110 |
'lightseagreen' => '#20b2aa', |
| 111 |
'lightskyblue' => '#87cefa', |
| 112 |
'lightslategray' => '#778899', |
| 113 |
'lightslategrey' => '#778899', |
| 114 |
'lightsteelblue' => '#b0c4de', |
| 115 |
'lightyellow' => '#ffffe0', |
| 116 |
'lime' => '#00ff00', |
| 117 |
'limegreen' => '#32cd32', |
| 118 |
'linen' => '#faf0e6', |
| 119 |
'magenta' => '#ff00ff', |
| 120 |
'maroon' => '#800000', |
| 121 |
'mediumaquamarine' => '#66cdaa', |
| 122 |
'mediumblue' => '#0000cd', |
| 123 |
'mediumorchid' => '#ba55d3', |
| 124 |
'mediumpurple' => '#9370db', |
| 125 |
'mediumseagreen' => '#3cb371', |
| 126 |
'mediumslateblue' => '#7b68ee', |
| 127 |
'mediumspringgreen' => '#00fa9a', |
| 128 |
'mediumturquoise' => '#48d1cc', |
| 129 |
'mediumvioletred' => '#c71585', |
| 130 |
'midnightblue' => '#191970', |
| 131 |
'mintcream' => '#f5fffa', |
| 132 |
'mistyrose' => '#ffe4e1', |
| 133 |
'moccasin' => '#ffe4b5', |
| 134 |
'navajowhite' => '#ffdead', |
| 135 |
'navy' => '#000080', |
| 136 |
'oldlace' => '#fdf5e6', |
| 137 |
'olive' => '#808000', |
| 138 |
'olivedrab' => '#6b8e23', |
| 139 |
'orange' => '#ffa500', |
| 140 |
'orangered' => '#ff4500', |
| 141 |
'orchid' => '#da70d6', |
| 142 |
'palegoldenrod' => '#eee8aa', |
| 143 |
'palegreen' => '#98fb98', |
| 144 |
'paleturquoise' => '#afeeee', |
| 145 |
'palevioletred' => '#db7093', |
| 146 |
'papayawhip' => '#ffefd5', |
| 147 |
'peachpuff' => '#ffdab9', |
| 148 |
'peru' => '#cd853f', |
| 149 |
'pink' => '#ffc0cb', |
| 150 |
'plum' => '#dda0dd', |
| 151 |
'powderblue' => '#b0e0e6', |
| 152 |
'purple' => '#800080', |
| 153 |
'red' => '#ff0000', |
| 154 |
'rosybrown' => '#bc8f8f', |
| 155 |
'royalblue' => '#4169e1', |
| 156 |
'saddlebrown' => '#8b4513', |
| 157 |
'salmon' => '#fa8072', |
| 158 |
'sandybrown' => '#f4a460', |
| 159 |
'seagreen' => '#2e8b57', |
| 160 |
'seashell' => '#fff5ee', |
| 161 |
'sienna' => '#a0522d', |
| 162 |
'silver' => '#c0c0c0', |
| 163 |
'skyblue' => '#87ceeb', |
| 164 |
'slateblue' => '#6a5acd', |
| 165 |
'slategray' => '#708090', |
| 166 |
'slategrey' => '#708090', |
| 167 |
'snow' => '#fffafa', |
| 168 |
'springgreen' => '#00ff7f', |
| 169 |
'steelblue' => '#4682b4', |
| 170 |
'tan' => '#d2b48c', |
| 171 |
'teal' => '#008080', |
| 172 |
'thistle' => '#d8bfd8', |
| 173 |
'tomato' => '#ff6347', |
| 174 |
'turquoise' => '#40e0d0', |
| 175 |
'violet' => '#ee82ee', |
| 176 |
'wheat' => '#f5deb3', |
| 177 |
'white' => '#ffffff', |
| 178 |
'whitesmoke' => '#f5f5f5', |
| 179 |
'yellow' => '#ffff00', |
| 180 |
'yellowgreen' => '#9acd32', |
| 181 |
]; |
| 182 |
|
| 183 |
public function __construct($colour) |
| 184 |
{ |
| 185 |
if(!is_string($colour)) |
| 186 |
throw new \InvalidArgumentException('Expected string'); |
| 187 |
|
| 188 |
$original = $colour; |
| 189 |
$colour = strtolower($colour); |
| 190 |
if($colour === 'transparent') { |
| 191 |
$this->r = $this->g = $this->b = $this->a = 0; |
| 192 |
return; |
| 193 |
} |
| 194 |
|
| 195 |
if(isset(RGBColour::KEYWORDS[$colour])) |
| 196 |
$colour = RGBColour::KEYWORDS[$colour]; |
| 197 |
|
| 198 |
$r = $g = $b = 0; |
| 199 |
$c = $colour; |
| 200 |
if(strpos($colour, '#') !== 0) { |
| 201 |
$c = RGBColour::fromRGB($colour); |
| 202 |
if($c === null) |
| 203 |
$c = RGBColour::fromRGBA($colour); |
| 204 |
if($c === null) |
| 205 |
$c = RGBColour::fromHSL($colour); |
| 206 |
if($c === null) |
| 207 |
$c = RGBColour::fromHSLA($colour); |
| 208 |
} |
| 209 |
|
| 210 |
if(is_array($c)) { |
| 211 |
$this->r = $c[0]; |
| 212 |
$this->g = $c[1]; |
| 213 |
$this->b = $c[2]; |
| 214 |
if(isset($c[3])) |
| 215 |
$this->a = $c[3]; |
| 216 |
return; |
| 217 |
} |
| 218 |
|
| 219 |
if(strlen($colour) === 4) |
| 220 |
$c = RGBColour::hex3ToHex($colour); |
| 221 |
|
| 222 |
if($c !== null && preg_match('/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $c, $m)) { |
| 223 |
$red = $m[1]; |
| 224 |
$green = $m[2]; |
| 225 |
$blue = $m[3]; |
| 226 |
$this->r = base_convert($red, 16, 10); |
| 227 |
$this->g = base_convert($green, 16, 10); |
| 228 |
$this->b = base_convert($blue, 16, 10); |
| 229 |
return; |
| 230 |
} |
| 231 |
throw new \Exception('Unable to parse colour: [' . $original .']'); |
| 232 |
} |
| 233 |
|
| 234 |
/** |
| 235 |
* Converts rgb(r,g,b) to [$r, $g, $b] |
| 236 |
*/ |
| 237 |
public static function fromRGB($colour) |
| 238 |
{ |
| 239 |
$r = $g = $b = 0; |
| 240 |
$rgb_integer = '/^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/'; |
| 241 |
$rgb_percent = '/^rgb\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$/'; |
| 242 |
if(preg_match($rgb_integer, $colour, $m)) { |
| 243 |
$r = min(255, max(0, $m[1])); |
| 244 |
$g = min(255, max(0, $m[2])); |
| 245 |
$b = min(255, max(0, $m[3])); |
| 246 |
} elseif(preg_match($rgb_percent, $colour, $m)) { |
| 247 |
$r = min(100, max(0, $m[1])) * 255 / 100; |
| 248 |
$g = min(100, max(0, $m[2])) * 255 / 100; |
| 249 |
$b = min(100, max(0, $m[3])) * 255 / 100; |
| 250 |
} else { |
| 251 |
return null; |
| 252 |
} |
| 253 |
|
| 254 |
return [$r, $g, $b]; |
| 255 |
} |
| 256 |
|
| 257 |
/** |
| 258 |
* Converts rgba(r,g,b,a) to [$r, $g, $b, $a] |
| 259 |
*/ |
| 260 |
public static function fromRGBA($colour) |
| 261 |
{ |
| 262 |
$r = $g = $b = $a = 0; |
| 263 |
$rgba_integer = '/^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+(\.\d+)?)\s*\)$/'; |
| 264 |
$rgba_percent = '/^rgba\(\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+(\.\d+)?)\s*\)$/'; |
| 265 |
if(preg_match($rgba_integer, $colour, $m)) { |
| 266 |
$r = min(255, max(0, $m[1])); |
| 267 |
$g = min(255, max(0, $m[2])); |
| 268 |
$b = min(255, max(0, $m[3])); |
| 269 |
$a = min(1.0, max(0.0, $m[4])); |
| 270 |
} elseif(preg_match($rgba_percent, $colour, $m)) { |
| 271 |
$r = min(100, max(0, $m[1])) * 255 / 100; |
| 272 |
$g = min(100, max(0, $m[2])) * 255 / 100; |
| 273 |
$b = min(100, max(0, $m[3])) * 255 / 100; |
| 274 |
$a = min(1.0, max(0.0, $m[4])); |
| 275 |
} else { |
| 276 |
return null; |
| 277 |
} |
| 278 |
|
| 279 |
return [$r, $g, $b, $a]; |
| 280 |
} |
| 281 |
|
| 282 |
/** |
| 283 |
* Converts hsl(h, s%, l%) to [$r, $g, $b] |
| 284 |
*/ |
| 285 |
public static function fromHSL($colour) |
| 286 |
{ |
| 287 |
$r = $g = $b = 0; |
| 288 |
$hsl = '/^hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)$/'; |
| 289 |
if(preg_match($hsl, $colour, $m)) { |
| 290 |
$h = $m[1]; |
| 291 |
$s = $m[2] / 100.0; |
| 292 |
$l = $m[3] / 100.0; |
| 293 |
return RGBColour::hslToRgb($h, $s, $l); |
| 294 |
} |
| 295 |
return null; |
| 296 |
} |
| 297 |
|
| 298 |
/** |
| 299 |
* Converts hsla(h, s%, l%, a) to [$r, $g, $b, $a] |
| 300 |
*/ |
| 301 |
public static function fromHSLA($colour) |
| 302 |
{ |
| 303 |
$r = $g = $b = 0; |
| 304 |
$hsla = '/^hsla\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+(\.\d+)?)\s*\)$/'; |
| 305 |
if(preg_match($hsla, $colour, $m)) { |
| 306 |
$h = $m[1]; |
| 307 |
$s = $m[2] / 100.0; |
| 308 |
$l = $m[3] / 100.0; |
| 309 |
list($r, $g, $b) = RGBColour::hslToRgb($h, $s, $l); |
| 310 |
$a = min(1.0, max(0.0, $m[4])); |
| 311 |
return [$r, $g, $b, $a]; |
| 312 |
} |
| 313 |
return null; |
| 314 |
} |
| 315 |
|
| 316 |
/** |
| 317 |
* Converts 3-digit hex to 6-digit hex |
| 318 |
*/ |
| 319 |
public static function hex3ToHex($colour) |
| 320 |
{ |
| 321 |
if(strlen($colour) !== 4 || strpos($colour, '#') !== 0) |
| 322 |
return null; |
| 323 |
if(preg_match('/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i', $colour, $m)) |
| 324 |
return strtolower('#' . $m[1] . $m[1] . $m[2] . $m[2] . $m[3] . $m[3]); |
| 325 |
return null; |
| 326 |
} |
| 327 |
|
| 328 |
/** |
| 329 |
* Convert HSL to RGB |
| 330 |
*/ |
| 331 |
public static function hslToRgb($h, $s, $l) |
| 332 |
{ |
| 333 |
$h1 = fmod($h + 720, 360); |
| 334 |
$s1 = min(1.0, max(0.0, $s)); |
| 335 |
$l1 = min(1.0, max(0.0, $l)); |
| 336 |
|
| 337 |
$c = (1 - abs(2 * $l1 - 1)) * $s1; |
| 338 |
$x = $c * (1 - abs(fmod($h1 / 60, 2) - 1)); |
| 339 |
$m = $l1 - $c / 2; |
| 340 |
|
| 341 |
$c = 255 * ($c + $m); |
| 342 |
$x = 255 * ($x + $m); |
| 343 |
$m *= 255; |
| 344 |
switch(floor($h1 / 60)) { |
| 345 |
case 0 : $rgb = [$c, $x, $m]; break; |
| 346 |
case 1 : $rgb = [$x, $c, $m]; break; |
| 347 |
case 2 : $rgb = [$m, $c, $x]; break; |
| 348 |
case 3 : $rgb = [$m, $x, $c]; break; |
| 349 |
case 4 : $rgb = [$x, $m, $c]; break; |
| 350 |
case 5 : $rgb = [$c, $m, $x]; break; |
| 351 |
} |
| 352 |
|
| 353 |
return $rgb; |
| 354 |
} |
| 355 |
|
| 356 |
/** |
| 357 |
* Convert RGB to HSL |
| 358 |
*/ |
| 359 |
public static function rgbToHsl($r, $g, $b) |
| 360 |
{ |
| 361 |
$r1 = min(255.0, max(0.0, $r)) / 255.0; |
| 362 |
$g1 = min(255.0, max(0.0, $g)) / 255.0; |
| 363 |
$b1 = min(255.0, max(0.0, $b)) / 255.0; |
| 364 |
$cmax = max($r1, $g1, $b1); |
| 365 |
$cmin = min($r1, $g1, $b1); |
| 366 |
$delta = $cmax - $cmin; |
| 367 |
|
| 368 |
$l = ($cmax + $cmin) / 2; |
| 369 |
if($delta == 0) { |
| 370 |
$h = $s = 0; |
| 371 |
} else { |
| 372 |
if($cmax == $r1) { |
| 373 |
$h = fmod(($g1 - $b1) / $delta, 6); |
| 374 |
} elseif($cmax == $g1) { |
| 375 |
$h = 2 + ($b1 - $r1) / $delta; |
| 376 |
} else { |
| 377 |
$h = 4 + ($r1 - $g1) / $delta; |
| 378 |
} |
| 379 |
$h = fmod(360 + ($h * 60), 360); |
| 380 |
$s = $delta / (1 - abs(2 * $l - 1)); |
| 381 |
} |
| 382 |
return [$h, $s, $l]; |
| 383 |
} |
| 384 |
|
| 385 |
/** |
| 386 |
* Sets the colour components and alpha |
| 387 |
*/ |
| 388 |
public function setRGB($r, $g, $b, $a = null) |
| 389 |
{ |
| 390 |
$this->r = $r; |
| 391 |
$this->g = $g; |
| 392 |
$this->b = $b; |
| 393 |
if($a !== null) |
| 394 |
$this->a = $a; |
| 395 |
} |
| 396 |
|
| 397 |
/** |
| 398 |
* Returns the R,G,B values |
| 399 |
*/ |
| 400 |
public function getRGB() |
| 401 |
{ |
| 402 |
return [$this->r, $this->g, $this->b]; |
| 403 |
} |
| 404 |
|
| 405 |
/** |
| 406 |
* Sets the colour from HSL and alpha |
| 407 |
*/ |
| 408 |
public function setHSL($h, $s, $l, $a = null) |
| 409 |
{ |
| 410 |
list($r, $g, $b) = RGBColour::hslToRgb($h, $s, $l); |
| 411 |
$this->setRGB($r, $g, $b, $a); |
| 412 |
} |
| 413 |
|
| 414 |
/** |
| 415 |
* Returns H, S, L values |
| 416 |
*/ |
| 417 |
public function getHSL() |
| 418 |
{ |
| 419 |
return RGBColour::rgbToHsl($this->r, $this->g, $this->b); |
| 420 |
} |
| 421 |
|
| 422 |
/** |
| 423 |
* Returns the A value |
| 424 |
*/ |
| 425 |
public function getA() |
| 426 |
{ |
| 427 |
return $this->a; |
| 428 |
} |
| 429 |
|
| 430 |
/** |
| 431 |
* Returns the value as a hex string |
| 432 |
*/ |
| 433 |
public function getHex() |
| 434 |
{ |
| 435 |
return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b); |
| 436 |
} |
| 437 |
} |
| 438 |
|
| 439 |
|