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