RGB 空间不是衡量两种颜色之间距离的最佳选择,因为它忽略了一些因素,例如,我们将深绿色和浅绿色都算作“绿色”(#000000 和 #7f7f7f 之间的 RGB 距离与 #000000 和 #5443c0 之间的距离相同 - 一种略微变暗的 SlateBlue)。
一个更好的颜色空间选择,它更符合颜色的感知方式,是所谓的 Lab 空间,它根据颜色的明暗、红绿和黄蓝来测量颜色。(还有更好的模型,但它们是以增加计算量为代价的。)
<?php
function warp1($c)
{
if($c > 10.3148)
{
return pow((561 + 40*$c)/10761, 2.4);
}
else
{
return $c / 3294.6;
}
}
function warp2($c)
{
if($c > 0.008856)
{
return pow($c, 1/3);
}
else
{
return 7.787 * $c + 4/29;
}
}
function rgb2lab($rgb)
{
[$red, $green, $blue] = array_map('warp1', $rgb);
$x = warp2($red * 0.4339 + $green * 0.3762 + $blue * 0.1899);
$y = warp2($red * 0.2126 + $green * 0.7152 + $blue * 0.0722);
$z = warp2($red * 0.0178 + $green * 0.1098 + $blue * 0.8730);
$l = 116*$y - 16;
$a = 500 * ($x - $y);
$b = 200 * ($y - $z);
return array_map('intval', [$l, $a, $b]);
}
function generate_palette_from_image($image)
{
$pal = [];
$width = imagesx($image);
$height = imagesy($image);
for($x = 0; $x < $width; ++$x)
{
for($y = 0; $y < $height; ++$y)
{
$pal[] = imagecolorat($image, $x, $y);
}
}
return array_map(function($col)use($image)
{
$rgba = imagecolorsforindex($image, $col);
return [$rgba['red'], $rgba['green'], $rgba['blue']];
}, array_unique($pal));
}
function closest_rgb_in_palette($rgb, $palette)
{
if(($idx = array_search($rgb, $palette)) !== false)
{
return $idx;
}
[$tl, $ta, $tb] = rgb2lab($rgb);
$dists = array_map(function($plab)use($tl, $ta, $tb)
{
[$pl, $pa, $pb] = $plab;
$dl = $pl - $tl;
$da = $pa - $ta;
$db = $pa - $tb;
return $dl * $dl + $da * $da + $db * $db;
}, array_map('rgb2lab', $palette));
return array_search(min($dists), $dists);
}
function closest_rgb_in_image($rgb, $image)
{
$palette = generate_palette_from_image($image);
return $palette[closest_rgb_in_palette($rgb, $palette)];
}
$lena = imagecreatefrompng('lena.png');
$red = closest_rgb_in_image([255,0,0],$lena);
echo join(' ', $red); ?>
如果你要将许多颜色与调色板匹配,你可能希望预先计算并重用 Lab 调色板,而不是像这里一样每次都重新生成它。