r90123 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r90122‎ | r90123 | r90124 >
Date:17:35, 15 June 2011
Author:neilk
Status:ok (Comments)
Tags:
Comment:
add canvas scaling and rotation. mostly fixes bug 29383 (a few issues remaining), related bug 6672, followup to r89918
Modified paths:
  • /trunk/extensions/UploadWizard/UploadWizardHooks.php (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.UploadWizard.js (modified) (history)
  • /trunk/extensions/UploadWizard/resources/mw.canvas.js (added) (history)
  • /trunk/extensions/UploadWizard/resources/mw.fileApi.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UploadWizard/UploadWizardHooks.php
@@ -25,6 +25,7 @@
2626 'jquery.ui.widget',
2727 'mediawiki.language',
2828 'mediawiki.util',
 29+ 'mediawiki.libs.jpegmeta',
2930 'ext.uploadwizard.mediawiki.language.parser',
3031 ),
3132 'scripts' => array(
@@ -48,6 +49,7 @@
4950 // common utilities
5051 'resources/mw.fileApi.js',
5152 'resources/mw.units.js',
 53+ 'resources/mw.canvas.js',
5254 'resources/mw.Log.js',
5355 'resources/mw.Utilities.js',
5456 'resources/mw.UtilitiesTime.js',
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js
@@ -269,19 +269,46 @@
270270 this.transportWeight = this.file.size;
271271
272272 if ( mw.fileApi.isPreviewableFile( this.file ) ) {
273 - // TODO all that other complicated file rotation / binary stuff from mediawiki.special.upload.js
 273+ /*
 274+ * The following part is a bit tricky, but not easy to untangle in code
 275+ * First, we will read the file as binary
 276+ * in the binary reader's onload function
 277+ * we try to get metadata with jpegmeta library, and add it to the upload object
 278+ * we re-read the file as a data URL
 279+ * in that file reader's onload function
 280+ * assign it to an image
 281+ * in that image's onload function
 282+ * publish the event, that this upload can now do thumbnails, with the image itself
 283+ * (listeners will then use that image to create thumbnails in the DOM,
 284+ * with local scaling or canvas)
 285+ */
 286+
274287 var image = document.createElement( 'img' );
275288 image.onload = function() {
276289 $.publishReady( 'thumbnails.' + _this.index, image );
277290 };
278 - var reader = new FileReader();
279 - reader.onload = function( progressEvent ) {
280 - _this.thumbnails['*'] = reader.result;
281 - image.src = reader.result;
 291+
 292+ var binReader = new FileReader();
 293+ binReader.onload = function() {
 294+ var meta;
 295+ try {
 296+ meta = mw.libs.jpegmeta( binReader.result, _this.file.fileName );
 297+ meta._binary_data = null;
 298+ } catch ( e ) {
 299+ meta = null;
 300+ }
 301+ _this.imageinfo = {};
 302+ _this.imageinfo.metadata = meta; // TODO this is not the same format as API imageinfo; reconcile
 303+
 304+ var dataUrlReader = new FileReader();
 305+ dataUrlReader.onload = function() {
 306+ image.src = dataUrlReader.result;
 307+ _this.thumbnails['*'] = image;
 308+ };
 309+ dataUrlReader.readAsDataURL( _this.file );
282310 };
283 - reader.readAsDataURL( _this.file );
 311+ binReader.readAsBinaryString( _this.file );
284312 }
285 -
286313 }
287314 // TODO sanitize filename
288315 var filename = fileInput.value;
@@ -295,6 +322,7 @@
296323 }
297324 },
298325
 326+
299327 /**
300328 * Accept the result from a successful API upload transport, and fill our own info
301329 *
@@ -567,6 +595,167 @@
568596 },
569597
570598 /**
 599+ * Return the orientation of the image. Relies on TIFF metadata that
 600+ * may have been extracted at filereader stage, or returns 0.
 601+ * @param {Object} metadata as yielded by getMetadata()
 602+ * @return {Integer} rotation to be applied: 0, 90, 180 or 270
 603+ */
 604+ getOrientation: function() {
 605+ var orientation = 0;
 606+ if ( this.imageinfo && this.imageinfo.metadata &&
 607+ this.imageinfo.metadata.tiff && this.imageinfo.metadata.tiff.Orientation ) {
 608+ switch ( this.imageinfo.metadata.tiff.Orientation.value ) {
 609+ case 8:
 610+ orientation = 90;
 611+ break;
 612+ case 3:
 613+ orientation = 180;
 614+ break;
 615+ case 6:
 616+ orientation = 270;
 617+ break;
 618+ default:
 619+ orientation = 0;
 620+ break;
 621+
 622+ }
 623+ }
 624+ return orientation;
 625+ },
 626+
 627+ /**
 628+ * Fit an image into width & height constraints with scaling factor
 629+ * @param {HTMLImageElement}
 630+ * @param {Object} with width & height properties
 631+ * @return {Number}
 632+ */
 633+ getScalingFromConstraints: function( image, constraints ) {
 634+ var scaling = 1;
 635+ $j.each( [ 'width', 'height' ], function( i, dim ) {
 636+ if ( constraints[dim] && image[dim] > constraints[dim] ) {
 637+ var s = constraints[dim] / image[dim];
 638+ if ( s < scaling ) {
 639+ scaling = s;
 640+ }
 641+ }
 642+ } );
 643+ return scaling;
 644+ },
 645+
 646+ /**
 647+ * Given an image (already loaded), dimension constraints
 648+ * return canvas object scaled & transformedi ( & rotated if metadata indicates it's needed )
 649+ * @param {HTMLImageElement}
 650+ * @param {Object} containing width & height constraints
 651+ * @return {HTMLCanvasElement}
 652+ */
 653+ getTransformedCanvasElement: function( image, constraints ) {
 654+
 655+ var rotation = 0;
 656+
 657+ // if this wiki can rotate images to match their EXIF metadata,
 658+ // we should do the same in our preview
 659+ if ( mw.config.get( 'wgFileCanRotate' ) ) {
 660+ var orientation = this.getOrientation();
 661+ rotation = orientation ? 360 - orientation : 0;
 662+ }
 663+
 664+ // swap scaling constraints if needed by rotation...
 665+ var scaleConstraints;
 666+ if ( rotation === 90 || rotation === 270 ) {
 667+ scaleConstraints = {
 668+ width: constraints.height,
 669+ height: constraints.width
 670+ };
 671+ } else {
 672+ scaleConstraints = {
 673+ width: constraints.width,
 674+ height: constraints.height
 675+ };
 676+ }
 677+
 678+ var scaling = this.getScalingFromConstraints( image, constraints );
 679+
 680+ var width = image.width * scaling;
 681+ var height = image.height * scaling;
 682+
 683+ // Determine the offset required to center the image
 684+ var dx = (constraints.width - width) / 2;
 685+ var dy = (constraints.height - height) / 2;
 686+
 687+ switch ( rotation ) {
 688+ // If a rotation is applied, the direction of the axis
 689+ // changes as well. You can derive the values below by
 690+ // drawing on paper an axis system, rotate it and see
 691+ // where the positive axis direction is
 692+ case 90:
 693+ x = dx;
 694+ y = dy - constraints.height;
 695+ break;
 696+ case 180:
 697+ x = dx - constraints.width;
 698+ y = dy - constraints.height;
 699+ break;
 700+ case 270:
 701+ x = dx - constraints.width;
 702+ y = dy;
 703+ break;
 704+ case 0:
 705+ default:
 706+ x = dx;
 707+ y = dy;
 708+ break;
 709+ }
 710+
 711+ var $canvas = $j( '<canvas></canvas>' ).attr( constraints );
 712+ var ctx = $canvas[0].getContext( '2d' );
 713+ ctx.clearRect( 0, 0, width, height );
 714+ ctx.rotate( rotation / 180 * Math.PI );
 715+ ctx.drawImage( image, x, y, width, height );
 716+
 717+ return $canvas;
 718+ },
 719+
 720+ /**
 721+ * Return a browser-scaled image element, given an image and constraints.
 722+ * @param {HTMLImageElement}
 723+ * @param {Object} with width and height properties
 724+ * @return {HTMLImageElement} with same src, but different attrs
 725+ */
 726+ getBrowserScaledImageElement: function( image, constraints ) {
 727+ var scaling = this.getScalingFromConstraints( image, constraints );
 728+ return $j( '<img/>' )
 729+ .attr( {
 730+ width: parseInt( image.width * scaling, 10 ),
 731+ height: parseInt( image.height * scaling, 10 ),
 732+ src: image.src
 733+ } )
 734+ .css( {
 735+ 'margin-top': ( parseInt( ( constraints.height - image.height * scaling ) / 2, 10 ) ).toString() + 'px'
 736+ } );
 737+ },
 738+
 739+ /**
 740+ * Return an element suitable for the preview of a certain size. Uses canvas when possible
 741+ * @param {HTMLImageElement}
 742+ * @param {Integer} width
 743+ * @param {Integer} height
 744+ * @return {HTMLCanvasElement|HTMLImageElement}
 745+ */
 746+ getScaledImageElement: function( image, width, height ) {
 747+ if ( typeof width === 'undefined' || width === null || width <= 0 ) {
 748+ width = mw.UploadWizard.config['thumbnailWidth'];
 749+ }
 750+ var constraints = {
 751+ width: parseInt( width, 10 ),
 752+ height: ( mw.isDefined( height ) ? parseInt( height, 10 ) : null )
 753+ };
 754+
 755+ return mw.canvas.isAvailable() ? this.getTransformedCanvasElement( image, constraints )
 756+ : this.getBrowserScaledImageElement( image, constraints );
 757+ },
 758+
 759+ /**
571760 * Given a jQuery selector, subscribe to the "ready" event that fills the thumbnail
572761 * This will trigger if the thumbnail is added in the future or if it already has been
573762 *
@@ -577,13 +766,6 @@
578767 */
579768 setThumbnail: function( selector, width, height, isLightBox ) {
580769 var _this = this;
581 - if ( typeof width === 'undefined' || width === null || width <= 0 ) {
582 - width = mw.UploadWizard.config['thumbnailWidth'];
583 - }
584 - var constraints = {
585 - width: parseInt( width, 10 ),
586 - height: ( mw.isDefined( height ) ? parseInt( height, 10 ) : null )
587 - };
588770
589771 /**
590772 * This callback will add an image to the selector, using in-browser scaling if necessary
@@ -596,36 +778,14 @@
597779 _this.ui.setStatus( 'mwe-upwiz-thumbnail-failed' );
598780 return;
599781 }
600 -
601 - // if this debugger isn't here, it's okay??
602 - // figure out what scaling is needed, if any
603 - var scaling = 1;
604 - $j.each( [ 'width', 'height' ], function( i, dim ) {
605 - if ( constraints[dim] && image[dim] > constraints[dim] ) {
606 - var s = constraints[dim] / image[dim];
607 - if ( s < scaling ) {
608 - scaling = s;
609 - }
610 - }
611 - } );
612 -
 782+ var elm = _this.getScaledImageElement( image, width, height );
613783 // add the image to the DOM, finally
614784 $j( selector )
615785 .css( { background: 'none' } )
616786 .html(
617787 $j( '<a/></a>' )
618788 .addClass( "mwe-upwiz-thumbnail-link" )
619 - .append(
620 - $j( '<img/>' )
621 - .attr( {
622 - width: parseInt( image.width * scaling, 10 ),
623 - height: parseInt( image.height * scaling, 10 ),
624 - src: image.src
625 - } )
626 - .css( {
627 - 'margin-top': ( parseInt( ( height - image.height * scaling ) / 2, 10 ) ).toString() + 'px'
628 - } )
629 - )
 789+ .append( elm )
630790 );
631791 placed = true;
632792 };
@@ -656,7 +816,6 @@
657817 }
658818 }
659819 );
660 -
661820 },
662821
663822 /**
Index: trunk/extensions/UploadWizard/resources/mw.fileApi.js
@@ -26,6 +26,8 @@
2727 return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge;
2828 }
2929
 30+
 31+
3032 };
3133
3234 } )( jQuery, mediaWiki );
Index: trunk/extensions/UploadWizard/resources/mw.canvas.js
@@ -0,0 +1,13 @@
 2+( function( mw ) {
 3+
 4+ mw.canvas = {
 5+ /**
 6+ * @return boolean
 7+ */
 8+ isAvailable: function() {
 9+ return !! ( document.createElement('canvas')['getContext'] );
 10+ }
 11+
 12+ }
 13+
 14+} )( mediaWiki );
Property changes on: trunk/extensions/UploadWizard/resources/mw.canvas.js
___________________________________________________________________
Added: svn:eol-style
115 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r97671* (bug 6672, 31024) Fixes for handling of images with an EXIF orientation...brion22:13, 20 September 2011
r97675MFT r97651, r97656, r97659 updated test cases for bug 6672, 31024...brion22:40, 20 September 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r79859Follow-up r79845: add function documentation; only call wfMakeDirsParent if w...btongminh11:49, 8 January 2011
r89918Use FileAPI for previews when possible....neilk05:28, 12 June 2011
r90016Start on test cases for bug 6672 (Exif orientation support), follow up to r79......brion22:13, 13 June 2011

Comments

#Comment by Brion VIBBER (talk | contribs)   18:01, 15 June 2011

Soo.... good news and bad news. The Exif rotation handling in core is in 1.18/1.19 but isn't in 1.17.

Since this is the trunk version of UploadWizard, handling Exif rotation in UW as well as in Special:Upload and other core stuff is good.

But if you want to deploy this on live 1.17, which does not do exif rotation handling, then adding this will probably actually be more confusing.

#Comment by Brion VIBBER (talk | contribs)   18:06, 15 June 2011

mediawiki.libs.jpegmeta is also not available in core resources until 1.19; 1.18 has a mediawiki.util.jpegmeta. 1.17 seems to have neither.

#Comment by Brion VIBBER (talk | contribs)   21:54, 21 June 2011

OK for trunk; 1.17/1.18 will need slight tweaks.

Status & tagging log