GENERATING THUMBNAIL IMAGES 221 It doesnt matter which order your methods appear inside the class definition, but its common practice to keep all public methods together after the constructor and to put protected methods at the bottom of the file. This makes the code easier to maintain. Insert the following definition between the constructor and the checkType() definition: public function test() { echo 'File: ' . $this->_original . '<br>'; echo 'Original width: ' . $this->_originalwidth . '<br>'; echo 'Original height: ' . $this->_originalheight . '<br>'; echo 'Image type: ' . $this->_imageType . '<br>'; if ($this->_messages) { print_r($this->_messages); } } This uses echo and print_r() to display the value of the properties. 6. To test the class definition so far, save Thumbnail.php, and add the following code block above the DOCTYPE declaration in create_thumb.php (the code is in create_thumb_win02.php and create_thumb_mac02.php in the ch08 folder): <?php if (isset($_POST['create'])) { require_once(' /classes/Ps2/Thumbnail.php'); try { $thumb = new Ps2_Thumbnail($_POST['pix']); $thumb->test(); } catch (Exception $e) { echo $e->getMessage(); } } ?> The name of the submit button in create_thumb.php is create, so this code runs only when the form has been submitted. It includes the Ps2_Thumbnail class definition, creates an instance of the class, passing the selected value from the form as an argument, and calls the test() method. 7. Save create_thumb.php, and load it into a browser. Select an image, and click Create Thumbnail. This produces output similar to Figure 8-1. CHAPTER 8 222 Figure 8-1. Displaying the details of the selected image confirms the code is working. If necessary, check your code against Thumbnail_01.php in the ch08 folder. Although some properties have default values, you need to provide the option to change them by creating public methods to set the maximum size of the thumbnail image, and the suffix applied to the base of the filename. You also need to tell the class where to create the thumbnail. The formal term for this type of method is a mutator method. However, because it sets a value, its commonly referred to as a setter method. The next stage is to create the setter methods. PHP Solution 8-2: Creating the setter methods In addition to setting the value of protected properties, setter methods play an important role in ensuring the validity of the value being assigned. Continue working with the same class definition. Alternatively, use Thumbnail_01.php in the ch08 folder. 1. Begin by creating the setter method for the folder where the thumbnail is to be created. Add the following code to Thumbnail.php after the constructor definition. public function setDestination($destination) { if (is_dir($destination) && is_writable($destination)) { // get last character $last = substr($destination, -1); // add a trailing slash if missing if ($last == '/') || $last == '\\') { $this->_destination = $destination; } else { $this->_destination = $destination . DIRECTORY_SEPARATOR; } } else { $this->_messages[] = "Cannot write to $destination."; } } Download from Wow! eBook <www.wowebook.com> GENERATING THUMBNAIL IMAGES 223 This begins by checking that $destination is a folder (directory) and that its writable. If it isnt, the error message in the else clause at the end of the method definition is added to the $_messages property. Otherwise, the rest of the code is executed. Before assigning the value of $destination to the $_destination property, the code checks whether the value submitted ends in a forward slash or backslash. It does this by extracting the final character in $destination, using the substr() function. The second argument to substr() determines the position from which to start the extract. A negative number counts from the end of the string. If the third argument is omitted, the function returns the rest of the string. So, $last = substr($destination, -1) has the effect of extracting the last character, and storing it in $last. The conditional statement checks whether $last is a forward slash or a backslash. Two backslashes are needed because PHP uses a backslash to escape quotes (see “Understanding when to use quotes” and “Using escape sequences” in Chapter 3). Its necessary to check for both forward slashes and backslashes in $destination because a Windows user might use backslashes out of habit. If the conditional statement confirms that the final character is a forward slash or a backslash, $destination is assigned to the $_destination property. Otherwise, the else clause concatenates the PHP constant DIRECTORY_SEPARATOR to the end of $destination before assigning it to the $_destination property. The DIRECTORY_SEPARATOR constant automatically chooses the right type of slash depending on the operating system. PHP treats forward slashes or backslashes equally in a path. Even if this results in adding the opposite type of slash, the path remains valid as far as PHP is concerned. 2. The setter method for the maximum size of the thumbnail simply needs to check that the value is a number. Add the following code to the class definition: public function setMaxSize($size) { if (is_numeric($size)) { $this->_maxSize = abs($size); } } The is_numeric() function checks that the submitted value is a number. If it is, its assigned to the $_maxSize property. As a precaution, the value is passed to the abs() function, which converts a number to its absolute value. In other words, a negative number is converted into a positive one. If the submitted value isnt a number, nothing happens. The propertys default value remains unchanged. 3. The setter function for the suffix inserted in the filename needs to make sure the value doesnt contain any special characters. The code looks like this: CHAPTER 8 224 public function setSuffix($suffix) { if (preg_match('/^\w+$/', $suffix)) { if (strpos($suffix, '_') !== 0) { $this->_suffix = '_' . $suffix; } else { $this->_suffix = $suffix; } } else { $this->_suffix = ''; } } This uses preg_match(), which takes a regular expression as its first argument and searches for a match in the value passed as the second argument. Regular expressions need to be wrapped in a pair of matching delimiter characters—normally forward slashes, as used here. Stripped of the delimiters, the regex looks like this: ^\w+$ In this context, the caret (^) tells the regex to start at the beginning of the string. The \w matches any alphanumeric character or an underscore. The + means match one or more of the preceding character, and the $ means match the end of the string. In other words, the regex matches a string that contains only alphanumeric characters and underscores. If the string contains spaces or special characters, it wont match. As mentioned before, regexes can be difficult to learn, but theyre extremely useful in PHP, JavaScript, and other web-related languages. If the match fails, the else clause at the end of the method definition sets the $_suffix property to an empty string. Otherwise, this conditional statement is executed. if (strpos($suffix, '_') !== 0) { The condition equates to true if the first character of $suffix is not an underscore. It uses the strpos() function to find the position of the first underscore. If the first character is an underscore, the value returned by strpos() is 0. However, if $suffix doesnt contain an underscore, strpos() returns false. As explained in Chapter 3, 0 is treated by PHP as false, so the condition needs to use the “not identical” operator (with two equal signs). So, if the suffix doesnt begin with an underscore, one is added. Otherwise, the original value is preserved. Dont confuse strpos() and strrpos() . The former finds the position of the first matching character. The latter searches the string in reverse. GENERATING THUMBNAIL IMAGES 225 4. Update the test() method to display the values of the properties for which you have just created setter methods. The revised code looks like this: public function test() { echo 'File: ' . $this->_original . '<br>'; echo 'Original width: ' . $this->_originalwidth . '<br>'; echo 'Original height: ' . $this->_originalheight . '<br>'; echo 'Image type: ' . $this->_imageType . '<br>'; echo 'Destination: ' . $this->_destination . '<br>'; echo 'Max size: ' . $this->_maxSize . '<br>'; echo 'Suffix: ' . $this->_suffix . '<br>'; if ($this->_messages) { print_r($this->_messages); } } 5. Test the updated class by using the new setter methods in create_thumb.php like this: $thumb = new Ps2_Thumbnail($_POST['pix']); $thumb->setDestination('C:/upload_test/thumbs/'); $thumb->setMaxSize(100); $thumb->setSuffix('small'); $thumb->test(); 6. Save both pages, and select an image from the list in create_thumb.php. You should see results similar to Figure 8-2. Figure 8-2. Verifying that the setter methods work 7. Try a number of tests, omitting the trailing slash from the value passed to setDestination() or selecting a nonexisting folder. Also pass invalid values to the setters for the maximum size and suffix. An invalid destination folder produces an error message, but the others fail silently, using the default value for the maximum size or an empty string for the suffix. CHAPTER 8 226 If necessary, compare your code with Thumbnail_02.php in the ch08 folder. You might not agree with my decision to fail silently when the values passed as arguments are invalid. By now, though, you should have sufficient experience of conditional statements to adapt the code to your own requirements. For example, if you want the setter method for the thumbnails maximum size to return an error message instead of failing silently, check that the value is greater than zero, and add an else clause to generate the error message. The else clause should also set the $_canProcess property to false to prevent the class from attempting to create a thumbnail image. This is how you would adapt the setMaxSize() method: public function setMaxSize($size) { if (is_numeric($size) && $size > 0) { $this->_maxSize = $size; } else { $this->_messages[] = 'The value for setMaxSize() must be a positive number.'; $this->_canProcess = false; } } Before you can create the thumbnail image, you need to calculate its size. The value set in the $_maxSize property determines the width or height, depending which is larger. To avoid distorting the thumbnail, you need to calculate the scaling ratio for the shorter dimension. The ratio is calculated by dividing the maximum thumbnail size by the larger dimension of the original image. For example, the original image of the Golden Pavilion (kinkakuji.jpg) is 270 346 pixels. If the maximum size is set at 120, dividing 120 by 346 produces a scaling ratio of 0.3468. Multiplying the width of the original image by this ratio fixes the thumbnails width at 94 pixels (rounded up to the nearest whole number), maintaining the correct proportions. Figure 8-3 shows how the scaling ratio works. Figure 8-3. Working out the scaling ratio for a thumbnail image. GENERATING THUMBNAIL IMAGES 227 The base filename also needs to be split from the filename extension in preparation for inserting the suffix indicating its a thumbnail. PHP Solution 8-3: Final preparations for generating the thumbnail This PHP solution adds three new methods to the Ps2_Thumbnail class: a public method that initiates the generation of the thumbnail image, and two internal methods that calculate the thumbnails dimensions and split the images base name from its filename extension. In PHP Solution 6-5, isolating the filename extension was done by searching for a dot or period in the filename. This time, you know the file types in advance, so a regular expression is used. Continue working with your existing class definition. Alternatively, use Thumbnail_02.php in the ch08 folder. 1. Calculating the thumbnail dimensions doesnt require any further user input, so it can be handled by an internal method. Add the following code to the class definition. Its a protected method, so put it at the end of the file, just inside the closing curly brace. protected function calculateSize($width, $height) { if ($width <= $this->_maxSize && $height <= $this->_maxSize) { $ratio = 1; } elseif ($width > $height) { $ratio = $this->_maxSize/$width; } else { $ratio = $this->_maxSize/$height; } $this->_thumbwidth = round($width * $ratio); $this->_thumbheight = round($height * $ratio); } The dimensions of the original image are stored as properties of the Ps2_Thumbnail object, so you could refer to them directly as $this->_originalWidth and $this->_originalHeight. However, the method needs to refer to these values often, so I decided to pass them as arguments to make the code easier to read and type. The conditional statement begins by checking if the width and height of the original image are less than or equal to the maximum size. If they are, the image doesnt need to be resized, so the scaling ratio is set to 1. The elseif clause checks if the width is greater than the height. If it is, the width is used to calculate the scaling ratio. The else clause is invoked if the height is greater or both sides are equal. In either case, the height is used to calculate the scaling ratio. The last two lines multiply the original width and height by the scaling ratio, and assign the results to the $_thumbwidth and $_thumbheight properties. The calculation is wrapped in the round() function, which rounds the result to the nearest whole number. 2. Next, add the method that gets the filename and strips off the filename extension. The code looks like this: protected function getName() { CHAPTER 8 228 $extensions = array('/\.jpg$/i', '/\.jpeg$/i', '/\.png$/i', '/\.gif$/i'); $this->_name = preg_replace($extensions, '', basename($this->_original)); } The code inside the method is only two lines, but theres a lot going on. The first line creates an array of regular expressions. As mentioned earlier, regexes are wrapped in delimiter characters, normally forward slashes. The i after the closing slash of each regex tells it to peform a case-insensitive search. A period normally represents any character, but escaping it with a backslash makes the regex look only for a period. The $ matches the end of the string. Everything else matches a literal character. In other words, these regexes match .jpg, .jpeg, .png, and .gif in a case- insensitive manner. The second line uses the preg_replace() function, which performs a find and replace operation using a regex or array of regexes. The first argument is the value(s) you want to replace. The second argument is the replacement text—in this case, an empty string. The third argument is the subject of the search and replace operation. Here, the third argument is the value of the $_original property, which has been passed to the basename() function. You met basename() in PHP Solution 4-3. It extracts the filename from a path. So, the code in the second line searches the filename for an image filename extension and replaces it with nothing. In other words, it strips off the filename extension, and assigns the result to the $_name property. 3. These two methods need to be called by the method that creates the thumbnail image. Add the following public method to the class definition above the protected methods: public function create() { if ($this->_canProcess && $this->_originalwidth != 0) { $this->calculateSize($this->_originalwidth, $this->_originalheight); $this->getName(); } elseif ($this->_originalwidth == 0) { $this->_messages[] = 'Cannot determine size of ' . $this->_original; } } This checks that $_canProcess is true and that the width of the original image is not 0. The second test is necessary because getimagesize() sets the width and height to 0 if it cant determine the size. This usually happens if the image format contains multiple images. The method then calls the two internal methods you have just created. If the $_originalwidth property is 0, an error message is added to the $_messages property. 4. To test the new methods, amend the test() method like this: public function test() { echo 'File: ' . $this->_original . '<br>'; echo 'Original width: ' . $this->_originalwidth . '<br>'; echo 'Original height: ' . $this->_originalheight . '<br>'; echo 'Image type: ' . $this->_imageType . '<br>'; GENERATING THUMBNAIL IMAGES 229 echo 'Destination: ' . $this->_destination . '<br>'; echo 'Max size: ' . $this->_maxSize . '<br>'; echo 'Suffix: ' . $this->_suffix . '<br>'; echo 'Thumb width: ' . $this->_thumbwidth . '<br>'; echo 'Thumb height: ' . $this->_thumbheight . '<br>'; echo 'Base name: ' . $this->_name . '<br>'; if ($this->_messages) { print_r($this->_messages); } } 5. The call to create() needs to come before the test() method. Otherwise, the new values wont have been generated. Amend the code in create_thumb.php like this: $thumb = new Ps2_Thumbnail($_POST['pix']); $thumb->setDestination('C:/upload_test/thumbs/'); $thumb->setMaxSize(100); $thumb->setSuffix('small'); $thumb->create(); $thumb->test(); 6. Test the updated class by selecting an image in create_thumb.php and clicking Create Thumbnail. You should see the values displayed onscreen, as shown in Figure 8-4. Figure 8-4. The class is now generating all the values needed to create the thumbnail image. If necessary, check your code against Thumbnail_03.php in the ch08 folder. After you have gathered all the necessary information, you can generate a thumbnail image from a larger one. This involves creating image resources for the original image and the thumbnail. For the original image, you need to use one of these functions specific to the images MIME type: CHAPTER 8 230 • imagecreatefromjpeg() • imagecreatefrompng() • imagecreatefromgif() The functions take a single argument: the path to the file. Because the thumbnail doesnt yet exist, you use a different function, imagecreatetruecolor(), which takes two arguments—the width and height (in pixels). The function that creates a resized copy of an image is imagecopyresampled(), which takes no fewer than ten arguments—all of them required. The arguments fall into five pairs as follows: • References to the two image resources—copy first, original second • The x and y coordinates of where to position the top-left corner of the copied image • The x and y coordinates of the top-left corner of the original • The width and height of the copy • The width and height of the area to copy from the original Figure 8-5 shows how the last four pairs of arguments can be used to extract a specific area, rather than copy the whole image, using the following arguments to imagecopyresampled(): imagecopyresampled($thumb, $source, 0, 0, 170, 20, $thbwidth,$thbheight, 170, 102); Figure 8-5. The imagecopyresampled() function allows you to copy part of an image. The x and y coordinates of the area to copy are measured in pixels from the top left of the image. The x and y axes begin at 0 at the top left, and increase to the right and down. By setting the width and height of the area to copy to 170 and 102, respectively, PHP extracts the area outlined in white. So, now you know how websites manage to crop uploaded images. They calculate the coordinates dynamically using JavaScript or Flash, both of which are beyond the scope of this book. For the Ps2_Thumbnail class, youll use the whole of the original image to generate the thumbnail. After creating the copy with imagecopyresampled(), you need to save it, again using a function specific to the MIME type, namely: • imagejpeg() . above the DOCTYPE declaration in create_thumb .php (the code is in create_thumb_win02 .php and create_thumb_mac02 .php in the ch08 folder): < ?php if (isset($_POST['create'])) { . create_thumb .php like this: $thumb = new Ps2_Thumbnail($_POST['pix']); $thumb->setDestination('C:/upload_test/thumbs/'); $thumb->setMaxSize(100); $thumb->setSuffix('small');. require_once(' /classes/Ps2/Thumbnail .php& apos;); try { $thumb = new Ps2_Thumbnail($_POST['pix']); $thumb->test(); } catch (Exception $e) { echo $e->getMessage();