MediaWiki
REL1_20
|
00001 <?php 00029 class BitmapHandler extends ImageHandler { 00037 function normaliseParams( $image, &$params ) { 00038 if ( !parent::normaliseParams( $image, $params ) ) { 00039 return false; 00040 } 00041 00042 # Obtain the source, pre-rotation dimensions 00043 $srcWidth = $image->getWidth( $params['page'] ); 00044 $srcHeight = $image->getHeight( $params['page'] ); 00045 00046 # Don't make an image bigger than the source 00047 if ( $params['physicalWidth'] >= $srcWidth ) { 00048 $params['physicalWidth'] = $srcWidth; 00049 $params['physicalHeight'] = $srcHeight; 00050 00051 # Skip scaling limit checks if no scaling is required 00052 # due to requested size being bigger than source. 00053 if ( !$image->mustRender() ) { 00054 return true; 00055 } 00056 } 00057 00058 # Check if the file is smaller than the maximum image area for thumbnailing 00059 $checkImageAreaHookResult = null; 00060 wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) ); 00061 if ( is_null( $checkImageAreaHookResult ) ) { 00062 global $wgMaxImageArea; 00063 00064 if ( $srcWidth * $srcHeight > $wgMaxImageArea && 00065 !( $image->getMimeType() == 'image/jpeg' && 00066 self::getScalerType( false, false ) == 'im' ) ) { 00067 # Only ImageMagick can efficiently downsize jpg images without loading 00068 # the entire file in memory 00069 return false; 00070 } 00071 } else { 00072 return $checkImageAreaHookResult; 00073 } 00074 00075 return true; 00076 } 00077 00078 00091 public function extractPreRotationDimensions( $params, $rotation ) { 00092 if ( $rotation == 90 || $rotation == 270 ) { 00093 # We'll resize before rotation, so swap the dimensions again 00094 $width = $params['physicalHeight']; 00095 $height = $params['physicalWidth']; 00096 } else { 00097 $width = $params['physicalWidth']; 00098 $height = $params['physicalHeight']; 00099 } 00100 return array( $width, $height ); 00101 } 00102 00103 00111 function getImageArea( $image ) { 00112 return $image->getWidth() * $image->getHeight(); 00113 } 00114 00123 function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { 00124 if ( !$this->normaliseParams( $image, $params ) ) { 00125 return new TransformParameterError( $params ); 00126 } 00127 # Create a parameter array to pass to the scaler 00128 $scalerParams = array( 00129 # The size to which the image will be resized 00130 'physicalWidth' => $params['physicalWidth'], 00131 'physicalHeight' => $params['physicalHeight'], 00132 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", 00133 # The size of the image on the page 00134 'clientWidth' => $params['width'], 00135 'clientHeight' => $params['height'], 00136 # Comment as will be added to the EXIF of the thumbnail 00137 'comment' => isset( $params['descriptionUrl'] ) ? 00138 "File source: {$params['descriptionUrl']}" : '', 00139 # Properties of the original image 00140 'srcWidth' => $image->getWidth(), 00141 'srcHeight' => $image->getHeight(), 00142 'mimeType' => $image->getMimeType(), 00143 'dstPath' => $dstPath, 00144 'dstUrl' => $dstUrl, 00145 ); 00146 00147 # Determine scaler type 00148 $scaler = self::getScalerType( $dstPath ); 00149 00150 wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" ); 00151 00152 if ( !$image->mustRender() && 00153 $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] 00154 && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) { 00155 00156 # normaliseParams (or the user) wants us to return the unscaled image 00157 wfDebug( __METHOD__ . ": returning unscaled image\n" ); 00158 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00159 } 00160 00161 00162 if ( $scaler == 'client' ) { 00163 # Client-side image scaling, use the source URL 00164 # Using the destination URL in a TRANSFORM_LATER request would be incorrect 00165 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00166 } 00167 00168 if ( $flags & self::TRANSFORM_LATER ) { 00169 wfDebug( __METHOD__ . ": Transforming later per flags.\n" ); 00170 $params = array( 00171 'width' => $scalerParams['clientWidth'], 00172 'height' => $scalerParams['clientHeight'] 00173 ); 00174 return new ThumbnailImage( $image, $dstUrl, false, $params ); 00175 } 00176 00177 # Try to make a target path for the thumbnail 00178 if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) { 00179 wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" ); 00180 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00181 } 00182 00183 # Transform functions and binaries need a FS source file 00184 $scalerParams['srcPath'] = $image->getLocalRefPath(); 00185 00186 # Try a hook 00187 $mto = null; 00188 wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) ); 00189 if ( !is_null( $mto ) ) { 00190 wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" ); 00191 $scaler = 'hookaborted'; 00192 } 00193 00194 switch ( $scaler ) { 00195 case 'hookaborted': 00196 # Handled by the hook above 00197 $err = $mto->isError() ? $mto : false; 00198 break; 00199 case 'im': 00200 $err = $this->transformImageMagick( $image, $scalerParams ); 00201 break; 00202 case 'custom': 00203 $err = $this->transformCustom( $image, $scalerParams ); 00204 break; 00205 case 'imext': 00206 $err = $this->transformImageMagickExt( $image, $scalerParams ); 00207 break; 00208 case 'gd': 00209 default: 00210 $err = $this->transformGd( $image, $scalerParams ); 00211 break; 00212 } 00213 00214 # Remove the file if a zero-byte thumbnail was created, or if there was an error 00215 $removed = $this->removeBadFile( $dstPath, (bool)$err ); 00216 if ( $err ) { 00217 # transform returned MediaTransforError 00218 return $err; 00219 } elseif ( $removed ) { 00220 # Thumbnail was zero-byte and had to be removed 00221 return new MediaTransformError( 'thumbnail_error', 00222 $scalerParams['clientWidth'], $scalerParams['clientHeight'] ); 00223 } elseif ( $mto ) { 00224 return $mto; 00225 } else { 00226 $params = array( 00227 'width' => $scalerParams['clientWidth'], 00228 'height' => $scalerParams['clientHeight'] 00229 ); 00230 return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); 00231 } 00232 } 00233 00240 protected static function getScalerType( $dstPath, $checkDstPath = true ) { 00241 global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand; 00242 00243 if ( !$dstPath && $checkDstPath ) { 00244 # No output path available, client side scaling only 00245 $scaler = 'client'; 00246 } elseif ( !$wgUseImageResize ) { 00247 $scaler = 'client'; 00248 } elseif ( $wgUseImageMagick ) { 00249 $scaler = 'im'; 00250 } elseif ( $wgCustomConvertCommand ) { 00251 $scaler = 'custom'; 00252 } elseif ( function_exists( 'imagecreatetruecolor' ) ) { 00253 $scaler = 'gd'; 00254 } elseif ( class_exists( 'Imagick' ) ) { 00255 $scaler = 'imext'; 00256 } else { 00257 $scaler = 'client'; 00258 } 00259 return $scaler; 00260 } 00261 00272 protected function getClientScalingThumbnailImage( $image, $scalerParams ) { 00273 $params = array( 00274 'width' => $scalerParams['clientWidth'], 00275 'height' => $scalerParams['clientHeight'] 00276 ); 00277 return new ThumbnailImage( $image, $image->getURL(), null, $params ); 00278 } 00279 00288 protected function transformImageMagick( $image, $params ) { 00289 # use ImageMagick 00290 global $wgSharpenReductionThreshold, $wgSharpenParameter, 00291 $wgMaxAnimatedGifArea, 00292 $wgImageMagickTempDir, $wgImageMagickConvertCommand; 00293 00294 $quality = ''; 00295 $sharpen = ''; 00296 $scene = false; 00297 $animation_pre = ''; 00298 $animation_post = ''; 00299 $decoderHint = ''; 00300 if ( $params['mimeType'] == 'image/jpeg' ) { 00301 $quality = "-quality 80"; // 80% 00302 # Sharpening, see bug 6193 00303 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) 00304 / ( $params['srcWidth'] + $params['srcHeight'] ) 00305 < $wgSharpenReductionThreshold ) { 00306 $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter ); 00307 } 00308 if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) { 00309 // JPEG decoder hint to reduce memory, available since IM 6.5.6-2 00310 $decoderHint = "-define jpeg:size={$params['physicalDimensions']}"; 00311 } 00312 00313 } elseif ( $params['mimeType'] == 'image/png' ) { 00314 $quality = "-quality 95"; // zlib 9, adaptive filtering 00315 00316 } elseif ( $params['mimeType'] == 'image/gif' ) { 00317 if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) { 00318 // Extract initial frame only; we're so big it'll 00319 // be a total drag. :P 00320 $scene = 0; 00321 00322 } elseif ( $this->isAnimatedImage( $image ) ) { 00323 // Coalesce is needed to scale animated GIFs properly (bug 1017). 00324 $animation_pre = '-coalesce'; 00325 // We optimize the output, but -optimize is broken, 00326 // use optimizeTransparency instead (bug 11822) 00327 if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) { 00328 $animation_post = '-fuzz 5% -layers optimizeTransparency'; 00329 } 00330 } 00331 } elseif ( $params['mimeType'] == 'image/x-xcf' ) { 00332 $animation_post = '-layers merge'; 00333 } 00334 00335 // Use one thread only, to avoid deadlock bugs on OOM 00336 $env = array( 'OMP_NUM_THREADS' => 1 ); 00337 if ( strval( $wgImageMagickTempDir ) !== '' ) { 00338 $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir; 00339 } 00340 00341 $rotation = $this->getRotation( $image ); 00342 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00343 00344 $cmd = 00345 wfEscapeShellArg( $wgImageMagickConvertCommand ) . 00346 // Specify white background color, will be used for transparent images 00347 // in Internet Explorer/Windows instead of default black. 00348 " {$quality} -background white" . 00349 " {$decoderHint} " . 00350 wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) . 00351 " {$animation_pre}" . 00352 // For the -thumbnail option a "!" is needed to force exact size, 00353 // or ImageMagick may decide your ratio is wrong and slice off 00354 // a pixel. 00355 " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) . 00356 // Add the source url as a comment to the thumb, but don't add the flag if there's no comment 00357 ( $params['comment'] !== '' 00358 ? " -set comment " . wfEscapeShellArg( $this->escapeMagickProperty( $params['comment'] ) ) 00359 : '' ) . 00360 " -depth 8 $sharpen " . 00361 " -rotate -$rotation " . 00362 " {$animation_post} " . 00363 wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1"; 00364 00365 wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" ); 00366 wfProfileIn( 'convert' ); 00367 $retval = 0; 00368 $err = wfShellExec( $cmd, $retval, $env ); 00369 wfProfileOut( 'convert' ); 00370 00371 if ( $retval !== 0 ) { 00372 $this->logErrorForExternalProcess( $retval, $err, $cmd ); 00373 return $this->getMediaTransformError( $params, $err ); 00374 } 00375 00376 return false; # No error 00377 } 00378 00387 protected function transformImageMagickExt( $image, $params ) { 00388 global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea; 00389 00390 try { 00391 $im = new Imagick(); 00392 $im->readImage( $params['srcPath'] ); 00393 00394 if ( $params['mimeType'] == 'image/jpeg' ) { 00395 // Sharpening, see bug 6193 00396 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) 00397 / ( $params['srcWidth'] + $params['srcHeight'] ) 00398 < $wgSharpenReductionThreshold ) { 00399 // Hack, since $wgSharpenParamater is written specifically for the command line convert 00400 list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter ); 00401 $im->sharpenImage( $radius, $sigma ); 00402 } 00403 $im->setCompressionQuality( 80 ); 00404 } elseif( $params['mimeType'] == 'image/png' ) { 00405 $im->setCompressionQuality( 95 ); 00406 } elseif ( $params['mimeType'] == 'image/gif' ) { 00407 if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) { 00408 // Extract initial frame only; we're so big it'll 00409 // be a total drag. :P 00410 $im->setImageScene( 0 ); 00411 } elseif ( $this->isAnimatedImage( $image ) ) { 00412 // Coalesce is needed to scale animated GIFs properly (bug 1017). 00413 $im = $im->coalesceImages(); 00414 } 00415 } 00416 00417 $rotation = $this->getRotation( $image ); 00418 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00419 00420 $im->setImageBackgroundColor( new ImagickPixel( 'white' ) ); 00421 00422 // Call Imagick::thumbnailImage on each frame 00423 foreach ( $im as $i => $frame ) { 00424 if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) { 00425 return $this->getMediaTransformError( $params, "Error scaling frame $i" ); 00426 } 00427 } 00428 $im->setImageDepth( 8 ); 00429 00430 if ( $rotation ) { 00431 if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) { 00432 return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" ); 00433 } 00434 } 00435 00436 if ( $this->isAnimatedImage( $image ) ) { 00437 wfDebug( __METHOD__ . ": Writing animated thumbnail\n" ); 00438 // This is broken somehow... can't find out how to fix it 00439 $result = $im->writeImages( $params['dstPath'], true ); 00440 } else { 00441 $result = $im->writeImage( $params['dstPath'] ); 00442 } 00443 if ( !$result ) { 00444 return $this->getMediaTransformError( $params, 00445 "Unable to write thumbnail to {$params['dstPath']}" ); 00446 } 00447 00448 } catch ( ImagickException $e ) { 00449 return $this->getMediaTransformError( $params, $e->getMessage() ); 00450 } 00451 00452 return false; 00453 00454 } 00455 00464 protected function transformCustom( $image, $params ) { 00465 # Use a custom convert command 00466 global $wgCustomConvertCommand; 00467 00468 # Variables: %s %d %w %h 00469 $src = wfEscapeShellArg( $params['srcPath'] ); 00470 $dst = wfEscapeShellArg( $params['dstPath'] ); 00471 $cmd = $wgCustomConvertCommand; 00472 $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames 00473 $cmd = str_replace( '%h', $params['physicalHeight'], 00474 str_replace( '%w', $params['physicalWidth'], $cmd ) ); # Size 00475 wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" ); 00476 wfProfileIn( 'convert' ); 00477 $retval = 0; 00478 $err = wfShellExec( $cmd, $retval ); 00479 wfProfileOut( 'convert' ); 00480 00481 if ( $retval !== 0 ) { 00482 $this->logErrorForExternalProcess( $retval, $err, $cmd ); 00483 return $this->getMediaTransformError( $params, $err ); 00484 } 00485 return false; # No error 00486 } 00487 00495 protected function logErrorForExternalProcess( $retval, $err, $cmd ) { 00496 wfDebugLog( 'thumbnail', 00497 sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', 00498 wfHostname(), $retval, trim( $err ), $cmd ) ); 00499 } 00507 public function getMediaTransformError( $params, $errMsg ) { 00508 return new MediaTransformError( 'thumbnail_error', $params['clientWidth'], 00509 $params['clientHeight'], $errMsg ); 00510 } 00511 00520 protected function transformGd( $image, $params ) { 00521 # Use PHP's builtin GD library functions. 00522 # 00523 # First find out what kind of file this is, and select the correct 00524 # input routine for this. 00525 00526 $typemap = array( 00527 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), 00528 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ), 00529 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), 00530 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), 00531 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), 00532 ); 00533 if ( !isset( $typemap[$params['mimeType']] ) ) { 00534 $err = 'Image type not supported'; 00535 wfDebug( "$err\n" ); 00536 $errMsg = wfMessage( 'thumbnail_image-type' )->text(); 00537 return $this->getMediaTransformError( $params, $errMsg ); 00538 } 00539 list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']]; 00540 00541 if ( !function_exists( $loader ) ) { 00542 $err = "Incomplete GD library configuration: missing function $loader"; 00543 wfDebug( "$err\n" ); 00544 $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text(); 00545 return $this->getMediaTransformError( $params, $errMsg ); 00546 } 00547 00548 if ( !file_exists( $params['srcPath'] ) ) { 00549 $err = "File seems to be missing: {$params['srcPath']}"; 00550 wfDebug( "$err\n" ); 00551 $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text(); 00552 return $this->getMediaTransformError( $params, $errMsg ); 00553 } 00554 00555 $src_image = call_user_func( $loader, $params['srcPath'] ); 00556 00557 $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0; 00558 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00559 $dst_image = imagecreatetruecolor( $width, $height ); 00560 00561 // Initialise the destination image to transparent instead of 00562 // the default solid black, to support PNG and GIF transparency nicely 00563 $background = imagecolorallocate( $dst_image, 0, 0, 0 ); 00564 imagecolortransparent( $dst_image, $background ); 00565 imagealphablending( $dst_image, false ); 00566 00567 if ( $colorStyle == 'palette' ) { 00568 // Don't resample for paletted GIF images. 00569 // It may just uglify them, and completely breaks transparency. 00570 imagecopyresized( $dst_image, $src_image, 00571 0, 0, 0, 0, 00572 $width, $height, 00573 imagesx( $src_image ), imagesy( $src_image ) ); 00574 } else { 00575 imagecopyresampled( $dst_image, $src_image, 00576 0, 0, 0, 0, 00577 $width, $height, 00578 imagesx( $src_image ), imagesy( $src_image ) ); 00579 } 00580 00581 if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) { 00582 $rot_image = imagerotate( $dst_image, $rotation, 0 ); 00583 imagedestroy( $dst_image ); 00584 $dst_image = $rot_image; 00585 } 00586 00587 imagesavealpha( $dst_image, true ); 00588 00589 call_user_func( $saveType, $dst_image, $params['dstPath'] ); 00590 imagedestroy( $dst_image ); 00591 imagedestroy( $src_image ); 00592 00593 return false; # No error 00594 } 00595 00601 function escapeMagickProperty( $s ) { 00602 // Double the backslashes 00603 $s = str_replace( '\\', '\\\\', $s ); 00604 // Double the percents 00605 $s = str_replace( '%', '%%', $s ); 00606 // Escape initial - or @ 00607 if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) { 00608 $s = '\\' . $s; 00609 } 00610 return $s; 00611 } 00612 00629 function escapeMagickInput( $path, $scene = false ) { 00630 # Die on initial metacharacters (caller should prepend path) 00631 $firstChar = substr( $path, 0, 1 ); 00632 if ( $firstChar === '~' || $firstChar === '@' ) { 00633 throw new MWException( __METHOD__ . ': cannot escape this path name' ); 00634 } 00635 00636 # Escape glob chars 00637 $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path ); 00638 00639 return $this->escapeMagickPath( $path, $scene ); 00640 } 00641 00647 function escapeMagickOutput( $path, $scene = false ) { 00648 $path = str_replace( '%', '%%', $path ); 00649 return $this->escapeMagickPath( $path, $scene ); 00650 } 00651 00660 protected function escapeMagickPath( $path, $scene = false ) { 00661 # Die on format specifiers (other than drive letters). The regex is 00662 # meant to match all the formats you get from "convert -list format" 00663 if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) { 00664 if ( wfIsWindows() && is_dir( $m[0] ) ) { 00665 // OK, it's a drive letter 00666 // ImageMagick has a similar exception, see IsMagickConflict() 00667 } else { 00668 throw new MWException( __METHOD__ . ': unexpected colon character in path name' ); 00669 } 00670 } 00671 00672 # If there are square brackets, add a do-nothing scene specification 00673 # to force a literal interpretation 00674 if ( $scene === false ) { 00675 if ( strpos( $path, '[' ) !== false ) { 00676 $path .= '[0--1]'; 00677 } 00678 } else { 00679 $path .= "[$scene]"; 00680 } 00681 return $path; 00682 } 00683 00690 protected function getMagickVersion() { 00691 global $wgMemc; 00692 00693 $cache = $wgMemc->get( "imagemagick-version" ); 00694 if ( !$cache ) { 00695 global $wgImageMagickConvertCommand; 00696 $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version'; 00697 wfDebug( __METHOD__ . ": Running convert -version\n" ); 00698 $retval = ''; 00699 $return = wfShellExec( $cmd, $retval ); 00700 $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches ); 00701 if ( $x != 1 ) { 00702 wfDebug( __METHOD__ . ": ImageMagick version check failed\n" ); 00703 return null; 00704 } 00705 $wgMemc->set( "imagemagick-version", $matches[1], 3600 ); 00706 return $matches[1]; 00707 } 00708 return $cache; 00709 } 00710 00711 static function imageJpegWrapper( $dst_image, $thumbPath ) { 00712 imageinterlace( $dst_image ); 00713 imagejpeg( $dst_image, $thumbPath, 95 ); 00714 } 00715 00731 public function getRotation( $file ) { 00732 return 0; 00733 } 00734 00740 public static function canRotate() { 00741 $scaler = self::getScalerType( null, false ); 00742 switch ( $scaler ) { 00743 case 'im': 00744 # ImageMagick supports autorotation 00745 return true; 00746 case 'imext': 00747 # Imagick::rotateImage 00748 return true; 00749 case 'gd': 00750 # GD's imagerotate function is used to rotate images, but not 00751 # all precompiled PHP versions have that function 00752 return function_exists( 'imagerotate' ); 00753 default: 00754 # Other scalers don't support rotation 00755 return false; 00756 } 00757 } 00758 00766 public function mustRender( $file ) { 00767 return self::canRotate() && $this->getRotation( $file ) != 0; 00768 } 00769 }