"Distance" Between Colours in PHP

Distance between colours in PHP

Each color is represented as a tuple in the HEX code. To determine close matches you need to subtract each RGB component separately.

Example:

Color 1: #112233 
Color 2: #122334
Color 3: #000000

Difference between color1 and color2: R=1, G=1 B=1 = 0x3
Difference between color3 and color1: R=11, G=22, B=33 = 0x66

So color 1 and color 2 are closer than
1 and 3.

edit

So you want the closest named color? Create an array with the hex values of each color, iterate it and return the name. Something like this;

function getColor($rgb)
{
// these are not the actual rgb values
$colors = array(BLUE =>0xFFEEBB, RED => 0x103ABD, GREEN => 0x123456);

$largestDiff = 0;
$closestColor = "";
foreach ($colors as $name => $rgbColor)
{
if (colorDiff($rgbColor,$rgb) > $largestDiff)
{
$largestDiff = colorDiff($rgbColor,$rgb);
$closestColor = $name;
}

}
return $closestColor;

}

function colorDiff($rgb1,$rgb2)
{
// do the math on each tuple
// could use bitwise operates more efficiently but just do strings for now.
$red1 = hexdec(substr($rgb1,0,2));
$green1 = hexdec(substr($rgb1,2,2));
$blue1 = hexdec(substr($rgb1,4,2));

$red2 = hexdec(substr($rgb2,0,2));
$green2 = hexdec(substr($rgb2,2,2));
$blue2 = hexdec(substr($rgb2,4,2));

return abs($red1 - $red2) + abs($green1 - $green2) + abs($blue1 - $blue2) ;

}

RGB to closest predefined color

You have to calculate the distance to each color, and pick the smallest.

There are a few ways to do this. A simple method would be to calculate the distance would be:

sqrt((r-r1)^2+(g-g1)^2+(b-b1)^2)

A better method might be to incorporate the weighted values to calculate a distance, for instance the values used when converting RGB->YUV:

Y = 0.299 * R + 0.587 * G + 0.114 * B

in that case you would use

sqrt(((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2)

Of course, since you don't need the exact distances, just a comparison, you can and probably should just skip the square root, making the last calculation:

((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2

Functions that calculate distance between HEX colors and eliminate colours too similar

There are many definitions of the similarity of colours. You might be looking for similar hue, or similar brightness.

For these purposes, perhaps the simplest is to take the euclidian distance between the two points in RGB space, that is:

sqrt(pow($r1 - $r0, 2) + pow($g1 - $g0, 2) + pow($b1 - $b0, 2));

and reject those where that value is too small (e.g. 10?)

Followup: Finding an accurate distance between colors

Convert to La*b* (aka just plain "Lab", and you'll also see reference to "CIELAB"). A good quick measaure of color difference is

(L1-L2)^2 + (a1-a2)^2 + (b1-b2)^2

Color scientists have other more refined measures, which may not be worth the bother, depending on accuracy needed for what you're doing.

The a and b values represent opposing colors in a way similar to how cones work, and may be negative or positive. Neutral colors - white, grays are a=0,b=0. The L is brightness defined in a particular way, from zero (pure darkness) up to whatever.

Crude explanation :>> Given a color, our eyes distinguish between two broad ranges of wavelength - blue vs longer wavelengths. and then, thanks to a more recent genetic mutation, the longer wavelength cones bifurcated into two, distinguishing for us red vs. green.

By the way, it'll be great for your career to rise above your color caveman collegues who know of only "RGB" or "CMYK" which are great for devices but suck for serious perception work. I've worked for imaging scientists who didn't know a thing about this stuff!

For more fun reading on color difference theory, try:

  • http://white.stanford.edu/~brian/scielab/introduction.html and info
  • and links on color theory in general, websurf starting with http://www.efg2.com/Lab/Library/Color/ and
  • http://www.poynton.com/Poynton-color.html

More detail on Lab at http://en.kioskea.net/video/cie-lab.php3 I can't at this time find a non-ugly page that actually had the conversion formulas but I'm sure someone will edit this answer to include one.

Finding Similar HEX colors using a threshold

quick and dirty:

$dr = $red1   - $red2;
$dg = $green1 - $green2;
$db = $blue1 - $blue2;
$fr = 2; // may be adjusted
$fg = 4; // "
$fb = 1; // "
$distance_squared = $fr * $dr * $dr + $fg * $dg * $dg + $fb * $db * $db;

You would then compare $distance_squared to the square of the threshold. The factors may be adjusted (especially blue might get a higher factor), as well as their sum (in order to match the threshold)

For a "slow and clean" solution, I would start from here (and here for a more practical approach).

Detect if color is in range

It looks fairly linear to me. However, you need to understand a bit of color theory.

I hope you don't mind me including a simple refresher of color theory here before answering your question. But it helps in understanding or formulating any solution.

First, I'm sure most will remember the color wheel they learned in kindergarten:

                                          Red
Red Orange | Purple
| \_|_/
_o_ ----------> _o_
/ \ / | \
Yellow Blue Yellow | Blue
Green

Well, we need to modify it a bit. Because it turns out that Red and Blue are not really primary colors since you can get red by mixing Magenta with Yellow and you can get Blue by mixing Cyan with a bit of Red. So The modified color wheel uses printing colors: CMY:

        Magenta
Red | Blue
\_|_/
_o_
/ | \
Yellow | Cyan
Green

This is basically the HSV color space (with S usually plotted going from the center to the edge of the wheel and V not plotted at all but usually can be changed by a slider). It is derived from printing colors CMY. So how can this help us with screen colors RGB?

Well, if you look carefully at it you'll notice that it is actually a composite of both printing and screen color space. It actually depicts the relationship between CMY and RGB:

        Magenta                 Magenta
Red | Blue | Red Blue
\_|_/ | \_ _/
_o_ == _o_ + o
/ | \ / \ |
Yellow | Cyan Yellow Cyan |
Green Green

So, any RGB color can be understood by understanding this color wheel. For example, there is no Yellow in RGB. But notice that Yellow is the opposite of Blue. So, to get Yellow you subtract blue from a color:

rgb(100,80,10)  is a "yellowish" version of rgb(100,80,120)
^ ^

Once you understand this color wheel working with RGB values makes more sense. With enough practice you can compose and edit colors directly in #rrggbb syntax without consulting a color chart.

So, to answer your question. Say your target shade of pink is:

rgb(255,182,193)

We know two things about the color "pink":

  1. We percieve it as a kind of Red. So it makes sense that the target color has high Red value (in fact it is maximum).

  2. We percieve it as a very light Red. So it makes sense that the non-Red components are also quite high (both above 150).

So, to get shades close to your target color we need:

  1. R value significantly higher than G or B.
  2. All values to be quite high (above 150 or so).

By this definition we can identify pink as:

// Pseudo code:

is_pink (R,G,B) {
return R > 200 && // make sure R is high
G > 150 && // make sure G & B are relatively high
B > 150 &&
R > G && // make sure G & B are not higher than R
R > B;
}

We can add another condition to reject "pink" that is slightly bluish or greenish by making sure that G & B are fairly similar:

// Pseudo code:

is_pink (R,G,B) {
return R > 200 &&
G > 150 &&
B > 150 &&
R > G &&
R > B &&
abs(G-B) < 20; // make sure it's neither bluish or greenish
}

You can tweak the values 200, 150 and 20 to suit your taste of pinkishness but you'll see that if we apply the above function to both your test colors (255,182,193) and (238,162,173) they'll both be identified as "pink".

Finding nearest match RGB color from array of colors

There are many different ways to determine color "distance."

There's absolute distance, i.e. the sum of the differences between each channel value:

/**
* Find the "naive" difference between two colors.
* @param int[] $color_a Three-element array with R,G,B color values 0-255.
* @param int[] $color_b Three-element array with R,G,B color values 0-255.
* @return int
*/
function absoluteColorDistance(array $color_a, array $color_b): int {
return
abs($color_a[0] - $color_b[0]) +
abs($color_a[1] - $color_b[1]) +
abs($color_a[2] - $color_b[2]);
}

There's also difference in luminosity, which will give more of a color-independent comparison:

/**
* Find the difference between two colors' luminance values.
* @param int[] $color_a Three-element array with R,G,B color values 0-255.
* @param int[] $color_b Three-element array with R,G,B color values 0-255.
* @return int
*/
function luminanceDistance(int $color_a, int $color_b): int {
$luminance_f = function ($red, $green, $blue): int {
// Source: https://en.wikipedia.org/wiki/Relative_luminance
$luminance = (int) (0.2126 * $red + 0.7152 * $green + 0.0722 * $blue);
return $luminance;
};

return abs(
$luminance_f($color_a[0], $color_a[1], $color_a[2]) -
$luminance_f($color_b[0], $color_b[1], $color_b[2])
);
}

Once you figure out how to compare colors, the next problem you need to solve is finding the color with the least distance from your target color:

$nearest_distance = null;
$nearest_color = null;
foreach ($palate as $test_color) {
$test_distance = luminanceDistance($test_color, $rgbcolor);
if (isset($nearest_distance)) {
if ($nearest_distance > $test_distance) {
// found a closer color
$nearest_distance = $test_distance;
$nearest_color = $test_color;
}
} else {
$nearest_color = $test_color;
$nearest_distance = $test_distance;
}
}


Related Topics



Leave a reply



Submit