Fix switch fail in r83778: add missing break.
[lhc/web/wiklou.git] / includes / media / Bitmap.php
index f5f7ba6..0c717f7 100644 (file)
  * @ingroup Media
  */
 class BitmapHandler extends ImageHandler {
+
+       /**
+        * @param $image File
+        * @param  $params
+        * @return bool
+        */
        function normaliseParams( $image, &$params ) {
                global $wgMaxImageArea;
                if ( !parent::normaliseParams( $image, $params ) ) {
@@ -21,6 +27,17 @@ class BitmapHandler extends ImageHandler {
                $mimeType = $image->getMimeType();
                $srcWidth = $image->getWidth( $params['page'] );
                $srcHeight = $image->getHeight( $params['page'] );
+               
+               if ( self::canRotate() ) {
+                       $rotation = $this->getRotation( $image );
+                       if ( $rotation == 90 || $rotation == 270 ) {
+                               wfDebug( __METHOD__ . ": Swapping width and height because the file will be rotated $rotation degrees\n" );
+                               
+                               $width = $params['width'];
+                               $params['width'] = $params['height'];
+                               $params['height'] = $width;
+                       }
+               }
 
                # Don't make an image bigger than the source
                $params['physicalWidth'] = $params['width'];
@@ -54,10 +71,15 @@ class BitmapHandler extends ImageHandler {
                return $width * $height;
        }
 
+       /**
+        * @param $image File
+        * @param  $dstPath
+        * @param  $dstUrl
+        * @param  $params
+        * @param int $flags
+        * @return MediaTransformError|ThumbnailImage|TransformParameterError
+        */
        function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
-               global $wgUseImageMagick;
-               global $wgCustomConvertCommand, $wgUseImageResize;
-
                if ( !$this->normaliseParams( $image, $params ) ) {
                        return new TransformParameterError( $params );
                }
@@ -93,20 +115,7 @@ class BitmapHandler extends ImageHandler {
                }
 
                # Determine scaler type
-               if ( !$dstPath ) {
-                       # No output path available, client side scaling only
-                       $scaler = 'client';
-               } elseif ( !$wgUseImageResize ) {
-                       $scaler = 'client';
-               } elseif ( $wgUseImageMagick ) {
-                       $scaler = 'im';
-               } elseif ( $wgCustomConvertCommand ) {
-                       $scaler = 'custom';
-               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
-                       $scaler = 'gd';
-               } else {
-                       $scaler = 'client';
-               }
+               $scaler = self::getScalerType( $dstPath );
                wfDebug( __METHOD__ . ": scaler $scaler\n" );
 
                if ( $scaler == 'client' ) {
@@ -134,6 +143,9 @@ class BitmapHandler extends ImageHandler {
                        case 'custom':
                                $err = $this->transformCustom( $image, $scalerParams );
                                break;
+                       case 'imext':
+                               $err = $this->transformImageMagickExt( $image, $scalerParams );
+                               break;
                        case 'gd':
                        default:
                                $err = $this->transformGd( $image, $scalerParams );
@@ -154,6 +166,41 @@ class BitmapHandler extends ImageHandler {
                                $scalerParams['clientHeight'], $dstPath );
                }
        }
+       
+       /**
+        * Returns which scaler type should be used. Creates parent directories 
+        * for $dstPath and returns 'client' on error
+        * 
+        * @return string client,im,custom,gd
+        */
+       protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+               global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
+               
+               if ( !$dstPath && $checkDstPath ) {
+                       # No output path available, client side scaling only
+                       $scaler = 'client';
+               } elseif ( !$wgUseImageResize ) {
+                       $scaler = 'client';
+               } elseif ( $wgUseImageMagick ) {
+                       $scaler = 'im';
+               } elseif ( $wgCustomConvertCommand ) {
+                       $scaler = 'custom';
+               } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
+                       $scaler = 'gd';
+               } elseif ( class_exists( 'Imagick' ) ) {
+                       $scaler = 'imext';
+               } else {
+                       $scaler = 'client';
+               }
+               
+               if ( $scaler != 'client' && $dstPath ) {
+                       if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+                               # Unable to create a path for the thumbnail
+                               return 'client';
+                       }
+               }
+               return $scaler;
+       }
 
        /**
         * Get a ThumbnailImage that respresents an image that will be scaled
@@ -167,7 +214,7 @@ class BitmapHandler extends ImageHandler {
                return new ThumbnailImage( $image, $image->getURL(),
                                $params['clientWidth'], $params['clientHeight'], $params['srcPath'] );
        }
-
+       
        /**
         * Transform an image using ImageMagick
         *
@@ -242,7 +289,7 @@ class BitmapHandler extends ImageHandler {
                        ( $params['comment'] !== ''
                                ? " -set comment " . wfEscapeShellArg( $this->escapeMagickProperty( $params['comment'] ) )
                                : '' ) .
-                       " -depth 8 $sharpen" .
+                       " -depth 8 $sharpen -auto-orient" .
                        " {$animation_post} " .
                        wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1";
 
@@ -259,6 +306,91 @@ class BitmapHandler extends ImageHandler {
 
                return false; # No error
        }
+       
+       /**
+        * Transform an image using the Imagick PHP extension
+        * 
+        * @param $image File File associated with this thumbnail
+        * @param $params array Array with scaler params
+        *
+        * @return MediaTransformError Error object if error occured, false (=no error) otherwise
+        */
+       protected function transformImageMagickExt( $image, $params ) {
+               global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea;
+               
+               try {
+                       $im = new Imagick();
+                       $im->readImage( $params['srcPath'] );
+       
+                       if ( $params['mimeType'] == 'image/jpeg' ) {
+                               // Sharpening, see bug 6193
+                               if ( ( $params['physicalWidth'] + $params['physicalHeight'] )
+                                               / ( $params['srcWidth'] + $params['srcHeight'] )
+                                               < $wgSharpenReductionThreshold ) {
+                                       // Hack, since $wgSharpenParamater is written specifically for the command line convert
+                                       list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter );
+                                       $im->sharpenImage( $radius, $sigma );
+                               }
+                               $im->setCompressionQuality( 80 );
+                       } elseif( $params['mimeType'] == 'image/png' ) {
+                               $im->setCompressionQuality( 95 );
+                       } elseif ( $params['mimeType'] == 'image/gif' ) {
+                               if ( $this->getImageArea( $image, $params['srcWidth'],
+                                               $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+                                       // Extract initial frame only; we're so big it'll
+                                       // be a total drag. :P
+                                       $im->setImageScene( 0 );
+                               } elseif ( $this->isAnimatedImage( $image ) ) {
+                                       // Coalesce is needed to scale animated GIFs properly (bug 1017).
+                                       $im = $im->coalesceImages();
+                               }
+                       }
+                       
+                       $rotation = $this->getRotation( $image );
+                       if ( $rotation == 90 || $rotation == 270 ) {
+                               // We'll resize before rotation, so swap the dimensions again
+                               $width = $params['physicalHeight'];
+                               $height = $params['physicalWidth'];
+                       } else {
+                               $width = $params['physicalWidth'];
+                               $height = $params['physicalHeight'];                    
+                       }
+                       
+                       $im->setImageBackgroundColor( new ImagickPixel( 'white' ) );
+                       
+                       // Call Imagick::thumbnailImage on each frame
+                       foreach ( $im as $i => $frame ) {
+                               if ( !$frame->thumbnailImage( $width, $height, /* fit */ false ) ) {
+                                       return $this->getMediaTransformError( $params, "Error scaling frame $i" );
+                               }
+                       }
+                       $im->setImageDepth( 8 );
+                       
+                       if ( $rotation ) {
+                               if ( !$im->rotateImage( new ImagickPixel( 'white' ), $rotation ) ) {
+                                       return $this->getMediaTransformError( $params, "Error rotating $rotation degrees" );
+                               }
+                       }
+       
+                       if ( $this->isAnimatedImage( $image ) ) {
+                               wfDebug( __METHOD__ . ": Writing animated thumbnail\n" );
+                               // This is broken somehow... can't find out how to fix it
+                               $result = $im->writeImages( $params['dstPath'], true );
+                       } else {
+                               $result = $im->writeImage( $params['dstPath'] );
+                       }
+                       if ( !$result ) {
+                               return $this->getMediaTransformError( $params, 
+                                       "Unable to write thumbnail to {$params['dstPath']}" );
+                       }
+
+               } catch ( ImagickException $e ) {
+                       return $this->getMediaTransformError( $params, $e->getMessage() ); 
+               }
+               
+               return false;
+               
+       }
 
        /**
         * Transform an image using a custom command
@@ -360,8 +492,17 @@ class BitmapHandler extends ImageHandler {
                }
 
                $src_image = call_user_func( $loader, $params['srcPath'] );
-               $dst_image = imagecreatetruecolor( $params['physicalWidth'],
-                       $params['physicalHeight'] );
+               
+               $rotation = function_exists( 'imagerotate' ) ? $this->getRotation( $image ) : 0;
+               if ( $rotation == 90 || $rotation == 270 ) {
+                       # We'll resize before rotation, so swap the dimensions again
+                       $width = $params['physicalHeight'];
+                       $height = $params['physicalWidth'];
+               } else {
+                       $width = $params['physicalWidth'];
+                       $height = $params['physicalHeight'];                    
+               }
+               $dst_image = imagecreatetruecolor( $width, $height );
 
                // Initialise the destination image to transparent instead of
                // the default solid black, to support PNG and GIF transparency nicely
@@ -374,15 +515,21 @@ class BitmapHandler extends ImageHandler {
                        // It may just uglify them, and completely breaks transparency.
                        imagecopyresized( $dst_image, $src_image,
                                0, 0, 0, 0,
-                               $params['physicalWidth'], $params['physicalHeight'],
+                               $width, $height,
                                imagesx( $src_image ), imagesy( $src_image ) );
                } else {
                        imagecopyresampled( $dst_image, $src_image,
                                0, 0, 0, 0,
-                               $params['physicalWidth'], $params['physicalHeight'],
+                               $width, $height,
                                imagesx( $src_image ), imagesy( $src_image ) );
                }
-
+               
+               if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) {
+                       $rot_image = imagerotate( $dst_image, $rotation, 0 );
+                       imagedestroy( $dst_image );
+                       $dst_image = $rot_image;
+               }
+               
                imagesavealpha( $dst_image, true );
 
                call_user_func( $saveType, $dst_image, $params['dstPath'] );
@@ -572,6 +719,10 @@ class BitmapHandler extends ImageHandler {
                return $fields;
        }
 
+       /**
+        * @param $image File
+        * @return array|bool
+        */
        function formatMetadata( $image ) {
                $result = array(
                        'visible' => array(),
@@ -602,4 +753,67 @@ class BitmapHandler extends ImageHandler {
                }
                return $result;
        }
+       
+       /**
+        * Try to read out the orientation of the file and return the angle that 
+        * the file needs to be rotated to be viewed
+        * 
+        * @param $file File
+        * @return int 0, 90, 180 or 270
+        */
+       public function getRotation( $file ) {
+               $data = $file->getMetadata();
+               if ( !$data ) {
+                       return 0;
+               }
+               $data = unserialize( $data );
+               if ( isset( $data['Orientation'] ) ) {
+                       # See http://sylvana.net/jpegcrop/exif_orientation.html
+                       switch ( $data['Orientation'] ) {
+                               case 8:
+                                       return 90;
+                               case 3:
+                                       return 180;
+                               case 6:
+                                       return 270;
+                               default:
+                                       return 0;
+                       }
+               }
+               return 0;
+       }
+       /**
+        * Returns whether the current scaler supports rotation (im and gd do)
+        * 
+        * @return bool
+        */
+       public static function canRotate() {
+               $scaler = self::getScalerType( null, false );
+               switch ( $scaler ) {
+                       case 'im':
+                               # ImageMagick supports autorotation
+                               return true;
+                       case 'imext':
+                               # Imagick::rotateImage
+                               return true;
+                       case 'gd':
+                               # GD's imagerotate function is used to rotate images, but not
+                               # all precompiled PHP versions have that function
+                               return function_exists( 'imagerotate' );
+                       default:
+                               # Other scalers don't support rotation
+                               return false;
+               }
+       }
+       
+       /**
+        * Rerurns whether the file needs to be rendered. Returns true if the 
+        * file requires rotation and we are able to rotate it.
+        * 
+        * @param $file File
+        * @return bool
+        */
+       public function mustRender( $file ) {
+               return self::canRotate() && $this->getRotation( $file ) != 0;
+       }
 }