Reputation: 265
I have a long list (1000+) of hex colors broken up in general color categories (Red, Orange, Blue, etc). When I show the list of colors in each category I need to show them in order of shade. i.e. light red first and dark red last.
What would the algorithm be to do this? (googling has failed me)
Upvotes: 26
Views: 12376
Reputation: 156
I developed such a mechanism for the Mini-CMS Effcore. It didn't turn out perfect, but everything works without errors. Works on the principle of converting color to an HSV palette. In the future it will be possible to improve this method.
function RGB_to_HSV($r, $g, $b) {
$r = max(0, min((int)$r, 255));
$g = max(0, min((int)$g, 255));
$b = max(0, min((int)$b, 255));
$result = [];
$min = min($r, $g, $b);
$max = max($r, $g, $b);
$delta_min_max = $max - $min;
$result_h = 0;
if ($delta_min_max !== 0 && $max === $r && $g >= $b) $result_h = 60 * (($g - $b) / $delta_min_max) + 0;
elseif ($delta_min_max !== 0 && $max === $r && $g < $b) $result_h = 60 * (($g - $b) / $delta_min_max) + 360;
elseif ($delta_min_max !== 0 && $max === $g ) $result_h = 60 * (($b - $r) / $delta_min_max) + 120;
elseif ($delta_min_max !== 0 && $max === $b ) $result_h = 60 * (($r - $g) / $delta_min_max) + 240;
$result_s = $max === 0 ? 0 : (1 - ($min / $max));
$result_v = $max;
$result['h'] = (int)(round($result_h));
$result['s'] = (int)($result_s * 100);
$result['v'] = (int)($result_v / 2.55);
return $result;
}
$colors = [
'#002c44', '#8dd157', '#160060', '#4dd9ff', '#82c64c',
'#2c1376', '#00425a', '#4d30aa', '#ae64c9', '#004be9',
'#98dc62', '#2c0f89', '#160073', '#cc507d', '#ffc258',
'#2f6500', '#e5ffaf', '#ff4013', '#9d4b21', '#cfff99',
'#0b0055', '#9a3b5f', '#583bb5', '#d33625', '#7cb24b',
'#245a00', '#bd6800', '#c80900', '#e95400', '#1677ff',
'#3a7009', '#450cbc', '#ba4ade', '#620086', '#e94c3b',
'#9929bd', '#f43508', '#dfa6ff', '#ffa916', '#ff87b4',
'#7262ab', '#2a6e00', '#2e5907', '#8f3054', '#350050',
'#d29d00', '#ff6134', '#2f0043', '#0b97bf', '#ff2600',
];
uasort($colors, function ($color_a, $color_b) {
$hex_a = ltrim($color_a, '#');
$hex_b = ltrim($color_b, '#');
if (strlen($hex_a) === 6 &&
strlen($hex_b) === 6) {
$r_a = (int)hexdec($hex_a[0].$hex_a[1]);
$r_b = (int)hexdec($hex_b[0].$hex_b[1]);
$g_a = (int)hexdec($hex_a[2].$hex_a[3]);
$g_b = (int)hexdec($hex_b[2].$hex_b[3]);
$b_a = (int)hexdec($hex_a[4].$hex_a[5]);
$b_b = (int)hexdec($hex_b[4].$hex_b[5]);
$hsv_a = RGB_to_HSV($r_a, $g_a, $b_a);
$hsv_b = RGB_to_HSV($r_b, $g_b, $b_b);
return (($hsv_a['h'] <=> $hsv_b['h']) * 100) +
(($hsv_a['s'] <=> $hsv_b['s']) * 10 ) +
(($hsv_a['v'] <=> $hsv_b['v']) * 1 );
}
});
foreach ($colors as $c_value) {
print '<span style="display: inline-block; width: 20px; height: 20px; background: '.$c_value.'">'.'</span>';
}
.
var_dump( RGB_to_HSV( 0, 0, 0) === ['h' => 0, 's' => 0, 'v' => 0] );
var_dump( RGB_to_HSV( 0, 0, 255) === ['h' => 240, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV( 0, 127, 255) === ['h' => 210, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV( 0, 255, 0) === ['h' => 120, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV( 0, 255, 127) === ['h' => 150, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV( 0, 255, 255) === ['h' => 180, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(127, 0, 255) === ['h' => 270, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(127, 127, 255) === ['h' => 240, 's' => 50, 'v' => 100] );
var_dump( RGB_to_HSV(127, 255, 0) === ['h' => 90, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(127, 255, 127) === ['h' => 120, 's' => 50, 'v' => 100] );
var_dump( RGB_to_HSV(255, 0, 0) === ['h' => 0, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(255, 0, 127) === ['h' => 330, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(255, 0, 255) === ['h' => 300, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(255, 127, 0) === ['h' => 30, 's' => 100, 'v' => 100] );
var_dump( RGB_to_HSV(255, 127, 127) === ['h' => 0, 's' => 50, 'v' => 100] );
var_dump( RGB_to_HSV(255, 255, 255) === ['h' => 0, 's' => 0, 'v' => 100] );
var_dump( RGB_to_HSV(900, 900, 900) === ['h' => 0, 's' => 0, 'v' => 100] );
# trying all possible values for raise an error
for ($r = -10; $r <= 260; $r++) {
for ($g = -10; $g <= 260; $g++) {
for ($b = -10; $b <= 260; $b++) {
RGB_to_HSV($r, $g, $b);
}
}
}
Upvotes: 0
Reputation: 17960
Convert the colors from RGB to a HSV or HSL scale and then sort them by Value or Lightness. Lightness might work better as it would capture the "faded" colors better, such as pink->red->dark red.
Upvotes: 9
Reputation: 4053
I know this question is old, but I didn't found a pretty solution to this problem, so I investigate a little bit and want to share what I did for hypothetical future googlers.
First, convert to HSL is a great idea. But sort only by hue or light didn't solve completely the issue when your color are not "classified".
Given an array which look like:
$colors = [
[ 'color' => '#FDD4CD'],
[ 'color' => '#AE3B3B'],
[ 'color' => '#DB62A0'],
...
]
foreach ($colors as &$color) {
$color['hsl'] = hexToHsl($color['color']);
}
/**
* Convert a hexadecimal color in RGB
* @param string $hex
* @return array
*/
function hexToHsl($hex){
list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
return rgbToHsl($r, $g, $b);
}
/**
* Convert a RGB color in its HSL value
* @param int $r red
* @param int $g green
* @param int $b blue
* @return array
*/
function rgbToHsl($r, $g, $b)
{
$r /= 255;
$g /= 255;
$b /= 255;
$max = max($r, $g, $b);
$min = min($r, $g, $b);
$h = 0;
$l = ($max + $min) / 2;
$d = $max - $min;
if ($d == 0) {
$h = $s = 0; // achromatic
} else {
$s = $d / (1 - abs(2 * $l - 1));
switch ($max) {
case $r:
$h = 60 * fmod((($g - $b) / $d), 6);
if ($b > $g) {
$h += 360;
}
break;
case $g:
$h = 60 * (($b - $r) / $d + 2);
break;
case $b:
$h = 60 * (($r - $g) / $d + 4);
break;
}
}
return array('h' => round($h, 2), 's' => round($s, 2), 'l' => round($l, 2));
}
We compare:
So:
usort($colors, function ($a, $b) {
//Compare the hue when they are in the same "range"
if(!huesAreinSameInterval($a['hsl']['h'],$b['hsl']['h'])){
if ($a['hsl']['h'] < $b['hsl']['h'])
return -1;
if ($a['hsl']['h'] > $b['hsl']['h'])
return 1;
}
if ($a['hsl']['l'] < $b['hsl']['l'])
return 1;
if ($a['hsl']['l'] > $b['hsl']['l'])
return -1;
if ($a['hsl']['s'] < $b['hsl']['s'])
return -1;
if ($a['hsl']['s'] > $b['hsl']['s'])
return 1;
return 0;
});
/**
* Check if two hues are in the same given interval
* @param float $hue1
* @param float $hue2
* @param int $interval
* @return bool
*/
function huesAreinSameInterval($hue1, $hue2, $interval = 30){
return (round(($hue1 / $interval), 0, PHP_ROUND_HALF_DOWN) === round(($hue2 / $interval), 0, PHP_ROUND_HALF_DOWN));
}
rgbToHsl found on www.brandonheyer.com
hexToRgb found on stackoverflow
Upvotes: 16
Reputation: 564423
If you convert the colors to HSV space, you could potentially sort them by the hue, then the value.
The hue will determine the color "category" - ie: red, blue, green, etc.
The value and saturation will affect the "lightness" of the final color. You might need some experimentation to get the ideal sort, though.
Upvotes: 1