PHP generate rgb color to object based on unique value

106
June 28, 2018, at 00:50 AM

I know this has been asked before, but I need a little more help than the other answers on SO provide.

I currently have a foreach loop that iterates over records in my DB. Each row contains a unique "id" primary attribute (1,2,3,4,5,etc.).

In my loop, I need to generate a UNIQUE RGB value for each record based on its ID. The resulting RGB value will be applied to the HTML element's text bound to that record. The generated color must be unique to the record itself (via "id"), which is why I am not using the loop iterator.

I have already created a working function to do this for me, but I need one more thing - I need the rgb value to have a contrast ratio greater than 4:5:1 on a white background. The function I have generates colors that are too bright, making the text hard to read. How can I modify my function to produce darker colors that contrast well on a white background?

function makeRgbFromValue(int $value){
    $hash = md5($value);
    return implode(", ", [
        hexdec(substr($hash, 0, 2)), // r
        hexdec(substr($hash, 2, 2)), // g
        hexdec(substr($hash, 4, 2))  // b
    ]);
}
// Example output: "100, 201, 20"
// My html, using Laravel Blade syntax:
@foreach($categories as $cat)
    <a 
        href="/blog/channels/{{ $cat->slug }}"
        style="color: rgb(
           {{ makeRgbFromValue($cat->id) }}
        )"
    >
        {{ $cat->name }}
    </a>
@endforeach

Maybe this isnt possible... but I'm hoping one of you Math geniuses can help me out :)

Answer 1

You can calculate brightness:

function lumdiff($R1,$G1,$B1,$R2,$G2,$B2){
    $L1 = 0.2126 * pow($R1/255, 2.2) +
          0.7152 * pow($G1/255, 2.2) +
          0.0722 * pow($B1/255, 2.2);
    $L2 = 0.2126 * pow($R2/255, 2.2) +
          0.7152 * pow($G2/255, 2.2) +
          0.0722 * pow($B2/255, 2.2);
    if($L1 > $L2){
        return ($L1+0.05) / ($L2+0.05);
    }else{
        return ($L2+0.05) / ($L1+0.05);
    }
}

The returned value should be bigger than 5 for best readability. Function was found here

But you should get another chars from a hash to get another color, so collision more probable.

function makeRgbFromValue($value){
    $hash = md5($value);
    $brightness = 0;
    $shift = 0;
    while($brightness < 5 && $shift<26) {
        $color = [
            hexdec(substr($hash, $shift, 2)), // r
            hexdec(substr($hash, $shift+2, 2)), // g
            hexdec(substr($hash, $shift+4, 2))  // b
        ];
        $brightness = lumdiff($color[0], $color[1], $color[2], 255,255,255);
        $shift++;
    }
    return implode(", ", $color);
}

I believe that a color will be found in 25 cycles :)

Answer 2

While it's a pain to translate, HSV is probably a better color space for this. I like to pick a set Saturation [0.75 is nice] and then play with the Hue and Value.

Lifting the HSV/RGB conversion function from this gist:

function HSV_TO_RGB ($H, $S, $V) {
    $RGB = array(); 
    if($S == 0) { 
        $R = $G = $B = $V * 255; 
    } else { 
        $var_H = $H * 6; 
        $var_i = floor( $var_H ); 
        $var_1 = $V * ( 1 - $S ); 
        $var_2 = $V * ( 1 - $S * ( $var_H - $var_i ) ); 
        $var_3 = $V * ( 1 - $S * (1 - ( $var_H - $var_i ) ) ); 
        if       ($var_i == 0) { $var_R = $V     ; $var_G = $var_3  ; $var_B = $var_1 ; } 
        else if  ($var_i == 1) { $var_R = $var_2 ; $var_G = $V      ; $var_B = $var_1 ; } 
        else if  ($var_i == 2) { $var_R = $var_1 ; $var_G = $V      ; $var_B = $var_3 ; } 
        else if  ($var_i == 3) { $var_R = $var_1 ; $var_G = $var_2  ; $var_B = $V     ; } 
        else if  ($var_i == 4) { $var_R = $var_3 ; $var_G = $var_1  ; $var_B = $V     ; } 
        else                   { $var_R = $V     ; $var_G = $var_1  ; $var_B = $var_2 ; } 
        $R = $var_R * 255; 
        $G = $var_G * 255; 
        $B = $var_B * 255; 
    } 
    $RGB['R'] = $R; 
    $RGB['G'] = $G; 
    $RGB['B'] = $B; 
    return $RGB; 
}

And then:

$fmt = '<span style="background-color: #%s" title="%s">%s</span>' . "\n";
$fill = str_repeat('&nbsp;', 5);
$S = 0.75;
for($i=0; $i<25; $i++) {
    $hash = md5(mt_rand());
    $H =   hexdec(substr($hash, 0, 2))/255;
    $V = ( hexdec(substr($hash, 2, 2))/255 ) / 2 + 0.5; // pick from the brighter half
    $rgb = implode('', array_map(
    function($a){
        return str_pad(dechex(intval($a)), 2, '0', STR_PAD_LEFT);
    }, HSV_TO_RGB($H, $S, $V)));
    printf($fmt, $rgb, sprintf("HSV(%0.2f,%0.2f,%0.2f)", $H, $S, $V), $fill);
}

Gives us something like:

Rent Charter Buses Company
READ ALSO
How to set bing ads account in php with sample code [on hold]

How to set bing ads account in php with sample code [on hold]

You need to provide consent for the application to access your Bing Ads accountsCopy and paste this authorization endpoint into a web browser and sign in with a Microsoft account with access to a Bing Ads account: https://login

163
tcpdf generated pdf file -&gt; The file header was not found

tcpdf generated pdf file -> The file header was not found

I am trying to generate a table of user input data to SQL database to be generated into table, placed into PDF file and sent as an attachment to with an email

161
get asynchronous value from firebase firestore reference

get asynchronous value from firebase firestore reference

I'm trying to get the name value from this promise from firebase firestore database:

168
Positioning elements of different heights

Positioning elements of different heights

Sorry for the vague title, but I'm not exactly sure what to call thisI'm making an assignment planner using HTML and JavaScript, and I've ran into a bit of a problem while trying to make a feature to snap your text to the nearest line

189