Section 9.5. Graphics Manipulation with GD


9.5. Graphics Manipulation with GD

Instead of describing all the GD functions that PHP supports, we discuss two common uses of the GD image library. In the first example, we use the GD libraries to build an image with a code word on it. We also add some distortions so that the image is machine-unreadablethe perfect protection against automatic tools that fill in forms. In the second example, we create a bar chart, including axis, labels, background, TrueType text, and alpha blending.

Our examples require the bundled GD library. For UNIX OSs, you need to compile PHP using the option --with-gd (without path). For Windows, you can use the packaged php_gd2.dll and enable it in php.ini. Because we make use of some additional functions of the GD library, you need to see the information, shown in Figure 9.5, in the GD section of your phpinfo() output (except for WBMP and XPM support).

Figure 9.5. GD phpinfo() output.


A typical set of configuration options would be

 --with-gd --with-jpeg-dir=/usr --with-png-dir=/usr --with-freetype-dir=/usr 

9.5.1. Case 1: Bot-Proof Submission Forms

The following script makes it difficult for automatic tools to submit forms. The steps involved in this basic script are create a drawing space, allocate colors, fill the background, draw characters, add distortions, and output the image to the browser:

 <?php      $size_x = 200;      $size_y = 75;      if (!isset($_GET['code'])) {            $code = 'unknown';      }      $code = substr($_GET['code'], 0, 8);      $space_per_char = $size_x / (strlen($code) + 1); 

In the preceding code, we set the horizontal and vertical sizes of the images to variables, making possible future changes easier. Next, we grab the code from the GET parameter code and trim it to a maximum of eight characters. Then, we calculate $space_per_charthe space between characters for use in rendering later in the script.

Note

Using $_GET parameters to grab the code, of course, defeats the whole purpose of this script because a robot can simply read the HTML file that includes the <img src='/books/1/76/1/html/2/image.php?code=foobar'/> line. For this to work, you need to store the code in a database and, for example, with a random key read the code back in the script generating the image, as in something like this:

 mysql_connect(); $res = mysql_query('SELECT code FROM codes WHERE key='.      (int) $_GET['key']); $code = mysql_result($res, 0); 

and embed it into the HTML page with:

 <img src='/books/1/76/1/html/2/image.php?key=90'/> /* Create canvas */ $img = imagecreatetruecolor($size_x, $size_y); 


With imagecreatetruecolor(), we create a new "canvas" to draw on with 256 different shades of red, green, and blue available, and an alpha channel per pixel. PHP provides another variant of imagecreate that can be used to create "paletted images" with 256 colors maximum, but imagecreatetruecolor() is used more often because images produced by it usually look better. Both JPEG and PNG files support true color images, so we use this function for our PNG file. The default background is black. Because we want to change the background, we need to "allocate" some colors, as follows:

 /* Allocate colors */ $background = imagecolorallocate($img, 255, 255, 255); $border = imagecolorallocate($img, 128, 128, 128); $colors[] = imagecolorallocate($img, 128, 64, 192); $colors[] = imagecolorallocate($img, 192, 64, 128); $colors[] = imagecolorallocate($img, 108, 192, 64); 

In the previous code, we use imagecolorallocate() to define five different colors$background, $border, and $colors, an array containing three colors to use in rendering the text. In each function call, we pass the variable $img (the image resource returned by the imagecreatetruecolor() function earlier in the script), followed by three parameters specifying color values. The first specifies the amount of red in the color, the second specifies a value for the blue channel, and the third indicates the amount of green in the color. The color values can range from 0 to 255. For example, white is specified by 255, 255, 255 (the highest possible color value for all three channels) and black is specified by 0, 0, 0 (the lowest possible color value for all three channels). In the script, $background is white and $border is defined with color values of 50%, which is gray. You can add more colors if you wish.

 /* Fill background */ imagefilledrectangle($img, 1, 1, $size_x - 2, $size_y - 2, $background); imagerectangle($img, 0, 0, $size_x - 1, $size_y - 1, $border); 

By using the two functions, we change the background color to white and add the gray border. Both functions accept the same parameters: the image resource, the coordinates of the top-left corner, the coordinates of the bottom-right corner, and the color. The coordinates range from 0, 0 to size_x 1, size_y 1, so we draw a filled rectangle from position 1, 1 to size_x 2, size_y 2. We also draw a gray border around the edge of the image.

 /* Draw text */ for ($i = 0; $i < strlen($code); $i++) {               $color = $colors[$i % count($colors)];               imagettftext(                      $img,                      28 + rand(0, 8),                      -20 + rand(0, 40),                      ($i + 0.3) * $space_per_char,                      50 + rand(0, 10),                      $color,                      'arial.ttf',                      $code{$i}                );    } 

In this code, we loop through all the characters in our code string. First, we pick the next element in the colors array. We use the modulo (%) operator to be sure we have an element with this key in the array. Next, we use the imagettftext() function to draw the letter. We pass the parameters shown in Table 9.8 to imagettftext().

 /* Adding some random distortions */ imageantialias($img, true); 

Table 9.8. Parameters to imagettftext()

Parameter

Content

Remarks

img

$img

The image resource on which to draw.

fontsize

28 + rand(0, 8)

The size in points (not pixels) of the characters to be drawn. For randomness, we select a size between 28 and 36 points.

angle

-20 + rand(0, 40)

The angle in which the character is drawn in degrees (the range is 0360). We use it here to "twist" the characters a bit, which makes it harder for an automatic tool to read it.

x

($i + 0.3) * $space_per_char

The x location where the character is drawn (also some additional randomness here).

y

50 + rand(0, 10)

The y location for the character. This is not the upper limit, but the place where the baseline of the character is drawn. The baseline is usually the location of the lower boundary of characters without any tails, such as s (and not p).

colour

$color

The color to use for drawing the text.

font

'arial.ttf'

The name of the font file to use.

text

$code$i)

The character from the code that we draw.


This line turns on anti-aliasing. Anti-aliasing is a technique to create smoother lines. Because it is much better explained with an image, see the effect in Figure 9.6.

Figure 9.6. Anti-aliasing.


Tip

Text drawn with the imagettftext() function is always anti-aliased. If you do not want this, you need to use a negative color number (like -$color) in the previous example. This trick does not work for totally black colors because the handle returned for black in a true color image is just 0. Because 0 is the same as -0 for PHP, the anti-aliasing is not turned off. You can easily work around this by allocating black with $black = imagecolorallocate($img, 0, 0, 1) (changing one of the components from 0 to 1).


 for ($i = 0; $i < 1000; $i++) {        $x1 = rand(5, $size_x - 5);        $y1 = rand(5, $size_y - 5);        $x2 = $x1 - 4 + rand(0, 8);        $y2 = $y1 - 4 + rand(0, 8);        imageline($img, $x1, $y1, $x2, $y2,               $colors[rand(0, count($colors) - 1)]        ); } 

We draw 1,000 small lines with randomized coordinates for both the start and end. The imageline() function has the following parameters: image resource, starting x and y coordinates, ending x and y coordinates, and the color with which to draw the line.

 /* Output to browser */ header('Content-type: image/png'); imagepng($img); ?> 

At the end of our script, we use the header() function to tell the browser to expect data representing image/png. This mime-type is associated with a PNG image by the browser, so that it knows how to handle the data properly. Different data types have different mime types. For images, you can specify image/gif (for GIF images), image/jpeg (for JPEG images), application/octet-stream (for binary data), and other mime types. With the Content-type HTTP header, we tell the browser what to expect. This header() function can only be used if no content is output before the header statement. That means no whitespace, no HTML tags, nothing at all. If output is sent before the header statement, you receive a warning like the following:

[View full width]

Warning: Cannot modify header information - headers already sent by (output started at /dat/docs/book/gd/no-bot.php:2) in /dat/docs/book/gd/no-bot.php on line 53

Finally, we call the imagepng() function, which accepts the image resource as its first parameter. It accepts a second optional parameter: a file name where the image will be stored. If the second parameter is not included, the function "echoes" all image data to the browser. Figure 9.7 shows the image output by the preceding script.

Figure 9.7. Output of the anti-bot script.


Each image type has a specific output function. Two functions are imagewbmp(), for WBMP images (some wireless format), and imagejpeg(), for JPEG images. In addition to the two parameters $img and $filename, the JPEG output function accepts a third parameter that is the compression quality of the JPEG image. The default value is 75. A value of 100 gives the best quality image, but even with this value, you might still encounter little distortions in the image. For a better quality image, use a PNG image. If you want to change the default quality setting but don't want to save the image to a file, you need to set the second parameter of imagejpeg() to an empty string, as in

 imagejpeg($img, '', 95); 

It's best to use JPEG images with a quality greater than 85 for photos and PNG images, because that setting gives a better result for line-based images, such as charts. You can see the difference clearly in Figure 9.8, which is a closeup of the bar chart image we will create in the second example.

Figure 9.8. Comparing 75 percent quality JPEG and PNG.


The left image is created with imagejpg($img) and the right one with imagepng($img). You can see clearly that the JPEG image is not really sharp. JPEG images have the advantage in size. They are usually much smaller then PNG images. In this specific example, the full JPEG image is 44KB and the PNG image is 293KB.

9.5.2. Case 2: Bar Chart

Figure 9.8 already gave you a peek at the chart we will make. Some keywords include background, transparent bars, and TrueType text positioning.

 <?php      $size_x = 640;      $size_y = 480;      $title  = 'People møving to the snow every winter';      $title2 = 'Head count (in 1.000)'; 

As in the previous example, we first store the horizontal and vertical size of the image in variables. The rest of the script will scale correctly (except for the background) if these values are changed. To make things easier, we also defined the titles statically at the beginning.

 $values = array(        1999 => 5300,        2000 => 5700,        2001 => 6400,        2002 => 6700,        2003 => 6600,        2004 => 7100 ); $max_value = 8000; $units     = 500; 

The $values array defines our data set from which we will draw the bars on our chart. Normally, you would not hardcode those values into your script. Rather, the values would come from another source such as a database. The $max_value variable defines the maximum value in the chart and is used for the automatic scaling of the values. The $units variable defines the distance between vertical lines of the grid.

 $img = imagecreatetruecolor($size_x, $size_y); imageantialias($img, true); imagealphablending($img, true); 

As before, we create a true-color image and turn on anti-aliasing. The call to imagealphablending() is not always needed because the setting TRue is default for true-color images. Alpha blending is a technique to "blend" new pixels being drawn onto an image by using its alpha channel. We need to use the function here because we want our bars on the chart to be transparent (letting us see the background through the image). Transparency is a color property for PHP, defined in the fifth parameter to imagecolorallocatealpha() used later in the script.

 $bg_image = '../images/chart-bg.png'; $bg = imagecreatefrompng($bg_image); $sizes = getimagesize($bg_image); 

The previous section of the script loads the background image with imagecreatefrompng(). Similar functions for reading JPEG files (imagecreatefromjpg()) and GIF files (imagecreatefromgif()) are available. getimagesize() is a function that returns an array containing the width and height of an image, along with additional information. The width and height are the first two elements in the array. The third element is a text string, width='640' height='480', that you can embed into HTML where needed. The fourth element is the type of image. PHP can determine the size of about 18 different file types, including PNG, JPEG, GIF, SWF (Flash files), TIFF, BMP, and PSD (Photoshop). With the image_type_to_mime_type() function, you can transform the type in the array to a valid mime type like image/png or application/x-shockwave-flash.

 imagecopyresampled(       $img, $bg,       0, 0, 0, 0,       $size_x, $size_y, $sizes[0], $sizes[1] ); 

We copy the PNG we read from file onto the destination imageour chart. The function requires 10 parameters. The first two are the handle of the destination image and the handle of the loaded PNG image, followed by four sets of coordinates: the top-left coordinates for the destination image, the top-left coordinates of the source image, the bottom-right coordinates for the destination image, and the bottom-right coordinates of the source image. You can copy a part of the source image onto the destination image by using the appropriate coordinates of the source image. The function imagecopyresized() also copies images and is faster, but the result is not as good because the algorithm is less capable.

 /* Chart area */ $background = imagecolorallocatealpha($img, 127, 127, 192, 32); imagefilledrectangle(       $img,       20, 20, $size_x - 20, $size_y  80,       $background ); imagefilledrectangle(       $img, 20, $size_y - 60, $size_x - 20, $size_y  20,       $background ); 

We draw the two bluish areas on the background image: one for the chart and one for the title. Because we want the areas to be transparent, we create a color with an alpha value of 32. The alpha value must lie between 0 and 127, where zero means a fully opaque color and 127 means fully transparent.

 /* Values */ $barcolor = imagecolorallocatealpha($img, 0, 0, 128, 80); $spacing = ($size_x - 140) / count($values); $start_x = 120; foreach ($values as $key => $value) {        $x1 = $start_x + 0.2 * $spacing;        $x2 = $start_x + 0.8 * $spacing;        $y1 = $size_y - 120;        $y2 = $y1 - (($value / $max_value) * ($size_y - 160));        imagefilledrectangle($img, $x1, $y1, $x2, $y2, $barcolor);        $start_x += $spacing; } 

We draw the bars (as defined in the $values array created at the beginning of the script) with the imagefilledrectangle(). We calculate the spacing between the bars by dividing the width available for the bars (image width minus the outside margins, which total 140-120 on the left and 20 on the right) by the number of values in our array. The loop increments the $start_x component by the correct amount and the bar is drawn from 20 percent to 80 percent of its available horizontal space. Vertically, we take into account the maximum drawable value and adjust the size accordingly.

 /* Grid */ $black = imagecolorallocate($img, 0, 0, 0); $grey = imagecolorallocate($img, 128, 128, 192); for ($i = $units; $i <= $max_value; $i += $units) {       $x1 = 110;       $y1 = $size_y - 120 - (($i / $max_value) * ($size_y - 160));       $x2 = $size_x - 20;       $y2 = $y1;       imageline(            $img,            $x1, $y1, $x2, $y2,            ($i % (2 * $units)) == 0 ? $black : $grey       ); } /* Axis */ imageline($img, 120, $size_y - 120, 120, 40, $black); imageline(       $img,       120, $size_y - 120, $size_x - 20, $size_y  120,       $black ); 

The grid and axis are drawn in a similar way. The only thing worth mentioning is that we color every second horizontal line black and the others gray.

 /* Title */ $c_x = $size_x / 2; $c_y = $size_y - 40; $box = imagettfbbox(20, 0, 'arial.ttf', $title); $sx = $box[4] - $box[0]; $sy = $box[5] + $box[1]; imagettftext(        $img,        20, 0,        $c_x - $sx / 2, $c_y - ($sy / 2),        $black,        'arial.ttf', $title ); 

We want to draw the title in the exact middle of our bottom blue bar. Therefore, we need to calculate the exact space (bounding box) required for our text. We use imagettfbbox() to do this. The parameters passed are the fontsize, angle, fontfile, and the text. These parameters need to be the same as the text we are drawing later. The function returns an array with eight elements, grouped by two, to provide the coordinates of the four corners of the bounding box. The groups stand for the lower-left corner, the lower-right corner, the upper-right corner and the upper-left corner. In Figure 9.9, you can see the bounding box drawn around the text "Imågêß?".

Figure 9.9. Different measurements for TrueType.


The baseline (x) and (y) axis drawn in Figure 9.9 are the 0-lines to which the bounding box coordinates are related. As you can see, the left side is not exactly zero. In addition, the bottom of the normal letters is on the baseline, with the "tails" below the baseline. To calculate the width of the text to be drawn, we subtract Element 0 (lower-left x) from Element 4 (upper-right x); to calculate the height, we add Element 1 (lower-left y) to Element 5 (upper-right y). The resulting sizes can then be used to center the text on the image. Calculating sizes with the bounding box only works reliably for angles of 0, 90, 180, and 270. The GD library does not calculate the bounding boxes totally correctly, but this problem does not account for the angles mentioned.

 $c_x = 50; $c_y = ($size_y - 60) / 2; $box = imagettfbbox(14, 90, 'arial.ttf', $title2); $sx = $box[4] - $box[0]; $sy = $box[5] + $box[1]; imagettftext(       $img,       14, 90,       $c_x - ($sx / 2), $c_y - ($sy / 2),       $black,       'arial.ttf', $title2 ); 

We do the same for the title for the Y axis, except that we use an angle of 90. The rest of the code remains the same.

 /* Labels */ $c_y = $size_y - 100; $start_x = 120; foreach ($values as $label => $dummy) {       $box = imagettfbbox(12, 0, 'arial.ttf', $label);       $sx = $box[4] - $box[0];       $sy = $box[5] + $box[1];       $c_x = $start_x + (0.5 * $spacing);       imagettftext(              $img,              12, 0,              $c_x - ($sx / 2), $c_y - ($sy / 2),              $black,              'arial.ttf', $label       );              $start_x += $spacing;       } $r_x = 100; for ($i = 0; $i <= $max_value; $i += ($units * 2)) {       $c_y = $size_y - 120 - (($i / $max_value) * ($size_y - 160));              $box = imagettfbbox(12, 0, 'arial.ttf', $i / 100);              $sx = $box[4] - $box[0];              $sy = $box[5] + $box[1];              imagettftext(                     $img,                     12, 0,                     $r_x - $sx, $c_y - ($sy / 2),                     $black,                     'arial.ttf', $i / 100              ); } 

In the previous code, we draw the different labels. The ones for the X axis are not interesting, but for the Y axis, we try to align the text on the right margin by not dividing the width of the text to be drawn by 2.

 /* Output to browser */ header('Content-type: image/png'); imagepng($img); ?> 

With those final lines, we output the bar chart to the browser. The result can be seen in Figure 9.10.

Figure 9.10. The result of the bar chart script.


9.5.3. Exif

Exif is not totally related to handling image content. Exif is a method, normally used by digital cameras, of storing metadata (such as time, focal length, and exposure time) inside a digital image. It's a nice feature provided by PHP for learning more about how a photo was taken. To read Exif tags from images, compile PHP with the --enable-exif configure option, which does not require any external library. (On Windows, you need to enable the php_exif.dll in php.ini.) The section in phpinfo() should be similar to Figure 9.11.

Figure 9.11. Exif phpinfo() output.


In the following example, we read Exif data from an image and display the aperture, shutter speed, focal length, and owner name.

Tip

For information in addition to the information stored in an image with Exif, see http://exif.org/specifications.html.


Note

Not all cameras set all headers, so you have to test whether a header exists!


 <?php      $image = '../images/img_1554.jpg';      $size = getimagesize($image);      $img = imagecreatefromjpeg($image); 

First, we open the image and assign it to the $img handle.

 $exif = exif_read_data($image); 

exif_read_data() reads the Exif information from the image and returns an array with elements that contain all the information. If you dump this array, you will see that a lot of information is stored by your digital camera. In our script, we pick some of the most interesting values.

 $str = array(); $items = array('ShutterSpeedValue', 'ApertureValue', 'FocalLength'); foreach ($items as $item) {       if (isset($exif[$item])) {          $parts = split('/', $exif[$item]);          if ($item == 'ShutterSpeedValue') {               $str[] = 'Shutter Speed: 1/'.                  (int) pow(2, $parts[0] / $parts[1]). ' sec';          } else if ($item == 'ApertureValue') {              $str[] = 'Aperture: '.                 round(exp(($parts[0]/$parts[1]) * 0.5 * log(2)), 1);          } else if ($item == 'FocalLength') {               $str[] = 'FocalLength: '.                   round($parts[0] / $parts[1], 2). ' mm';          }       } } 

Unfortunately, the values we want are not stored in a nice format at all. They are stored as an APEX (Additive System of Photographic Exposure) number, which means that we have to convert them. With some luck, you might find an ExposureTime (the same as the shutter speed) and FNumber (the same as aperture) element in the array, which should contain the converted value already but still in a number/divider format.

 if (isset($exif['OwnerName'])) {       $str[] = '© '. $exif['OwnerName']; } 

The OwnerString is usually the name of the owner of the camera. If it's available, we display it prefixed by the copyright sign.

 imagestring(      $img, 5,      3, $size[1]  21,      implode('; ', $str),      imagecolorallocate($img, 0, 0, 0) ); imagestring(      $img, 5,      2, $size[1]  20,      implode('; ', $str),      imagecolorallocate($img, 0, 255, 0) ); 

With imagestring(), we draw the recorded data onto the image. imagestring() is not as nice as imagettftext() because it can only draw bitmap fonts, but it does the trick here. The first parameter is the image handle, and the second is the font number. The first two parameters are followed by the x and y coordinates, and then by the string to draw. The last parameter is the color.

 header('Content-Type: image/jpeg'); imagejpeg($img, '', 90); ?> 

The result of this script is the image shown in Figure 9.12 with the information added to it.

Figure 9.12. Exif data drawn on the image.


If you look closely, you see that the copyright sign (©) is replaced by something we didn't expect (). This is because the default fonts for imagestring() are always in the ISO-8859-2 character set and the script was written in ISO-8859-1. This brings us to the next topic.



    PHP 5 Power Programming
    PHP 5 Power Programming
    ISBN: 013147149X
    EAN: 2147483647
    Year: 2003
    Pages: 240

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net