Recolouring images with PHP
About a year ago I was working on a project where one of its features allowed the user to upload an image, select a colour on the image with a colour picker tool, then select from a group of colours to replace the picked colour with. I had a somewhat stable build running where it simply replaced the hue of the picked colour – Introducing saturation and value changes invoked a number of ‘random’ glitches and odd colour effects. This feature was subsequently dropped from the project due to it costing too much to continue developing.
Skip forward about a year and I find this script knocking around on a back-up disc, so I decide to put some more development time in purely for my own satisfaction of enhancing it. I have made it work to a nearly passable level, it works nice on images with isolated high contrasting colours. I created a function to create a tiled pop-arty graphic from an inputted image too just for fun. For example, input this and it will spit out this. There is plenty of room for improvement: You can’t convert colours to or from white/black and it replaces all instances of a colour, not a flood-fill effect. So, on to the code…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | <?php set_time_limit(0); function rgbtohex($r,$g,$b) { return sprintf("%02X%02X%02X",$r,$g,$b); } function rgbtohsv($r,$g,$b) { $r /= 255; $g /= 255; $b /= 255; $min = min($r,$g,$b); $max = max($r,$g,$b); switch($max) { case 0: $h = $s = $v = 0; break; case $min: $h = $s = 0; $v = $max; break; default: $delta = $max - $min; if($r == $max) { $h = 0 + ($g - $b) / $delta; } elseif($g == $max) { $h = 2 + ($b - $r) / $delta; } else { $h = 4 + ($r - $g) / $delta; } $h *= 60; if($h < 0 ) $h += 360; $s = $delta / $max; $v = $max; } return array((integer)$h,(integer)($s*100),(integer)($v*100)); } function hsvtorgb($h,$s,$v) { $s /= 100; $v /= 100; if($s == 0) { $r = $g = $b = $v; } else { $h /= 60.0; $hi = floor($h); $f = $h - $hi; $p = ($v * (1.0 - $s)); $q = ($v * (1.0 - ($f * $s))); $t = ($v * (1.0 - ((1.0 - $f) * $s))); switch($hi) { case 0: $r = $v; $g = $t; $b = $p; break; case 1: $r = $q; $g = $v; $b = $p; break; case 2: $r = $p; $g = $v; $b = $t; break; case 3: $r = $p; $g = $q; $b = $v; break; case 4: $r = $t; $g = $p; $b = $v; break; default: $r = $v; $g = $p; $b = $q; break; } } return array( (integer) ($r * 255 + 0.5), (integer) ($g * 255 + 0.5), (integer) ($b * 255 + 0.5) ); } function recolor($file,$colorToChange,$newColor) { $threshold = 40; $image = imagecreatefrompng($file); $width = imagesx($image); $height = imagesy($image); $coords = explode(",",$colorToChange); $rgb = imagecolorat($image,$coords[0],$coords[1]); $c1[0] = ($rgb >> 16) & 0xFF; $c1[1] = ($rgb >> 8) & 0xFF; $c1[2] = $rgb & 0xFF; list($hueToReplace,$satToReplace,$valToReplace) = rgbtohsv($c1[0],$c1[1],$c1[2]); $c2 = sscanf($newColor,"%2x%2x%2x"); list($newHue,$newSat,$newVal) = rgbtohsv($c2[0],$c2[1],$c2[2]); $c = array(); $n = array(); for($y=0; $y<$height; $y++) { for($x=0; $x<$width; $x++) { $rgb = imagecolorat($image,$x,$y); $a = ($rgb >> 24) & 0xFF; $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; list($h,$s,$v) = rgbtohsv($r,$g,$b); $angle = ($h >= $hueToReplace) ? 360 - ($h - $hueToReplace) : 360 - ($hueToReplace - $h); $angle = ($angle > 180) ? 360 - $angle : $angle; if($angle <= $threshold && ($s > 10 && $v < 90)) { $h = $newHue + ($h - $hueToReplace); if($h < 0) $h += 360; if($h > 360) $h -= 360; $s = ($satToReplace > $s) ? $newSat - ($satToReplace - $s) : $newSat - ($s - $satToReplace); if($s < 0) $s += 100; if($s > 100) $s -= 100; $v = ($valToReplace > $v) ? $newVal - ($valToReplace - $v) : $newVal - ($v - $valToReplace); if($v < 0) $v += 100; if($v > 100) $v -= 100; list($r,$g,$b) = hsvtorgb($h,$s,$v); $color = imagecolorallocatealpha($image,$r,$g,$b,$a); imagesetpixel($image,$x,$y,$color); } } } return $image; } function popart($in,$out,$coords) { @unlink("test.png"); //Requires ImageMagick exec("convert $in temp.png"); $in = "temp.png"; list($width,$height,$type,$attr) = getimagesize($in); $w = $width * 6; $h = $height * 6; $im = imagecreatetruecolor($w,$h); $x = 0; $y = 0; $s = 80; $v = 90; for($h=0; $h<=360; $h+=10) { list($r,$g,$b) = hsvtorgb($h,$s,$v); $hex = rgbtohex($r,$g,$b); $c = recolor($in,$coords,$hex); imagecopymerge($im,$c,$x,$y,0,0,$width,$height,100); if($h != 0) { if($h % 60 > 0) { $x += $width; } else { $x = 0; $y += $height; } } echo "XY: $x,$y | HSV: $h,$s,$v | RGB: $r,$g,$b | HEX: $hex\r\n"; } imagepng($im,$out); unlink($in); } $start = time(); popart("test.jpg","test.png","230,160"); $end = time(); echo (($end - $start)/60)." minutes execution time.\r\n"; ?> |
Feel free to suggest enhancements – I know it handles a few things a bit inefficient, but it’s more of a prototype / proof-of-concept kinda thing at this stage.
This entry was posted on Wednesday, December 12th, 2007 at 9:07 pm and is filed under PHP, Web Development. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
