r97671 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r97670‎ | r97671 | r97672 >
Date:22:13, 20 September 2011
Author:brion
Status:resolved (Comments)
Tags:
Comment:
* (bug 6672, 31024) Fixes for handling of images with an EXIF orientation

- sets an image's reported width/height to the logical form (portait image reports itself as portait)
- everything works in logical coordinates when sizing -- we don't touch the physical pre-rotation dimensions again until it's actual low-level resize time. This fixes several problems with incorrect thumb sizing (eg getting a 600x800 image when we asked for something that fits in 800x600 box)
- fixes unit test cases in ExifRotationTest that were reporting that the width/height were coming back with the physical form which we don't want
- removes some test cases on ExifRotationTest that tested dimension swapping in a place where we don't want it
- ensures that only logical width/height need be exposed to API etc, making exif-rotated images work via ForeignAPIRepo

Note that this may actually cause file metadata to get loaded twice during File::getPropsFromPath, as the $image parameter it passes in to the handler's getImageSize function is bogus and can't be used to fetch an already-loaded metadata blob. This should not generally be too expensive though; it's not a fast path.

Rotated files that were uploaded under previous versions may still have their width/height reversed; an action=purge on the file page will refresh it and cause thumbs to be regenerated.

Follows up on r79845, r90016, r92246, r92279, r96687, r97651, r97656, r97659.

Needs merge to 1.18.
Modified paths:
  • /trunk/phase3/includes/filerepo/File.php (modified) (history)
  • /trunk/phase3/includes/media/Bitmap.php (modified) (history)
  • /trunk/phase3/includes/media/ExifBitmap.php (modified) (history)
  • /trunk/phase3/tests/phpunit/includes/media/ExifRotationTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/tests/phpunit/includes/media/ExifRotationTest.php
@@ -145,27 +145,5 @@
146146 ),
147147 );
148148 }
149 -
150 - function testWidthFlipping() {
151 - # Any further test require a scaler that can rotate
152 - if ( !BitmapHandler::canRotate() ) {
153 - $this->markTestSkipped( 'Scaler does not support rotation' );
154 - return;
155 - }
156 - $file = UnregisteredLocalFile::newFromPath( $this->filePath . 'portrait-rotated.jpg', 'image/jpeg' );
157 - $params = array( 'width' => '50' );
158 - $this->assertTrue( $this->handler->normaliseParams( $file, $params ) );
159 -
160 - $this->assertEquals( 50, $params['height'] );
161 - $this->assertEquals( round( (768/1024)*50 ), $params['width'], '', 0.1 );
162 - }
163 - function testWidthNotFlipping() {
164 - $file = UnregisteredLocalFile::newFromPath( $this->filePath . 'landscape-plain.jpg', 'image/jpeg' );
165 - $params = array( 'width' => '50' );
166 - $this->assertTrue( $this->handler->normaliseParams( $file, $params ) );
167 -
168 - $this->assertEquals( 50, $params['width'] );
169 - $this->assertEquals( round( (768/1024)*50 ), $params['height'], '', 0.1 );
170 - }
171149 }
172150
Index: trunk/phase3/includes/filerepo/File.php
@@ -1342,7 +1342,11 @@
13431343 * @return string
13441344 */
13451345 function getDescriptionUrl() {
1346 - return $this->repo->getDescriptionUrl( $this->getName() );
 1346+ if ( $this->repo ) {
 1347+ return $this->repo->getDescriptionUrl( $this->getName() );
 1348+ } else {
 1349+ return false;
 1350+ }
13471351 }
13481352
13491353 /**
Index: trunk/phase3/includes/media/Bitmap.php
@@ -43,20 +43,6 @@
4444 }
4545 }
4646
47 -
48 - if ( self::canRotate() ) {
49 - $rotation = $this->getRotation( $image );
50 - if ( $rotation == 90 || $rotation == 270 ) {
51 - wfDebug( __METHOD__ . ": Swapping width and height because the file will be rotated $rotation degrees\n" );
52 -
53 - list( $params['width'], $params['height'] ) =
54 - array( $params['height'], $params['width'] );
55 - list( $params['physicalWidth'], $params['physicalHeight'] ) =
56 - array( $params['physicalHeight'], $params['physicalWidth'] );
57 - }
58 - }
59 -
60 -
6147 # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
6248 # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
6349 # an exception for it.
@@ -73,6 +59,11 @@
7460 /**
7561 * Extracts the width/height if the image will be scaled before rotating
7662 *
 63+ * This will match the physical size/aspect ratio of the original image
 64+ * prior to application of the rotation -- so for a portrait image that's
 65+ * stored as raw landscape with 90-degress rotation, the resulting size
 66+ * will be wider than it is tall.
 67+ *
7768 * @param $params array Parameters as returned by normaliseParams
7869 * @param $rotation int The rotation angle that will be applied
7970 * @return array ($width, $height) array
@@ -249,6 +240,8 @@
250241 * @param $image File File associated with this thumbnail
251242 * @param $params array Array with scaler params
252243 * @return ThumbnailImage
 244+ *
 245+ * @fixme no rotation support
253246 */
254247 protected function getClientScalingThumbnailImage( $image, $params ) {
255248 return new ThumbnailImage( $image, $image->getURL(),
@@ -685,33 +678,21 @@
686679 }
687680
688681 /**
689 - * Try to read out the orientation of the file and return the angle that
690 - * the file needs to be rotated to be viewed
 682+ * On supporting image formats, try to read out the low-level orientation
 683+ * of the file and return the angle that the file needs to be rotated to
 684+ * be viewed.
691685 *
 686+ * This information is only useful when manipulating the original file;
 687+ * the width and height we normally work with is logical, and will match
 688+ * any produced output views.
 689+ *
 690+ * The base BitmapHandler doesn't understand any metadata formats, so this
 691+ * is left up to child classes to implement.
 692+ *
692693 * @param $file File
693694 * @return int 0, 90, 180 or 270
694695 */
695696 public function getRotation( $file ) {
696 - $data = $file->getMetadata();
697 - if ( !$data ) {
698 - return 0;
699 - }
700 - wfSuppressWarnings();
701 - $data = unserialize( $data );
702 - wfRestoreWarnings();
703 - if ( isset( $data['Orientation'] ) ) {
704 - # See http://sylvana.net/jpegcrop/exif_orientation.html
705 - switch ( $data['Orientation'] ) {
706 - case 8:
707 - return 90;
708 - case 3:
709 - return 180;
710 - case 6:
711 - return 270;
712 - default:
713 - return 0;
714 - }
715 - }
716697 return 0;
717698 }
718699
Index: trunk/phase3/includes/media/ExifBitmap.php
@@ -124,5 +124,77 @@
125125 function getMetadataType( $image ) {
126126 return 'exif';
127127 }
 128+
 129+ /**
 130+ * Wrapper for base classes ImageHandler::getImageSize() that checks for
 131+ * rotation reported from metadata and swaps the sizes to match.
 132+ *
 133+ * @param File $image
 134+ * @param string $path
 135+ * @return array
 136+ */
 137+ function getImageSize( $image, $path ) {
 138+ $gis = parent::getImageSize( $image, $path );
 139+
 140+ // Don't just call $image->getMetadata(); File::getPropsFromPath() calls us with a bogus object.
 141+ // This may mean we read EXIF data twice on initial upload.
 142+ $meta = $this->getMetadata( $image, $path );
 143+ $rotation = $this->getRotationForExif( $meta );
 144+
 145+ if ($rotation == 90 || $rotation == 270) {
 146+ $width = $gis[0];
 147+ $gis[0] = $gis[1];
 148+ $gis[1] = $width;
 149+ }
 150+ return $gis;
 151+ }
 152+
 153+ /**
 154+ * On supporting image formats, try to read out the low-level orientation
 155+ * of the file and return the angle that the file needs to be rotated to
 156+ * be viewed.
 157+ *
 158+ * This information is only useful when manipulating the original file;
 159+ * the width and height we normally work with is logical, and will match
 160+ * any produced output views.
 161+ *
 162+ * @param $file File
 163+ * @return int 0, 90, 180 or 270
 164+ */
 165+ public function getRotation( $file ) {
 166+ $data = $file->getMetadata();
 167+ return $this->getRotationForExif( $data );
 168+ }
 169+
 170+ /**
 171+ * Given a chunk of serialized Exif metadata, return the orientation as
 172+ * degrees of rotation.
 173+ *
 174+ * @param string $data
 175+ * @return int 0, 90, 180 or 270
 176+ * @fixme orientation can include flipping as well; see if this is an issue!
 177+ */
 178+ protected function getRotationForExif( $data ) {
 179+ if ( !$data ) {
 180+ return 0;
 181+ }
 182+ wfSuppressWarnings();
 183+ $data = unserialize( $data );
 184+ wfRestoreWarnings();
 185+ if ( isset( $data['Orientation'] ) ) {
 186+ # See http://sylvana.net/jpegcrop/exif_orientation.html
 187+ switch ( $data['Orientation'] ) {
 188+ case 8:
 189+ return 90;
 190+ case 3:
 191+ return 180;
 192+ case 6:
 193+ return 270;
 194+ default:
 195+ return 0;
 196+ }
 197+ }
 198+ return 0;
 199+ }
128200 }
129201

Sign-offs

UserFlagDate
Aaron Schulzinspected03:31, 21 September 2011
Aaron Schulztested03:31, 21 September 2011

Follow-up revisions

RevisionCommit summaryAuthorDate
r97675MFT r97651, r97656, r97659 updated test cases for bug 6672, 31024...brion22:40, 20 September 2011
r97676Merge from REL1_18 r97675 -- exif rotation fixesbrion22:41, 20 September 2011
r99910Per r97671, add $wgEnableAutoRotation setting that can be used to explicitly ...btongminh20:30, 15 October 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r79845$wgMaxUploadSize may now be set to an array to specify the upload size limit ...btongminh22:12, 7 January 2011
r79859Follow-up r79845: add function documentation; only call wfMakeDirsParent if w...btongminh11:49, 8 January 2011
r90016Start on test cases for bug 6672 (Exif orientation support), follow up to r79......brion22:13, 13 June 2011
r90123add canvas scaling and rotation. mostly fixes bug 29383 (a few issues remaini...neilk17:35, 15 June 2011
r92246Follow-up r79845: Fix rotation. Turns out that we need to pass the pre-rotati...btongminh15:13, 15 July 2011
r92279(bug 28762) Resizing to specified height broken for very thin images...btongminh19:03, 15 July 2011
r96687(bug 30640; follow-up r92279) Rotating images was making skewed images...bawolff20:13, 9 September 2011
r97651Partial revert of broken test changes from r92246 -- for some reason it was t...brion19:13, 20 September 2011
r97656Further tweaks to r96687, r90016, r97398 etc tests: actually produce a thumbn...brion19:39, 20 September 2011
r97659Further tweaks to r96687, r90016, r97398, r97656 etc tests: try several thumb...brion20:04, 20 September 2011

Comments

#Comment by Bryan (talk | contribs)   14:43, 24 September 2011

This will return the rotated image size even if the scaler doesn't support rotation, right?

#Comment by Brion VIBBER (talk | contribs)   21:47, 26 September 2011

Yes, though that means you're using client-side resizing only and things'll probably look bad in general.

I'd hate to have internal interpretation of the image dependent on our ability to produce pretty thumbnails...

#Comment by Bryan (talk | contribs)   09:42, 15 October 2011

Well, but now it means that we're going from "pretty bad" to plain wrong. I'm thinking of a config var to optionally enable or disable auto-rotation entirely. This is probably also useful when wiki administrators think it is not a good idea to have auto-rotation.

#Comment by Brion VIBBER (talk | contribs)   16:21, 15 October 2011

Hmm, we could also do rotation via jpegtran if available... wonder how hard it would be to do a pure PHP implementation. ;)

#Comment by Bryan (talk | contribs)   19:30, 15 October 2011

Let's... not do that?

About jpegtran, I don't think that is necessary. If people don't have image scalers, that means that they are on a crappy host, and that almost always means that jpegtran is not available.

Status & tagging log