Hudzilla.org - the homepage of Paul Hudson
Contents > Multimedia > Images Wish List | Report Bug | About Me ]

11.2.20     Special FX, Greyscale: imagecolorat()

This is NOT the latest copy of this book; click here for the latest version.

int imagecolorat ( resource image, int x, int y)

Converting an image from full-colour to shades of grey is a technique often called desaturating. Here we need to cycle through every pixel, read its red, green, and blue values, then take an average of all three - this becomes the pixel's grey value, and is used to re-colour it.

Reading the colour of an existing pixel is done with the imagecolorat() function, which takes an image as its first pixel and X and Y co-ordinates as parameters two and three. The return value is a colour you can use in other calls, but to be useful here we need to break it up into its component red, green, and blue parts.

Here's the code:

function greyscale (&$image) {
    
$imagex = imagesx($image);
    
$imagey = imagesy($image);

    for (
$x = 0; $x <$imagex; ++$x) {
        for (
$y = 0; $y <$imagey; ++$y) {
            
$rgb = imagecolorat($image, $x, $y);
            
$red = ($rgb >> 16) & 255;
            
$green = ($rgb >> 8) & 255;
            
$blue = $rgb & 255;
            
$grey = (int)(($red+$green+$blue)/3);
            
$newcol = imagecolorallocate($image, $grey,$grey,$grey);
            
imagesetpixel($image, $x, $y, $newcol);
        }
    }
}

As with the screen effect code, you can drop that into your existing script and only change the function call from screen() to greyscale().

There are several important lines in that script. First, notice that imagecolorat() is being used to grab the colour value for the current pixel being examined. This is then passed through three lines of simple bit-shifting that sets $red, $green, and $blue. These lines are there because the value returned by imagecolorat() contains red, green, and blue all in the same number by converting each value to binary, concatenating them together, and converting it back to decimal.

For example, if the pixel had a red value of 192, a blue value of 193, and a green value of 194, the colour would be 12632514. Here is why:

  • 192 in binary: 11000000

  • 193 in binary: 11000001

  • 194 in binary: 11000010

  • Concatenation of all three: 110000001100000111000010

  • Concatenation in decimal: 12632514

So, to get red, green, and blue from our concatenated decimal process, we need to reverse the process: convert it to decimal, and split it into three. The splitting is done using the & operator, which performs bitwise AND. This is not used all that often, but it essentially compares two numbers and returns a new one where all the bits set in both numbers are set in the new number.

Therefore, 123 & 456 = 72. The binary for 123 is 1111011, and the binary for 456 is 111001000. As you can see, they are different lengths, so they are right-aligned when compared, like this:

1111011
111001000
---------
  1001000

As you can see, there are only two instances where the first number and the second number both have a 1 set in the same place, so the returned number just has two ones. That number in decimal is 72, so as you can see 123 & 456 is equal to 72. Now, the reason I have mentioned all this is so you can see that not having a bit is equal to having the bit set to 0. For example, the above equation could be written as this:

001111011
111001000
---------
001001000

The result is still the same, because the two leading bits in the first number that are not set could be thought of as being 0. This is helpful for us when we want to split up our binary number, because we can use & to only retrieve certain numbers. If you AND a number with 255, it will look like this:

1100100111001001
        11111111
----------------
0000000011001001

As you can see, the binary value for 255 is eight straight 1s, which means it will simply return the last eight bits in a given number. As you already know, the blue element in the return value from imagecolorat() is the last eight bits, which should explain what the following code does:

$blue = $rgb & 255;

That line from the script uses the above principle to take the last eight bits as the blue value. To get the same for green and red, we need to use the bit-shifting operator >>, which literally moves all the binary digits one or more places to the right.

For example, here is the number printed as is, then shifted to the right first one place, then two places, then three:

1100100111001001
110010011100100
  11001001110010
   1100100111001

As you can see, it simply takes one digit off from the right for each shift, moving the others along by one. What this means is that we can use the >> operator in combination with the & operator to get red and green. Our red 192, green 193, blue 194 colour was 110000001100000111000010 in binary. To get the blue out of that, as you know, we can just & the number with 255 to get the last eight bits. To get green, we need to shift the whole number eight places to the right so that the blue number is cut off, then & the result with 255. To get red, we need to shift the number sixteen places to the right so that both the green and blue numbers are cut off, then again & the result with 255. Hopefully you can see that is exactly what these three lines of code do:

$red = ($rgb >> 16) & 255;
$green = ($rgb >> 8) & 255;
$blue = $rgb & 255;

Now, once we have the red, green, and blue component parts, they are all added together and the result is divided by three, giving the mean average. This is our grey value, so imagecolorallocate() is called passing in this grey as the red, green, and blue values, giving it a uniform shade.







<< 11.2.19 Special FX, Screen   11.2.21 Special FX, Duotone >>
Table of Contents
Want to see this stuff in print? PHP in a Nutshell takes the core topics covered here, adds in thousands of edits from the editorial team and myself, and combines them to make an unbeatable reference for PHP programmers at all levels.



My latest book has hundreds more tips on how to use PHP, Apache, and MySQL, plus Perl, Python, shell scripts, performance tuning, and more!



Top-right shadow
 
Bottom-left shadow Bottom shadow

Comments from other readers
matt, at mattmontag.com - 29 Aug 2008

Your desaturation algorithm should take into account the fact that 255 red, 255 green and 255 blue have perceptually different lightness values.

Green appears the brightest, and therefore should have the most influence when combining the RGB values in a desaturation function.

The weight coefficients are .299 for Red, .587 for Green, and .114 for Blue. The true calculation depends on the phosphors in your monitor, so it is marginally subjective.

Here is a function I use for computing brightness with the proper weight given to red, green, and blue:

function getBrightness($rgb_array) {
$brightness = (($rgb_array['r'] / 255) * 299 + ($rgb_array['g'] / 255) * 587 + ($rgb_array['b'] / 255) * 114) / 1000;
return $brightness;
}

More here http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html

Crusty - 29 Aug 2008

This is rather a suggestion/question.

In the first box dealing with binary numbers, you write "...so they are right-aligned when compared...", but in the preformatted box right below, they are LEFT aligned.

Did I misunderstand you?

in case that is intentionally, you are wrong.
try

1101 AND
011001

= 0000 (ignored left 2 digits like you)

(left aligned)

and then fill the smaller number on top with leading zeroes like in your second box :

001101 AND
011001

= 1000


this will show your first box showing binaries is wrong.

Matt - 29 Aug 2008

That was a very complex chapter using bitwise operators, but it does show how they can be used in a real situation. Pretty helpful in understanding them I think.



Add comment
Please note that by posting a comment here you are committing it to the public domain. This is important so that others can make use of your code themselves, and also so that I can incorporate helpful notes directly into the main text. Comments are limited to 2000 characters in length.

If you are reporting an error in the content, please tell me directly.

Your name/email address:
Your comment:
 
Now, in order to verify that you're a real person, please answer this simple question: what is ten plus eight?
The answer is:
(please write in
numbers, eg 19)


Top-right shadow
 
Bottom-left shadow Bottom shadow