user154442
user154442

Reputation: 265

Sorting by Color

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

Answers (4)

Maxim Rysevets
Maxim Rysevets

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.

Code

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>';
}

Result.

Test for "RGB_to_HSV()":

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

James Schek
James Schek

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

Getz
Getz

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'],
            ...
          ]

First we convert all Hex colors to HSL

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));
}

Then sort colors

We compare:

  • Their hue if they are in the same 'interval' (This help to understand why I choose 30°). So we compare the hue only if both are in [0-30], [30-60], [60-90],...
  • If not in the same interval, sort by their Lightness, then by saturation if both share the same Lightness.

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

Reed Copsey
Reed Copsey

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

Related Questions