Tint Images with PHP

Penguin tinted 000099 Penguin tinted 006600 Penguin tinted 990099 Penguin tinted ff0000 Penguin tinted 008080 Penguin tinted c85028

Have you ever wanted to make an army of multicolored penguins? If so, you've come to the right place. This code works for quite a few more useful applications such as creating custom colored buttons and icons on the fly too. Basically, we're just going through the colors of a grayscale image and replacing each with an appropriately tinted new color.

PenguinTo start, we'll need to take our source image and turn it into a grayscale one. The IMG_FILTER_GRAYSCALE filter type of PHP's imagefilter function does this, but we recommend converting the image yourself in PhotoShop instead for best results. Also, we've found that the tinting function below works best on GIF images, so you should probably convert your starting image to that format if it's currently a JPEG or PNG. (Note: You can make slight edits to the functions below – using imagecreatefromjpeg instead of imagecreatefromgif, for example – and use other formats if you prefer.) The black and white penguin GIF on our left is the one that we'll be using in our example.

Our example includes opening up the GIF, converting it to blue, and then outputting it to the browser as a GIF. You could also save the image at this point using the content-disposition header instead if you'd like.

$image_path = 'images/inkplant/code/penguin.gif';
$new_hex = '000099';
$img = image_convert_grays_to_color($image_path,$new_hex);
header('Content-Type: image/gif');

Now, let's walk through what each of the functions are doing…

This first one, image_convert_grays_to_color, calls the other two and is the real core of this functionality. $image_path is your source image; it can be either a local path or a URL. $new_hex is the 6-digit hexadecimal color code of the tint you want to apply. (Make sure to remove the # from the front.

After opening the black and white image, it goes through, pixel by pixel and composes a list of all available colors. Those are then converted to the new tinted version. Finally, imagecolorset is used to convert all instances of the old color to the new one.

function image_convert_grays_to_color($image_path,$new_hex) {
	$img = @imagecreatefromgif($image_path);
	if (!$img) { die('Could not open image: '.$image_path); }

	$new_color = hex_to_rgb($new_hex);

	$colors = image_get_all_colors($img);

	$image_is_bw = true;
	foreach ($colors as $key => $c) {
		//check that color is gray or black
		if (($c['red'] != $c['blue']) || ($c['blue'] != $c['green']) || ($c['green'] != $c['red'])) { $image_is_bw = false; break; }
	if (!$image_is_bw) { //if image isn't black & white, convert it
		imagefilter($img, IMG_FILTER_GRAYSCALE);
		$colors = image_get_all_colors($img); //refresh the color palette

	//remove white & lookup old color index
	foreach ($colors as $key => $c) {
		if (($c['red'] == 255) && ($c['green'] == 255) && ($c['blue'] == 255)) {
		} else {
			$colors[$key]['index'] = imagecolorexact($img,$c['red'],$c['green'],$c['blue']);

	foreach ($colors as $key => $c) {
		//generate new color
		foreach (array('red','green','blue') as $hue) {
			$colors[$key]['new_'.$hue] = round($c[$hue] + $new_color[$hue] - ($c[$hue] / 3));
			if ($colors[$key]['new_'.$hue] > 255) { $colors[$key]['new_'.$hue] = 255; }

		//make sure it's light enough
		$old_total = $c['red'] + $c['blue'] + $c['green'];
		$new_total = $colors[$key]['new_red'] + $colors[$key]['new_blue'] + $colors[$key]['new_green'];
		$i = 0;
		while ($old_total > $new_total) {
			$colors[$key]['diff'] = round(($old_total - $new_total) / 3);
			//$colors[$key]['extra'] = 0;
			foreach (array('red','green','blue') as $hue) {
				$colors[$key]['new_'.$hue] = $colors[$key]['new_'.$hue] + $colors[$key]['diff'];
				if ($colors[$key]['new_'.$hue] > 255) {
					//$colors[$key]['extra'] = $colors[$key]['extra'] + ($colors[$key]['new_'.$hue] - 255);
					$colors[$key]['new_'.$hue] = 255;
			$new_total = $colors[$key]['new_red'] + $colors[$key]['new_blue'] + $colors[$key]['new_green'];
			if ($i > 5) { break; } //prevent too many loops

		//get hex codes (this isn't needed here, but it's useful for debugging or other applications)
		$get_hex_codes = false;
		if ($get_hex_codes) {
			foreach (array('','new_') as $prefix) {
				${$prefix.'hex'} = '';
				foreach (array('red','green','blue') as $hue) {
					$hue_hex = dechex($colors[$key][$prefix.$hue]);
					if (strlen($hue_hex) < 2) { $hue_hex = '0'.$hue_hex; }
					${$prefix.'hex'} .= $hue_hex;
				$colors[$key][$prefix.'hex'] = '<span style="color:#'.${$prefix.'hex'}.';background-color:#'.${$prefix.'hex'}.';">'.${$prefix.'hex'}.'</span>';

	//this is just here for debugging
	if ($get_hex_codes) { echo '<pre>'.print_r($colors,true).'</pre>'; die(); }

	//replace old colors with new
	foreach ($colors as $key => $c) {
		if ($c['index'] === false) { die('Error: No index could be found for color '.$key.'!'); }

	return $img;

The image_get_all_colors function, below, does exactly what its name would suggest – it gets an array of all the colors used in an image. To do this, it goes through pixel by pixel and checks to see if that pixel's color has already been used. So, needless to say, if you were using this on a very large image, it would take a while.

function image_get_all_colors($img) {
	$colors = array();
	$width = imagesx($img);
	$height = imagesy($img);
	$x = 0;
	$y = 0;
	while ($x < $width) {
		while ($y < $height) {
			$c = imagecolorat($img, $x, $y);
			if (!array_key_exists($c,$colors)) { $colors[$c] = imagecolorsforindex($img,$c); }
		$y = 0;
	return $colors;

The final function, hex_to_rgb, simply converts a hexadecimal color code into RGB 0-255 values so that we can do math on them to get our new tinted colors.

function hex_to_rgb($hex) {
	if (strlen($hex) != 6) { die('Error: Color hex code must be 6 characters for this function.'); }
	return array(

PenguinAt the end of all this, you're left with a blue penguin. Or, in your case, something much more useful…

If you have questions or comments, please leave them below.



This post was published on July 13th, 2014 by Robert James Reese in the following categories: Images and PHP. Before using any of the code or other content in this post, you must read and agree to our terms of use.