KrisWillis.com

Flower

Recolouring images with PHP

Blue roseAbout 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.

Tags: , ,

Leave a Reply