Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

hex to real name 1

Status
Not open for further replies.

JamesGMills

Programmer
Aug 15, 2005
157
GB
Hi guys,

I am currently working on a website which is for artists to upload and showcase their artwork.

I have created the script to upload the images of the artwork. From these images I work out the top ten hex colours found in the image.

Now what I want to do is run those hex colour codes through a function which will cross reference with a list of colour names and return the colour which is closest.

Much like what this does:
I have found this which is a great start but unfortunately a colour I would very much expect to return Yellow for example, does not... it returns orange. Also one which i would expect to return blue returns silver.

Does anyone know of a good solution for this? Which works like the first javascript example?

Thanks in advance,

James


------------------------
 
James

I suspect that you are using an object as an array. often db abstraction layers provide objects by default. so either change your notation or use get_object_vars() to transform the object properties into an associative array.

i cannot help further on that as the upload.php script is not within the paste bucket.

re the beer - thank you. I'd ask that you discharge whatever obligation you feel by making a gift of food or time to your local homeless shelter, most of which are trying to do their best this christmas.

Justin
 
Hi Justin,

Thought you might like to see your code at work!


You were right about the array/object issue. Sometimes I am blind as a bat! Things like that take so much of my time, its like when you miss a comma or something and yours searching for it for days...

Couple of colours which are not getting matched or picked up correctly but I can live with that. Main thing is I can now get an array of 'human readable' colour names for an image.

Cheers,

James

------------------------
 
if i have time tonight i will look at the anomalies. let me have some sample colours that are failing.

nice site, btw
 
Hiya,

Thanks for comments on site. It's not been designed yet it's just build using a CSS framework.

I do not have a list of colours that are failing really. If you see in the screenn shot, for example, you will see some came back as black when they are not really...

Not sure what this is down to.

James

------------------------
 
the colour comparison works on calculating a 'value' for each colour and then running through the database to get the closest match.

we could filter out the white and blacks from the comparisons. to do this change the big query to the following

Code:
$sql = <<<SQL
select     c1.*, (select c2.hex from colors c2 where c2.name = c1.shade) as shadeHex
from colors c1
order by
            sqrt(
                        (
                            pow((cial - $cial),2) /* first */        
                        )
                        +
                        (
                            pow(
                                (
                                sqrt(pow(ciaa,2) + pow(ciab,2)) /* c1 */
                                 -
                                sqrt(pow($ciaa,2) + pow($ciab,2)) /*c2 */
                                )
                                / (1 + (0.045 * sqrt(pow(ciaa,2) + pow(ciab,2))))    /* c1 */
                            ,2) /* second */    
                        )
                        +
                        (
                            pow(sqrt(
                            
                                pow((ciaa - $ciaa),2) /*da*/
                                +
                                pow((ciab - $ciab),2) /*db*/
                                -
                                pow(
                                    sqrt(pow(ciaa,2) + pow(ciab,2)) 
                                    -
                                    sqrt(pow($ciaa,2) + pow($ciab,2))
                                ,2)
                            )    /*dh*/
                            /(1 + ( 0.015 * sqrt(pow(ciaa,2) + pow(ciab,2))))
                            ,2)
                        )
                ) /* end sqrt */ ASC
where hex NOT IN ('000000','FFFFFF')
LIMIT 1            
SQL;
 
Hi,

So if it makes a match does it the stop looping? So are you saying that if I specify to skip 000000 and FFFFFF then it would then find the next closes colour?

That would make more sense because for some reason some colours are coming back as black which really are not.

Cheers

James

------------------------
 
nope.

the script first searches for an exact match by hex value.

if none is found it performs a single query that calculates the lowest diff between the requested color and those that are in the db. there is no looping, unlike the js solution.

the problem comes when for some reason the calculation does not work (not examined why yet). then the closest to null is 0 and out comes black.

here is revised code which cures the problem in a better way than just excluding black and white. it also now returns the colour delta
Code:
<?php 

//connect to database
$link = mysql_connect(
  ':/Applications/MAMP/tmp/mysql/mysql.sock',
  'root',
  'root'
);

mysql_select_db('colors');
class ntc {
    
	/**
     * [URL unfurl="true"]http://www.emanueleferonato.com/2009/09/08/color-difference-algorithm-part-2/[/URL]
     * @param object $color
     * @return 
     */
    private function rgb_to_xyz($color) {
        $rgb = $this->rgb($color);
        $red = $rgb[0];
        $green = $rgb[1];
        $blue = $rgb[2];
        $_red = $red / 255;
        $_green = $green / 255;
        $_blue = $blue / 255;
        if ($_red > 0.04045) {
            $_red = ($_red + 0.055) / 1.055;
            $_red = pow($_red, 2.4);
        } else {
            $_red = $_red / 12.92;
        }
        if ($_green > 0.04045) {
            $_green = ($_green + 0.055) / 1.055;
            $_green = pow($_green, 2.4);
        } else {
            $_green = $_green / 12.92;
        }
        if ($_blue > 0.04045) {
            $_blue = ($_blue + 0.055) / 1.055;
            $_blue = pow($_blue, 2.4);
        } else {
            $_blue = $_blue / 12.92;
        }
        $_red *= 100;
        $_green *= 100;
        $_blue *= 100;
        $x = $_red * 0.4124 + $_green * 0.3576 + $_blue * 0.1805;
        $y = $_red * 0.2126 + $_green * 0.7152 + $_blue * 0.0722;
        $z = $_red * 0.0193 + $_green * 0.1192 + $_blue * 0.9505;
        return (array($x, $y, $z));
    }
    /**
     * [URL unfurl="true"]http://www.emanueleferonato.com/2009/09/08/color-difference-algorithm-part-2/[/URL]
     * @param object $xyz
     * @return 
     */
    private function xyz_to_lab($xyz) {
        $x = $xyz[0];
        $y = $xyz[1];
        $z = $xyz[2];
        $_x = $x / 95.047;
        $_y = $y / 100;
        $_z = $z / 108.883;
        if ($_x > 0.008856) {
            $_x = pow($_x, 1 / 3);
        } else {
            $_x = 7.787 * $_x + 16 / 116;
        }
        if ($_y > 0.008856) {
            $_y = pow($_y, 1 / 3);
        } else {
            $_y = (7.787 * $_y) + (16 / 116);
        }
        if ($_z > 0.008856) {
            $_z = pow($_z, 1 / 3);
        } else {
            $_z = 7.787 * $_z + 16 / 116;
        }
        $l = 116 * $_y - 16;
        $a = 500 * ($_x - $_y);
        $b = 200 * ($_y - $_z);
        return (array($l, $a, $b));
    }
   
    public function color2lab($color) {
        return $this->xyz_to_lab($this->rgb_to_xyz($color));
    }
    
    public function name($color) {
        $color = strtoupper($color);
        if (strlen($color) < 3 || strlen($color) > 7):
            return array("#000000", "Invalid Color: ".$color, "#000000", "", false);
        endif;
        if (strlen($color) % 3 == 0):
            $color = "#" + $color;
        endif;
        if (strlen($color) == 4):
            $_color = '#';
            for ($i = 1; $i <= 3; $i++):
                $_color .= $color[$i];
                $_color .= $color[$i];
            endfor;
            $color = $_color;
        endif;
        
        $result = mysql_query("
Select *, (select c2.hex from colors c2 where c2.name = c1.shade) as shadeHex
FROM colors c1
WHERE c1.hex = '" .mysql_real_escape_string(substr($color, 1))."'");
        $row = mysql_fetch_object($result);
        if ($row) {
            return $row;
        }
        
        $cia = $this->color2lab($color);
        list($cial, $ciaa, $ciab) = $cia;
        $sql = <<<SQL
select 	c1.*, (select c2.hex from colors c2 where c2.name = c1.shade) as shadeHex,
sqrt(
						(
							pow((cial - $cial),2) /* first */		
						)
						+
						(
							pow(
								(
								sqrt(pow(ciaa,2) + pow(ciab,2)) /* c1 */
						 		-
								sqrt(pow($ciaa,2) + pow($ciab,2)) /*c2 */
								)
								/ (1 + (0.045 * sqrt(pow(ciaa,2) + pow(ciab,2))))	/* c1 */
							,2) /* second */	
						)
						+
						(
							pow(sqrt(
							
								pow((ciaa - $ciaa),2) /*da*/
								+
								pow((ciab - $ciab),2) /*db*/
								-
								pow(
									sqrt(pow(ciaa,2) + pow(ciab,2)) 
									-
									sqrt(pow($ciaa,2) + pow($ciab,2))
								,2)
							)	/*dh*/
							/(1 + ( 0.015 * sqrt(pow(ciaa,2) + pow(ciab,2))))
							,2)
						)
				) /* end sqrt */ AS diff
from colors c1
where 
(
	sqrt(
						(
							pow((cial - $cial),2) /* first */		
						)
						+
						(
							pow(
								(
								sqrt(pow(ciaa,2) + pow(ciab,2)) /* c1 */
						 		-
								sqrt(pow($ciaa,2) + pow($ciab,2)) /*c2 */
								)
								/ (1 + (0.045 * sqrt(pow(ciaa,2) + pow(ciab,2))))	/* c1 */
							,2) /* second */	
						)
						+
						(
							pow(sqrt(
							
								pow((ciaa - $ciaa),2) /*da*/
								+
								pow((ciab - $ciab),2) /*db*/
								-
								pow(
									sqrt(pow(ciaa,2) + pow(ciab,2)) 
									-
									sqrt(pow($ciaa,2) + pow($ciab,2))
								,2)
							)	/*dh*/
							/(1 + ( 0.015 * sqrt(pow(ciaa,2) + pow(ciab,2))))
							,2)
						)
				) /* end sqrt */
) is not null
order by
			sqrt(
						(
							pow((cial - $cial),2) /* first */		
						)
						+
						(
							pow(
								(
								sqrt(pow(ciaa,2) + pow(ciab,2)) /* c1 */
						 		-
								sqrt(pow($ciaa,2) + pow($ciab,2)) /*c2 */
								)
								/ (1 + (0.045 * sqrt(pow(ciaa,2) + pow(ciab,2))))	/* c1 */
							,2) /* second */	
						)
						+
						(
							pow(sqrt(
							
								pow((ciaa - $ciaa),2) /*da*/
								+
								pow((ciab - $ciab),2) /*db*/
								-
								pow(
									sqrt(pow(ciaa,2) + pow(ciab,2)) 
									-
									sqrt(pow($ciaa,2) + pow($ciab,2))
								,2)
							)	/*dh*/
							/(1 + ( 0.015 * sqrt(pow(ciaa,2) + pow(ciab,2))))
							,2)
						)
				) /* end sqrt */ ASC
LIMIT 1			
SQL;
		$result = mysql_query($sql) or die($sql);
		$row = mysql_fetch_assoc($result);
		return $row;
	}
	
	private function rgb($color) {
        if ($color[0] == '#')
            $color = substr($color, 1);
            
        if (strlen($color) == 6)
            list($r, $g, $b) = array($color[0].$color[1], $color[2].$color[3], $color[4].$color[5]);
        elseif (strlen($color) == 3)
            list($r, $g, $b) = array($color[0].$color[0], $color[1].$color[1], $color[2].$color[2]);
        else
            return false;
            
        $r = hexdec($r);
        $g = hexdec($g);
        $b = hexdec($b);
        
        return array($r, $g, $b);
    }
}
$ntc = new ntc;
$result = $ntc->name('#CCCC99');
print_r($result);
?>
 
Hi,

Whatever you did in the last version has fixed the issue of getting black back.


In that screen shot you can see (on the right hand side) the new table generated from the version. You will see that compared to the table on the left there is no back and it is actually doing a much better job at matching the 'actual' colours with 'closes colour name'. This ultimately means that I get a much better list of group shade names.

Now to do a little more testing and store and index that data.

Again, thank you so much for your help!

James

------------------------
 
still not found the 'cause' (not difficult but not had the time). it will be a flaw in the formula for some color lab values. and if there is a flaw then it will return a null, and null is ... closest to zero as far as mysql is concerned. hence we are getting black.

i changed the query to ignore null values (formula failures). this means that logically we should get a close match (but not necessarily exact) for colors that fail the formula. better, but not best yet.

If I have time I will write a test script to test why the formula fails for some values. not high on the agenda though, to be honest
 
Hi,

I think you have overdone yourself with what you have done so far. Do you think this would be useful to others? I think it would! I searched high and low to find something like this... do you think we should put in a public page some place?

Thanks again for your time, I will be sure to make a generous donation to some poor person collecting money for a charity in this cold weather.

If you do ever get time to take a look at the root cause of the problem I would be very grateful if you could post back on this thread.

Regards,

James

------------------------
 
here is some revised code which does not use the mysql db for the heavy lifting. it seems to be producing better results than the original js tool.

Code:
class ntc {
	private function de_1994($lab1,$lab2){
		$c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]);
		$c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
		$dc = $c1-$c2;
		$dl = $lab1[0]-$lab2[0];
		$da = $lab1[1]-$lab2[1];
		$db = $lab1[2]-$lab2[2];
		$dh = sqrt(($da*$da)+($db*$db)-($dc*$dc));
		$first = $dl;
		$second = $dc/(1+0.045*$c1);
		$third = $dh/(1+0.015*$c1);
		return(sqrt($first*$first+$second*$second+$third*$third));
	}
	/**
     * [URL unfurl="true"]http://www.emanueleferonato.com/2009/09/08/color-difference-algorithm-part-2/[/URL]
     * @param object $color
     * @return 
     */
    private function rgb_to_xyz($color) {
        $rgb = $this->rgb($color);
        $red = $rgb[0];
        $green = $rgb[1];
        $blue = $rgb[2];
        $_red = $red / 255;
        $_green = $green / 255;
        $_blue = $blue / 255;
        if ($_red > 0.04045) {
            $_red = ($_red + 0.055) / 1.055;
            $_red = pow($_red, 2.4);
        } else {
            $_red = $_red / 12.92;
        }
        if ($_green > 0.04045) {
            $_green = ($_green + 0.055) / 1.055;
            $_green = pow($_green, 2.4);
        } else {
            $_green = $_green / 12.92;
        }
        if ($_blue > 0.04045) {
            $_blue = ($_blue + 0.055) / 1.055;
            $_blue = pow($_blue, 2.4);
        } else {
            $_blue = $_blue / 12.92;
        }
        $_red *= 100;
        $_green *= 100;
        $_blue *= 100;
        $x = $_red * 0.4124 + $_green * 0.3576 + $_blue * 0.1805;
        $y = $_red * 0.2126 + $_green * 0.7152 + $_blue * 0.0722;
        $z = $_red * 0.0193 + $_green * 0.1192 + $_blue * 0.9505;
        return (array($x, $y, $z));
    }
    /**
     * [URL unfurl="true"]http://www.emanueleferonato.com/2009/09/08/color-difference-algorithm-part-2/[/URL]
     * @param object $xyz
     * @return 
     */
    private function xyz_to_lab($xyz) {
        $x = $xyz[0];
        $y = $xyz[1];
        $z = $xyz[2];
        $_x = $x / 95.047;
        $_y = $y / 100;
        $_z = $z / 108.883;
        if ($_x > 0.008856) {
            $_x = pow($_x, 1 / 3);
        } else {
            $_x = 7.787 * $_x + 16 / 116;
        }
        if ($_y > 0.008856) {
            $_y = pow($_y, 1 / 3);
        } else {
            $_y = (7.787 * $_y) + (16 / 116);
        }
        if ($_z > 0.008856) {
            $_z = pow($_z, 1 / 3);
        } else {
            $_z = 7.787 * $_z + 16 / 116;
        }
        $l = 116 * $_y - 16;
        $a = 500 * ($_x - $_y);
        $b = 200 * ($_y - $_z);
        return (array($l, $a, $b));
    }
   
    public function color2lab($color) {
        return $this->xyz_to_lab($this->rgb_to_xyz($color));
    }
    public function name($color){
    	$color = strtoupper($color);
        if (strlen($color) < 3 || strlen($color) > 7):
            return array("#000000", "Invalid Color: ".$color, "#000000", "", false);
        endif;
        if (strlen($color) % 3 == 0):
            $color = "#" + $color;
        endif;
        if (strlen($color) == 4):
            $_color = '#';
            for ($i = 1; $i <= 3; $i++):
                $_color .= $color[$i];
                $_color .= $color[$i];
            endfor;
            $color = $_color;
        endif;
		$lab = $this->color2lab($color);
		$result = mysql_query("select * from colors");
		$DIFF = 9999999;
		$_color = '';
		while ($row = mysql_fetch_object($result)):
			$diff = $this->de_1994($lab, array($row->cial, $row->ciaa, $row->ciab));
			if ($diff < $DIFF){
				$DIFF = $diff;
				$_color = $row;
			}
		endwhile;
		return $_color;
    }
	private function rgb($color) {
        if ($color[0] == '#')
            $color = substr($color, 1);
            
        if (strlen($color) == 6)
            list($r, $g, $b) = array($color[0].$color[1], $color[2].$color[3], $color[4].$color[5]);
        elseif (strlen($color) == 3)
            list($r, $g, $b) = array($color[0].$color[0], $color[1].$color[1], $color[2].$color[2]);
        else
            return false;
            
        $r = hexdec($r);
        $g = hexdec($g);
        $b = hexdec($b);
        
        return array($r, $g, $b);
    }
}
 
Hi,

This looks more efficient. My only issue with this version is that it does not return shadeHex like the other one did in the final result set.

James

------------------------
 
in fact it is less efficient as it needs to read through the entire dataset within php rather than doing a query on an indexed table.

however i am more confident that the maths is right. I did not spend a huge amount of time betting the query right (making sure that the maths in the query was correctly written) so there is a possibility that I had made a mistake in the query.

can you give me examples of the hex values that produce different answers for the query version and the php loop version? that will help me debug.

it does not give the same results as the javascript version. I believe that the code I have put above is more accurate and uses a more complex algorithm for calculating colour differences.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top