Поиск картинок по индексу hex-представления их цвета

19 октября 2009 г.

Для использования на неком фото-ресурсе автору сия поста потребовалось разработать/украсть некую фишку, а именно — поиск картинок по индексу hex-представленя их цвета — по аналогии с images.google.com. Так-как автор владеет лишь PHP, MySQL, JS и HTML, было решено найти готовое решение для этой проблемы используя именно PHP, что оказалось нелегко, поскольку работа с графикой в web-приложениях преимущественно производится на Си.

Итак, перерыв дюжинную гору нужной и не нужной информации, просмотрев десяток каталогов скриптов, и не найдя нужного, решил написать свое решение, соответственно, с помощью PHP.

Краткий обзор ТЗ:

  • при добавлении картинки через админку, помимо описания этой картинки нужно составлять индекс цветов для дальнейшего быстрого поиска по базе.
  • выборка из базы должна осуществляться как можно быстрее — посетителей на ресурсе планируется не одна тысяча.

Собственно, требуется написать скрипт из двух частей — админка и front-end.

Естественно, начнем с админки — эта часть индексирует цвета, вносит записи в БД и т.д.
Сперва получим картинку от пользователя, сохраним на диск и сделаем для себя копию 100х100 — если задействовать все пользовательское изображение, придется обрабатывать: 1280*800 (минимальное актуальное разрешение на сайте) = 1 024 000 точек, в уменьшенной копии 100х100 — всего 10 000, т.е. в 102 раза меньше, хотя размера уменьшенной картинки вполне хватит, чтобы составить основную гамму цветов.
Также нам прийдется составить рейтинг цветов. Дело в том, что даже в небольшом изображении при использовании градиента (например, тени) окажется порядка нескольких сотен цветов. Заносить их все в БД не имеет смысла — при 1000 итемов, индекс цветов перевалит за 100 000, что очень хорошо скажется на конечной работе приложения. Поэтому достаточно брать из рейтинга, скажем, первых тридцать цветов из всей гаммы. Для чего такое количество, см. построение front-end`а.

Back-end
Функция непропорционального изменения размера картинки — найдена в Сети, немного доработана, авторство неизвестно ($file_from — путь к изображению, $file_to — место сохранения уменьшеной копии, $newheight — max. высота/ширина уменьшеной картинки, возвращает $file_to).

<?
function simple_resize($file_from,$file_to,$newheight=200)
{
/*this can be used to resize image in jpeg format only
which provive a good quality thumbnails*/
// Create an Image from it so we can do the resize
$src = imagecreatefromjpeg($file_from);
// Capture the original size of the uploaded image
list($width,$height)=getimagesize($file_from);
//$newwidth=300;
$newwidth=$newheight;
//$newheight=($height/$width)*100;
$tmp=imagecreatetruecolor($newwidth,$newheight);
// this line actually does the image resizing, copying from the original
// image into the $tmp image
imagecopyresampled($tmp,$src,0,0,0,0,$newwidth,$newheight,$width,$height);
// now write the resized image to disk
imagejpeg($tmp,$file_to,100);
imagedestroy($src);
imagedestroy($tmp);
//
return $file_to;
}
?>

Эта функция составляет рейтинг цветов в hex картинки $source_src (путь к картинке), возвращает массив первых $index_colors в индексе.

<?
function color_index($source_src,$index_colors=30)//индексирование цветов картинки
{
$size = getimagesize($source_src);//размеры картинки
$image_type=file_ext($file);//расширение файла изображения, ничего сложного
switch ($image_type)//загнали img в дескриптор, для каждого типа файла своя ф-ия
{
case 'gif': $source = imagecreatefromgif($source_src); break;
default:case 'jpg': $source = imagecreatefromjpeg($source_src); break;
case 'png': $source = imagecreatefrompng($source_src); break;
}
$INDEX=array();
$I=array();
for($x=0;$x<$size[0];$x++)//перебираем точки картинки по x
{
for($y=0;$y<$size[1];$y++)//перебираем точки картинки по y
{
$color=ImageColorAt($source, $x, $y);//берем цвет в точке n
$rgb_arr_0=imagecolorsforindex($source, $color);//переводим цвет в rgb
$rgb_arr[0]=$rgb_arr_0['red'];
$rgb_arr[1]=$rgb_arr_0['green'];
$rgb_arr[2]=$rgb_arr_0['blue'];
$I[$color]++;
$INDEX[$color]=rgb2hex2rgb(implode('.',$rgb_arr));//rgb в hex, заносим все данные для последующего построения рейтинга
}
}
$out_0=array();
$out=array();
foreach($INDEX as $key=>$color_hex)//перебираем все полученые цвета, представим как номер цвета php GD в палитре изображения => цвет в hex
{
$out_0[$I[$key]][]=$color_hex;//для каждого цвета создадим суб-массив, в него запишем инкриментом количество вхождений данного цвета sizeof($out_0[$I[$key]])= к-во н-ого цвета в массиве цветов
#echo $color_hex.' => '.$I[$key].'
';//как бы такой массив
}
foreach($out_0 as $incolor)//переберем все hex суб-массивы в обратном порядке, обеденим многомерный массив в одномерный
{
rsort($incolor);
$out[]=implode(',',$incolor);
}
$out=implode(',',$out);//Объеденим массив в строку
$out=explode(',',$out);//разобем строку в массив
//две предыдущие операции - гарантия получения одномерного массива
$out_0=array();
for($i=sizeof($out);$i>sizeof($out)-$index_colors;$i--)//получим $out_0[0]=hex с max числом вхождений, т.е. самый частовстречающийся цвет по рейтингу
{
$out_0[]=$out[$i];
}
//
return $out_0;//вернули массив наиболее частовстречающихся цветов по рейтингу, где $out_0[0] = наиболее частый
}
//
function file_ext($file)//на входе - имя файла (можно с путями),возвращает расширение файла изображения
{
$image_type_arr=explode("/",$file);
$image_type_arr=explode(".",$image_type_arr[sizeof($image_type_arr)-1]);
return strtolower((strlen($image_type_arr[1])>3)?'jpg':$image_type_arr[1]);
}
?>

Функция конвертирует цвета палитры rgb в hex и обратно, взята на php.net, пояснений не требует.

<?
function rgb2hex2rgb($c)//конверт rgb->hex->rgb
{
if(!$c) return false;
$c = trim($c);
$out = false;
if(preg_match("/^[0-9ABCDEFabcdef\#]+$/i", $c)){
$c = str_replace('#','', $c);
$l = strlen($c) == 3 ? 1 : (strlen($c) == 6 ? 2 : false);

if($l){
unset($out);
$out[0] = $out['r'] = $out['red'] = hexdec(substr($c, 0,1*$l));
$out[1] = $out['g'] = $out['green'] = hexdec(substr($c, 1*$l,1*$l));
$out[2] = $out['b'] = $out['blue'] = hexdec(substr($c, 2*$l,1*$l));
}else $out = false;

}elseif (preg_match("/^[0-9]+(,| |.)+[0-9]+(,| |.)+[0-9]+$/i", $c)){
$spr = str_replace(array(',',' ','.'), ':', $c);
$e = explode(":", $spr);
if(count($e) != 3) return false;
$out = '#';
for($i = 0; $i<3; $i++)
$e[$i] = dechex(($e[$i] <= 0)?0:(($e[$i] >= 255)?255:$e[$i]));

for($i = 0; $i<3; $i++)
$out .= ((strlen($e[$i]) < 2)?'0':'').$e[$i];

$out = strtoupper($out);
}else $out = false;

return $out;
}
?>

Функция (RGB_TO_HSV ($R, $G, $B), обратно — HSV_TO_RGB($H, $S, $V)) конвертирует цвета палитры rgb в HSV (для выявления диапазона цвета лучше использовать эту палитру/формат, чем rgb — удобнее вертеть цвета в dec, чем в hex) и обратно, взята на где-то в БуржуйНете, Автору (чей-то блог) — респект, пояснений не требует, ибо технически универсально.

<?
function RGB_TO_HSV ($R, $G, $B) // RGB Values:Number 0-255
{ // HSV Results:Number 0-1
$HSL = array();

$var_R = ($R / 255);
$var_G = ($G / 255);
$var_B = ($B / 255);

$var_Min = min($var_R, $var_G, $var_B);
$var_Max = max($var_R, $var_G, $var_B);
$del_Max = $var_Max - $var_Min;

$V = $var_Max;

if ($del_Max == 0)
{
$H = 0;
$S = 0;
}
else
{
$S = $del_Max / $var_Max;

$del_R = ( ( ( $max - $var_R ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_G = ( ( ( $max - $var_G ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;
$del_B = ( ( ( $max - $var_B ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max;

if ($var_R == $var_Max) $H = $del_B - $del_G;
else if ($var_G == $var_Max) $H = ( 1 / 3 ) + $del_R - $del_B;
else if ($var_B == $var_Max) $H = ( 2 / 3 ) + $del_G - $del_R;

if (H<0) $H++;
if (H>1) $H--;
}

$HSL['H'] = $H;
$HSL['S'] = $S;
$HSL['V'] = $V;

return $HSL;
}
function HSV_TO_RGB ($H, $S, $V) // HSV Values:Number 0-1
{ // RGB Results:Number 0-255
$RGB = array();

if($S == 0)
{
$R = $G = $B = $V * 255;
}
else
{
$var_H = $H * 6;
$var_i = floor( $var_H );
$var_1 = $V * ( 1 - $S );
$var_2 = $V * ( 1 - $S * ( $var_H - $var_i ) );
$var_3 = $V * ( 1 - $S * (1 - ( $var_H - $var_i ) ) );

if ($var_i == 0) { $var_R = $V ; $var_G = $var_3 ; $var_B = $var_1 ; }
else if ($var_i == 1) { $var_R = $var_2 ; $var_G = $V ; $var_B = $var_1 ; }
else if ($var_i == 2) { $var_R = $var_1 ; $var_G = $V ; $var_B = $var_3 ; }
else if ($var_i == 3) { $var_R = $var_1 ; $var_G = $var_2 ; $var_B = $V ; }
else if ($var_i == 4) { $var_R = $var_3 ; $var_G = $var_1 ; $var_B = $V ; }
else { $var_R = $V ; $var_G = $var_1 ; $var_B = $var_2 ; }

$R = $var_R * 255;
$G = $var_G * 255;
$B = $var_B * 255;
}

$RGB['R'] = $R;
$RGB['G'] = $G;
$RGB['B'] = $B;

return $RGB;
}
?>

Собственно, мы конвертим HEX -> RGB -> HSV, заносим это в базу, и все. Свою миссию админка выполнила.

<?
$source_src='/tmp/some.jpg'
$color_index=color_index($source_src,30);//создали рейтинг цветов картинки
//->img_index
foreach($color_index as $cindex)//перебираем каждый цвет заносим его в базу
{
$cindex0=$cindex;
$cindex=rgb2hex2rgb($cindex);//конверт hex->rgb
$HSV=RGB_TO_HSV($cindex['r'], $cindex['g'], $cindex['b']);//rgb->hsv
// RGB_TO_HSV($cindex['r'], $cindex['g'], $cindex['b']) вернет массив с 'H'=>[-1,1],'S'=>[0,1]'V'=>[0,1],
//где 'H' - число градусов (см. графическое представлени в источниках, яндекс),
//'S','V' - проценты,
//потому переведем в нормальное представление
$HSV=array('H'=>ceil($HSV['H']*180),'S'=>ceil($HSV['S']*100),'V'=>ceil($HSV['V']*100));
//$connection->insert - вставляет в 'img_index' массив данных (см. ниже структуру БД),$img_id - внутренний номер вставляемого изображения, к теме не относится
$connection->insert('img_index',array('img_id'=>$img_id,'r'=>$cindex['r'],'g'=>$cindex['g'],'b'=>$cindex['b'],'hex'=>$cindex0,'H'=>trim($HSV['H']),'S'=>trim($HSV['S']),'V'=>trim($HSV['V'])));
}
/*
Думаю, все логично, объяснять не нужно
--
-- Структура таблицы `img_index`
--

CREATE TABLE `img_index` (
`img_id` int(15) NOT NULL,
`r` int(3) NOT NULL,
`g` int(3) NOT NULL,
`b` int(3) NOT NULL,
`H` tinyint(4) NOT NULL default '0',
`S` tinyint(4) NOT NULL default '0',
`V` tinyint(4) NOT NULL default '0',
`hex` varchar(7) NOT NULL,
KEY `img_id` (`img_id`)
) ENGINE=MyISAM DEFAULT CHARSET=cp1251;
*/
?>

Front-end
Если помните, мы вставляли в БД первые тридцать цветов — так вот, это было сделано для того, чтобы иметь возможность делать выборку по БД в диапазоне заданного цвета (±).И так, как гамма у нас состоит из цветов и оттенков, при запросе н-ого цвета мы получим одно из значений похожих оттенков. Все просто. Больше оттенков основного цвета в базе — можно настроить более релевантную выдачу (например, сортировать по n параметру и т.д.).

<?
$diapazone=15;//Диапазон сдвига цвета (±) по 3-м параметрам

global $connection;//внутренний клас работы с MySQL, к делу не относится
/*
Этот класс по работе с массивом изображений, и данных о нем - оправит в шаблон картинку и т.д., учавствует в дальнейшей работе с массивом, отданым мускулем на сформированный запрос, что мы и разберем

$wallpapers=new Wallpapers();
class Wallpapers()
{
var sql_limit=array();
var how_sort='DESC';//сортировать вверх/вниз
var thumbs_to_page=20;//к-во изображений на страницу
function get_random()//
{}
}
*/
$wallpapers->sql_limit=array($wallpapers->thumbs_to_page*((int)$n-1),$wallpapers->thumbs_to_page);
$wallpapers->sql_limit[0]=($wallpapers->sql_limit[0]<0)?0:$wallpapers->sql_limit[0];
//

//$d-запрашваемый цвет в hex, без диеза (например, fff00c)

$rgb_colors=rgb2hex2rgb($d);
$HSV=RGB_TO_HSV($rgb_colors['r'], $rgb_colors['g'], $rgb_colors['b']);//rgb->hsv
$HSV=array('H'=>ceil($HSV['H']*180),'S'=>ceil($HSV['S']*100),'V'=>ceil($HSV['V']*100),);//конвертнем hex->rgb->hsv по формуле, когда вносили значение в БД (см. вставку в БД)

//назначим диапазон выборки по полям H, S, V с коэфициентами (коэфициенты подобраны методом научного тыка, так наиболее точная выдача)
$HSV_H=array('min'=>($thisval=$HSV['H']-ceil($diapazone*180/100)*3),'max'=>$HSV['H']+ceil($diapazone*180/100)*3);
$HSV_S=array('min'=>($thisval=$HSV['S']-$diapazone+$diapazone/1.3),'max'=>$HSV['S']+$diapazone);
$HSV_V=array('min'=>($thisval=$HSV['V']-$diapazone+$diapazone/1.3),'max'=>$HSV['V']+$diapazone);
//выборка типа SELECT `img_id` FROM 'img_index' WHERE `H`>='$HSV_H['min']' AND `H`<='$HSV_H['max']' AND `S`>='$HSV_S['min']' AND `S`<='$HSV_S['max']' AND `V`>='$HSV_V['min']' AND `V`<='$HSV_V['max']' ORDER BY `H`$wallpapers->how_sort
$connection->select('img_index','img_id',"WHERE `H`>='".$HSV_H['min']."' AND `H`<='".$HSV_H['max']."' AND `S`>='".$HSV_S['min']."' AND `S`<='".$HSV_S['max']."' AND `V`>='".$HSV_V['min']."' AND `V`<='".$HSV_V['max']."' ORDER BY `H` ".$wallpapers->how_sort);
//работа с результатами , уникализация значений (однин оттенок - одно изображение), или выдача результатов - нет совпадений
$uniqe_ids=array();
if($connection->num_rows>0)//если выборка больше 0 строк
{
/*
функция $connection->assoc() возвращает массив с полями каждого ряда из полученых результатов==
array(
array('key'=>1,'key2'=>2),
array('key'=>1,'key2'=>2),
array('key'=>1,'key2'=>2),
...
);
*/
foreach($connection->assoc() as $img_id)
{
if(!in_array($img_id['img_id'],$uniqe_ids))
{
$uniqe_ids[]=$img_id['img_id'];//приводим к виду одно изображение - один цвет (чтоб не повторялись картинки на странице)
}
}

$query = array();
//подготовим запрос в БД для выборки нужных уникальных значений id изображений
for($i=$wallpapers->sql_limit[0];$i<$wallpapers->sql_limit[1];$i++)
{
if(!empty($uniqe_ids[$i]))
{
$query[]=$uniqe_ids[$i];
}
}
$query=implode(',',$query);
//получили запрос SELECT*FROM 'images' WHERE `id` IN (1,2,3,4,5), где таблица 'images' - произвольный набор записей характеристик изображений с номером `id` (id в images == img_id в img_index)
$connection->select('images','*',"WHERE `id` IN(".$query.")");
$result=$connection->assoc();//ассоциативный массив (с ключами == название полей в таблице БД) с n рядов с записями для нужных изображений

if($connection->num_rows>0)
{
//$result - многомерный массив
$SYSTEM['content']['main']=$wallpapers->get_random($result);//дело техники, обернули каждое изображение в нужный шаблон.
}
else
{
$SYSTEM['content']['main']='no images found';
}
}
else
{
$SYSTEM['content']['main']='no images found';
}
//$SYSTEM['content']['main'] - то, что выводится пользователю
?>

Вот собственно и все. Рабочий пример сайта где это все используется — www.oboimne.ru (справа цветные квадратики) .
Админку можно собрать свою на основе этих функций.

  • Похожие статьи
  • Предыдущие из рубрики