GENERATING THUMBNAIL IMAGES 231 • imagepng() • imagegif() Each function takes as its first two arguments: the image resource and the path to where you want to save it. The imagejpeg() and imagepng() functions take an optional third argument to set the image quality. For imagejpeg(), you set quality by specifying a number in the range of 0 (worst) to 100 (best). If you omit the argument, the default is 75. For imagepng(), the range is 0 to 9. Confusingly, 0 produces the best quality (no compression). Finally, once you have saved the thumbnail, you need to destroy the image resources by passing them to imagedestroy(). In spite of its destructive name, this function has no effect on the original image or the thumbnail. It simply frees the server memory by destroying the image resources required during processing. PHP Solution 8-4: Generating the thumbnail image This PHP Solution completes the Ps2_Thumbnail class by creating the image resources, copying the thumbnail, and saving it in the destination folder. Continue working with your existing class definition. Alternatively, use Thumbnail_03.php in the ch08 folder. 1. The image resource for the original image needs to be specific to its MIME type, so start by creating an internal method to select the correct type. Add the following code to the class definition. Its a protected method, so put it at the bottom of the page (but inside the classs closing curly brace). protected function createImageResource() { if ($this->_imageType == 'jpeg') { return imagecreatefromjpeg($this->_original); } elseif ($this->_imageType == 'png') { return imagecreatefrompng($this->_original); } elseif ($this->_imageType == 'gif') { return imagecreatefromgif($this->_original); } } The checkType() method that you created in PHP Solution 8-1 stores the MIME type as jpeg, png, or gif. So, the conditional statement checks the MIME type, matches it to the appropriate function, and passes the original image as an argument. The method then returns the resulting image resource. 2. Now its time to define the internal method that does all the hard work. It contains a lot of code, so Ill break it into sections. Start by defining the createThumbnail() method like this: protected function createThumbnail() { $resource = $this->createImageResource(); $thumb = imagecreatetruecolor($this->_thumbwidth, $this->_thumbheight); } CHAPTER 8 232 This calls the createImageResource() method that you created in step 1, and then creates an image resource for the thumbnail, passing the thumbnails width and height to imagecreatetruecolor(). 3. The next stage in creating the thumbnail involves passing both image resources to imagecopyresampled() and setting the coordinates and dimensions. Amend the createThumbnail() method like this: protected function createThumbnail() { $resource = $this->createImageResource(); $thumb = imagecreatetruecolor($this->_thumbwidth, $this->_thumbheight); imagecopyresampled($thumb, $resource, 0, 0, 0, 0, $this->_thumbwidth, $this->_thumbheight, $this->_originalwidth, $this->_originalheight); } The first two arguments are the image resources you have just created for the thumbnail and original image. The next four arguments set the x and y coordinates for both the copy and the original to the top left corner. Next come the width and height calculated for the thumbnail, followed by the original images width and height. By setting arguments 3–6 to the top left corner and both sets of dimensions to the full amounts, this copies the whole original image to the whole of the thumbnail. In other words, it creates a smaller copy of the original. Note that you dont need to assign the result of imagecopyresampled() to a variable. The scaled down image is now stored in $thumb, but you still need to save it. 4. Complete the definition of createThumbnail() like this: protected function createThumbnail() { $resource = $this->createImageResource(); $thumb = imagecreatetruecolor($this->_thumbwidth, $this->_thumbheight); imagecopyresampled($thumb, $resource, 0, 0, 0, 0, $this->_thumbwidth, $this->_thumbheight, $this->_originalwidth, $this->_originalheight); $newname = $this->_name . $this->_suffix; if ($this->_imageType == 'jpeg') { $newname .= '.jpg'; $success = imagejpeg($thumb, $this->_destination . $newname, 100); } elseif ($this->_imageType == 'png') { $newname .= '.png'; $success = imagepng($thumb, $this->_destination . $newname, 0); } elseif ($this->_imageType == 'gif') { $newname .= '.gif'; $success = imagegif($thumb, $this->_destination . $newname); } if ($success) { $this->_messages[] = "$newname created successfully."; } else { $this->_messages[] = "Couldn't create a thumbnail for " . basename($this->_original); } imagedestroy($resource); Download from Wow! eBook <www.wowebook.com> GENERATING THUMBNAIL IMAGES 233 imagedestroy($thumb); } The first line of new code concatenates the suffix to the filename stripped of its filename extension. So, if the original file is called menu.jpg and the default _thb suffix is used, $newname becomes menu_thb. The conditional statement checks the images MIME type and appends the appropriate filename extension. In the case of menu.jpg, $newname becomes menu_thb.jpg. The scaled down image is then passed to the appropriate function to save it, using the destination folder and $newname as the path where it is saved. For JPEG and PNG images, the optional quality argument is set to the highest level: 100 for JPEG and 0 for PNG. The result of the save operation is stored in $success. Depending on the outcome, $success is either true or false, and an appropriate message is added to the $_messages property. Finally, imagedestroy() frees the server memory by destroying the resources used to create the thumbnail image. 5. Update the definition of the create() method to call the createThumbnail() method: public function create() { if ($this->_canProcess && $this->_originalwidth != 0) { $this->calculateSize($this->_originalwidth, $this->_originalheight); $this->getName(); $this->createThumbnail(); } elseif ($this->_originalwidth == 0) { $this->_messages[] = 'Cannot determine size of ' . $this->_original; } } 6. You no longer need the test() method. You can either delete it from the class definition or comment it out. If you plan to experiment further or make enhancements to the class, commenting it out saves the effort of creating it again from scratch. 7. Up to now, you have used the test() method to display error messages. Create a public method to get the messages: public function getMessages() { return $this->_messages; } 8. Save Thumbnail.php. In create_thumb.php, replace the call to the test() method with a call to getMessages(), and assign the result to a variable like this: $thumb->create(); $messages = $thumb->getMessages(); 9. Add a PHP code block just after the opening <body> tag to display any messages: <?php if (isset($messages) && !empty($messages)) { CHAPTER 8 234 echo '<ul>'; foreach ($messages as $message) { echo "<li>$message</li>"; } echo '</ul>'; } ?> Youve seen this code in previous chapters, so it needs no explanation. 10. Save create_thumb.php, load it in a browser, and test it by selecting an image from the list and clicking Create Thumbnail. If all goes well, you should see a message reporting the creation of the thumbnail, and confirm its existence in the thumbs subfolder of upload_test, as shown in Figure 8-6. Figure 8-6. The thumbnail has been successfully created in the destination folder. 11. If the thumbnail isnt created, the error message generated by the Ps2_Thumbnail class should help you detect the source of the problem. Also, check your code carefully against Thumbnail_04.php in the ch08 folder. If the tests in the previous PHP solutions worked, the error is likely to be in the create(), createImageResource(),or createThumbnail() method definitions. The other place to check is, of course, your PHP configuration. The class depends on the GD extension being enabled. Although GD is widely supported, its not always on by default. GENERATING THUMBNAIL IMAGES 235 Resizing an image automatically on upload Now that you have a class that creates a thumbnail from a larger image, its relatively simple to adapt the Ps2_Upload class from Chapter 6 to generate a thumbnail from an uploaded image—in fact, not only from a single image, but also from multiple images. Instead of changing the code in the Ps2_Upload class, its more efficient to extend the class and create a subclass. You then have the choice of using the original class to perform uploads of any type of file, or the subclass to create thumbnail images on upload. The subclass also needs to provide the option to save or discard the larger image after the thumbnail has been created. Before diving into the code, lets take a quick look at how you create a subclass. Extending a class A major advantage of using classes is that theyre extensible. To extend a class, you simply include the original class definition and define the subclass using the extends keyword like this: require_once('OriginalClass.php'); class MyNewClass extends OriginalClass { // subclass definition } This creates a new subclass or child class called MyNewClass from the original or parent class, OriginalClass. The parent-child analogy is apposite, because the child inherits all the features of its parent, but can adapt some of them and acquire new ones of its own. This means that MyNewClass shares the same properties and methods as OriginalClass, but you can add new properties and methods. You can also redefine (or override) some of the parents methods and properties. This simplifies the process of creating a class to perform a more specialized task. The Ps2_Upload class you created in Chapter 6 performs basic file uploads. In this chapter, youll extend it to create a child class called Ps2_ThumbnailUpload that uses the basic upload features of its parent, but adds specialized features that create thumbnail images. Like all children, a child class often needs to borrow from its parent. This frequently happens when you override a method in the child class, but need to use the original version as well. To refer to the parent version, you prefix it with the parent keyword followed by two colons like this: parent::originalMethod(); Youll see how this works in PHP Solution 8-5, because the child class defines its own constructor to add an extra argument, but also needs to use the parent constructor. This description of inheritance covers only the bare minimum you need to understand PHP Solution 8- 5. For a more detailed insight into PHP classes, see my PHP Object-Oriented Solutions (friends of ED, 2008, ISBN: 978-1-4302-1011-5). So, lets create a class capable of uploading images and generating thumbnails at the same time. CHAPTER 8 236 PHP Solution 8-5: Creating the Ps2_ThumbnailUpload class This PHP solution extends the Ps2_Upload class from Chapter 6 and uses it in conjunction with the Ps2_Thumbnail class to upload and resize images. It demonstrates how to create a child class and override parent methods. To create the child class, you need Upload.php from Chapter 6 and Thumbnail.php from this chapter. Copies of both files are in the classes/completed folder. 1. Create a new file called ThumbnailUpload.php in the classes/Ps2 folder. It will contain only PHP code, so strip out any HTML inserted by your script editor, and add the following code: <?php require_once('Upload.php'); require_once('Thumbnail.php'); class Ps2_ThumbnailUpload extends Ps2_Upload { } This includes the definitions of the Ps2_Upload and Ps2_Thumbnail classes, and declares that the Ps2_ThumbnailUpload class extends Ps2_Upload. All subsequent code needs to be inserted between the curly braces. 2. The child class needs three properties: for the folder where the thumbnail is to be saved, a Boolean that determines whether to delete the original image, and for the suffix to be added to the thumbnail. The last of these is required in case you dont want to use the default suffix defined in Ps2_Thumbnail. Add the following property definitions inside the curly braces: protected $_thumbDestination; protected $_deleteOriginal; protected $_suffix = '_thb'; 3. When you extend a class, the only time you need to define a constructor method is when you want to change how the constructor works. The Ps2_ThumbnailUpload class takes an extra argument that determines whether to delete the original image, giving you the option to retain only the thumbnail or to keep both versions of the image. When testing locally, a Ps2_Thumbnail object can access the original image on your own hard drive. But generating the thumbnail is a server-side operation, so it wont work on a website without first uploading the original image to the server. The constructor also needs to call the parent constructor to define the path to the upload folder. Add the following definition to the class: public function __construct($path, $deleteOriginal = false) { parent::__construct($path); $this->_thumbDestination = $path; $this->_deleteOriginal = $deleteOriginal; } GENERATING THUMBNAIL IMAGES 237 The constructor takes two arguments: the path to the upload folder and a Boolean variable that determines whether to delete the original image. The second argument is set to false in the constructor signature, making it optional. The first line of code inside the constructor passes $path to the parent constructor to set the destination folder for the file uploads. The second line also assigns $path to the $_thumbDestination property, making the same folder the default for both images. The final line assigns the value of the second argument to the $_deleteOriginal property. Because the second argument is optional, its automatically set to false and both images are retained unless you set it explicitly to true. 4. Create the setter method for the thumbnail destination folder like this: public function setThumbDestination($path) { if (!is_dir($path) || !is_writable($path)) { throw new Exception("$path must be a valid, writable directory."); } $this->_thumbDestination = $path; } This takes a path as its only argument, checks that its a folder (directory) and is writable, and assigns the value to the $_thumbDestination property. If the value passed as an argument is invalid, the class throws an exception. Instead of creating a setter method for the thumbnail destination folder, I could have added an extra argument to the constructor. However, my choice simplifies the constructor for occasions when you want to save the thumbnail and original image in the same folder. Also, I could have silently used the original upload folder instead of throwing an exception if theres a problem with the thumbnail destination. I decided that a problem with the destination folder is too serious to ignore. Decisions like this are an integral part of writing any script, not just designing a class. 5. Apart from the name, the setter method for the thumbnail suffix is identical to the one in Thumbnail.php. It looks like this: public function setThumbSuffix($suffix) { if (preg_match('/\w+/', $suffix)) { if (strpos($suffix, '_') !== 0) { $this->_suffix = '_' . $suffix; } else { $this->_suffix = $suffix; } } else { $this->_suffix = ''; } } CHAPTER 8 238 You need to define the method here because the class inherits from Ps2_Upload, not Ps2_Thumbnail. A PHP class can have only a single parent. 6. Next, create a protected method to generate the thumbnail using the following code: protected function createThumbnail($image) { $thumb = new Ps2_Thumbnail($image); $thumb->setDestination($this->_thumbDestination); $thumb->setSuffix($this->_suffix); $thumb->create(); $messages = $thumb->getMessages(); $this->_messages = array_merge($this->_messages, $messages); } This takes a single argument, the path to an image, and creates a Ps2_Thumbnail object. The code is similar to create_thumb.php, so it shouldnt need explanation. The final line uses array_merge() to merge any messages generated by the Ps2_Thumbnail object with the $_messages property of the Ps2_ThumbnailUpload class. Although the properties you defined in step 2 dont include a $_messages property, the child class automatically inherits it from its parent. 7. In the parent class, the processFile() method saves an uploaded file to its target destination. The thumbnail needs to be generated from the original image, so you need to override the parents processFile() method, and use it to call the createThumbnail() method that you have just defined. Copy the processFile() method from Upload.php, and amend it by adding the code highlighted in bold. protected function processFile($filename, $error, $size, $type, $tmp_name, $overwrite) { $OK = $this->checkError($filename, $error); if ($OK) { $sizeOK = $this->checkSize($filename, $size); $typeOK = $this->checkType($filename, $type); if ($sizeOK && $typeOK) { $name = $this->checkName($filename, $overwrite); $success = move_uploaded_file($tmp_name, $this->_destination . $name); if ($success) { // don't add a message if the original image is deleted if (!$this->_deleteOriginal) { $message = $filename . ' uploaded successfully'; if ($this->_renamed) { $message .= " and renamed $name"; } $this->_messages[] = $message; } // create a thumbnail from the uploaded image $this->createThumbnail($this->_destination . $name); // delete the uploaded image if required if ($this->_deleteOriginal) { GENERATING THUMBNAIL IMAGES 239 unlink($this->_destination . $name); } } else { $this->_messages[] = 'Could not upload ' . $filename; } } } } If the original image has been uploaded successfully, the new code adds a conditional statement to generate the message only if $_deleteOriginal is false. It then calls the createThumbnail() method, passing it the uploaded image as the argument. Finally, if $_deleteOriginal has been set to true, it uses unlink() to delete the uploaded image, leaving only the thumbnail. 8. Save ThumbnailUpload.php. To test it, copy create_thumb_upload_01.php from the ch08 folder to the gd folder, and save it as create_thumb_upload.php. The file contains a simple form with a file field and a PHP block that displays messages. Add the following PHP code block above the DOCTYPE declaration: if (isset($_POST['upload'])) { require_once(' /classes/Ps2/ThumbnailUpload.php'); try { $upload = new Ps2_ThumbnailUpload('C:/upload_test/'); $upload->setThumbDestination('C:/upload_test/thumbs/'); $upload->move(); $messages = $upload->getMessages(); } catch (Exception $e) { echo $e->getMessage(); } } Adjust the paths in the constructor and setThumbDestination() method, if necessary. 9. Save create_thumb_upload.php, and load it in an HTML5-compliant browser. Click the Brows e or Choose File button, and select multiple images. When you click the Upload button, you should see messages informing you of the successful upload and creation of the thumbnails. Check the destination folders, as shown in Figure 8-7. CHAPTER 8 240 Figure 8-7. The thumbnails are created in the same operation as the images are uploaded. 10. Test the Ps2_ThumbnailUpload class by uploading the same images again. This time, the original images and thumbnails should be renamed in the same way as in Chapter 6 through the addition of a number before the filename extension. 11. Try different tests, changing the suffix inserted into the thumbnail names, or deleting the original image after the thumbnail has been created. If you run into problems, check your code carefully against ThumbnailUpload.php in the ch08 folder. In older browsers that dont support the multiple attribute on form fields, the class uploads a single image and creates a thumbnail from it. To support multiple uploads from older browsers, create multiple file fields in the form, and give them all the same name attribute followed by an empty pair of square brackets like this: name="image[]" . Using the Ps2_ThumbnailUpload class The Ps2_ThumbnailUpload class is easy to use. Just include the class definition in your file, and pass the path to the upload folder to the constructor as an argument like this: $upload = new Ps2_ThumbnailUpload('C:/upload_test/'); . (isset($_POST['upload'])) { require_once(' /classes/Ps2/ThumbnailUpload .php& apos;); try { $upload = new Ps2_ThumbnailUpload('C:/upload_test/'); $upload->setThumbDestination('C:/upload_test/thumbs/');. create_thumb_upload .php. The file contains a simple form with a file field and a PHP block that displays messages. Add the following PHP code block above the DOCTYPE declaration: if (isset($_POST['upload'])). require_once('Upload .php& apos;); require_once('Thumbnail .php& apos;); class Ps2_ThumbnailUpload extends Ps2_Upload { } This includes the definitions of the Ps2_Upload and Ps2_Thumbnail