* Invision Power Services * IP.Board v3.4.8 * Image Handler: GD2 Library * Last Updated: $Date: 2013-02-06 19:48:42 -0500 (Wed, 06 Feb 2013) $ * * * @author $Author: bfarber $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @subpackage Kernel * @link http://www.invisionpower.com * @since Monday 5th May 2008 14:00 * @version $Revision: 11951 $ * * GD Example * * $image = new classImageGd(); * $image->init( array( * 'image_path' => "/path/to/images/", * 'image_file' => "image_filename.jpg", * ) ); * * if( $image->error ) * { * print $image->error;exit; * } * * Set max width and height * $image->resizeImage( 600, 480 ); * // Add a watermark * $image->addWatermark( "/path/to/watermark/trans.png" ); * //$image->addCopyrightText( "Hello World!", array( 'color' => '#ffffff', 'font' => 3 ) ); * $image->displayImage(); * */ class classImageGd extends classImage implements interfaceImage { /** * Image resource * * @var resource */ protected $image = null; /** * Image quality settings * * @var array */ public $quality = array( 'png' => 8, 'jpg' => 75 ); /** * Initiate image handler, perform any necessary setup * * @param array Necessary options to init module [image_path, image_file] * @return @e boolean */ public function init( $opts=array() ) { //--------------------------------------------------------- // Verify input //--------------------------------------------------------- if( empty($opts['image_path']) ) { $this->error = 'no_image_path'; return false; } if( empty($opts['image_file']) ) { $this->error = 'no_image_file'; return false; } //--------------------------------------------------------- // Store paths //--------------------------------------------------------- $this->image_path = $this->_cleanPaths( $opts['image_path'] ); $this->image_file = $opts['image_file']; $this->image_full = $this->image_path . '/' . $this->image_file; //--------------------------------------------------------- // Get extension //--------------------------------------------------------- $this->image_extension = strtolower(pathinfo( $this->image_file, PATHINFO_EXTENSION )); //--------------------------------------------------------- // Verify this is a valid image type //--------------------------------------------------------- if( !in_array( $this->image_extension, $this->image_types ) ) { $this->error = 'image_not_supported'; return false; } //--------------------------------------------------------- // Quality values //--------------------------------------------------------- if( !empty($opts['jpg_quality']) ) { $this->quality['jpg'] = $opts['jpg_quality']; } if( !empty($opts['png_quality']) ) { $this->quality['png'] = $opts['png_quality']; } //--------------------------------------------------------- // Get and remember dimensions //--------------------------------------------------------- $dimensions = @getimagesize( $this->image_full ); $this->orig_dimensions = array( 'width' => $dimensions[0], 'height' => $dimensions[1] ); $this->cur_dimensions = $this->orig_dimensions; //--------------------------------------------------------- // Create image resource //--------------------------------------------------------- switch( $this->image_extension ) { case 'gif': $this->image = @imagecreatefromgif( $this->image_full ); break; case 'jpeg': case 'jpg': case 'jpe': $this->image = @imagecreatefromjpeg( $this->image_full ); break; case 'png': $this->image = @imagecreatefrompng( $this->image_full ); if( $this->image ) { @imagealphablending( $this->image, TRUE ); @imagesavealpha( $this->image, TRUE ); } break; } if( !$this->image ) { //----------------------------------------- // Fallback // @see http://forums.invisionpower.com/index.php?app=tracker&showissue=17836 //----------------------------------------- if( $this->image = @imagecreatefromstring( file_get_contents( $this->image_full ) ) ) { return true; } $this->error = 'no_full_image'; return false; } else { return true; } } /** * Resizes and image, then crops an image to a fixed ratio * * @param int Width of final cropped image * @param int Height of final cropped image * @param @e array */ public function croppedResize( $width, $height ) { /* Changes here made to prevent "division by 0" errors when getimagesize() failed */ $ratioOrig = $this->orig_dimensions['width'] / ( $this->orig_dimensions['height'] ? $this->orig_dimensions['height'] : 1 ); /* This breaks square thumbs - always adds black bars */ if( $ratioOrig < 1 ) { //$ratioOrig = 1; } if ( $width / $height > $ratioOrig ) { $nheight = $width / $ratioOrig; $nwidth = $width; } else { $nwidth = $height * $ratioOrig; $nheight = $height; } $vals = $this->resizeImage( $nwidth, $nheight ); /* Crop from center */ $_x = $vals['newWidth'] / 2; $_y = $vals['newHeight'] / 2; $this->_cropX = ( $_x - ( $width / 2 ) ); $this->_cropY = ( $_y - ( $height / 2 ) ); $this->cur_dimensions['width'] = $width; $this->cur_dimensions['height'] = $height; return $this->resizeImage( $width, $height, true ); } /** * Crops an image * * @param int Crop from X * @param int Crop from Y * @param int Width * @param int Height * @return @e array */ public function crop( $x1, $y1, $width, $height ) { $this->_cropX = $x1; $this->_cropY = $y1; $this->cur_dimensions['width'] = $width; $this->cur_dimensions['height'] = $height; return $this->resizeImage( $width, $height, true ); } /** * Resize image proportionately * * @param integer Maximum width * @param integer Maximum height * @param boolean Second pass of image for crop * @param boolean Instead of resizing image to same size, return false if the image doesn't need to be resized * @param array Canvas size - if larger than resized image, image will be placed in the center * @return @e array */ public function resizeImage( $width, $height, $secondPass=false, $returnIfUnnecessary=false, $canvas=array() ) { //--------------------------------------------------------- // Get proportionate dimensions and store //--------------------------------------------------------- $dst_x = 0; $dst_y = 0; if ( ! $secondPass ) { $new_dims = $this->_getResizeDimensions( $width, $height ); if( !$new_dims['img_width'] ) { $new_dims = array( 'img_width' => $this->cur_dimensions['width'], 'img_height' => $this->cur_dimensions['height'], 'cur_width' => $this->cur_dimensions['width'], 'cur_height' => $this->cur_dimensions['height'], ); } /* Canvas? */ if ( is_array( $canvas ) && count( $canvas ) ) { $storedNewDims = $new_dims; $new_dims = array( 'img_width' => $canvas[0], 'img_height' => $canvas[1], 'cur_width' => $new_dims['cur_width'] ? $new_dims['cur_width'] : $this->cur_dimensions['width'], 'cur_height' => $new_dims['cur_height'] ? $new_dims['cur_height'] : $this->cur_dimensions['height'] ); if ( $storedNewDims['img_width'] < $canvas[0] ) { $dst_x = ( $canvas[0] - $storedNewDims['img_width'] ) / 2; } if ( $storedNewDims['img_height'] < $canvas[1] ) { $dst_y = ( $canvas[1] - $storedNewDims['img_height'] ) / 2; } } } else { $new_dims['img_width'] = $width; $new_dims['img_height'] = $height; } //--------------------------------------------------------- // If new image is same size as original, just return original // This preserves GIF animation and allows images not to be "compressed" unnecessarily //--------------------------------------------------------- if ( $returnIfUnnecessary AND ! $this->force_resize ) { if ( ( $this->orig_dimensions['width'] <= $width ) AND ( $this->orig_dimensions['height'] <= $height ) ) { return array( 'originalWidth' => $this->orig_dimensions['width'], 'originalHeight' => $this->orig_dimensions['height'], 'newWidth' => $this->orig_dimensions['width'], 'newHeight' => $this->orig_dimensions['height'], 'noResize' => true ); } } if( ! is_array($new_dims) OR !count($new_dims) OR !$new_dims['img_width'] ) { if( ! $this->force_resize ) { return array( 'originalWidth' => $this->orig_dimensions['width'], 'originalHeight' => $this->orig_dimensions['height'], 'newWidth' => $this->orig_dimensions['width'], 'newHeight' => $this->orig_dimensions['height'], 'noResize' => true ); } else { // @see http://community.invisionpower.com/tracker/issue-20025-attached-images-gets-resized-even-when-its-dimensions-are-lower-than-limits/ $new_dims['img_width'] = ( $width > $this->orig_dimensions['width'] ) ? $this->orig_dimensions['width'] : $width; $new_dims['img_height'] = ( $height > $this->orig_dimensions['height'] ) ? $this->orig_dimensions['height'] : $height; } } //--------------------------------------------------------- // Create new image resource //--------------------------------------------------------- $new_img = @imagecreatetruecolor( $new_dims['img_width'], $new_dims['img_height'] ); if ( ! $new_img ) { $this->error = 'image_creation_failed'; return array(); } /* If we specified a canvas, then we need to restore the $new_dims ready for the image copy */ if ( is_array( $canvas ) and count( $canvas ) ) { $new_dims = $storedNewDims; /* And fill canvas with white */ @imagefilledrectangle( $new_img, 0, 0, $canvas[0], $canvas[1], imagecolorallocatealpha( $new_img, 255, 255, 255, 0 ) ); } //--------------------------------------------------------- // Apply alpha blending //--------------------------------------------------------- switch( $this->image_extension ) { /* GIFs require a lot of extra work */ case 'gif': /* Using the method described at http://us2.php.net/manual/en/function.imagecopyresampled.php#89987 for gif transparency. Bug #20826 */ @imagealphablending( $new_img, FALSE ); $transindex = @imagecolortransparent( $this->image ); if( $transindex >= 0 ) { $transcol = @imagecolorsforindex( $this->image, $transindex ); $transindex = @imagecolorallocatealpha( $new_img, $transcol['red'], $transcol['green'], $transcol['blue'], 127 ); @imagefill( $new_img, 0, 0, $transindex ); } break; case 'jpeg': case 'jpg': case 'jpe': @imagealphablending( $new_img, TRUE ); break; case 'png': @imagealphablending( $new_img, FALSE ); @imagesavealpha( $new_img, TRUE ); break; } //--------------------------------------------------------- // Copy image resampled //--------------------------------------------------------- @imagecopyresampled( $new_img, $this->image, $dst_x, $dst_y, intval( $this->_cropX ) , intval( $this->_cropY ), $new_dims['img_width'], $new_dims['img_height'], $this->cur_dimensions['width'], $this->cur_dimensions['height'] ); $this->cur_dimensions = array( 'width' => $new_dims['img_width'], 'height' => $new_dims['img_height'] ); //--------------------------------------------------------- // Don't forget the alpha blending //--------------------------------------------------------- switch( $this->image_extension ) { /* Even more gif work.... Bug #20826 */ case 'gif': if( $transindex >= 0 ) { @imagecolortransparent( $new_img, $transindex ); for( $y = 0; $y < $new_dims['img_height']; ++$y ) { for( $x = 0; $x < $new_dims['img_width']; ++$x ) { if( ( ( @imagecolorat( $new_img, $x, $y) >> 24 ) & 0x7F ) >= 100 ) { @imagesetpixel( $new_img, $x, $y, $transindex ); } } } } @imagetruecolortopalette( $new_img, true, 255 ); @imagesavealpha( $new_img, false ); break; case 'png': @imagealphablending( $new_img, FALSE ); @imagesavealpha( $new_img, TRUE ); break; } /* If this is the second pass, see if we need to do a BG fill */ if ( $secondPass ) { if ( $this->orig_dimensions['width'] < $width AND $this->orig_dimensions['height'] < $height ) { $_x = ( $new_dims['img_width'] - $this->orig_dimensions['width'] ) / 2; $_y = ( $new_dims['img_height'] - $this->orig_dimensions['height'] ) / 2; $rgb = @imagecolorat( $new_img, $_x + 1, $_y + 1 ); $col = @imagecolorsforindex( $new_img, $rgb ); /* If we've an almost transparent BG, then fill with offwhite */ if ( $col['alpha'] > 120 ) { $col = array( 'red' => '245', 'green' => '245', 'blue' => '245', 'alpha' => 0 ); } else { $col['alpha'] = 100; } $iCol = @imagecolorallocatealpha( $new_img, $col['red'], $col['green'], $col['blue'], $col['alpha'] ); /* T */ @imagefilledrectangle( $new_img, 0, 0, $new_dims['img_width'], $_y, $iCol ); /* R */ @imagefilledrectangle( $new_img, 0, 0, $_x, $new_dims['img_height'], $iCol ); /* B */ @imagefilledrectangle( $new_img, 0, ( $_y + $this->orig_dimensions['height'] ), $new_dims['img_width'], ( ( $_y * 2 ) + $this->orig_dimensions['height'] ) , $iCol ); /* L */ @imagefilledrectangle( $new_img, ( $_x + $this->orig_dimensions['width'] ), 0, $new_dims['img_width'], $new_dims['img_height'], $iCol ); } } //--------------------------------------------------------- // Destroy original resource and store new resource //--------------------------------------------------------- @imagedestroy( $this->image ); $this->image = $new_img; return array( 'originalWidth' => $this->orig_dimensions['width'], 'originalHeight' => $this->orig_dimensions['height'], 'newWidth' => ( is_array( $canvas ) && count( $canvas ) ) ? $canvas[0] : $new_dims['img_width'], 'newHeight' => ( is_array( $canvas ) && count( $canvas ) ) ? $canvas[1] : $new_dims['img_height'] ); } /** * Write image to file * * @param string File location (including file name) * @return @e boolean */ public function writeImage( $imagePath ) { //--------------------------------------------------------- // Remove image if it exists //--------------------------------------------------------- if( is_file( $imagePath ) ) { @unlink( $imagePath ); } //--------------------------------------------------------- // Write file and verify //--------------------------------------------------------- switch( $this->image_extension ) { case 'gif': @imagegif( $this->image, $imagePath ); break; case 'jpeg': case 'jpg': case 'jpe': @imagejpeg( $this->image, $imagePath, $this->quality['jpg'] ); break; case 'png': @imagepng( $this->image, $imagePath, $this->quality['png'] ); break; } if ( ! is_file( $imagePath ) ) { $this->error = 'unable_to_write_image'; return false; } //--------------------------------------------------------- // Chmod 777 //--------------------------------------------------------- @chmod( $imagePath, IPS_FILE_PERMISSION ); //--------------------------------------------------------- // Destroy image resource //--------------------------------------------------------- @imagedestroy( $this->image ); return true; } /** * Print image to screen * * @return @e void */ public function displayImage() { //--------------------------------------------------------- // Send appropriate header and output image //--------------------------------------------------------- switch( $this->image_extension ) { case 'gif': @header('Content-type: image/gif'); @imagegif( $this->image ); break; case 'jpeg': case 'jpg': case 'jpe': @header('Content-Type: image/jpeg' ); @imagejpeg( $this->image, null, $this->quality['jpg'] ); break; case 'png': @header('Content-Type: image/png' ); @imagepng( $this->image, null, $this->quality['png'] ); break; } //--------------------------------------------------------- // Destroy image resource //--------------------------------------------------------- @imagedestroy( $this->image ); exit; } /** * Add watermark to image * * @param string Watermark image path * @param integer [Optional] Opacity 0-100 * @return @e boolean */ public function addWatermark( $path, $opacity=100 ) { //--------------------------------------------------------- // Verify input //--------------------------------------------------------- if( !$path ) { $this->error = 'no_watermark_path'; return false; } $type = strtolower( pathinfo( basename($path), PATHINFO_EXTENSION ) ); $opacity = $opacity > 100 ? 100 : ( $opacity < 0 ? 1 : $opacity ); if( !in_array( $type, $this->image_types ) ) { $this->error = 'bad_watermark_type'; return false; } //--------------------------------------------------------- // Create resource from watermark and verify //--------------------------------------------------------- switch( $type ) { case 'gif': $mark = imagecreatefromgif( $path ); break; case 'jpeg': case 'jpg': case 'jpe': $mark = imagecreatefromjpeg( $path ); break; case 'png': $mark = imagecreatefrompng( $path ); break; } if( !$mark ) { $this->error = 'image_creation_failed'; return false; } //--------------------------------------------------------- // Alpha blending.. //--------------------------------------------------------- switch( $this->image_extension ) { case 'jpeg': case 'jpg': case 'jpe': case 'png': @imagealphablending( $this->image, TRUE ); break; } //--------------------------------------------------------- // Get dimensions of watermark //--------------------------------------------------------- $img_info = @getimagesize( $path ); $locate_x = $this->cur_dimensions['width'] - $img_info[0]; $locate_y = $this->cur_dimensions['height'] - $img_info[1]; //--------------------------------------------------------- // Merge watermark image onto original image // @see http://us.php.net/manual/en/function.imagecopymerge.php#32393 //--------------------------------------------------------- /* PNGs like to be difficult: Bug #20788 */ if( $type == 'png' ) { /* Create a new image */ $new_img = imagecreatetruecolor( $this->cur_dimensions['width'], $this->cur_dimensions['height'] ); /* Setup Transparency */ imagealphablending( $new_img, false ); $transparent = imagecolorallocatealpha( $new_img, 0, 0, 0, 127 ); imagefill( $new_img, 0, 0, $transparent ); imagesavealpha( $new_img, true ); imagealphablending( $new_img, true ); /* Copy the main image into the new image */ imagecopyresampled( $new_img, $this->image, 0, 0, 0, 0, $this->cur_dimensions['width'], $this->cur_dimensions['height'], $this->cur_dimensions['width'], $this->cur_dimensions['height'] ); /* Copy the watermark onto the new image */ imagecopyresampled( $new_img, $mark, $locate_x, $locate_y, 0, 0, $img_info[0], $img_info[1], $img_info[0], $img_info[1] ); /* Set the image */ $this->image = $new_img; } else { @imagecopymerge( $this->image, $mark, $locate_x, $locate_y, 0, 0, $img_info[0], $img_info[1], intval($opacity) ); } //--------------------------------------------------------- // And alpha blending again... //--------------------------------------------------------- switch( $this->image_extension ) { case 'png': @imagealphablending( $this->image, FALSE ); @imagesavealpha( $this->image, TRUE ); break; } //--------------------------------------------------------- // Destroy watermark image resource and return //--------------------------------------------------------- imagedestroy( $mark ); $this->force_resize = true; return true; } /** * Add copyright text to image * * @param string Copyright text to add * @param array [Optional] Text options (color, halign, valign, padding, font [1-5]) * @return @e boolean */ public function addCopyrightText( $text, $textOpts=array() ) { //--------------------------------------------------------- // Verify input //--------------------------------------------------------- if( !$text ) { $this->error = 'no_text_for_copyright'; return false; } $font = $textOpts['font'] ? $textOpts['font'] : 3; //--------------------------------------------------------- // Colors input as hex...convert to rgb //--------------------------------------------------------- $color = $textOpts['color'] ? array( hexdec( substr( ltrim( $textOpts['color'], '#' ), 0, 2 ) ), hexdec( substr( ltrim( $textOpts['color'], '#' ), 2, 2 ) ), hexdec( substr( ltrim( $textOpts['color'], '#' ), 5, 2 ) ) ) : array( 255, 255, 255 ); $width = $this->cur_dimensions['width'] - 10; $halign = ( isset($textOpts['halign']) AND in_array( $textOpts['halign'], array( 'right', 'center', 'left' ) ) ) ? $textOpts['halign'] : 'right'; $valign = ( isset($textOpts['valign']) AND in_array( $textOpts['valign'], array( 'top', 'middle', 'bottom' ) ) ) ? $textOpts['valign'] : 'bottom'; $padding = $textOpts['padding'] ? $textOpts['padding'] : 5; //--------------------------------------------------------- // Get some size info and set properties //--------------------------------------------------------- $fontwidth = imagefontwidth($font); $fontheight = imagefontheight($font); $margin = floor($padding / 2 ); if ( $width > 0 ) { $maxcharsperline = floor( ($width - ($margin * 2)) / $fontwidth); if( $maxcharsperline ) { $text = wordwrap( $text, $maxcharsperline, "\n", 1 ); } } $lines = explode( "\n", $text ); //--------------------------------------------------------- // Top, middle or bottom? //--------------------------------------------------------- switch( $valign ) { case "middle": $y = ( imagesy($this->image) - ( $fontheight * count($lines) ) ) / 2; break; case "bottom": $y = imagesy($this->image) - ( ( $fontheight * count($lines) ) + $margin ); break; default: $y = $margin; break; } //--------------------------------------------------------- // Allocate colors for text/bg //--------------------------------------------------------- $color = imagecolorallocate( $this->image, $color[0], $color[1], $color[2] ); $rect_back = imagecolorallocate( $this->image, 0,0,0 ); //--------------------------------------------------------- // Switch on horizontal position and write text lines //--------------------------------------------------------- switch( $halign ) { case "right": while( list($numl, $line) = each($lines) ) { imagefilledrectangle( $this->image, ( imagesx($this->image) - $fontwidth * strlen($line) ) - $margin, $y, imagesx($this->image) - 1, imagesy($this->image) - 1, $rect_back ); imagestring( $this->image, $font, ( imagesx($this->image) - $fontwidth * strlen($line) ) - $margin, $y, $line, $color ); $y += $fontheight; } break; case "center": while( list($numl, $line) = each($lines) ) { imagefilledrectangle( $this->image, floor( ( imagesx($this->image) - $fontwidth * strlen($line) ) / 2 ), $y, imagesx($this->image), imagesy($this->image), $rect_back ); imagestring( $this->image, $font, floor( ( imagesx($this->image) - $fontwidth * strlen($line) ) / 2 ), $y, $line, $color ); $y += $fontheight; } break; default: while( list($numl, $line) = each($lines) ) { imagefilledrectangle( $this->image, $margin, $y, imagesx($this->image), imagesy($this->image), $rect_back ); imagestring( $this->image, $font, $margin, $y, $line, $color ); $y += $fontheight; } break; } $this->force_resize = true; return true; } }