Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to invert RGB hex values by contrast in PHP

Tags:

algorithm

php

hex

Thus far I have the code below:

function hexrgb_invert($hex) {
    $arr = str_split($hex, 2);
    foreach ($arr as &$value) {
        $c = base_convert($value, 16, 10);
        $value = str_pad(base_convert(255 - $c, 10, 16), 2, '0', STR_PAD_LEFT);
    }
    return implode('', $arr);
}

The Problem: I need to invert colors based on contrast. The above function works for some things but not others.

Example: If the input is 9d702f the output will be 9d702f. (2 colors that have a low contrast)

I haven't found any luck looking elsewhere on StackOverflow, as most answers seem to use the same algorithm I am already using.


Further Examples:

Let's say that I am trying to find the contrasting opposite of #FFFFFF (white). This is very straight forward because white is a primary color so its opposite can be easily calculated. (Which the above function will work perfectly for.) The opposite of #FFFFFF is of course #000000 (black) and when you compare the 2 colors you get a contrast ratio of 21:1.

However, if we try to use the same function above on the color #808080 it will give us the color #7F7F7F. Those 2 colors are almost identical and have a contrast ratio of only 1.01:1. This is because the closer you get to hex 80 (decimal 128) the less contrast that function can provide.

In the specific case of #808080 the color #000000 would provide the most constrast at 5.32:1.


Solution:

function rgb_best_contrast($r, $g, $b) {
    return array(
        'r' => ($r < 128) ? 255 : 0,
        'g' => ($g < 128) ? 255 : 0,
        'b' => ($b < 128) ? 255 : 0
    );
}
like image 659
Nicholas Summers Avatar asked Jun 27 '16 21:06

Nicholas Summers


2 Answers

str_pad adds characters on the right by default — its optional $pad_type argument defaults to STR_PAD_RIGHT.

You need to force it add zeros on the left:

str_pad(base_convert(255 - $c, 10, 16), 2, '0', STR_PAD_LEFT)

Your example in details:

  • Input: 6ff060, taking only the G value into consideration: $c = 0xf0 (decimal: 240).
  • 255 - 240 = 15 (hex: f)
  • base_convert(255 - $c, 10, 16) produces: 'f' (as string!)
  • str_pad(base_convert(255 - $c, 10, 16), 2, '0') adds one zero on the right, thus producing 'f0'.
  • Setting $pad_type = STR_PAD_LEFT fixes the problem.
like image 161
Alex Shesterov Avatar answered Sep 30 '22 02:09

Alex Shesterov


After trying a few dozen different methods to try and calculate the best contrasting opposite color to any given color, I finally broke down and started manually testing individual color contrasts.

I used this script to search the entire RGB color space for the best match for any given color:

$test_a = ['r' => 128, 'g' => 128, 'b' => 128];
$best = [
    'color' => ['r' => 128, 'g' => 128, 'b' => 128],
    'diff'  => 0.0
];
foreach (range(0, 255) as $r) {
    foreach (range(0, 255) as $g) {
        foreach (range(0, 255) as $b) {
            $test_b = ['r' => $r, 'g' => $g, 'b' => $b];
            // YQI sensitive contrast check
            $diff = check::rgb_contrast($test_a, $test_b);
            if ($diff > $best['diff']) {
                $best = [
                    'color' => $test_b,
                    'diff'  => $diff
                ];
            }
        }
    }
}
var_dump($best);

In short, a very obvious pattern emerged with all of my results. This function works off that pattern:

function rgb_best_contrast($r, $g, $b) {
    return array(
        'r' => ($r < 128) ? 255 : 0,
        'g' => ($g < 128) ? 255 : 0,
        'b' => ($b < 128) ? 255 : 0
    );
}

Works exactly as expected. Always gives the best contrasting color.

like image 34
Nicholas Summers Avatar answered Sep 30 '22 04:09

Nicholas Summers