MediaWiki
REL1_23
|
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( 00061 'BitmapHandlerCheckImageArea', 00062 array( $image, &$params, &$checkImageAreaHookResult ) 00063 ); 00064 00065 if ( is_null( $checkImageAreaHookResult ) ) { 00066 global $wgMaxImageArea; 00067 00068 if ( $srcWidth * $srcHeight > $wgMaxImageArea 00069 && !( $image->getMimeType() == 'image/jpeg' 00070 && self::getScalerType( false, false ) == 'im' ) 00071 ) { 00072 # Only ImageMagick can efficiently downsize jpg images without loading 00073 # the entire file in memory 00074 return false; 00075 } 00076 } else { 00077 return $checkImageAreaHookResult; 00078 } 00079 00080 return true; 00081 } 00082 00095 public function extractPreRotationDimensions( $params, $rotation ) { 00096 if ( $rotation == 90 || $rotation == 270 ) { 00097 # We'll resize before rotation, so swap the dimensions again 00098 $width = $params['physicalHeight']; 00099 $height = $params['physicalWidth']; 00100 } else { 00101 $width = $params['physicalWidth']; 00102 $height = $params['physicalHeight']; 00103 } 00104 00105 return array( $width, $height ); 00106 } 00107 00116 function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { 00117 if ( !$this->normaliseParams( $image, $params ) ) { 00118 return new TransformParameterError( $params ); 00119 } 00120 # Create a parameter array to pass to the scaler 00121 $scalerParams = array( 00122 # The size to which the image will be resized 00123 'physicalWidth' => $params['physicalWidth'], 00124 'physicalHeight' => $params['physicalHeight'], 00125 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", 00126 # The size of the image on the page 00127 'clientWidth' => $params['width'], 00128 'clientHeight' => $params['height'], 00129 # Comment as will be added to the Exif of the thumbnail 00130 'comment' => isset( $params['descriptionUrl'] ) 00131 ? "File source: {$params['descriptionUrl']}" 00132 : '', 00133 # Properties of the original image 00134 'srcWidth' => $image->getWidth(), 00135 'srcHeight' => $image->getHeight(), 00136 'mimeType' => $image->getMimeType(), 00137 'dstPath' => $dstPath, 00138 'dstUrl' => $dstUrl, 00139 ); 00140 00141 # Determine scaler type 00142 $scaler = self::getScalerType( $dstPath ); 00143 00144 wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} " . 00145 "thumbnail at $dstPath using scaler $scaler\n" ); 00146 00147 if ( !$image->mustRender() && 00148 $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] 00149 && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] 00150 ) { 00151 00152 # normaliseParams (or the user) wants us to return the unscaled image 00153 wfDebug( __METHOD__ . ": returning unscaled image\n" ); 00154 00155 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00156 } 00157 00158 if ( $scaler == 'client' ) { 00159 # Client-side image scaling, use the source URL 00160 # Using the destination URL in a TRANSFORM_LATER request would be incorrect 00161 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00162 } 00163 00164 if ( $flags & self::TRANSFORM_LATER ) { 00165 wfDebug( __METHOD__ . ": Transforming later per flags.\n" ); 00166 $params = array( 00167 'width' => $scalerParams['clientWidth'], 00168 'height' => $scalerParams['clientHeight'] 00169 ); 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 " . 00177 "directory, falling back to client scaling\n" ); 00178 00179 return $this->getClientScalingThumbnailImage( $image, $scalerParams ); 00180 } 00181 00182 # Transform functions and binaries need a FS source file 00183 $scalerParams['srcPath'] = $image->getLocalRefPath(); 00184 if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy 00185 wfDebugLog( 'thumbnail', 00186 sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"', 00187 wfHostname(), $image->getName() ) ); 00188 00189 return new MediaTransformError( 'thumbnail_error', 00190 $scalerParams['clientWidth'], $scalerParams['clientHeight'], 00191 wfMessage( 'filemissing' )->text() 00192 ); 00193 } 00194 00195 # Try a hook 00196 $mto = null; 00197 wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) ); 00198 if ( !is_null( $mto ) ) { 00199 wfDebug( __METHOD__ . ": Hook to BitmapHandlerTransform created an mto\n" ); 00200 $scaler = 'hookaborted'; 00201 } 00202 00203 switch ( $scaler ) { 00204 case 'hookaborted': 00205 # Handled by the hook above 00206 00207 $err = $mto->isError() ? $mto : false; 00208 break; 00209 case 'im': 00210 $err = $this->transformImageMagick( $image, $scalerParams ); 00211 break; 00212 case 'custom': 00213 $err = $this->transformCustom( $image, $scalerParams ); 00214 break; 00215 case 'imext': 00216 $err = $this->transformImageMagickExt( $image, $scalerParams ); 00217 break; 00218 case 'gd': 00219 default: 00220 $err = $this->transformGd( $image, $scalerParams ); 00221 break; 00222 } 00223 00224 # Remove the file if a zero-byte thumbnail was created, or if there was an error 00225 $removed = $this->removeBadFile( $dstPath, (bool)$err ); 00226 if ( $err ) { 00227 # transform returned MediaTransforError 00228 return $err; 00229 } elseif ( $removed ) { 00230 # Thumbnail was zero-byte and had to be removed 00231 return new MediaTransformError( 'thumbnail_error', 00232 $scalerParams['clientWidth'], $scalerParams['clientHeight'], 00233 wfMessage( 'unknown-error' )->text() 00234 ); 00235 } elseif ( $mto ) { 00236 return $mto; 00237 } else { 00238 $params = array( 00239 'width' => $scalerParams['clientWidth'], 00240 'height' => $scalerParams['clientHeight'] 00241 ); 00242 00243 return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); 00244 } 00245 } 00246 00255 protected static function getScalerType( $dstPath, $checkDstPath = true ) { 00256 global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand; 00257 00258 if ( !$dstPath && $checkDstPath ) { 00259 # No output path available, client side scaling only 00260 $scaler = 'client'; 00261 } elseif ( !$wgUseImageResize ) { 00262 $scaler = 'client'; 00263 } elseif ( $wgUseImageMagick ) { 00264 $scaler = 'im'; 00265 } elseif ( $wgCustomConvertCommand ) { 00266 $scaler = 'custom'; 00267 } elseif ( function_exists( 'imagecreatetruecolor' ) ) { 00268 $scaler = 'gd'; 00269 } elseif ( class_exists( 'Imagick' ) ) { 00270 $scaler = 'imext'; 00271 } else { 00272 $scaler = 'client'; 00273 } 00274 00275 return $scaler; 00276 } 00277 00288 protected function getClientScalingThumbnailImage( $image, $scalerParams ) { 00289 $params = array( 00290 'width' => $scalerParams['clientWidth'], 00291 'height' => $scalerParams['clientHeight'] 00292 ); 00293 00294 return new ThumbnailImage( $image, $image->getURL(), null, $params ); 00295 } 00296 00305 protected function transformImageMagick( $image, $params ) { 00306 # use ImageMagick 00307 global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea, 00308 $wgImageMagickTempDir, $wgImageMagickConvertCommand; 00309 00310 $quality = array(); 00311 $sharpen = array(); 00312 $scene = false; 00313 $animation_pre = array(); 00314 $animation_post = array(); 00315 $decoderHint = array(); 00316 if ( $params['mimeType'] == 'image/jpeg' ) { 00317 $quality = array( '-quality', '80' ); // 80% 00318 # Sharpening, see bug 6193 00319 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) 00320 / ( $params['srcWidth'] + $params['srcHeight'] ) 00321 < $wgSharpenReductionThreshold 00322 ) { 00323 $sharpen = array( '-sharpen', $wgSharpenParameter ); 00324 } 00325 if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) { 00326 // JPEG decoder hint to reduce memory, available since IM 6.5.6-2 00327 $decoderHint = array( '-define', "jpeg:size={$params['physicalDimensions']}" ); 00328 } 00329 } elseif ( $params['mimeType'] == 'image/png' ) { 00330 $quality = array( '-quality', '95' ); // zlib 9, adaptive filtering 00331 00332 } elseif ( $params['mimeType'] == 'image/gif' ) { 00333 if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) { 00334 // Extract initial frame only; we're so big it'll 00335 // be a total drag. :P 00336 $scene = 0; 00337 } elseif ( $this->isAnimatedImage( $image ) ) { 00338 // Coalesce is needed to scale animated GIFs properly (bug 1017). 00339 $animation_pre = array( '-coalesce' ); 00340 // We optimize the output, but -optimize is broken, 00341 // use optimizeTransparency instead (bug 11822) 00342 if ( version_compare( $this->getMagickVersion(), "6.3.5" ) >= 0 ) { 00343 $animation_post = array( '-fuzz', '5%', '-layers', 'optimizeTransparency' ); 00344 } 00345 } 00346 } elseif ( $params['mimeType'] == 'image/x-xcf' ) { 00347 $animation_post = array( '-layers', 'merge' ); 00348 } 00349 00350 // Use one thread only, to avoid deadlock bugs on OOM 00351 $env = array( 'OMP_NUM_THREADS' => 1 ); 00352 if ( strval( $wgImageMagickTempDir ) !== '' ) { 00353 $env['MAGICK_TMPDIR'] = $wgImageMagickTempDir; 00354 } 00355 00356 $rotation = $this->getRotation( $image ); 00357 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00358 00359 $cmd = call_user_func_array( 'wfEscapeShellArg', array_merge( 00360 array( $wgImageMagickConvertCommand ), 00361 $quality, 00362 // Specify white background color, will be used for transparent images 00363 // in Internet Explorer/Windows instead of default black. 00364 array( '-background', 'white' ), 00365 $decoderHint, 00366 array( $this->escapeMagickInput( $params['srcPath'], $scene ) ), 00367 $animation_pre, 00368 // For the -thumbnail option a "!" is needed to force exact size, 00369 // or ImageMagick may decide your ratio is wrong and slice off 00370 // a pixel. 00371 array( '-thumbnail', "{$width}x{$height}!" ), 00372 // Add the source url as a comment to the thumb, but don't add the flag if there's no comment 00373 ( $params['comment'] !== '' 00374 ? array( '-set', 'comment', $this->escapeMagickProperty( $params['comment'] ) ) 00375 : array() ), 00376 array( '-depth', 8 ), 00377 $sharpen, 00378 array( '-rotate', "-$rotation" ), 00379 $animation_post, 00380 array( $this->escapeMagickOutput( $params['dstPath'] ) ) ) ); 00381 00382 wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" ); 00383 wfProfileIn( 'convert' ); 00384 $retval = 0; 00385 $err = wfShellExecWithStderr( $cmd, $retval, $env ); 00386 wfProfileOut( 'convert' ); 00387 00388 if ( $retval !== 0 ) { 00389 $this->logErrorForExternalProcess( $retval, $err, $cmd ); 00390 00391 return $this->getMediaTransformError( $params, "$err\nError code: $retval" ); 00392 } 00393 00394 return false; # No error 00395 } 00396 00405 protected function transformImageMagickExt( $image, $params ) { 00406 global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea; 00407 00408 try { 00409 $im = new Imagick(); 00410 $im->readImage( $params['srcPath'] ); 00411 00412 if ( $params['mimeType'] == 'image/jpeg' ) { 00413 // Sharpening, see bug 6193 00414 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) 00415 / ( $params['srcWidth'] + $params['srcHeight'] ) 00416 < $wgSharpenReductionThreshold 00417 ) { 00418 // Hack, since $wgSharpenParamater is written specifically for the command line convert 00419 list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter ); 00420 $im->sharpenImage( $radius, $sigma ); 00421 } 00422 $im->setCompressionQuality( 80 ); 00423 } elseif ( $params['mimeType'] == 'image/png' ) { 00424 $im->setCompressionQuality( 95 ); 00425 } elseif ( $params['mimeType'] == 'image/gif' ) { 00426 if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) { 00427 // Extract initial frame only; we're so big it'll 00428 // be a total drag. :P 00429 $im->setImageScene( 0 ); 00430 } elseif ( $this->isAnimatedImage( $image ) ) { 00431 // Coalesce is needed to scale animated GIFs properly (bug 1017). 00432 $im = $im->coalesceImages(); 00433 } 00434 } 00435 00436 $rotation = $this->getRotation( $image ); 00437 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00438 00439 $im->setImageBackgroundColor( new ImagickPixel( 'white' ) ); 00440 00441 // Call Imagick::thumbnailImage on each frame 00442 foreach ( $im as $i => $frame ) { 00443 if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) { 00444 return $this->getMediaTransformError( $params, "Error scaling frame $i" ); 00445 } 00446 } 00447 $im->setImageDepth( 8 ); 00448 00449 if ( $rotation ) { 00450 if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) { 00451 return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" ); 00452 } 00453 } 00454 00455 if ( $this->isAnimatedImage( $image ) ) { 00456 wfDebug( __METHOD__ . ": Writing animated thumbnail\n" ); 00457 // This is broken somehow... can't find out how to fix it 00458 $result = $im->writeImages( $params['dstPath'], true ); 00459 } else { 00460 $result = $im->writeImage( $params['dstPath'] ); 00461 } 00462 if ( !$result ) { 00463 return $this->getMediaTransformError( $params, 00464 "Unable to write thumbnail to {$params['dstPath']}" ); 00465 } 00466 } catch ( ImagickException $e ) { 00467 return $this->getMediaTransformError( $params, $e->getMessage() ); 00468 } 00469 00470 return false; 00471 } 00472 00481 protected function transformCustom( $image, $params ) { 00482 # Use a custom convert command 00483 global $wgCustomConvertCommand; 00484 00485 # Variables: %s %d %w %h 00486 $src = wfEscapeShellArg( $params['srcPath'] ); 00487 $dst = wfEscapeShellArg( $params['dstPath'] ); 00488 $cmd = $wgCustomConvertCommand; 00489 $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames 00490 $cmd = str_replace( '%h', wfEscapeShellArg( $params['physicalHeight'] ), 00491 str_replace( '%w', wfEscapeShellArg( $params['physicalWidth'] ), $cmd ) ); # Size 00492 wfDebug( __METHOD__ . ": Running custom convert command $cmd\n" ); 00493 wfProfileIn( 'convert' ); 00494 $retval = 0; 00495 $err = wfShellExecWithStderr( $cmd, $retval ); 00496 wfProfileOut( 'convert' ); 00497 00498 if ( $retval !== 0 ) { 00499 $this->logErrorForExternalProcess( $retval, $err, $cmd ); 00500 00501 return $this->getMediaTransformError( $params, $err ); 00502 } 00503 00504 return false; # No error 00505 } 00506 00514 public function getMediaTransformError( $params, $errMsg ) { 00515 return new MediaTransformError( 'thumbnail_error', $params['clientWidth'], 00516 $params['clientHeight'], $errMsg ); 00517 } 00518 00527 protected function transformGd( $image, $params ) { 00528 # Use PHP's builtin GD library functions. 00529 # 00530 # First find out what kind of file this is, and select the correct 00531 # input routine for this. 00532 00533 $typemap = array( 00534 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), 00535 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', 00536 array( __CLASS__, 'imageJpegWrapper' ) ), 00537 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), 00538 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), 00539 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), 00540 ); 00541 if ( !isset( $typemap[$params['mimeType']] ) ) { 00542 $err = 'Image type not supported'; 00543 wfDebug( "$err\n" ); 00544 $errMsg = wfMessage( 'thumbnail_image-type' )->text(); 00545 00546 return $this->getMediaTransformError( $params, $errMsg ); 00547 } 00548 list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']]; 00549 00550 if ( !function_exists( $loader ) ) { 00551 $err = "Incomplete GD library configuration: missing function $loader"; 00552 wfDebug( "$err\n" ); 00553 $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text(); 00554 00555 return $this->getMediaTransformError( $params, $errMsg ); 00556 } 00557 00558 if ( !file_exists( $params['srcPath'] ) ) { 00559 $err = "File seems to be missing: {$params['srcPath']}"; 00560 wfDebug( "$err\n" ); 00561 $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text(); 00562 00563 return $this->getMediaTransformError( $params, $errMsg ); 00564 } 00565 00566 $src_image = call_user_func( $loader, $params['srcPath'] ); 00567 00568 $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0; 00569 list( $width, $height ) = $this->extractPreRotationDimensions( $params, $rotation ); 00570 $dst_image = imagecreatetruecolor( $width, $height ); 00571 00572 // Initialise the destination image to transparent instead of 00573 // the default solid black, to support PNG and GIF transparency nicely 00574 $background = imagecolorallocate( $dst_image, 0, 0, 0 ); 00575 imagecolortransparent( $dst_image, $background ); 00576 imagealphablending( $dst_image, false ); 00577 00578 if ( $colorStyle == 'palette' ) { 00579 // Don't resample for paletted GIF images. 00580 // It may just uglify them, and completely breaks transparency. 00581 imagecopyresized( $dst_image, $src_image, 00582 0, 0, 0, 0, 00583 $width, $height, 00584 imagesx( $src_image ), imagesy( $src_image ) ); 00585 } else { 00586 imagecopyresampled( $dst_image, $src_image, 00587 0, 0, 0, 0, 00588 $width, $height, 00589 imagesx( $src_image ), imagesy( $src_image ) ); 00590 } 00591 00592 if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) { 00593 $rot_image = imagerotate( $dst_image, $rotation, 0 ); 00594 imagedestroy( $dst_image ); 00595 $dst_image = $rot_image; 00596 } 00597 00598 imagesavealpha( $dst_image, true ); 00599 00600 call_user_func( $saveType, $dst_image, $params['dstPath'] ); 00601 imagedestroy( $dst_image ); 00602 imagedestroy( $src_image ); 00603 00604 return false; # No error 00605 } 00606 00613 function escapeMagickProperty( $s ) { 00614 // Double the backslashes 00615 $s = str_replace( '\\', '\\\\', $s ); 00616 // Double the percents 00617 $s = str_replace( '%', '%%', $s ); 00618 // Escape initial - or @ 00619 if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) { 00620 $s = '\\' . $s; 00621 } 00622 00623 return $s; 00624 } 00625 00643 function escapeMagickInput( $path, $scene = false ) { 00644 # Die on initial metacharacters (caller should prepend path) 00645 $firstChar = substr( $path, 0, 1 ); 00646 if ( $firstChar === '~' || $firstChar === '@' ) { 00647 throw new MWException( __METHOD__ . ': cannot escape this path name' ); 00648 } 00649 00650 # Escape glob chars 00651 $path = preg_replace( '/[*?\[\]{}]/', '\\\\\0', $path ); 00652 00653 return $this->escapeMagickPath( $path, $scene ); 00654 } 00655 00663 function escapeMagickOutput( $path, $scene = false ) { 00664 $path = str_replace( '%', '%%', $path ); 00665 00666 return $this->escapeMagickPath( $path, $scene ); 00667 } 00668 00678 protected function escapeMagickPath( $path, $scene = false ) { 00679 # Die on format specifiers (other than drive letters). The regex is 00680 # meant to match all the formats you get from "convert -list format" 00681 if ( preg_match( '/^([a-zA-Z0-9-]+):/', $path, $m ) ) { 00682 if ( wfIsWindows() && is_dir( $m[0] ) ) { 00683 // OK, it's a drive letter 00684 // ImageMagick has a similar exception, see IsMagickConflict() 00685 } else { 00686 throw new MWException( __METHOD__ . ': unexpected colon character in path name' ); 00687 } 00688 } 00689 00690 # If there are square brackets, add a do-nothing scene specification 00691 # to force a literal interpretation 00692 if ( $scene === false ) { 00693 if ( strpos( $path, '[' ) !== false ) { 00694 $path .= '[0--1]'; 00695 } 00696 } else { 00697 $path .= "[$scene]"; 00698 } 00699 00700 return $path; 00701 } 00702 00709 protected function getMagickVersion() { 00710 global $wgMemc; 00711 00712 $cache = $wgMemc->get( "imagemagick-version" ); 00713 if ( !$cache ) { 00714 global $wgImageMagickConvertCommand; 00715 $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . ' -version'; 00716 wfDebug( __METHOD__ . ": Running convert -version\n" ); 00717 $retval = ''; 00718 $return = wfShellExec( $cmd, $retval ); 00719 $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches ); 00720 if ( $x != 1 ) { 00721 wfDebug( __METHOD__ . ": ImageMagick version check failed\n" ); 00722 00723 return null; 00724 } 00725 $wgMemc->set( "imagemagick-version", $matches[1], 3600 ); 00726 00727 return $matches[1]; 00728 } 00729 00730 return $cache; 00731 } 00732 00733 static function imageJpegWrapper( $dst_image, $thumbPath ) { 00734 imageinterlace( $dst_image ); 00735 imagejpeg( $dst_image, $thumbPath, 95 ); 00736 } 00737 00743 public static function canRotate() { 00744 $scaler = self::getScalerType( null, false ); 00745 switch ( $scaler ) { 00746 case 'im': 00747 # ImageMagick supports autorotation 00748 return true; 00749 case 'imext': 00750 # Imagick::rotateImage 00751 return true; 00752 case 'gd': 00753 # GD's imagerotate function is used to rotate images, but not 00754 # all precompiled PHP versions have that function 00755 return function_exists( 'imagerotate' ); 00756 default: 00757 # Other scalers don't support rotation 00758 return false; 00759 } 00760 } 00761 00766 public static function autoRotateEnabled() { 00767 global $wgEnableAutoRotation; 00768 00769 if ( $wgEnableAutoRotation === null ) { 00770 // Only enable auto-rotation when the bitmap handler can rotate 00771 $wgEnableAutoRotation = BitmapHandler::canRotate(); 00772 } 00773 00774 return $wgEnableAutoRotation; 00775 } 00776 00784 public function rotate( $file, $params ) { 00785 global $wgImageMagickConvertCommand; 00786 00787 $rotation = ( $params['rotation'] + $this->getRotation( $file ) ) % 360; 00788 $scene = false; 00789 00790 $scaler = self::getScalerType( null, false ); 00791 switch ( $scaler ) { 00792 case 'im': 00793 $cmd = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . 00794 wfEscapeShellArg( $this->escapeMagickInput( $params['srcPath'], $scene ) ) . 00795 " -rotate " . wfEscapeShellArg( "-$rotation" ) . " " . 00796 wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ); 00797 wfDebug( __METHOD__ . ": running ImageMagick: $cmd\n" ); 00798 wfProfileIn( 'convert' ); 00799 $retval = 0; 00800 $err = wfShellExecWithStderr( $cmd, $retval ); 00801 wfProfileOut( 'convert' ); 00802 if ( $retval !== 0 ) { 00803 $this->logErrorForExternalProcess( $retval, $err, $cmd ); 00804 00805 return new MediaTransformError( 'thumbnail_error', 0, 0, $err ); 00806 } 00807 00808 return false; 00809 case 'imext': 00810 $im = new Imagick(); 00811 $im->readImage( $params['srcPath'] ); 00812 if ( !$im->rotateImage( new ImagickPixel( 'white' ), 360 - $rotation ) ) { 00813 return new MediaTransformError( 'thumbnail_error', 0, 0, 00814 "Error rotating $rotation degrees" ); 00815 } 00816 $result = $im->writeImage( $params['dstPath'] ); 00817 if ( !$result ) { 00818 return new MediaTransformError( 'thumbnail_error', 0, 0, 00819 "Unable to write image to {$params['dstPath']}" ); 00820 } 00821 00822 return false; 00823 default: 00824 return new MediaTransformError( 'thumbnail_error', 0, 0, 00825 "$scaler rotation not implemented" ); 00826 } 00827 } 00828 00836 public function mustRender( $file ) { 00837 return self::canRotate() && $this->getRotation( $file ) != 0; 00838 } 00839 }