phpcolorsrgba

How convert RGBA to HEX6 color code in PHP?


I want to convert an RGBA value to HEX in PHP. I have found good help for RGB to HEX (e.g. Convert RGB to hex color values in PHP). Now I have made an attempt to change the A value as well (e.g rgba(80,80,80,0.5) to maybe #D3D3D3 (6 digits)). Assuming, of course, that the background is white.

Below the attempt. Does anyone have any tips on how I could do this better? How do I get a matching colour but lighter?

public static function convertRGBAtoHEX6(string $rgba): string
{

    if ( strpos( $rgba, '#' ) === 0 ) {
        return $rgba;
    }

    preg_match( '/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i', $rgba, $by_color );
    
    if(isset($by_color[4])) 
    {
        $by_color[4] = 2 - $by_color[4];

        $by_color[1] = $by_color[1] * $by_color[4];
        $by_color[2] = $by_color[2] * $by_color[4];
        $by_color[3] = $by_color[3] * $by_color[4];
    }

    return sprintf( '#%02x%02x%02x', $by_color[1], $by_color[2], $by_color[3] );

}

Addendum: The answer below is similar to Convert RGBA to HEX

The difference is that the answer below is much more detailed, goes into PHP (including how to skilfully split the RGBA string in PHP) and also works better in lighter areas.


Solution

  • I came up with this solution:

    <?php
    
    $rgba = 'rgba(80,80,80,0.5)';
    
    function blendChannels(float $alpha, int $channel1, int $channel2): int
    {
        // blend 2 channels
        return intval(($channel1 * $alpha) + ($channel2 * (1.0 - $alpha)));
    }
    
    function convertRGBAtoHEX6(string $rgba): string
    {
        // sanitize
        $rgba = strtolower(trim($rgba));
        // check
        if (substr($rgba, 0, 5) != 'rgba(') {
            return $rgba;
        }
        // extract channels
        $channels = explode(',', substr($rgba, 5, strpos($rgba, ')') - 5));
        // compute rgb with white background
        $alpha = $channels[3];
        $r = blendChannels($alpha, $channels[0], 0xFF);
        $g = blendChannels($alpha, $channels[1], 0xFF);
        $b = blendChannels($alpha, $channels[2], 0xFF);
        return sprintf('#%02x%02x%02x', $r, $g, $b);
    }
    
    echo convertRGBAtoHEX6($rgba);
    

    See: https://3v4l.org/ick2o

    The result is: "#a7a7a7".

    The code is basically the same as yours with the exception of the blendChannels() function and I got rid of that horrible regular expression.

    The blendChannels() function takes the needed amount from the two supplied channels. If alpha is 1.0 the output is equal to channel 1, if alpha is 0.0 the outout is equal to channel 2, and if alpha is 0.5 an equal amount is taken from both channels. Do this for all 3 channels and you're done.

    As mentioned by Sammitch, in a comment, the sRGB color space is not linear. To get the best results the blendChannels() function should take this into account:

    function blendChannels(float $alpha, int $channel1, int $channel2): int
    {
        $gamma = 2.2;
        // blend 2 channels
        return intval(pow((pow($channel1,  $gamma) * $alpha) + 
                          (pow($channel2,  $gamma) * (1.0 - $alpha)), 1 /  $gamma));
    }
    

    You can change the gamma to whatever you need.

    You can see the difference between the various calculations best as a gradient, in this case blending black and white with a variable alpha value.

    enter image description here

    The difference between Linear and Quadratic is fairly stark, and demonstrates part of the reason for this approach, in that our eye distinguishes subtle differences better in brighter colors than darker. The Gamma correction is quite subtle in the gradient, but would likely be more apparent when applied to images.