Special FX, Greyscale

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:


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:


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:


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:


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.


Want to learn PHP 7?

Hacking with PHP has been fully updated for PHP 7, and is now available as a downloadable PDF. Get over 1200 pages of hands-on PHP learning today!

If this was helpful, please take a moment to tell others about Hacking with PHP by tweeting about it!

Next chapter: Special FX, Duotone >>

Previous chapter: Special FX, Screen

Jump to:


Home: Table of Contents

Copyright ©2015 Paul Hudson. Follow me: @twostraws.