PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.2
Jetpack – WP Security, Backup, Speed, & Growth v16.2
16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 All 502 releases
jetpack / _inc / lib / class.color.php

class.color.php in Jetpack – WP Security, Backup, Speed, & Growth 16.2, at _inc/lib/class.color.php

905 lines 21.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Color utility and conversion
4 *
5 * Represents a color value, and converts between RGB/HSV/XYZ/Lab/HSL
6 *
7 * Example:
8 * $color = new Jetpack_Color(0xFFFFFF);
9 *
10 * @author Harold Asbridge <hasbridge@gmail.com>
11 * @author Matt Wiebe <wiebe@automattic.com>
12 * @license https://www.opensource.org/licenses/MIT
13 *
14 * @package automattic/jetpack
15 */
16
17 // phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
18
19 if ( ! class_exists( 'Jetpack_Color' ) ) {
20 /**
21 * Color utilities
22 */
23 class Jetpack_Color {
24 /**
25 * Color code (later array or string, depending on type)
26 *
27 * @var int|array|string
28 */
29 protected $color = 0;
30
31 /**
32 * Initialize object
33 *
34 * @param string|array $color A color of the type $type.
35 * @param string $type The type of color we will construct from.
36 * One of hex (default), rgb, hsl, int.
37 */
38 public function __construct( $color = null, $type = 'hex' ) {
39 _deprecated_function( 'Jetpack_Color::__construct', 'jetpack-13.8' );
40
41 if ( $color ) {
42 switch ( $type ) {
43 case 'hex':
44 $this->fromHex( $color );
45 break;
46 case 'rgb':
47 if ( is_array( $color ) && count( $color ) === 3 ) {
48 list( $r, $g, $b ) = array_values( $color );
49 $this->fromRgbInt( $r, $g, $b );
50 }
51 break;
52 case 'hsl':
53 if ( is_array( $color ) && count( $color ) === 3 ) {
54 list( $h, $s, $l ) = array_values( $color );
55 $this->fromHsl( $h, $s, $l );
56 }
57 break;
58 case 'int':
59 $this->fromInt( $color );
60 break;
61 default:
62 // there is no default.
63 break;
64 }
65 }
66 }
67
68 /**
69 * Init color from hex value
70 *
71 * @param string $hex_value Color hex value.
72 *
73 * @return $this
74 * @throws RangeException Invalid color code range error.
75 */
76 public function fromHex( $hex_value ) {
77 $hex_value = str_replace( '#', '', $hex_value );
78 // handle short hex codes like #fff.
79 if ( 3 === strlen( $hex_value ) ) {
80 $hex_value = $hex_value[0] . $hex_value[0] . $hex_value[1] . $hex_value[1] . $hex_value[2] . $hex_value[2];
81 }
82 return $this->fromInt( hexdec( $hex_value ) );
83 }
84
85 /**
86 * Init color from integer RGB values
87 *
88 * @param int $red Red color code.
89 * @param int $green Green color code.
90 * @param int $blue Blue color code.
91 *
92 * @return $this
93 * @throws RangeException Invalid color code range error.
94 */
95 public function fromRgbInt( $red, $green, $blue ) {
96 if ( $red < 0 || $red > 255 ) {
97 throw new RangeException( 'Red value ' . $red . ' out of valid color code range' );
98 }
99
100 if ( $green < 0 || $green > 255 ) {
101 throw new RangeException( 'Green value ' . $green . ' out of valid color code range' );
102 }
103
104 if ( $blue < 0 || $blue > 255 ) {
105 throw new RangeException( 'Blue value ' . $blue . ' out of valid color code range' );
106 }
107
108 $this->color = ( intval( $red ) << 16 ) + ( intval( $green ) << 8 ) + intval( $blue );
109
110 return $this;
111 }
112
113 /**
114 * Init color from hex RGB values
115 *
116 * @param string $red Red color code.
117 * @param string $green Green color code.
118 * @param string $blue Blue color code.
119 *
120 * @return $this
121 */
122 public function fromRgbHex( $red, $green, $blue ) {
123 return $this->fromRgbInt( hexdec( $red ), hexdec( $green ), hexdec( $blue ) );
124 }
125
126 /**
127 * Converts an HSL color value to RGB. Conversion formula
128 * adapted from https://en.wikipedia.org/wiki/HSL_color_space.
129 *
130 * @param int $h Hue. [0-360].
131 * @param int $s Saturation [0, 100].
132 * @param int $l Lightness [0, 100].
133 */
134 public function fromHsl( $h, $s, $l ) {
135 $h /= 360;
136 $s /= 100;
137 $l /= 100;
138
139 if ( 0 === $s ) {
140 // achromatic.
141 $r = $l;
142 $g = $l;
143 $b = $l;
144 } else {
145 $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;
146 $p = 2 * $l - $q;
147 $r = $this->hue2rgb( $p, $q, $h + 1 / 3 );
148 $g = $this->hue2rgb( $p, $q, $h );
149 $b = $this->hue2rgb( $p, $q, $h - 1 / 3 );
150 }
151
152 return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 );
153 }
154
155 /**
156 * Helper function for Jetpack_Color::fromHsl()
157 *
158 * @param float $p Minimum of R/G/B [0, 1].
159 * @param float $q Maximum of R/G/B [0, 1].
160 * @param float $t Adjusted hue [0, 1].
161 */
162 private function hue2rgb( $p, $q, $t ) {
163 if ( $t < 0 ) {
164 ++$t;
165 }
166 if ( $t > 1 ) {
167 --$t;
168 }
169 if ( $t < 1 / 6 ) {
170 return $p + ( $q - $p ) * 6 * $t;
171 }
172 if ( $t < 1 / 2 ) {
173 return $q;
174 }
175 if ( $t < 2 / 3 ) {
176 return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6;
177 }
178 return $p;
179 }
180
181 /**
182 * Init color from integer value
183 *
184 * @param int $int_value Color code.
185 *
186 * @return $this
187 * @throws RangeException Invalid color code range error.
188 */
189 public function fromInt( $int_value ) {
190 if ( $int_value < 0 || $int_value > 16777215 ) {
191 throw new RangeException( $int_value . ' out of valid color code range' );
192 }
193
194 $this->color = $int_value;
195
196 return $this;
197 }
198
199 /**
200 * Convert color to hex
201 *
202 * @return string
203 */
204 public function toHex() {
205 return sprintf( '%06x', $this->color );
206 }
207
208 /**
209 * Convert color to RGB array (integer values)
210 *
211 * @return array
212 */
213 public function toRgbInt() {
214 return array(
215 'red' => (int) ( 255 & ( $this->color >> 16 ) ),
216 'green' => (int) ( 255 & ( $this->color >> 8 ) ),
217 'blue' => (int) ( 255 & ( $this->color ) ),
218 );
219 }
220
221 /**
222 * Convert color to RGB array (hex values)
223 *
224 * @return array
225 */
226 public function toRgbHex() {
227 $r = array();
228 foreach ( $this->toRgbInt() as $item ) {
229 $r[] = dechex( $item );
230 }
231 return $r;
232 }
233
234 /**
235 * Get Hue/Saturation/Value for the current color
236 * (float values, slow but accurate)
237 *
238 * @return array
239 */
240 public function toHsvFloat() {
241 $rgb = $this->toRgbInt();
242
243 $rgb_min = min( $rgb );
244 $rgb_max = max( $rgb );
245
246 $hsv = array(
247 'hue' => 0,
248 'sat' => 0,
249 'val' => $rgb_max,
250 );
251
252 // If v is 0, color is black.
253 if ( 0 === $hsv['val'] ) {
254 return $hsv;
255 }
256
257 // Normalize RGB values to 1.
258 $rgb['red'] /= $hsv['val'];
259 $rgb['green'] /= $hsv['val'];
260 $rgb['blue'] /= $hsv['val'];
261 $rgb_min = min( $rgb );
262 $rgb_max = max( $rgb );
263
264 // Calculate saturation.
265 $hsv['sat'] = $rgb_max - $rgb_min;
266 if ( 0 === $hsv['sat'] ) {
267 $hsv['hue'] = 0;
268 return $hsv;
269 }
270
271 // Normalize saturation to 1.
272 $rgb['red'] = ( $rgb['red'] - $rgb_min ) / ( $rgb_max - $rgb_min );
273 $rgb['green'] = ( $rgb['green'] - $rgb_min ) / ( $rgb_max - $rgb_min );
274 $rgb['blue'] = ( $rgb['blue'] - $rgb_min ) / ( $rgb_max - $rgb_min );
275 $rgb_min = min( $rgb );
276 $rgb_max = max( $rgb );
277
278 // Calculate hue.
279 if ( $rgb_max === $rgb['red'] ) {
280 $hsv['hue'] = 0.0 + 60 * ( $rgb['green'] - $rgb['blue'] );
281 if ( $hsv['hue'] < 0 ) {
282 $hsv['hue'] += 360;
283 }
284 } elseif ( $rgb_max === $rgb['green'] ) {
285 $hsv['hue'] = 120 + ( 60 * ( $rgb['blue'] - $rgb['red'] ) );
286 } else {
287 $hsv['hue'] = 240 + ( 60 * ( $rgb['red'] - $rgb['green'] ) );
288 }
289
290 return $hsv;
291 }
292
293 /**
294 * Get HSV values for color
295 * (integer values from 0-255, fast but less accurate)
296 *
297 * @return array
298 */
299 public function toHsvInt() {
300 $rgb = $this->toRgbInt();
301
302 $rgb_min = min( $rgb );
303 $rgb_max = max( $rgb );
304
305 $hsv = array(
306 'hue' => 0,
307 'sat' => 0,
308 'val' => $rgb_max,
309 );
310
311 // If value is 0, color is black.
312 if ( 0 === $hsv['val'] ) {
313 return $hsv;
314 }
315
316 // Calculate saturation.
317 $hsv['sat'] = round( 255 * ( $rgb_max - $rgb_min ) / $hsv['val'] );
318 if ( 0 === $hsv['sat'] ) {
319 $hsv['hue'] = 0;
320 return $hsv;
321 }
322
323 // Calculate hue.
324 if ( $rgb_max === $rgb['red'] ) {
325 $hsv['hue'] = round( 0 + 43 * ( $rgb['green'] - $rgb['blue'] ) / ( $rgb_max - $rgb_min ) );
326 } elseif ( $rgb_max === $rgb['green'] ) {
327 $hsv['hue'] = round( 85 + 43 * ( $rgb['blue'] - $rgb['red'] ) / ( $rgb_max - $rgb_min ) );
328 } else {
329 $hsv['hue'] = round( 171 + 43 * ( $rgb['red'] - $rgb['green'] ) / ( $rgb_max - $rgb_min ) );
330 }
331 if ( $hsv['hue'] < 0 ) {
332 $hsv['hue'] += 255;
333 }
334
335 return $hsv;
336 }
337
338 /**
339 * Converts an RGB color value to HSL. Conversion formula
340 * adapted from https://en.wikipedia.org/wiki/HSL_color_space.
341 * Assumes r, g, and b are contained in the set [0, 255] and
342 * returns h in [0, 360], s in [0, 100], l in [0, 100]
343 *
344 * @return Array The HSL representation
345 */
346 public function toHsl() {
347 list( $r, $g, $b ) = array_values( $this->toRgbInt() );
348 $r /= 255;
349 $g /= 255;
350 $b /= 255;
351 $max = max( $r, $g, $b );
352 $min = min( $r, $g, $b );
353 $l = ( $max + $min ) / 2;
354
355 if ( $max === $min ) {
356 // achromatic.
357 $s = 0;
358 $h = 0;
359 } else {
360 $d = $max - $min;
361 $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
362 switch ( $max ) {
363 case $r:
364 $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
365 break;
366 case $g:
367 $h = ( $b - $r ) / $d + 2;
368 break;
369 case $b:
370 $h = ( $r - $g ) / $d + 4;
371 break;
372 }
373 $h /= 6;
374 }
375 $h = (int) round( $h * 360 );
376 $s = (int) round( $s * 100 );
377 $l = (int) round( $l * 100 );
378 return compact( 'h', 's', 'l' );
379 }
380
381 /**
382 * From a color code to a string to be used in CSS declaration.
383 *
384 * @param string $type Color code type.
385 * @param int $alpha Transparency.
386 *
387 * @return string
388 */
389 public function toCSS( $type = 'hex', $alpha = 1 ) {
390 switch ( $type ) {
391 case 'hex':
392 return $this->toString();
393 case 'rgb':
394 case 'rgba':
395 list( $r, $g, $b ) = array_values( $this->toRgbInt() );
396 if ( is_numeric( $alpha ) && $alpha < 1 ) {
397 return "rgba( {$r}, {$g}, {$b}, $alpha )";
398 } else {
399 return "rgb( {$r}, {$g}, {$b} )";
400 }
401 case 'hsl':
402 case 'hsla':
403 list( $h, $s, $l ) = array_values( $this->toHsl() );
404 if ( is_numeric( $alpha ) && $alpha < 1 ) {
405 return "hsla( {$h}, {$s}, {$l}, $alpha )";
406 } else {
407 return "hsl( {$h}, {$s}, {$l} )";
408 }
409 default:
410 return $this->toString();
411 }
412 }
413
414 /**
415 * Get current color in XYZ format
416 *
417 * @return array
418 */
419 public function toXyz() {
420 $rgb = $this->toRgbInt();
421
422 // Normalize RGB values to 1.
423 $rgb_new = array();
424 foreach ( $rgb as $item ) {
425 $rgb_new[] = $item / 255;
426 }
427 $rgb = $rgb_new;
428
429 $rgb_new = array();
430 foreach ( $rgb as $item ) {
431 if ( $item > 0.04045 ) {
432 $item = pow( ( ( $item + 0.055 ) / 1.055 ), 2.4 );
433 } else {
434 $item = $item / 12.92;
435 }
436 $rgb_new[] = $item * 100;
437 }
438 $rgb = $rgb_new;
439
440 // Observer. = 2°, Illuminant = D65.
441 $xyz = array(
442 'x' => ( $rgb['red'] * 0.4124 ) + ( $rgb['green'] * 0.3576 ) + ( $rgb['blue'] * 0.1805 ),
443 'y' => ( $rgb['red'] * 0.2126 ) + ( $rgb['green'] * 0.7152 ) + ( $rgb['blue'] * 0.0722 ),
444 'z' => ( $rgb['red'] * 0.0193 ) + ( $rgb['green'] * 0.1192 ) + ( $rgb['blue'] * 0.9505 ),
445 );
446
447 return $xyz;
448 }
449
450 /**
451 * Get color CIE-Lab values
452 *
453 * @return array
454 */
455 public function toLabCie() {
456 $xyz = $this->toXyz();
457
458 // Ovserver = 2*, Iluminant=D65.
459 $xyz['x'] /= 95.047;
460 $xyz['y'] /= 100;
461 $xyz['z'] /= 108.883;
462
463 $xyz_new = array();
464 foreach ( $xyz as $item ) {
465 if ( $item > 0.008856 ) {
466 $xyz_new[] = pow( $item, 1 / 3 );
467 } else {
468 $xyz_new[] = ( 7.787 * $item ) + ( 16 / 116 );
469 }
470 }
471 $xyz = $xyz_new;
472
473 $lab = array(
474 'l' => ( 116 * $xyz['y'] ) - 16,
475 'a' => 500 * ( $xyz['x'] - $xyz['y'] ),
476 'b' => 200 * ( $xyz['y'] - $xyz['z'] ),
477 );
478
479 return $lab;
480 }
481
482 /**
483 * Convert color to integer
484 *
485 * @return int
486 */
487 public function toInt() {
488 return $this->color;
489 }
490
491 /**
492 * Alias of toString()
493 *
494 * @return string
495 */
496 public function __toString() {
497 return $this->toString();
498 }
499
500 /**
501 * Get color as string
502 *
503 * @return string
504 */
505 public function toString() {
506 $str = $this->toHex();
507 return strtoupper( "#{$str}" );
508 }
509
510 /**
511 * Get the distance between this color and the given color
512 *
513 * @param Jetpack_Color $color Color code.
514 *
515 * @return int
516 */
517 public function getDistanceRgbFrom( Jetpack_Color $color ) {
518 $rgb1 = $this->toRgbInt();
519 $rgb2 = $color->toRgbInt();
520
521 $r_diff = abs( $rgb1['red'] - $rgb2['red'] );
522 $g_diff = abs( $rgb1['green'] - $rgb2['green'] );
523 $b_diff = abs( $rgb1['blue'] - $rgb2['blue'] );
524
525 // Sum of RGB differences.
526 $diff = $r_diff + $g_diff + $b_diff;
527 return $diff;
528 }
529
530 /**
531 * Get distance from the given color using the Delta E method
532 *
533 * @param Jetpack_Color $color Color code.
534 *
535 * @return float
536 */
537 public function getDistanceLabFrom( Jetpack_Color $color ) {
538 $lab1 = $this->toLabCie();
539 $lab2 = $color->toLabCie();
540
541 $l_diff = abs( $lab2['l'] - $lab1['l'] );
542 $a_diff = abs( $lab2['a'] - $lab1['a'] );
543 $b_diff = abs( $lab2['b'] - $lab1['b'] );
544
545 $delta = sqrt( $l_diff + $a_diff + $b_diff );
546
547 return $delta;
548 }
549
550 /**
551 * Calculate luminosity.
552 *
553 * @return float
554 */
555 public function toLuminosity() {
556 $lum = array();
557 foreach ( $this->toRgbInt() as $slot => $value ) {
558 $chan = $value / 255;
559 $lum[ $slot ] = ( $chan <= 0.03928 ) ? $chan / 12.92 : pow( ( ( $chan + 0.055 ) / 1.055 ), 2.4 );
560 }
561 return 0.2126 * $lum['red'] + 0.7152 * $lum['green'] + 0.0722 * $lum['blue'];
562 }
563
564 /**
565 * Get distance between colors using luminance.
566 * Should be more than 5 for readable contrast
567 *
568 * @param Jetpack_Color $color Another color.
569 * @return float
570 */
571 public function getDistanceLuminosityFrom( Jetpack_Color $color ) {
572 $l1 = $this->toLuminosity();
573 $l2 = $color->toLuminosity();
574 if ( $l1 > $l2 ) {
575 return ( $l1 + 0.05 ) / ( $l2 + 0.05 );
576 } else {
577 return ( $l2 + 0.05 ) / ( $l1 + 0.05 );
578 }
579 }
580
581 /**
582 * Get maximum contrast color.
583 *
584 * @return $this
585 */
586 public function getMaxContrastColor() {
587 $with_black = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000' ) );
588 $with_white = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff' ) );
589 $color = new Jetpack_Color();
590 $hex = ( $with_black >= $with_white ) ? '#000000' : '#ffffff';
591 return $color->fromHex( $hex );
592 }
593
594 /**
595 * Get grayscale contrasting color.
596 *
597 * @param bool|int $contrast Contrast.
598 *
599 * @return $this
600 */
601 public function getGrayscaleContrastingColor( $contrast = false ) {
602 if ( ! $contrast ) {
603 return $this->getMaxContrastColor();
604 }
605 // don't allow less than 5.
606 $target_contrast = ( $contrast < 5 ) ? 5 : $contrast;
607 $color = $this->getMaxContrastColor();
608 $contrast = $color->getDistanceLuminosityFrom( $this );
609
610 // if current max contrast is less than the target contrast, we had wishful thinking.
611 if ( $contrast <= $target_contrast ) {
612 return $color;
613 }
614
615 $incr = ( '#000000' === $color->toString() ) ? 1 : -1;
616 while ( $contrast > $target_contrast ) {
617 $color = $color->incrementLightness( $incr );
618 $contrast = $color->getDistanceLuminosityFrom( $this );
619 }
620
621 return $color;
622 }
623
624 /**
625 * Gets a readable contrasting color. $this is assumed to be the text and $color the background color.
626 *
627 * @param object $bg_color A Color object that will be compared against $this.
628 * @param integer $min_contrast The minimum contrast to achieve, if possible.
629 * @return object A Color object, an increased contrast $this compared against $bg_color
630 */
631 public function getReadableContrastingColor( $bg_color = false, $min_contrast = 5 ) {
632 if ( ! $bg_color || ! is_a( $bg_color, 'Jetpack_Color' ) ) {
633 return $this;
634 }
635 // you shouldn't use less than 5, but you might want to.
636 $target_contrast = $min_contrast;
637 // working things.
638 $contrast = $bg_color->getDistanceLuminosityFrom( $this );
639 $max_contrast_color = $bg_color->getMaxContrastColor();
640 $max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color );
641
642 // if current max contrast is less than the target contrast, we had wishful thinking.
643 // still, go max.
644 if ( $max_contrast <= $target_contrast ) {
645 return $max_contrast_color;
646 }
647 // or, we might already have sufficient contrast.
648 if ( $contrast >= $target_contrast ) {
649 return $this;
650 }
651
652 $incr = ( 0 === $max_contrast_color->toInt() ) ? -1 : 1;
653 while ( $contrast < $target_contrast ) {
654 $this->incrementLightness( $incr );
655 $contrast = $bg_color->getDistanceLuminosityFrom( $this );
656 // infininite loop prevention: you never know.
657 if ( 0 === $this->color || 16777215 === $this->color ) {
658 break;
659 }
660 }
661
662 return $this;
663 }
664
665 /**
666 * Detect if color is grayscale
667 *
668 * @param int $threshold Max difference between colors.
669 *
670 * @return bool
671 */
672 public function isGrayscale( $threshold = 16 ) {
673 $rgb = $this->toRgbInt();
674
675 // Get min and max rgb values, then difference between them.
676 $rgb_min = min( $rgb );
677 $rgb_max = max( $rgb );
678 $diff = $rgb_max - $rgb_min;
679
680 return $diff < $threshold;
681 }
682
683 /**
684 * Get the closest matching color from the given array of colors
685 *
686 * @param array $colors array of integers or Jetpack_Color objects.
687 *
688 * @return mixed the array key of the matched color
689 */
690 public function getClosestMatch( array $colors ) {
691 $match_dist = 10000;
692 $match_key = null;
693 foreach ( $colors as $key => $color ) {
694 if ( false === ( $color instanceof Jetpack_Color ) ) {
695 $c = new Jetpack_Color( $color );
696 }
697 $dist = $this->getDistanceLabFrom( $c );
698 if ( $dist < $match_dist ) {
699 $match_dist = $dist;
700 $match_key = $key;
701 }
702 }
703
704 return $match_key;
705 }
706
707 /* TRANSFORMS */
708
709 /**
710 * Transform -- Darken color.
711 *
712 * @param int $amount Amount. Default to 5.
713 *
714 * @return $this
715 */
716 public function darken( $amount = 5 ) {
717 return $this->incrementLightness( - $amount );
718 }
719
720 /**
721 * Transform -- Lighten color.
722 *
723 * @param int $amount Amount. Default to 5.
724 *
725 * @return $this
726 */
727 public function lighten( $amount = 5 ) {
728 return $this->incrementLightness( $amount );
729 }
730
731 /**
732 * Transform -- Increment lightness.
733 *
734 * @param int $amount Amount.
735 *
736 * @return $this
737 */
738 public function incrementLightness( $amount ) {
739 $hsl = $this->toHsl();
740
741 $h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
742 $s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
743 $l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
744
745 $l += $amount;
746 if ( $l < 0 ) {
747 $l = 0;
748 }
749 if ( $l > 100 ) {
750 $l = 100;
751 }
752 return $this->fromHsl( $h, $s, $l );
753 }
754
755 /**
756 * Transform -- Saturate color.
757 *
758 * @param int $amount Amount. Default to 15.
759 *
760 * @return $this
761 */
762 public function saturate( $amount = 15 ) {
763 return $this->incrementSaturation( $amount );
764 }
765
766 /**
767 * Transform -- Desaturate color.
768 *
769 * @param int $amount Amount. Default to 15.
770 *
771 * @return $this
772 */
773 public function desaturate( $amount = 15 ) {
774 return $this->incrementSaturation( - $amount );
775 }
776
777 /**
778 * Transform -- Increment saturation.
779 *
780 * @param int $amount Amount.
781 *
782 * @return $this
783 */
784 public function incrementSaturation( $amount ) {
785 $hsl = $this->toHsl();
786
787 $h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
788 $s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
789 $l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
790
791 $s += $amount;
792 if ( $s < 0 ) {
793 $s = 0;
794 }
795 if ( $s > 100 ) {
796 $s = 100;
797 }
798 return $this->fromHsl( $h, $s, $l );
799 }
800
801 /**
802 * Transform -- To grayscale.
803 *
804 * @return $this
805 */
806 public function toGrayscale() {
807 $hsl = $this->toHsl();
808
809 $h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
810 $s = 0;
811 $l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
812
813 return $this->fromHsl( $h, $s, $l );
814 }
815
816 /**
817 * Transform -- To the complementary color.
818 *
819 * The complement is the color on the opposite side of the color wheel, 180° away.
820 *
821 * @return $this
822 */
823 public function getComplement() {
824 return $this->incrementHue( 180 );
825 }
826
827 /**
828 * Transform -- To an analogous color of the complement.
829 *
830 * @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
831 *
832 * @return $this
833 */
834 public function getSplitComplement( $step = 1 ) {
835 $incr = 180 + ( $step * 30 );
836 return $this->incrementHue( $incr );
837 }
838
839 /**
840 * Transform -- To an analogous color.
841 *
842 * Analogous colors are those adjacent on the color wheel, separated by 30°.
843 *
844 * @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
845 *
846 * @return $this
847 */
848 public function getAnalog( $step = 1 ) {
849 $incr = $step * 30;
850 return $this->incrementHue( $incr );
851 }
852
853 /**
854 * Transform -- To a tetradic (rectangular) color.
855 *
856 * A rectangular color scheme uses a color, its complement, and the colors 60° from each.
857 * This transforms the color to its 60° "tetrad".
858 *
859 * @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
860 *
861 * @return $this
862 */
863 public function getTetrad( $step = 1 ) {
864 $incr = $step * 60;
865 return $this->incrementHue( $incr );
866 }
867
868 /**
869 * Transform -- To a triadic color.
870 *
871 * A triadic color scheme uses three colors evenly spaced (120°) around the color wheel.
872 * This transforms the color to one of its triadic colors.
873 *
874 * @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
875 *
876 * @return $this
877 */
878 public function getTriad( $step = 1 ) {
879 $incr = $step * 120;
880 return $this->incrementHue( $incr );
881 }
882
883 /**
884 * Transform -- Increment hue.
885 *
886 * @param int $amount Amount.
887 *
888 * @return $this
889 */
890 public function incrementHue( $amount ) {
891 $hsl = $this->toHsl();
892
893 $h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
894 $s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
895 $l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
896
897 $h = ( $h + $amount ) % 360;
898 if ( $h < 0 ) {
899 $h += 360;
900 }
901 return $this->fromHsl( $h, $s, $l );
902 }
903 }
904 }
905