Index: trunk/phase3/includes/ImagePage.php |
— | — | @@ -368,10 +368,7 @@ |
369 | 369 | $thumbnail = $this->displayImg->transform( $params ); |
370 | 370 | |
371 | 371 | $showLink = true; |
372 | | - $anchorclose = ''; |
373 | | - if ( !$this->displayImg->mustRender() ) { |
374 | | - $anchorclose = "<br />" . $msgsmall; |
375 | | - } |
| 372 | + $anchorclose = "<br />" . $msgsmall; |
376 | 373 | |
377 | 374 | $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1; |
378 | 375 | if ( $isMulti ) { |
Index: trunk/phase3/includes/media/Bitmap.php |
— | — | @@ -21,6 +21,17 @@ |
22 | 22 | $mimeType = $image->getMimeType(); |
23 | 23 | $srcWidth = $image->getWidth( $params['page'] ); |
24 | 24 | $srcHeight = $image->getHeight( $params['page'] ); |
| 25 | + |
| 26 | + if ( $this->canRotate() ) { |
| 27 | + $rotation = $this->getRotation( $image ); |
| 28 | + if ( $rotation == 90 || $rotation == 270 ) { |
| 29 | + wfDebug( __METHOD__ . ": Swapping width and height because the file will be rotation $rotation degrees\n" ); |
| 30 | + |
| 31 | + $width = $params['width']; |
| 32 | + $params['width'] = $params['height']; |
| 33 | + $params['height'] = $width; |
| 34 | + } |
| 35 | + } |
25 | 36 | |
26 | 37 | # Don't make an image bigger than the source |
27 | 38 | $params['physicalWidth'] = $params['width']; |
— | — | @@ -55,8 +66,7 @@ |
56 | 67 | } |
57 | 68 | |
58 | 69 | function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { |
59 | | - global $wgUseImageMagick; |
60 | | - global $wgCustomConvertCommand, $wgUseImageResize; |
| 70 | + global $wgCustomConvertCommand; |
61 | 71 | |
62 | 72 | if ( !$this->normaliseParams( $image, $params ) ) { |
63 | 73 | return new TransformParameterError( $params ); |
— | — | @@ -93,20 +103,7 @@ |
94 | 104 | } |
95 | 105 | |
96 | 106 | # Determine scaler type |
97 | | - if ( !$dstPath ) { |
98 | | - # No output path available, client side scaling only |
99 | | - $scaler = 'client'; |
100 | | - } elseif ( !$wgUseImageResize ) { |
101 | | - $scaler = 'client'; |
102 | | - } elseif ( $wgUseImageMagick ) { |
103 | | - $scaler = 'im'; |
104 | | - } elseif ( $wgCustomConvertCommand ) { |
105 | | - $scaler = 'custom'; |
106 | | - } elseif ( function_exists( 'imagecreatetruecolor' ) ) { |
107 | | - $scaler = 'gd'; |
108 | | - } else { |
109 | | - $scaler = 'client'; |
110 | | - } |
| 107 | + $scaler = $this->getScalerType( $dstPath ); |
111 | 108 | wfDebug( __METHOD__ . ": scaler $scaler\n" ); |
112 | 109 | |
113 | 110 | if ( $scaler == 'client' ) { |
— | — | @@ -154,6 +151,39 @@ |
155 | 152 | $scalerParams['clientHeight'], $dstPath ); |
156 | 153 | } |
157 | 154 | } |
| 155 | + |
| 156 | + /** |
| 157 | + * Returns which scaler type should be used. Creates parent directories |
| 158 | + * for $dstPath and returns 'client' on error |
| 159 | + * |
| 160 | + * @return string client,im,custom,gd |
| 161 | + */ |
| 162 | + protected function getScalerType( $dstPath, $checkDstPath = true ) { |
| 163 | + global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand; |
| 164 | + |
| 165 | + if ( !$dstPath && $checkDstPath ) { |
| 166 | + # No output path available, client side scaling only |
| 167 | + $scaler = 'client'; |
| 168 | + } elseif ( !$wgUseImageResize ) { |
| 169 | + $scaler = 'client'; |
| 170 | + } elseif ( $wgUseImageMagick ) { |
| 171 | + $scaler = 'im'; |
| 172 | + } elseif ( $wgCustomConvertCommand ) { |
| 173 | + $scaler = 'custom'; |
| 174 | + } elseif ( function_exists( 'imagecreatetruecolor' ) ) { |
| 175 | + $scaler = 'gd'; |
| 176 | + } else { |
| 177 | + $scaler = 'client'; |
| 178 | + } |
| 179 | + |
| 180 | + if ( $scaler != 'client' ) { |
| 181 | + if ( !wfMkdirParents( dirname( $dstPath ) ) ) { |
| 182 | + # Unable to create a path for the thumbnail |
| 183 | + return 'client'; |
| 184 | + } |
| 185 | + } |
| 186 | + return $scaler; |
| 187 | + } |
158 | 188 | |
159 | 189 | /** |
160 | 190 | * Get a ThumbnailImage that respresents an image that will be scaled |
— | — | @@ -242,7 +272,7 @@ |
243 | 273 | ( $params['comment'] !== '' |
244 | 274 | ? " -set comment " . wfEscapeShellArg( $this->escapeMagickProperty( $params['comment'] ) ) |
245 | 275 | : '' ) . |
246 | | - " -depth 8 $sharpen" . |
| 276 | + " -depth 8 $sharpen -auto-orient" . |
247 | 277 | " {$animation_post} " . |
248 | 278 | wfEscapeShellArg( $this->escapeMagickOutput( $params['dstPath'] ) ) . " 2>&1"; |
249 | 279 | |
— | — | @@ -360,8 +390,16 @@ |
361 | 391 | } |
362 | 392 | |
363 | 393 | $src_image = call_user_func( $loader, $params['srcPath'] ); |
364 | | - $dst_image = imagecreatetruecolor( $params['physicalWidth'], |
365 | | - $params['physicalHeight'] ); |
| 394 | + $rotation = $this->getRotation( $image ); |
| 395 | + if ( $rotation == 90 || $rotation == 270 ) { |
| 396 | + # We'll resize before rotation, so swap the dimensions again |
| 397 | + $width = $params['physicalHeight']; |
| 398 | + $height = $params['physicalWidth']; |
| 399 | + } else { |
| 400 | + $width = $params['physicalWidth']; |
| 401 | + $height = $params['physicalHeight']; |
| 402 | + } |
| 403 | + $dst_image = imagecreatetruecolor( $width, $height ); |
366 | 404 | |
367 | 405 | // Initialise the destination image to transparent instead of |
368 | 406 | // the default solid black, to support PNG and GIF transparency nicely |
— | — | @@ -374,15 +412,21 @@ |
375 | 413 | // It may just uglify them, and completely breaks transparency. |
376 | 414 | imagecopyresized( $dst_image, $src_image, |
377 | 415 | 0, 0, 0, 0, |
378 | | - $params['physicalWidth'], $params['physicalHeight'], |
| 416 | + $width, $height, |
379 | 417 | imagesx( $src_image ), imagesy( $src_image ) ); |
380 | 418 | } else { |
381 | 419 | imagecopyresampled( $dst_image, $src_image, |
382 | 420 | 0, 0, 0, 0, |
383 | | - $params['physicalWidth'], $params['physicalHeight'], |
| 421 | + $width, $height, |
384 | 422 | imagesx( $src_image ), imagesy( $src_image ) ); |
385 | 423 | } |
386 | | - |
| 424 | + |
| 425 | + if ( $rotation % 360 != 0 && $rotation % 90 == 0 ) { |
| 426 | + $rot_image = imagerotate( $dst_image, $rotation, 0 ); |
| 427 | + imagedestroy( $dst_image ); |
| 428 | + $dst_image = $rot_image; |
| 429 | + } |
| 430 | + |
387 | 431 | imagesavealpha( $dst_image, true ); |
388 | 432 | |
389 | 433 | call_user_func( $saveType, $dst_image, $params['dstPath'] ); |
— | — | @@ -602,4 +646,34 @@ |
603 | 647 | } |
604 | 648 | return $result; |
605 | 649 | } |
| 650 | + |
| 651 | + public function getRotation( $file ) { |
| 652 | + $data = $file->getMetadata(); |
| 653 | + if ( !$data ) { |
| 654 | + return 0; |
| 655 | + } |
| 656 | + $data = unserialize( $data ); |
| 657 | + if ( isset( $data['Orientation'] ) ) { |
| 658 | + # See http://sylvana.net/jpegcrop/exif_orientation.html |
| 659 | + switch ( $data['Orientation'] ) { |
| 660 | + case 8: |
| 661 | + return 90; |
| 662 | + case 3: |
| 663 | + return 180; |
| 664 | + case 6: |
| 665 | + return 270; |
| 666 | + default: |
| 667 | + return 0; |
| 668 | + } |
| 669 | + } |
| 670 | + return 0; |
| 671 | + } |
| 672 | + public function canRotate() { |
| 673 | + $scaler = $this->getScalerType( null, false ); |
| 674 | + return $scaler == 'im' || $scaler == 'gd'; |
| 675 | + } |
| 676 | + |
| 677 | + public function mustRender( $file ) { |
| 678 | + return $this->canRotate() && $this->getRotation( $file ) != 0; |
| 679 | + } |
606 | 680 | } |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -41,6 +41,8 @@ |
42 | 42 | * Search suggestions (other than in the Vector skin) will now use the HTML5 |
43 | 43 | datalist feature where supported, currently only Firefox 4. |
44 | 44 | * Special:Contribs now redirects to Special:Contributions |
| 45 | +* (bug 6672) Images are now autorotated according to their EXIF orientation. |
| 46 | + This only affects thumbnails; the source remains unrotated. |
45 | 47 | |
46 | 48 | === Bug fixes in 1.18 === |
47 | 49 | * (bug 23119) WikiError class and subclasses are now marked as deprecated |