Sorting by Color

前端 未结 3 660
無奈伤痛
無奈伤痛 2021-01-31 04:23

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

3条回答
  •  北恋
    北恋 (楼主)
    2021-01-31 04:54

    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

提交回复
热议问题