PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.3
Jetpack – WP Security, Backup, Speed, & Growth v6.3
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 14.3.1 All 501 releases
jetpack / _inc / lib / class.color.php

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

756 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
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 http://www.opensource.org/licenses/MIT
13 */
14
15 class Jetpack_Color {
16 /**
17 * @var int
18 */
19 protected $color = 0;
20
21 /**
22 * Initialize object
23 *
24 * @param string|array $color A color of the type $type
25 * @param string $type The type of color we will construct from.
26 * One of hex (default), rgb, hsl, int
27 */
28 public function __construct( $color = null, $type = 'hex' ) {
29 if ( $color ) {
30 switch ( $type ) {
31 case 'hex':
32 $this->fromHex( $color );
33 break;
34 case 'rgb':
35 if ( is_array( $color ) && count( $color ) == 3 ) {
36 list( $r, $g, $b ) = array_values( $color );
37 $this->fromRgbInt( $r, $g, $b );
38 }
39 break;
40 case 'hsl':
41 if ( is_array( $color ) && count( $color ) == 3 ) {
42 list( $h, $s, $l ) = array_values( $color );
43 $this->fromHsl( $h, $s, $l );
44 }
45 break;
46 case 'int':
47 $this->fromInt( $color );
48 break;
49 default:
50 // there is no default.
51 break;
52 }
53 }
54 }
55
56 /**
57 * Init color from hex value
58 *
59 * @param string $hexValue
60 *
61 * @return Jetpack_Color
62 */
63 public function fromHex($hexValue) {
64 $hexValue = str_replace( '#', '', $hexValue );
65 // handle short hex codes like #fff
66 if ( 3 === strlen( $hexValue ) ) {
67 $short = $hexValue;
68 $i = 0;
69 $hexValue = '';
70 while ( $i < 3 ) {
71 $chunk = substr($short, $i, 1 );
72 $hexValue .= $chunk . $chunk;
73 $i++;
74 }
75 }
76 $intValue = hexdec( $hexValue );
77
78 if ( $intValue < 0 || $intValue > 16777215 ) {
79 throw new RangeException( $hexValue . " out of valid color code range" );
80 }
81
82 $this->color = $intValue;
83
84 return $this;
85 }
86
87 /**
88 * Init color from integer RGB values
89 *
90 * @param int $red
91 * @param int $green
92 * @param int $blue
93 *
94 * @return Jetpack_Color
95 */
96 public function fromRgbInt($red, $green, $blue)
97 {
98 if ( $red < 0 || $red > 255 )
99 throw new RangeException( "Red value " . $red . " out of valid color code range" );
100
101 if ( $green < 0 || $green > 255 )
102 throw new RangeException( "Green value " . $green . " out of valid color code range" );
103
104 if ( $blue < 0 || $blue > 255 )
105 throw new RangeException( "Blue value " . $blue . " out of valid color code range" );
106
107 $this->color = (int)(($red << 16) + ($green << 8) + $blue);
108
109 return $this;
110 }
111
112 /**
113 * Init color from hex RGB values
114 *
115 * @param string $red
116 * @param string $green
117 * @param string $blue
118 *
119 * @return Jetpack_Color
120 */
121 public function fromRgbHex($red, $green, $blue)
122 {
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 http://en.wikipedia.org/wiki/HSL_color_space.
129 * @param int $h Hue. [0-360]
130 * @param in $s Saturation [0, 100]
131 * @param int $l Lightness [0, 100]
132 */
133 public function fromHsl( $h, $s, $l ) {
134 $h /= 360; $s /= 100; $l /= 100;
135
136 if ( $s == 0 ) {
137 $r = $g = $b = $l; // achromatic
138 }
139 else {
140 $q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;
141 $p = 2 * $l - $q;
142 $r = $this->hue2rgb( $p, $q, $h + 1/3 );
143 $g = $this->hue2rgb( $p, $q, $h );
144 $b = $this->hue2rgb( $p, $q, $h - 1/3 );
145 }
146
147 return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 );
148 }
149
150 /**
151 * Helper function for Jetpack_Color::fromHsl()
152 */
153 private function hue2rgb( $p, $q, $t ) {
154 if ( $t < 0 ) $t += 1;
155 if ( $t > 1 ) $t -= 1;
156 if ( $t < 1/6 ) return $p + ( $q - $p ) * 6 * $t;
157 if ( $t < 1/2 ) return $q;
158 if ( $t < 2/3 ) return $p + ( $q - $p ) * ( 2/3 - $t ) * 6;
159 return $p;
160 }
161
162 /**
163 * Init color from integer value
164 *
165 * @param int $intValue
166 *
167 * @return Jetpack_Color
168 */
169 public function fromInt($intValue)
170 {
171 if ( $intValue < 0 || $intValue > 16777215 )
172 throw new RangeException( $intValue . " out of valid color code range" );
173
174 $this->color = $intValue;
175
176 return $this;
177 }
178
179 /**
180 * Convert color to hex
181 *
182 * @return string
183 */
184 public function toHex()
185 {
186 return str_pad(dechex($this->color), 6, '0', STR_PAD_LEFT);
187 }
188
189 /**
190 * Convert color to RGB array (integer values)
191 *
192 * @return array
193 */
194 public function toRgbInt()
195 {
196 return array(
197 'red' => (int)(255 & ($this->color >> 16)),
198 'green' => (int)(255 & ($this->color >> 8)),
199 'blue' => (int)(255 & ($this->color))
200 );
201 }
202
203 /**
204 * Convert color to RGB array (hex values)
205 *
206 * @return array
207 */
208 public function toRgbHex()
209 {
210 $r = array();
211 foreach ($this->toRgbInt() as $item) {
212 $r[] = dechex($item);
213 }
214 return $r;
215 }
216
217 /**
218 * Get Hue/Saturation/Value for the current color
219 * (float values, slow but accurate)
220 *
221 * @return array
222 */
223 public function toHsvFloat()
224 {
225 $rgb = $this->toRgbInt();
226
227 $rgbMin = min($rgb);
228 $rgbMax = max($rgb);
229
230 $hsv = array(
231 'hue' => 0,
232 'sat' => 0,
233 'val' => $rgbMax
234 );
235
236 // If v is 0, color is black
237 if ($hsv['val'] == 0) {
238 return $hsv;
239 }
240
241 // Normalize RGB values to 1
242 $rgb['red'] /= $hsv['val'];
243 $rgb['green'] /= $hsv['val'];
244 $rgb['blue'] /= $hsv['val'];
245 $rgbMin = min($rgb);
246 $rgbMax = max($rgb);
247
248 // Calculate saturation
249 $hsv['sat'] = $rgbMax - $rgbMin;
250 if ($hsv['sat'] == 0) {
251 $hsv['hue'] = 0;
252 return $hsv;
253 }
254
255 // Normalize saturation to 1
256 $rgb['red'] = ($rgb['red'] - $rgbMin) / ($rgbMax - $rgbMin);
257 $rgb['green'] = ($rgb['green'] - $rgbMin) / ($rgbMax - $rgbMin);
258 $rgb['blue'] = ($rgb['blue'] - $rgbMin) / ($rgbMax - $rgbMin);
259 $rgbMin = min($rgb);
260 $rgbMax = max($rgb);
261
262 // Calculate hue
263 if ($rgbMax == $rgb['red']) {
264 $hsv['hue'] = 0.0 + 60 * ($rgb['green'] - $rgb['blue']);
265 if ($hsv['hue'] < 0) {
266 $hsv['hue'] += 360;
267 }
268 } else if ($rgbMax == $rgb['green']) {
269 $hsv['hue'] = 120 + (60 * ($rgb['blue'] - $rgb['red']));
270 } else {
271 $hsv['hue'] = 240 + (60 * ($rgb['red'] - $rgb['green']));
272 }
273
274 return $hsv;
275 }
276
277 /**
278 * Get HSV values for color
279 * (integer values from 0-255, fast but less accurate)
280 *
281 * @return int
282 */
283 public function toHsvInt()
284 {
285 $rgb = $this->toRgbInt();
286
287 $rgbMin = min($rgb);
288 $rgbMax = max($rgb);
289
290 $hsv = array(
291 'hue' => 0,
292 'sat' => 0,
293 'val' => $rgbMax
294 );
295
296 // If value is 0, color is black
297 if ($hsv['val'] == 0) {
298 return $hsv;
299 }
300
301 // Calculate saturation
302 $hsv['sat'] = round(255 * ($rgbMax - $rgbMin) / $hsv['val']);
303 if ($hsv['sat'] == 0) {
304 $hsv['hue'] = 0;
305 return $hsv;
306 }
307
308 // Calculate hue
309 if ($rgbMax == $rgb['red']) {
310 $hsv['hue'] = round(0 + 43 * ($rgb['green'] - $rgb['blue']) / ($rgbMax - $rgbMin));
311 } else if ($rgbMax == $rgb['green']) {
312 $hsv['hue'] = round(85 + 43 * ($rgb['blue'] - $rgb['red']) / ($rgbMax - $rgbMin));
313 } else {
314 $hsv['hue'] = round(171 + 43 * ($rgb['red'] - $rgb['green']) / ($rgbMax - $rgbMin));
315 }
316 if ($hsv['hue'] < 0) {
317 $hsv['hue'] += 255;
318 }
319
320 return $hsv;
321 }
322
323 /**
324 * Converts an RGB color value to HSL. Conversion formula
325 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
326 * Assumes r, g, and b are contained in the set [0, 255] and
327 * returns h in [0, 360], s in [0, 100], l in [0, 100]
328 *
329 * @return Array The HSL representation
330 */
331 public function toHsl() {
332 list( $r, $g, $b ) = array_values( $this->toRgbInt() );
333 $r /= 255; $g /= 255; $b /= 255;
334 $max = max( $r, $g, $b );
335 $min = min( $r, $g, $b );
336 $h = $s = $l = ( $max + $min ) / 2;
337 #var_dump( array( compact('max', 'min', 'r', 'g', 'b')) );
338 if ( $max == $min ) {
339 $h = $s = 0; // achromatic
340 }
341 else {
342 $d = $max - $min;
343 $s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
344 switch ( $max ) {
345 case $r:
346 $h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
347 break;
348 case $g:
349 $h = ( $b - $r ) / $d + 2;
350 break;
351 case $b:
352 $h = ( $r - $g ) / $d + 4;
353 break;
354 }
355 $h /= 6;
356 }
357 $h = (int) round( $h * 360 );
358 $s = (int) round( $s * 100 );
359 $l = (int) round( $l * 100 );
360 return compact( 'h', 's', 'l' );
361 }
362
363 public function toCSS( $type = 'hex', $alpha = 1 ) {
364 switch ( $type ) {
365 case 'hex':
366 return $this->toString();
367 break;
368 case 'rgb':
369 case 'rgba':
370 list( $r, $g, $b ) = array_values( $this->toRgbInt() );
371 if ( is_numeric( $alpha ) && $alpha < 1 ) {
372 return "rgba( {$r}, {$g}, {$b}, $alpha )";
373 }
374 else {
375 return "rgb( {$r}, {$g}, {$b} )";
376 }
377 break;
378 case 'hsl':
379 case 'hsla':
380 list( $h, $s, $l ) = array_values( $this->toHsl() );
381 if ( is_numeric( $alpha ) && $alpha < 1 ) {
382 return "hsla( {$h}, {$s}, {$l}, $alpha )";
383 }
384 else {
385 return "hsl( {$h}, {$s}, {$l} )";
386 }
387 break;
388 default:
389 return $this->toString();
390 break;
391 }
392 }
393
394 /**
395 * Get current color in XYZ format
396 *
397 * @return array
398 */
399 public function toXyz()
400 {
401 $rgb = $this->toRgbInt();
402
403 // Normalize RGB values to 1
404
405 $rgb_new = array();
406 foreach ($rgb as $item) {
407 $rgb_new[] = $item / 255;
408 }
409 $rgb = $rgb_new;
410
411 $rgb_new = array();
412 foreach ($rgb as $item) {
413 if ($item > 0.04045) {
414 $item = pow((($item + 0.055) / 1.055), 2.4);
415 } else {
416 $item = $item / 12.92;
417 }
418 $rgb_new[] = $item * 100;
419 }
420 $rgb = $rgb_new;
421
422 // Observer. = 2°, Illuminant = D65
423 $xyz = array(
424 'x' => ($rgb['red'] * 0.4124) + ($rgb['green'] * 0.3576) + ($rgb['blue'] * 0.1805),
425 'y' => ($rgb['red'] * 0.2126) + ($rgb['green'] * 0.7152) + ($rgb['blue'] * 0.0722),
426 'z' => ($rgb['red'] * 0.0193) + ($rgb['green'] * 0.1192) + ($rgb['blue'] * 0.9505)
427 );
428
429 return $xyz;
430 }
431
432 /**
433 * Get color CIE-Lab values
434 *
435 * @return array
436 */
437 public function toLabCie()
438 {
439 $xyz = $this->toXyz();
440
441 //Ovserver = 2*, Iluminant=D65
442 $xyz['x'] /= 95.047;
443 $xyz['y'] /= 100;
444 $xyz['z'] /= 108.883;
445
446 $xyz_new = array();
447 foreach ($xyz as $item) {
448 if ($item > 0.008856) {
449 $xyz_new[] = pow($item, 1/3);
450 } else {
451 $xyz_new[] = (7.787 * $item) + (16 / 116);
452 }
453 }
454 $xyz = $xyz_new;
455
456 $lab = array(
457 'l' => (116 * $xyz['y']) - 16,
458 'a' => 500 * ($xyz['x'] - $xyz['y']),
459 'b' => 200 * ($xyz['y'] - $xyz['z'])
460 );
461
462 return $lab;
463 }
464
465 /**
466 * Convert color to integer
467 *
468 * @return int
469 */
470 public function toInt()
471 {
472 return $this->color;
473 }
474
475 /**
476 * Alias of toString()
477 *
478 * @return string
479 */
480 public function __toString()
481 {
482 return $this->toString();
483 }
484
485 /**
486 * Get color as string
487 *
488 * @return string
489 */
490 public function toString()
491 {
492 $str = $this->toHex();
493 return strtoupper("#{$str}");
494 }
495
496 /**
497 * Get the distance between this color and the given color
498 *
499 * @param Jetpack_Color $color
500 *
501 * @return int
502 */
503 public function getDistanceRgbFrom(Jetpack_Color $color)
504 {
505 $rgb1 = $this->toRgbInt();
506 $rgb2 = $color->toRgbInt();
507
508 $rDiff = abs($rgb1['red'] - $rgb2['red']);
509 $gDiff = abs($rgb1['green'] - $rgb2['green']);
510 $bDiff = abs($rgb1['blue'] - $rgb2['blue']);
511
512 // Sum of RGB differences
513 $diff = $rDiff + $gDiff + $bDiff;
514 return $diff;
515 }
516
517 /**
518 * Get distance from the given color using the Delta E method
519 *
520 * @param Jetpack_Color $color
521 *
522 * @return float
523 */
524 public function getDistanceLabFrom(Jetpack_Color $color)
525 {
526 $lab1 = $this->toLabCie();
527 $lab2 = $color->toLabCie();
528
529 $lDiff = abs($lab2['l'] - $lab1['l']);
530 $aDiff = abs($lab2['a'] - $lab1['a']);
531 $bDiff = abs($lab2['b'] - $lab1['b']);
532
533 $delta = sqrt($lDiff + $aDiff + $bDiff);
534
535 return $delta;
536 }
537
538 public function toLuminosity() {
539 $lum = array();
540 foreach( $this->toRgbInt() as $slot => $value ) {
541 $chan = $value / 255;
542 $lum[ $slot ] = ( $chan <= 0.03928 ) ? $chan / 12.92 : pow( ( ( $chan + 0.055 ) / 1.055 ), 2.4 );
543 }
544 return 0.2126 * $lum['red'] + 0.7152 * $lum['green'] + 0.0722 * $lum['blue'];
545 }
546
547 /**
548 * Get distance between colors using luminance.
549 * Should be more than 5 for readable contrast
550 *
551 * @param Jetpack_Color $color Another color
552 * @return float
553 */
554 public function getDistanceLuminosityFrom( Jetpack_Color $color ) {
555 $L1 = $this->toLuminosity();
556 $L2 = $color->toLuminosity();
557 if ( $L1 > $L2 ) {
558 return ( $L1 + 0.05 ) / ( $L2 + 0.05 );
559 }
560 else{
561 return ( $L2 + 0.05 ) / ( $L1 + 0.05 );
562 }
563 }
564
565 public function getMaxContrastColor() {
566 $withBlack = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000') );
567 $withWhite = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff') );
568 $color = new Jetpack_Color;
569 $hex = ( $withBlack >= $withWhite ) ? '#000000' : '#ffffff';
570 return $color->fromHex( $hex );
571 }
572
573 public function getGrayscaleContrastingColor( $contrast = false ) {
574 if ( ! $contrast ) {
575 return $this->getMaxContrastColor();
576 }
577 // don't allow less than 5
578 $target_contrast = ( $contrast < 5 ) ? 5 : $contrast;
579 $color = $this->getMaxContrastColor();
580 $contrast = $color->getDistanceLuminosityFrom( $this );
581
582 // if current max contrast is less than the target contrast, we had wishful thinking.
583 if ( $contrast <= $target_contrast ) {
584 return $color;
585 }
586
587 $incr = ( '#000000' === $color->toString() ) ? 1 : -1;
588 while ( $contrast > $target_contrast ) {
589 $color = $color->incrementLightness( $incr );
590 $contrast = $color->getDistanceLuminosityFrom( $this );
591 }
592
593 return $color;
594 }
595
596 /**
597 * Gets a readable contrasting color. $this is assumed to be the text and $color the background color.
598 * @param object $bg_color A Color object that will be compared against $this
599 * @param integer $min_contrast The minimum contrast to achieve, if possible.
600 * @return object A Color object, an increased contrast $this compared against $bg_color
601 */
602 public function getReadableContrastingColor( $bg_color = false, $min_contrast = 5 ) {
603 if ( ! $bg_color || ! is_a( $bg_color, 'Jetpack_Color' ) ) {
604 return $this;
605 }
606 // you shouldn't use less than 5, but you might want to.
607 $target_contrast = $min_contrast;
608 // working things
609 $contrast = $bg_color->getDistanceLuminosityFrom( $this );
610 $max_contrast_color = $bg_color->getMaxContrastColor();
611 $max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color );
612
613 // if current max contrast is less than the target contrast, we had wishful thinking.
614 // still, go max
615 if ( $max_contrast <= $target_contrast ) {
616 return $max_contrast_color;
617 }
618 // or, we might already have sufficient contrast
619 if ( $contrast >= $target_contrast ) {
620 return $this;
621 }
622
623 $incr = ( 0 === $max_contrast_color->toInt() ) ? -1 : 1;
624 while ( $contrast < $target_contrast ) {
625 $this->incrementLightness( $incr );
626 $contrast = $bg_color->getDistanceLuminosityFrom( $this );
627 // infininite loop prevention: you never know.
628 if ( $this->color === 0 || $this->color === 16777215 ) {
629 break;
630 }
631 }
632
633 return $this;
634 }
635
636 /**
637 * Detect if color is grayscale
638 *
639 * @param int @threshold
640 *
641 * @return bool
642 */
643 public function isGrayscale($threshold = 16)
644 {
645 $rgb = $this->toRgbInt();
646
647 // Get min and max rgb values, then difference between them
648 $rgbMin = min($rgb);
649 $rgbMax = max($rgb);
650 $diff = $rgbMax - $rgbMin;
651
652 return $diff < $threshold;
653 }
654
655 /**
656 * Get the closest matching color from the given array of colors
657 *
658 * @param array $colors array of integers or Jetpack_Color objects
659 *
660 * @return mixed the array key of the matched color
661 */
662 public function getClosestMatch(array $colors)
663 {
664 $matchDist = 10000;
665 $matchKey = null;
666 foreach($colors as $key => $color) {
667 if (false === ($color instanceof Jetpack_Color)) {
668 $c = new Jetpack_Color($color);
669 }
670 $dist = $this->getDistanceLabFrom($c);
671 if ($dist < $matchDist) {
672 $matchDist = $dist;
673 $matchKey = $key;
674 }
675 }
676
677 return $matchKey;
678 }
679
680 /* TRANSFORMS */
681
682 public function darken( $amount = 5 ) {
683 return $this->incrementLightness( - $amount );
684 }
685
686 public function lighten( $amount = 5 ) {
687 return $this->incrementLightness( $amount );
688 }
689
690 public function incrementLightness( $amount ) {
691 $hsl = $this->toHsl();
692 extract( $hsl );
693 $l += $amount;
694 if ( $l < 0 ) $l = 0;
695 if ( $l > 100 ) $l = 100;
696 return $this->fromHsl( $h, $s, $l );
697 }
698
699 public function saturate( $amount = 15 ) {
700 return $this->incrementSaturation( $amount );
701 }
702
703 public function desaturate( $amount = 15 ) {
704 return $this->incrementSaturation( - $amount );
705 }
706
707 public function incrementSaturation( $amount ) {
708 $hsl = $this->toHsl();
709 extract( $hsl );
710 $s += $amount;
711 if ( $s < 0 ) $s = 0;
712 if ( $s > 100 ) $s = 100;
713 return $this->fromHsl( $h, $s, $l );
714 }
715
716 public function toGrayscale() {
717 $hsl = $this->toHsl();
718 extract( $hsl );
719 $s = 0;
720 return $this->fromHsl( $h, $s, $l );
721 }
722
723 public function getComplement() {
724 return $this->incrementHue( 180 );
725 }
726
727 public function getSplitComplement( $step = 1 ) {
728 $incr = 180 + ( $step * 30 );
729 return $this->incrementHue( $incr );
730 }
731
732 public function getAnalog( $step = 1 ) {
733 $incr = $step * 30;
734 return $this->incrementHue( $incr );
735 }
736
737 public function getTetrad( $step = 1 ) {
738 $incr = $step * 60;
739 return $this->incrementHue( $incr );
740 }
741
742 public function getTriad( $step = 1 ) {
743 $incr = $step * 120;
744 return $this->incrementHue( $incr );
745 }
746
747 public function incrementHue( $amount ) {
748 $hsl = $this->toHsl();
749 extract( $hsl );
750 $h = ( $h + $amount ) % 360;
751 if ( $h < 0 ) $h = 360 - $h;
752 return $this->fromHsl( $h, $s, $l );
753 }
754
755 } // class Jetpack_Color
756