File "class.color.php"
Full Path: /home/warrior1/public_html/plugins/jetpack/_inc/lib/class.color.php
File size: 20.88 KB
MIME-type: text/x-php
Charset: utf-8
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* Color utility and conversion
*
* Represents a color value, and converts between RGB/HSV/XYZ/Lab/HSL
*
* Example:
* $color = new Jetpack_Color(0xFFFFFF);
*
* @author Harold Asbridge <hasbridge@gmail.com>
* @author Matt Wiebe <wiebe@automattic.com>
* @license https://www.opensource.org/licenses/MIT
*
* @package automattic/jetpack
*/
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
/**
* Color utilities
*/
class Jetpack_Color {
/**
* Color code (later array or string, depending on type)
*
* @var int|array|string
*/
protected $color = 0;
/**
* Initialize object
*
* @param string|array $color A color of the type $type.
* @param string $type The type of color we will construct from.
* One of hex (default), rgb, hsl, int.
*/
public function __construct( $color = null, $type = 'hex' ) {
if ( $color ) {
switch ( $type ) {
case 'hex':
$this->fromHex( $color );
break;
case 'rgb':
if ( is_array( $color ) && count( $color ) === 3 ) {
list( $r, $g, $b ) = array_values( $color );
$this->fromRgbInt( $r, $g, $b );
}
break;
case 'hsl':
if ( is_array( $color ) && count( $color ) === 3 ) {
list( $h, $s, $l ) = array_values( $color );
$this->fromHsl( $h, $s, $l );
}
break;
case 'int':
$this->fromInt( $color );
break;
default:
// there is no default.
break;
}
}
}
/**
* Init color from hex value
*
* @param string $hex_value Color hex value.
*
* @return $this
* @throws RangeException Invalid color code range error.
*/
public function fromHex( $hex_value ) {
$hex_value = str_replace( '#', '', $hex_value );
// handle short hex codes like #fff.
if ( 3 === strlen( $hex_value ) ) {
$hex_value = $hex_value[0] . $hex_value[0] . $hex_value[1] . $hex_value[1] . $hex_value[2] . $hex_value[2];
}
return $this->fromInt( hexdec( $hex_value ) );
}
/**
* Init color from integer RGB values
*
* @param int $red Red color code.
* @param int $green Green color code.
* @param int $blue Blue color code.
*
* @return $this
* @throws RangeException Invalid color code range error.
*/
public function fromRgbInt( $red, $green, $blue ) {
if ( $red < 0 || $red > 255 ) {
throw new RangeException( 'Red value ' . $red . ' out of valid color code range' );
}
if ( $green < 0 || $green > 255 ) {
throw new RangeException( 'Green value ' . $green . ' out of valid color code range' );
}
if ( $blue < 0 || $blue > 255 ) {
throw new RangeException( 'Blue value ' . $blue . ' out of valid color code range' );
}
$this->color = (int) ( ( $red << 16 ) + ( $green << 8 ) + $blue );
return $this;
}
/**
* Init color from hex RGB values
*
* @param string $red Red color code.
* @param string $green Green color code.
* @param string $blue Blue color code.
*
* @return $this
*/
public function fromRgbHex( $red, $green, $blue ) {
return $this->fromRgbInt( hexdec( $red ), hexdec( $green ), hexdec( $blue ) );
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
*
* @param int $h Hue. [0-360].
* @param int $s Saturation [0, 100].
* @param int $l Lightness [0, 100].
*/
public function fromHsl( $h, $s, $l ) {
$h /= 360;
$s /= 100;
$l /= 100;
if ( 0 === $s ) {
// achromatic.
$r = $l;
$g = $l;
$b = $l;
} else {
$q = $l < 0.5 ? $l * ( 1 + $s ) : $l + $s - $l * $s;
$p = 2 * $l - $q;
$r = $this->hue2rgb( $p, $q, $h + 1 / 3 );
$g = $this->hue2rgb( $p, $q, $h );
$b = $this->hue2rgb( $p, $q, $h - 1 / 3 );
}
return $this->fromRgbInt( $r * 255, $g * 255, $b * 255 );
}
/**
* Helper function for Jetpack_Color::fromHsl()
*
* @param float $p Minimum of R/G/B [0, 1].
* @param float $q Maximum of R/G/B [0, 1].
* @param float $t Adjusted hue [0, 1].
*/
private function hue2rgb( $p, $q, $t ) {
if ( $t < 0 ) {
++$t;
}
if ( $t > 1 ) {
--$t;
}
if ( $t < 1 / 6 ) {
return $p + ( $q - $p ) * 6 * $t;
}
if ( $t < 1 / 2 ) {
return $q;
}
if ( $t < 2 / 3 ) {
return $p + ( $q - $p ) * ( 2 / 3 - $t ) * 6;
}
return $p;
}
/**
* Init color from integer value
*
* @param int $int_value Color code.
*
* @return $this
* @throws RangeException Invalid color code range error.
*/
public function fromInt( $int_value ) {
if ( $int_value < 0 || $int_value > 16777215 ) {
throw new RangeException( $int_value . ' out of valid color code range' );
}
$this->color = $int_value;
return $this;
}
/**
* Convert color to hex
*
* @return string
*/
public function toHex() {
return sprintf( '%06x', $this->color );
}
/**
* Convert color to RGB array (integer values)
*
* @return array
*/
public function toRgbInt() {
return array(
'red' => (int) ( 255 & ( $this->color >> 16 ) ),
'green' => (int) ( 255 & ( $this->color >> 8 ) ),
'blue' => (int) ( 255 & ( $this->color ) ),
);
}
/**
* Convert color to RGB array (hex values)
*
* @return array
*/
public function toRgbHex() {
$r = array();
foreach ( $this->toRgbInt() as $item ) {
$r[] = dechex( $item );
}
return $r;
}
/**
* Get Hue/Saturation/Value for the current color
* (float values, slow but accurate)
*
* @return array
*/
public function toHsvFloat() {
$rgb = $this->toRgbInt();
$rgb_min = min( $rgb );
$rgb_max = max( $rgb );
$hsv = array(
'hue' => 0,
'sat' => 0,
'val' => $rgb_max,
);
// If v is 0, color is black.
if ( 0 === $hsv['val'] ) {
return $hsv;
}
// Normalize RGB values to 1.
$rgb['red'] /= $hsv['val'];
$rgb['green'] /= $hsv['val'];
$rgb['blue'] /= $hsv['val'];
$rgb_min = min( $rgb );
$rgb_max = max( $rgb );
// Calculate saturation.
$hsv['sat'] = $rgb_max - $rgb_min;
if ( 0 === $hsv['sat'] ) {
$hsv['hue'] = 0;
return $hsv;
}
// Normalize saturation to 1.
$rgb['red'] = ( $rgb['red'] - $rgb_min ) / ( $rgb_max - $rgb_min );
$rgb['green'] = ( $rgb['green'] - $rgb_min ) / ( $rgb_max - $rgb_min );
$rgb['blue'] = ( $rgb['blue'] - $rgb_min ) / ( $rgb_max - $rgb_min );
$rgb_min = min( $rgb );
$rgb_max = max( $rgb );
// Calculate hue.
if ( $rgb_max === $rgb['red'] ) {
$hsv['hue'] = 0.0 + 60 * ( $rgb['green'] - $rgb['blue'] );
if ( $hsv['hue'] < 0 ) {
$hsv['hue'] += 360;
}
} elseif ( $rgb_max === $rgb['green'] ) {
$hsv['hue'] = 120 + ( 60 * ( $rgb['blue'] - $rgb['red'] ) );
} else {
$hsv['hue'] = 240 + ( 60 * ( $rgb['red'] - $rgb['green'] ) );
}
return $hsv;
}
/**
* Get HSV values for color
* (integer values from 0-255, fast but less accurate)
*
* @return array
*/
public function toHsvInt() {
$rgb = $this->toRgbInt();
$rgb_min = min( $rgb );
$rgb_max = max( $rgb );
$hsv = array(
'hue' => 0,
'sat' => 0,
'val' => $rgb_max,
);
// If value is 0, color is black.
if ( 0 === $hsv['val'] ) {
return $hsv;
}
// Calculate saturation.
$hsv['sat'] = round( 255 * ( $rgb_max - $rgb_min ) / $hsv['val'] );
if ( 0 === $hsv['sat'] ) {
$hsv['hue'] = 0;
return $hsv;
}
// Calculate hue.
if ( $rgb_max === $rgb['red'] ) {
$hsv['hue'] = round( 0 + 43 * ( $rgb['green'] - $rgb['blue'] ) / ( $rgb_max - $rgb_min ) );
} elseif ( $rgb_max === $rgb['green'] ) {
$hsv['hue'] = round( 85 + 43 * ( $rgb['blue'] - $rgb['red'] ) / ( $rgb_max - $rgb_min ) );
} else {
$hsv['hue'] = round( 171 + 43 * ( $rgb['red'] - $rgb['green'] ) / ( $rgb_max - $rgb_min ) );
}
if ( $hsv['hue'] < 0 ) {
$hsv['hue'] += 255;
}
return $hsv;
}
/**
* Converts an RGB color value to HSL. Conversion formula
* adapted from https://en.wikipedia.org/wiki/HSL_color_space.
* Assumes r, g, and b are contained in the set [0, 255] and
* returns h in [0, 360], s in [0, 100], l in [0, 100]
*
* @return Array The HSL representation
*/
public function toHsl() {
list( $r, $g, $b ) = array_values( $this->toRgbInt() );
$r /= 255;
$g /= 255;
$b /= 255;
$max = max( $r, $g, $b );
$min = min( $r, $g, $b );
$l = ( $max + $min ) / 2;
if ( $max === $min ) {
// achromatic.
$s = 0;
$h = 0;
} else {
$d = $max - $min;
$s = $l > 0.5 ? $d / ( 2 - $max - $min ) : $d / ( $max + $min );
switch ( $max ) {
case $r:
$h = ( $g - $b ) / $d + ( $g < $b ? 6 : 0 );
break;
case $g:
$h = ( $b - $r ) / $d + 2;
break;
case $b:
$h = ( $r - $g ) / $d + 4;
break;
}
$h /= 6;
}
$h = (int) round( $h * 360 );
$s = (int) round( $s * 100 );
$l = (int) round( $l * 100 );
return compact( 'h', 's', 'l' );
}
/**
* From a color code to a string to be used in CSS declaration.
*
* @param string $type Color code type.
* @param int $alpha Transparency.
*
* @return string
*/
public function toCSS( $type = 'hex', $alpha = 1 ) {
switch ( $type ) {
case 'hex':
return $this->toString();
case 'rgb':
case 'rgba':
list( $r, $g, $b ) = array_values( $this->toRgbInt() );
if ( is_numeric( $alpha ) && $alpha < 1 ) {
return "rgba( {$r}, {$g}, {$b}, $alpha )";
} else {
return "rgb( {$r}, {$g}, {$b} )";
}
case 'hsl':
case 'hsla':
list( $h, $s, $l ) = array_values( $this->toHsl() );
if ( is_numeric( $alpha ) && $alpha < 1 ) {
return "hsla( {$h}, {$s}, {$l}, $alpha )";
} else {
return "hsl( {$h}, {$s}, {$l} )";
}
default:
return $this->toString();
}
}
/**
* Get current color in XYZ format
*
* @return array
*/
public function toXyz() {
$rgb = $this->toRgbInt();
// Normalize RGB values to 1.
$rgb_new = array();
foreach ( $rgb as $item ) {
$rgb_new[] = $item / 255;
}
$rgb = $rgb_new;
$rgb_new = array();
foreach ( $rgb as $item ) {
if ( $item > 0.04045 ) {
$item = pow( ( ( $item + 0.055 ) / 1.055 ), 2.4 );
} else {
$item = $item / 12.92;
}
$rgb_new[] = $item * 100;
}
$rgb = $rgb_new;
// Observer. = 2°, Illuminant = D65.
$xyz = array(
'x' => ( $rgb['red'] * 0.4124 ) + ( $rgb['green'] * 0.3576 ) + ( $rgb['blue'] * 0.1805 ),
'y' => ( $rgb['red'] * 0.2126 ) + ( $rgb['green'] * 0.7152 ) + ( $rgb['blue'] * 0.0722 ),
'z' => ( $rgb['red'] * 0.0193 ) + ( $rgb['green'] * 0.1192 ) + ( $rgb['blue'] * 0.9505 ),
);
return $xyz;
}
/**
* Get color CIE-Lab values
*
* @return array
*/
public function toLabCie() {
$xyz = $this->toXyz();
// Ovserver = 2*, Iluminant=D65.
$xyz['x'] /= 95.047;
$xyz['y'] /= 100;
$xyz['z'] /= 108.883;
$xyz_new = array();
foreach ( $xyz as $item ) {
if ( $item > 0.008856 ) {
$xyz_new[] = pow( $item, 1 / 3 );
} else {
$xyz_new[] = ( 7.787 * $item ) + ( 16 / 116 );
}
}
$xyz = $xyz_new;
$lab = array(
'l' => ( 116 * $xyz['y'] ) - 16,
'a' => 500 * ( $xyz['x'] - $xyz['y'] ),
'b' => 200 * ( $xyz['y'] - $xyz['z'] ),
);
return $lab;
}
/**
* Convert color to integer
*
* @return int
*/
public function toInt() {
return $this->color;
}
/**
* Alias of toString()
*
* @return string
*/
public function __toString() {
return $this->toString();
}
/**
* Get color as string
*
* @return string
*/
public function toString() {
$str = $this->toHex();
return strtoupper( "#{$str}" );
}
/**
* Get the distance between this color and the given color
*
* @param Jetpack_Color $color Color code.
*
* @return int
*/
public function getDistanceRgbFrom( Jetpack_Color $color ) {
$rgb1 = $this->toRgbInt();
$rgb2 = $color->toRgbInt();
$r_diff = abs( $rgb1['red'] - $rgb2['red'] );
$g_diff = abs( $rgb1['green'] - $rgb2['green'] );
$b_diff = abs( $rgb1['blue'] - $rgb2['blue'] );
// Sum of RGB differences.
$diff = $r_diff + $g_diff + $b_diff;
return $diff;
}
/**
* Get distance from the given color using the Delta E method
*
* @param Jetpack_Color $color Color code.
*
* @return float
*/
public function getDistanceLabFrom( Jetpack_Color $color ) {
$lab1 = $this->toLabCie();
$lab2 = $color->toLabCie();
$l_diff = abs( $lab2['l'] - $lab1['l'] );
$a_diff = abs( $lab2['a'] - $lab1['a'] );
$b_diff = abs( $lab2['b'] - $lab1['b'] );
$delta = sqrt( $l_diff + $a_diff + $b_diff );
return $delta;
}
/**
* Calculate luminosity.
*
* @return float
*/
public function toLuminosity() {
$lum = array();
foreach ( $this->toRgbInt() as $slot => $value ) {
$chan = $value / 255;
$lum[ $slot ] = ( $chan <= 0.03928 ) ? $chan / 12.92 : pow( ( ( $chan + 0.055 ) / 1.055 ), 2.4 );
}
return 0.2126 * $lum['red'] + 0.7152 * $lum['green'] + 0.0722 * $lum['blue'];
}
/**
* Get distance between colors using luminance.
* Should be more than 5 for readable contrast
*
* @param Jetpack_Color $color Another color.
* @return float
*/
public function getDistanceLuminosityFrom( Jetpack_Color $color ) {
$l1 = $this->toLuminosity();
$l2 = $color->toLuminosity();
if ( $l1 > $l2 ) {
return ( $l1 + 0.05 ) / ( $l2 + 0.05 );
} else {
return ( $l2 + 0.05 ) / ( $l1 + 0.05 );
}
}
/**
* Get maximum contrast color.
*
* @return $this
*/
public function getMaxContrastColor() {
$with_black = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#000' ) );
$with_white = $this->getDistanceLuminosityFrom( new Jetpack_Color( '#fff' ) );
$color = new Jetpack_Color();
$hex = ( $with_black >= $with_white ) ? '#000000' : '#ffffff';
return $color->fromHex( $hex );
}
/**
* Get grayscale contrasting color.
*
* @param bool|int $contrast Contrast.
*
* @return $this
*/
public function getGrayscaleContrastingColor( $contrast = false ) {
if ( ! $contrast ) {
return $this->getMaxContrastColor();
}
// don't allow less than 5.
$target_contrast = ( $contrast < 5 ) ? 5 : $contrast;
$color = $this->getMaxContrastColor();
$contrast = $color->getDistanceLuminosityFrom( $this );
// if current max contrast is less than the target contrast, we had wishful thinking.
if ( $contrast <= $target_contrast ) {
return $color;
}
$incr = ( '#000000' === $color->toString() ) ? 1 : -1;
while ( $contrast > $target_contrast ) {
$color = $color->incrementLightness( $incr );
$contrast = $color->getDistanceLuminosityFrom( $this );
}
return $color;
}
/**
* Gets a readable contrasting color. $this is assumed to be the text and $color the background color.
*
* @param object $bg_color A Color object that will be compared against $this.
* @param integer $min_contrast The minimum contrast to achieve, if possible.
* @return object A Color object, an increased contrast $this compared against $bg_color
*/
public function getReadableContrastingColor( $bg_color = false, $min_contrast = 5 ) {
if ( ! $bg_color || ! is_a( $bg_color, 'Jetpack_Color' ) ) {
return $this;
}
// you shouldn't use less than 5, but you might want to.
$target_contrast = $min_contrast;
// working things.
$contrast = $bg_color->getDistanceLuminosityFrom( $this );
$max_contrast_color = $bg_color->getMaxContrastColor();
$max_contrast = $max_contrast_color->getDistanceLuminosityFrom( $bg_color );
// if current max contrast is less than the target contrast, we had wishful thinking.
// still, go max.
if ( $max_contrast <= $target_contrast ) {
return $max_contrast_color;
}
// or, we might already have sufficient contrast.
if ( $contrast >= $target_contrast ) {
return $this;
}
$incr = ( 0 === $max_contrast_color->toInt() ) ? -1 : 1;
while ( $contrast < $target_contrast ) {
$this->incrementLightness( $incr );
$contrast = $bg_color->getDistanceLuminosityFrom( $this );
// infininite loop prevention: you never know.
if ( 0 === $this->color || 16777215 === $this->color ) {
break;
}
}
return $this;
}
/**
* Detect if color is grayscale
*
* @param int $threshold Max difference between colors.
*
* @return bool
*/
public function isGrayscale( $threshold = 16 ) {
$rgb = $this->toRgbInt();
// Get min and max rgb values, then difference between them.
$rgb_min = min( $rgb );
$rgb_max = max( $rgb );
$diff = $rgb_max - $rgb_min;
return $diff < $threshold;
}
/**
* Get the closest matching color from the given array of colors
*
* @param array $colors array of integers or Jetpack_Color objects.
*
* @return mixed the array key of the matched color
*/
public function getClosestMatch( array $colors ) {
$match_dist = 10000;
$match_key = null;
foreach ( $colors as $key => $color ) {
if ( false === ( $color instanceof Jetpack_Color ) ) {
$c = new Jetpack_Color( $color );
}
$dist = $this->getDistanceLabFrom( $c );
if ( $dist < $match_dist ) {
$match_dist = $dist;
$match_key = $key;
}
}
return $match_key;
}
/* TRANSFORMS */
/**
* Transform -- Darken color.
*
* @param int $amount Amount. Default to 5.
*
* @return $this
*/
public function darken( $amount = 5 ) {
return $this->incrementLightness( - $amount );
}
/**
* Transform -- Lighten color.
*
* @param int $amount Amount. Default to 5.
*
* @return $this
*/
public function lighten( $amount = 5 ) {
return $this->incrementLightness( $amount );
}
/**
* Transform -- Increment lightness.
*
* @param int $amount Amount.
*
* @return $this
*/
public function incrementLightness( $amount ) {
$hsl = $this->toHsl();
$h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
$s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
$l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
$l += $amount;
if ( $l < 0 ) {
$l = 0;
}
if ( $l > 100 ) {
$l = 100;
}
return $this->fromHsl( $h, $s, $l );
}
/**
* Transform -- Saturate color.
*
* @param int $amount Amount. Default to 15.
*
* @return $this
*/
public function saturate( $amount = 15 ) {
return $this->incrementSaturation( $amount );
}
/**
* Transform -- Desaturate color.
*
* @param int $amount Amount. Default to 15.
*
* @return $this
*/
public function desaturate( $amount = 15 ) {
return $this->incrementSaturation( - $amount );
}
/**
* Transform -- Increment saturation.
*
* @param int $amount Amount.
*
* @return $this
*/
public function incrementSaturation( $amount ) {
$hsl = $this->toHsl();
$h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
$s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
$l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
$s += $amount;
if ( $s < 0 ) {
$s = 0;
}
if ( $s > 100 ) {
$s = 100;
}
return $this->fromHsl( $h, $s, $l );
}
/**
* Transform -- To grayscale.
*
* @return $this
*/
public function toGrayscale() {
$hsl = $this->toHsl();
$h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
$s = 0;
$l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
return $this->fromHsl( $h, $s, $l );
}
/**
* Transform -- To the complementary color.
*
* The complement is the color on the opposite side of the color wheel, 180° away.
*
* @return $this
*/
public function getComplement() {
return $this->incrementHue( 180 );
}
/**
* Transform -- To an analogous color of the complement.
*
* @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
*
* @return $this
*/
public function getSplitComplement( $step = 1 ) {
$incr = 180 + ( $step * 30 );
return $this->incrementHue( $incr );
}
/**
* Transform -- To an analogous color.
*
* Analogous colors are those adjacent on the color wheel, separated by 30°.
*
* @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
*
* @return $this
*/
public function getAnalog( $step = 1 ) {
$incr = $step * 30;
return $this->incrementHue( $incr );
}
/**
* Transform -- To a tetradic (rectangular) color.
*
* A rectangular color scheme uses a color, its complement, and the colors 60° from each.
* This transforms the color to its 60° "tetrad".
*
* @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
*
* @return $this
*/
public function getTetrad( $step = 1 ) {
$incr = $step * 60;
return $this->incrementHue( $incr );
}
/**
* Transform -- To a triadic color.
*
* A triadic color scheme uses three colors evenly spaced (120°) around the color wheel.
* This transforms the color to one of its triadic colors.
*
* @param int $step Pass `1` or `-1` to choose which direction around the color wheel.
*
* @return $this
*/
public function getTriad( $step = 1 ) {
$incr = $step * 120;
return $this->incrementHue( $incr );
}
/**
* Transform -- Increment hue.
*
* @param int $amount Amount.
*
* @return $this
*/
public function incrementHue( $amount ) {
$hsl = $this->toHsl();
$h = isset( $hsl['h'] ) ? $hsl['h'] : 0;
$s = isset( $hsl['s'] ) ? $hsl['s'] : 0;
$l = isset( $hsl['l'] ) ? $hsl['l'] : 0;
$h = ( $h + $amount ) % 360;
if ( $h < 0 ) {
$h += 360;
}
return $this->fromHsl( $h, $s, $l );
}
}