RGB 色彩空間並不是衡量兩種顏色之間距離的最佳選擇,因為它忽略了例如我們將深綠色和淺綠色都視為「綠色」的事實(#000000 和 #7f7f7f 之間的 RGB 距離與 #000000 和 #5443c0(稍微暗一點的青藍色)之間的距離相同)。
一種更符合顏色感知方式的色彩空間是所謂的 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 調色板,而不是像這裡那樣每次都重新生成它。