Analyse Image Colors using PHP
December 4th, 2011 | Posted by - (3 Comments)I was asked to develop a search extension for open cart that would allow customers to search product images by color. This is similar to the search by color that Google images uses.
In order to make this extension work I needed an algorithm that would analyse the colors in an image and match it to the closest color in a set of colour swatches such as those shown in the Advanced Search image below.
The basic program I developed uses the PHP GD library to process the images and follows these general steps.
- Define the colors in the color swatches
- Create an image object using the image that is to be analysed, resizing the image if it is too large
- Create an image object and load the colors from the the color swatches into image palette. This is the palette that a GIF image uses. This is the comparison palette that the colors in the image being analysed will be compared to.
- Loop through all the pixels in the image and tally it against the closest match to the pixel in the comparison palette.
- Sort the tally results in descending order
- Display the top results
There are 2 files here. The ColorCompare does all the work while the file following demonstrates the program by analysing some test images.
<?php
// Filename: ColorCompare.php
// Copyright (C) 2011 Chris Gosling
// Licence: GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
class ColorCompare
{
// max width and height for testing and resampling
static private $resize_dim = 512;
// define colour swatches
static public $swatches = array(
"WHITE" => "FFFFFF",
"CREAM" => "F2EAC6",
"YELLOW" => "F8E275",
"ORANGE" => "FA9A4D",
"PEACH" => "F9A986",
"PINK" => "FAACA8",
"MANGENTA" => "FC6D99",
"RED" => "FF0000",
"BURGANDY" => "AC2424",
"PURPLE" => "A746B1",
"LAVENDER" => "C791F1",
"LIGHT_BLUE" => "A4A6FD",
"DARK_BLUE" => "1D329D",
"TURQUIOSE" => "2CCACD",
"AQUA" => "9CD8F4",
"DARK_GREEN" => "62854F",
"LIGHT_GREEN" => "A9CB6C",
"TAN" => "CAB775",
"BROWN" => "815B10",
"GRAY" => "777777",
"BLACK" => "000000"
);
// this will be the swatch index for each color
// eg: $swatch_index[0] = "WHITE" and so on
static private $swatch_index;
// this image contains the palette the test image will be compared to
static private $comp_palette = false;
// percentage that colour needs to reach of total
// pixels for colour to be considered significant
static public $threshold_filter = 5;
// ------------------------------------------------------
// ACCEPTS: max number of colours to return
// filename of image
// comparison method id
// 1 = resize method
// RETURNS: array (up to max_colors) containing indexed with the color
// name and
// the value will be the pixel count in order of highest to
// lowest pixels
// eg: "TAN" => 1000, "PINK" => 800, "MAGENTA" => 600
// or false if failed
static public function compare($max_colors, $filename)
{
$tally = array();
// size image to something managable (256 x 256)
$image_data = getimagesize($filename);
// if small image then use its current size
if ($image_data[0] < self::$resize_dim
&& $image_data[1] < self::$resize_dim)
{
$image = self::createImage($filename, $image_data[2]);
$width = $image_data[0];
$height = $image_data[1];
}
else // resize the image
{
$res = self::createResizedImage($filename, $image_data[0],
$image_data[1], $image_data[2]);
if ($res == false)
{
print "[failed on resize]";
return false;
}
else
{
$image = $res[0];
$width = $res[1];
$height = $res[2];
}
}
// create the comparison palette
self::createComparisonPalette();
// loop through x axis
for ($x = 0; $x < $width; $x++)
{
// loop through y axis
for ($y = 0; $y < $height; $y++)
{
// compare to find colest match and tally
list($red, $green, $blue) = self::getRGBFromPixel($image, $x, $y);
$index = imagecolorclosest(self::$comp_palette, $red, $green, $blue);
$tally[$index] = $tally[$index] + 1;
}
}
// sort the tally results
arsort($tally);
$ret_array = array();
$i = 0;
$threshold = ($width * $height) * (self::$threshold_filter / 100);
// build the return array of the top results
foreach($tally as $index => $count)
{
// make sure the count is high enough to be considered significant
if ($count >= $threshold)
{
$ret_array[self::$swatch_index[$index]] = $count;
$i++;
}
else
break;
if ($i >= $max_colors)
break;
}
return $ret_array;
}
// --------------------------------------------------------
// ACCEPTS: image resource
// x/y coordinate
// RETURNS: array contain red, green, blue value of pixel
static private function getRGBFromPixel($image, $x, $y)
{
$rgb = imagecolorat($image, $x, $y);
$red = ($rgb >> 16) & 0xFF;
$green = ($rgb >> 8) & 0xFF;
$blue = $rgb & 0xFF;
return array ($red, $green, $blue);
}
// -------------------------------------------------------
// Creates the comparison palette if not already created
static private function createComparisonPalette()
{
if (self::$comp_palette === false)
{
$swatch_index = array();
self::$comp_palette = imagecreate(16, 16);
foreach(self::$swatches as $name => $hex)
{
$color = self::hexToRGB($hex);
$index = imagecolorallocate(self::$comp_palette,
$color['red'], $color['green'], $color['blue']);
self::$swatch_index[$index] = $name;
}
}
}
// ------------------------------------------------------
// ACCEPTS: hex color value without the # (eg: FF0000)
// RETURNS: associative array with values for ref, green and blue
static private function hexToRGB($hexStr)
{
$colorVal = hexdec($hexStr);
$rgbArray['red'] = 0xFF & ($colorVal >> 0x10);
$rgbArray['green'] = 0xFF & ($colorVal >> 0x8);
$rgbArray['blue'] = 0xFF & $colorVal;
return $rgbArray;
}
// ------------------------------------------------------
// ACCEPTS: filename of input image,
// original width and height of image
// type of image
// RETURNS: resized image or false if failed
static private function createResizedImage($filename, $width, $height, $type)
{
// create image from file
$image = self::createImage($filename, $type);
if (!$image)
return false;
//calculate dimensions based on smallest size
$new_width = $width < self::$resize_dim ? $width : self::$resize_dim;
$new_height = $height < self::$resize_dim ? $height : self::$resize_dim;
// create resampled image
$new_image = imagecreatetruecolor($new_width, $new_height);
if ($new_image === false)
return false;
if (imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width,
$new_height, $width, $height))
return array($new_image, $new_width, $new_height);
else
return false;
}
// ---------------------------------------------------
// Creates an image object for the supplied image file and type
// ACCEPTS: the filename of the image and the image type
// RETURNS: image object
static private function createImage($filename, $image_type)
{
// determine image type
switch ($image_type)
{
case IMAGETYPE_GIF:
$image = imagecreatefromgif($filename);
break;
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($filename);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($filename);
break;
default:
return false;
}
return $image;
}
}
?>
This is the code to demonstrate the ColorCompare class
<?php
date_default_timezone_set("Australia/Sydney");
require_once("ColorCompare.php");
// files to be tested
$filenames = array(
"test1.png",
"test2.png",
"test3.png",
"test4.png",
"test5.png",
"test6.png");
// max colors
$max_colors = 5;
// display the palette
print "<table width='600' border='1'><tr>
<td colspan=\"2\"><b>Color Swatches</b></td></tr>";
foreach (ColorCompare::$swatches as $name => $color)
print "<tr><td width='50%'>$name</td><td bgcolor='#$color'> </td></tr>\n";
print "</table><p><b>Test Images</b></p>
<p>Max Colors = $max_colors
<br>Threshold Filter = ".ColorCompare::$threshold_filter."%</p>";
// loop through each test pattern
for ($i = 0; $i < count($filenames); $i++)
{
print "<table><tr><td><img src='{$filenames[$i]}'></td>\n";
$result = ColorCompare::compare($max_colors, $filenames[$i]);
print "<td><table width='200' border='1'>";
if ($result == false)
print "<tr><td>[failed]</td></tr>\n";
else
{
print "<tr><td><b>Color</b></td><td><b>Pixel Count</b></td></tr>";
foreach ($result as $color => $count)
{
print "<tr><td width='50' bgcolor='#".ColorCompare::$swatches[$color]
."'> </td><td>".$count."</td></tr>\n";
}
}
print "</table></td></tr></table>\n";
}
?>
This is output from the above code.
| Color Swatches | |
| WHITE | |
| CREAM | |
| YELLOW | |
| ORANGE | |
| PEACH | |
| PINK | |
| MANGENTA | |
| RED | |
| BURGANDY | |
| PURPLE | |
| LAVENDER | |
| LIGHT_BLUE | |
| DARK_BLUE | |
| TURQUIOSE | |
| AQUA | |
| DARK_GREEN | |
| LIGHT_GREEN | |
| TAN | |
| BROWN | |
| GRAY | |
| BLACK | |
Test Images
Max Colors = 5
Threshold Filter = 5%
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
|
How to improve the analysis results and create a color search function
The code above does the basic color analysis. To build a color search program you’ll need to store the analysed data in a database against the image. I also discovered through a bit experimentation that you get better results if each color in the color swatches is defined by multiple colors.
This image shows part of the color definition interface. The color below the name is the color that is displayed in the color swatch. The multiple colors on the right are loaded into the color palette and associate with the color name on the left.
This is part of the DB diagram showing how the color swatches (color names) are associate with colors and the images.
- cc_color_name holds the name of the colors in the swatches
- cc_color are the individual colors that define the color in cc_color_name
- cc_product_color_name is where the data from the analysed images is stored
Some tips for calibrating the color swatches
- You will also need to spend time selecting the colors for the color swatches and tweaking them.
- Make sure that colors from one color name don’t overlap the colors in another.
- You may need to experiment picking whites, blacks and greys as they can interfere with the identification of other colors.
Download the zip file containing the php code and test images here










Thank you very much, this is good idea. I’ll try implement this.
good work done
According to the color coordinates of red and black paint?