MediaWiki  REL1_20
Bitmap.php
Go to the documentation of this file.
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 }