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.

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.