MediaWiki  REL1_19
Bitmap.php
Go to the documentation of this file.
00001 <?php
00014 class BitmapHandler extends ImageHandler {
00022         function normaliseParams( $image, &$params ) {
00023                 if ( !parent::normaliseParams( $image, $params ) ) {
00024                         return false;
00025                 }
00026 
00027                 # Obtain the source, pre-rotation dimensions
00028                 $srcWidth = $image->getWidth( $params['page'] );
00029                 $srcHeight = $image->getHeight( $params['page'] );
00030 
00031                 # Don't make an image bigger than the source
00032                 if ( $params['physicalWidth'] >= $srcWidth ) {
00033                         $params['physicalWidth'] = $srcWidth;
00034                         $params['physicalHeight'] = $srcHeight;
00035 
00036                         # Skip scaling limit checks if no scaling is required
00037                         # due to requested size being bigger than source.
00038                         if ( !$image->mustRender() ) {
00039                                 return true;
00040                         }
00041                 }
00042 
00043                 # Check if the file is smaller than the maximum image area for thumbnailing
00044                 $checkImageAreaHookResult = null;
00045                 wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) );
00046                 if ( is_null( $checkImageAreaHookResult ) ) {
00047                         global $wgMaxImageArea;
00048 
00049                         if ( $srcWidth * $srcHeight > $wgMaxImageArea &&
00050                                         !( $image->getMimeType() == 'image/jpeg' &&
00051                                                 self::getScalerType( false, false ) == 'im' ) ) {
00052                                 # Only ImageMagick can efficiently downsize jpg images without loading
00053                                 # the entire file in memory
00054                                 return false;
00055                         }
00056                 } else {
00057                         return $checkImageAreaHookResult;
00058                 }
00059 
00060                 return true;
00061         }
00062 
00063 
00076         public function extractPreRotationDimensions( $params, $rotation ) {
00077                 if ( $rotation == 90 || $rotation == 270 ) {
00078                         # We'll resize before rotation, so swap the dimensions again
00079                         $width = $params['physicalHeight'];
00080                         $height = $params['physicalWidth'];
00081                 } else {
00082                         $width = $params['physicalWidth'];
00083                         $height = $params['physicalHeight'];
00084                 }
00085                 return array( $width, $height );
00086         }
00087 
00088 
00096         function getImageArea( $image ) {
00097                 return $image->getWidth() * $image->getHeight();
00098         }
00099 
00108         function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
00109                 if ( !$this->normaliseParams( $image, $params ) ) {
00110                         return new TransformParameterError( $params );
00111                 }
00112                 # Create a parameter array to pass to the scaler
00113                 $scalerParams = array(
00114                         # The size to which the image will be resized
00115                         'physicalWidth' => $params['physicalWidth'],
00116                         'physicalHeight' => $params['physicalHeight'],
00117                         'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}",
00118                         # The size of the image on the page
00119                         'clientWidth' => $params['width'],
00120                         'clientHeight' => $params['height'],
00121                         # Comment as will be added to the EXIF of the thumbnail
00122                         'comment' => isset( $params['descriptionUrl'] ) ?
00123                                 "File source: {$params['descriptionUrl']}" : '',
00124                         # Properties of the original image
00125                         'srcWidth' => $image->getWidth(),
00126                         'srcHeight' => $image->getHeight(),
00127                         'mimeType' => $image->getMimeType(),
00128                         'dstPath' => $dstPath,
00129                         'dstUrl' => $dstUrl,
00130                 );
00131 
00132                 # Determine scaler type
00133                 $scaler = self::getScalerType( $dstPath );
00134 
00135                 wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" );
00136 
00137                 if ( !$image->mustRender() &&
00138                                 $scalerParams['physicalWidth'] == $scalerParams['srcWidth']
00139                                 && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) {
00140 
00141                         # normaliseParams (or the user) wants us to return the unscaled image
00142                         wfDebug( __METHOD__ . ": returning unscaled image\n" );
00143                         return $this->getClientScalingThumbnailImage( $image, $scalerParams );
00144                 }
00145 
00146 
00147                 if ( $scaler == 'client' ) {
00148                         # Client-side image scaling, use the source URL
00149                         # Using the destination URL in a TRANSFORM_LATER request would be incorrect
00150                         return $this->getClientScalingThumbnailImage( $image, $scalerParams );
00151                 }
00152 
00153                 if ( $flags & self::TRANSFORM_LATER ) {
00154                         wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
00155                         return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
00156                                 $scalerParams['clientHeight'], false );
00157                 }
00158 
00159                 # Try to make a target path for the thumbnail
00160                 if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
00161                         wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" );
00162                         return $this->getClientScalingThumbnailImage( $image, $scalerParams );
00163                 }
00164 
00165                 # Transform functions and binaries need a FS source file
00166                 $scalerParams['srcPath'] = $image->getLocalRefPath();
00167 
00168                 # Try a hook
00169                 $mto = null;
00170                 wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
00171                 if ( !is_null( $mto ) ) {
00172                         wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" );
00173                         $scaler = 'hookaborted';
00174                 }
00175 
00176                 switch ( $scaler ) {
00177                         case 'hookaborted':
00178                                 # Handled by the hook above
00179                                 $err = $mto->isError() ? $mto : false;
00180                                 break;
00181                         case 'im':
00182                                 $err = $this->transformImageMagick( $image, $scalerParams );
00183                                 break;
00184                         case 'custom':
00185                                 $err = $this->transformCustom( $image, $scalerParams );
00186                                 break;
00187                         case 'imext':
00188                                 $err = $this->transformImageMagickExt( $image, $scalerParams );
00189                                 break;
00190                         case 'gd':
00191                         default:
00192                                 $err = $this->transformGd( $image, $scalerParams );
00193                                 break;
00194                 }
00195 
00196                 # Remove the file if a zero-byte thumbnail was created, or if there was an error
00197                 $removed = $this->removeBadFile( $dstPath, (bool)$err );
00198                 if ( $err ) {
00199                         # transform returned MediaTransforError
00200                         return $err;
00201                 } elseif ( $removed ) {
00202                         # Thumbnail was zero-byte and had to be removed
00203                         return new MediaTransformError( 'thumbnail_error',
00204                                 $scalerParams['clientWidth'], $scalerParams['clientHeight'] );
00205                 } elseif ( $mto ) {
00206                         return $mto;
00207                 } else {
00208                         return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
00209                                 $scalerParams['clientHeight'], $dstPath );
00210                 }
00211         }
00212 
00219         protected static function getScalerType( $dstPath, $checkDstPath = true ) {
00220                 global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
00221 
00222                 if ( !$dstPath && $checkDstPath ) {
00223                         # No output path available, client side scaling only
00224                         $scaler = 'client';
00225                 } elseif ( !$wgUseImageResize ) {
00226                         $scaler = 'client';
00227                 } elseif ( $wgUseImageMagick ) {
00228                         $scaler = 'im';
00229                 } elseif ( $wgCustomConvertCommand ) {
00230                         $scaler = 'custom';
00231                 } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
00232                         $scaler = 'gd';
00233                 } elseif ( class_exists( 'Imagick' ) ) {
00234                         $scaler = 'imext';
00235                 } else {
00236                         $scaler = 'client';
00237                 }
00238                 return $scaler;
00239         }
00240 
00251         protected function getClientScalingThumbnailImage( $image, $params ) {
00252                 return new ThumbnailImage( $image, $image->getURL(),
00253                         $params['clientWidth'], $params['clientHeight'], null );
00254         }
00255 
00264         protected function transformImageMagick( $image, $params ) {
00265                 # use ImageMagick
00266                 global $wgSharpenReductionThreshold, $wgSharpenParameter,
00267                         $wgMaxAnimatedGifArea,
00268                         $wgImageMagickTempDir, $wgImageMagickConvertCommand;
00269 
00270                 $quality = array();
00271                 $sharpen = array();
00272                 $scene = false;
00273                 $animation_pre = array();
00274                 $animation_post = array();
00275                 $decoderHint = array();
00276                 if ( $params['mimeType'] == 'image/jpeg' ) {
00277                         $quality = array( '-quality', '80' ); // 80%
00278                         # Sharpening, see bug 6193
00279                         if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
00280                                         / ( $params['srcWidth'] + $params['srcHeight'] )
00281                                         < $wgSharpenReductionThreshold ) {
00282                                 $sharpen = array( '-sharpen', $wgSharpenParameter );
00283                         }
00284                         if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) {
00285                                 // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
00286                                 $decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" );
00287                         }
00288 
00289                 } elseif ( $params['mimeType'] == 'image/png' ) {
00290                         $quality = array( '-quality', '95' ); // zlib 9, adaptive filtering
00291 
00292                 } elseif ( $params['mimeType'] == 'image/gif' ) {
00293                         if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
00294                                 // Extract initial frame only; we're so big it'll
00295                                 // be a total drag. :P
00296                                 $scene = 0;
00297 
00298                         } elseif ( $this->isAnimatedImage( $image ) ) {
00299                                 // Coalesce is needed to scale animated GIFs properly (bug 1017).
00300                                 $animation_pre = array( '-coalesce' );
00301                                 // We optimize the output, but -optimize is broken,
00302                                 // use optimizeTransparency instead (bug 11822)
00303                                 if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) {
00304                                         $animation_post = array( '-fuzz', '5%', '-layers', 'optimizeTransparency' );
00305                                 }
00306                         }
00307                 } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
00308                         $animation_post = array( '-layers', 'merge' );
00309                 }
00310 
00311                 // Use one thread only, to avoid deadlock bugs on OOM
00312                 $env = array( 'OMP_NUM_THREADS' => 1 );
00313                 if ( strval( $wgImageMagickTempDir ) !== '' ) {
00314                         $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir;
00315                 }
00316 
00317                 $rotation = $this->getRotation( $image );
00318                 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
00319 
00320                 $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge(
00321                         array( $wgImageMagickConvertCommand ),
00322                         $quality,
00323                         // Specify white background color, will be used for transparent images
00324                         // in Internet Explorer/Windows instead of default black.
00325                         array( '-background', 'white' ),
00326                         $decoderHint,
00327                         array( $this->escapeMagickInput( $params['srcPath'], $scene ) ),
00328                         $animation_pre,
00329                         // For the -thumbnail option a "!" is needed to force exact size,
00330                         // or ImageMagick may decide your ratio is wrong and slice off
00331                         // a pixel.
00332                         array( '-thumbnail', "{$width}x{$height}!" ),
00333                         // Add the source url as a comment to the thumb, but don't add the flag if there's no comment
00334                         ( $params['comment'] !== ''
00335                                 ? array( '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) )
00336                                 : array() ),
00337                         array( '-depth', 8 ),
00338                         $sharpen,
00339                         array( '-rotate', "-$rotation" ),
00340                         $animation_post,
00341                         array( $this->escapeMagickOutput( $params['dstPath'] ) ) ) ) . " 2>&1";
00342 
00343                 wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" );
00344                 wfProfileIn( 'convert' );
00345                 $retval = 0;
00346                 $err = wfShellExec( $cmd, $retval, $env );
00347                 wfProfileOut( 'convert' );
00348 
00349                 if ( $retval !== 0 ) {
00350                         $this->logErrorForExternalProcess( $retval, $err, $cmd );
00351                         return $this->getMediaTransformError( $params, $err );
00352                 }
00353 
00354                 return false; # No error
00355         }
00356 
00365         protected function transformImageMagickExt( $image, $params ) {
00366                 global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea;
00367 
00368                 try {
00369                         $im = new Imagick();
00370                         $im->readImage( $params['srcPath'] );
00371 
00372                         if ( $params['mimeType'] == 'image/jpeg' ) {
00373                                 // Sharpening, see bug 6193
00374                                 if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
00375                                                 / ( $params['srcWidth'] + $params['srcHeight'] )
00376                                                 < $wgSharpenReductionThreshold ) {
00377                                         // Hack, since $wgSharpenParamater is written specifically for the command line convert
00378                                         list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
00379                                         $im->sharpenImage( $radius, $sigma );
00380                                 }
00381                                 $im->setCompressionQuality( 80 );
00382                         } elseif( $params['mimeType'] == 'image/png' ) {
00383                                 $im->setCompressionQuality( 95 );
00384                         } elseif ( $params['mimeType'] == 'image/gif' ) {
00385                                 if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
00386                                         // Extract initial frame only; we're so big it'll
00387                                         // be a total drag. :P
00388                                         $im->setImageScene( 0 );
00389                                 } elseif ( $this->isAnimatedImage( $image ) ) {
00390                                         // Coalesce is needed to scale animated GIFs properly (bug 1017).
00391                                         $im = $im->coalesceImages();
00392                                 }
00393                         }
00394 
00395                         $rotation = $this->getRotation( $image );
00396                         list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
00397 
00398                         $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
00399 
00400                         // Call Imagick::thumbnailImage on each frame
00401                         foreach ( $im as $i => $frame ) {
00402                                 if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
00403                                         return $this->getMediaTransformError( $params, "Error scaling frame $i" );
00404                                 }
00405                         }
00406                         $im->setImageDepth( 8 );
00407 
00408                         if ( $rotation ) {
00409                                 if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) {
00410                                         return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
00411                                 }
00412                         }
00413 
00414                         if ( $this->isAnimatedImage( $image ) ) {
00415                                 wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
00416                                 // This is broken somehow... can't find out how to fix it
00417                                 $result = $im->writeImages( $params['dstPath'], true );
00418                         } else {
00419                                 $result = $im->writeImage( $params['dstPath'] );
00420                         }
00421                         if ( !$result ) {
00422                                 return $this->getMediaTransformError( $params,
00423                                         "Unable to write thumbnail to {$params['dstPath']}" );
00424                         }
00425 
00426                 } catch ( ImagickException $e ) {
00427                         return $this->getMediaTransformError( $params, $e->getMessage() );
00428                 }
00429 
00430                 return false;
00431 
00432         }
00433 
00442         protected function transformCustom( $image, $params ) {
00443                 # Use a custom convert command
00444                 global $wgCustomConvertCommand;
00445 
00446                 # Variables: %s %d %w %h
00447                 $src = wfEscapeShellArg( $params['srcPath'] );
00448                 $dst = wfEscapeShellArg( $params['dstPath'] );
00449                 $cmd = $wgCustomConvertCommand;
00450                 $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
00451                 $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ),
00452                         str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size
00453                 wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" );
00454                 wfProfileIn( 'convert' );
00455                 $retval = 0;
00456                 $err = wfShellExec( $cmd, $retval );
00457                 wfProfileOut( 'convert' );
00458 
00459                 if ( $retval !== 0 ) {
00460                         $this->logErrorForExternalProcess( $retval, $err, $cmd );
00461                         return $this->getMediaTransformError( $params, $err );
00462                 }
00463                 return false; # No error
00464         }
00465 
00473         protected function logErrorForExternalProcess( $retval, $err, $cmd ) {
00474                 wfDebugLog( 'thumbnail',
00475                         sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
00476                                         wfHostname(), $retval, trim( $err ), $cmd ) );
00477         }
00485         public function getMediaTransformError( $params, $errMsg ) {
00486                 return new MediaTransformError( 'thumbnail_error', $params['clientWidth'],
00487                                         $params['clientHeight'], $errMsg );
00488         }
00489 
00498         protected function transformGd( $image, $params ) {
00499                 # Use PHP's builtin GD library functions.
00500                 #
00501                 # First find out what kind of file this is, and select the correct
00502                 # input routine for this.
00503 
00504                 $typemap = array(
00505                         'image/gif'          => array( 'imagecreatefromgif',  'palette',   'imagegif'  ),
00506                         'image/jpeg'         => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
00507                         'image/png'          => array( 'imagecreatefrompng',  'bits',      'imagepng'  ),
00508                         'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette',   'imagewbmp'  ),
00509                         'image/xbm'          => array( 'imagecreatefromxbm',  'palette',   'imagexbm'  ),
00510                 );
00511                 if ( !isset( $typemap[$params['mimeType']] ) ) {
00512                         $err = 'Image type not supported';
00513                         wfDebug( "$err\n" );
00514                         $errMsg = wfMsg( 'thumbnail_image-type' );
00515                         return $this->getMediaTransformError( $params, $errMsg );
00516                 }
00517                 list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
00518 
00519                 if ( !function_exists( $loader ) ) {
00520                         $err = "Incomplete GD library configuration: missing function $loader";
00521                         wfDebug( "$err\n" );
00522                         $errMsg = wfMsg( 'thumbnail_gd-library', $loader );
00523                         return $this->getMediaTransformError( $params, $errMsg );
00524                 }
00525 
00526                 if ( !file_exists( $params['srcPath'] ) ) {
00527                         $err = "File seems to be missing: {$params['srcPath']}";
00528                         wfDebug( "$err\n" );
00529                         $errMsg = wfMsg( 'thumbnail_image-missing', $params['srcPath'] );
00530                         return $this->getMediaTransformError( $params, $errMsg );
00531                 }
00532 
00533                 $src_image = call_user_func( $loader, $params['srcPath'] );
00534 
00535                 $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0;
00536                 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation );
00537                 $dst_image = imagecreatetruecolor( $width, $height );
00538 
00539                 // Initialise the destination image to transparent instead of
00540                 // the default solid black, to support PNG and GIF transparency nicely
00541                 $background = imagecolorallocate( $dst_image, 0, 0, 0 );
00542                 imagecolortransparent( $dst_image, $background );
00543                 imagealphablending( $dst_image, false );
00544 
00545                 if ( $colorStyle == 'palette' ) {
00546                         // Don't resample for paletted GIF images.
00547                         // It may just uglify them, and completely breaks transparency.
00548                         imagecopyresized( $dst_image, $src_image,
00549                                 0, 0, 0, 0,
00550                                 $width, $height,
00551                                 imagesx( $src_image ), imagesy( $src_image ) );
00552                 } else {
00553                         imagecopyresampled( $dst_image, $src_image,
00554                                 0, 0, 0, 0,
00555                                 $width, $height,
00556                                 imagesx( $src_image ), imagesy( $src_image ) );
00557                 }
00558 
00559                 if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
00560                         $rot_image = imagerotate( $dst_image, $rotation, 0 );
00561                         imagedestroy( $dst_image );
00562                         $dst_image = $rot_image;
00563                 }
00564 
00565                 imagesavealpha( $dst_image, true );
00566 
00567                 call_user_func( $saveType, $dst_image, $params['dstPath'] );
00568                 imagedestroy( $dst_image );
00569                 imagedestroy( $src_image );
00570 
00571                 return false; # No error
00572         }
00573 
00578         function escapeMagickProperty( $s ) {
00579                 // Double the backslashes
00580                 $s = str_replace( '\\', '\\\\', $s );
00581                 // Double the percents
00582                 $s = str_replace( '%', '%%', $s );
00583                 // Escape initial - or @
00584                 if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) {
00585                         $s = '\\' . $s;
00586                 }
00587                 return $s;
00588         }
00589 
00605         function escapeMagickInput( $path, $scene = false ) {
00606                 # Die on initial metacharacters (caller should prepend path)
00607                 $firstChar = substr( $path, 0, 1 );
00608                 if ( $firstChar === '~' || $firstChar === '@' ) {
00609                         throw new MWException( __METHOD__ . ': cannot escape this path name' );
00610                 }
00611 
00612                 # Escape glob chars
00613                 $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path );
00614 
00615                 return $this->escapeMagickPath( $path, $scene );
00616         }
00617 
00622         function escapeMagickOutput( $path, $scene = false ) {
00623                 $path = str_replace( '%', '%%', $path );
00624                 return $this->escapeMagickPath( $path, $scene );
00625         }
00626 
00634         protected function escapeMagickPath( $path, $scene = false ) {
00635                 # Die on format specifiers (other than drive letters). The regex is
00636                 # meant to match all the formats you get from "convert -list format"
00637                 if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) {
00638                         if ( wfIsWindows() && is_dir( $m[0] ) ) {
00639                                 // OK, it's a drive letter
00640                                 // ImageMagick has a similar exception, see IsMagickConflict()
00641                         } else {
00642                                 throw new MWException( __METHOD__ . ': unexpected colon character in path name' );
00643                         }
00644                 }
00645 
00646                 # If there are square brackets, add a do-nothing scene specification
00647                 # to force a literal interpretation
00648                 if ( $scene === false ) {
00649                         if ( strpos( $path, '[' ) !== false ) {
00650                                 $path .= '[0--1]';
00651                         }
00652                 } else {
00653                         $path .= "[$scene]";
00654                 }
00655                 return $path;
00656         }
00657 
00664         protected function getMagickVersion() {
00665                 global $wgMemc;
00666 
00667                 $cache = $wgMemc->get( "imagemagick-version" );
00668                 if ( !$cache ) {
00669                         global $wgImageMagickConvertCommand;
00670                         $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version';
00671                         wfDebug( __METHOD__ . ": Running convert -version\n" );
00672                         $retval = '';
00673                         $return = wfShellExec( $cmd, $retval );
00674                         $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches );
00675                         if ( $x != 1 ) {
00676                                 wfDebug( __METHOD__ . ": ImageMagick version check failed\n" );
00677                                 return null;
00678                         }
00679                         $wgMemc->set( "imagemagick-version", $matches[1], 3600 );
00680                         return $matches[1];
00681                 }
00682                 return $cache;
00683         }
00684 
00685         static function imageJpegWrapper( $dst_image, $thumbPath ) {
00686                 imageinterlace( $dst_image );
00687                 imagejpeg( $dst_image, $thumbPath, 95 );
00688         }
00689 
00705         public function getRotation( $file ) {
00706                 return 0;
00707         }
00708 
00714         public static function canRotate() {
00715                 $scaler = self::getScalerType( null, false );
00716                 switch ( $scaler ) {
00717                         case 'im':
00718                                 # ImageMagick supports autorotation
00719                                 return true;
00720                         case 'imext':
00721                                 # Imagick::rotateImage
00722                                 return true;
00723                         case 'gd':
00724                                 # GD's imagerotate function is used to rotate images, but not
00725                                 # all precompiled PHP versions have that function
00726                                 return function_exists( 'imagerotate' );
00727                         default:
00728                                 # Other scalers don't support rotation
00729                                 return false;
00730                 }
00731         }
00732 
00740         public function mustRender( $file ) {
00741                 return self::canRotate() && $this->getRotation( $file ) != 0;
00742         }
00743 }