Index: trunk/extensions/UploadWizard/UploadWizardHooks.php |
— | — | @@ -25,6 +25,7 @@ |
26 | 26 | 'jquery.ui.widget', |
27 | 27 | 'mediawiki.language', |
28 | 28 | 'mediawiki.util', |
| 29 | + 'mediawiki.libs.jpegmeta', |
29 | 30 | 'ext.uploadwizard.mediawiki.language.parser', |
30 | 31 | ), |
31 | 32 | 'scripts' => array( |
— | — | @@ -48,6 +49,7 @@ |
49 | 50 | // common utilities |
50 | 51 | 'resources/mw.fileApi.js', |
51 | 52 | 'resources/mw.units.js', |
| 53 | + 'resources/mw.canvas.js', |
52 | 54 | 'resources/mw.Log.js', |
53 | 55 | 'resources/mw.Utilities.js', |
54 | 56 | 'resources/mw.UtilitiesTime.js', |
Index: trunk/extensions/UploadWizard/resources/mw.UploadWizard.js |
— | — | @@ -269,19 +269,46 @@ |
270 | 270 | this.transportWeight = this.file.size; |
271 | 271 | |
272 | 272 | 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 | + |
274 | 287 | var image = document.createElement( 'img' ); |
275 | 288 | image.onload = function() { |
276 | 289 | $.publishReady( 'thumbnails.' + _this.index, image ); |
277 | 290 | }; |
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 ); |
282 | 310 | }; |
283 | | - reader.readAsDataURL( _this.file ); |
| 311 | + binReader.readAsBinaryString( _this.file ); |
284 | 312 | } |
285 | | - |
286 | 313 | } |
287 | 314 | // TODO sanitize filename |
288 | 315 | var filename = fileInput.value; |
— | — | @@ -295,6 +322,7 @@ |
296 | 323 | } |
297 | 324 | }, |
298 | 325 | |
| 326 | + |
299 | 327 | /** |
300 | 328 | * Accept the result from a successful API upload transport, and fill our own info |
301 | 329 | * |
— | — | @@ -567,6 +595,167 @@ |
568 | 596 | }, |
569 | 597 | |
570 | 598 | /** |
| 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 | + /** |
571 | 760 | * Given a jQuery selector, subscribe to the "ready" event that fills the thumbnail |
572 | 761 | * This will trigger if the thumbnail is added in the future or if it already has been |
573 | 762 | * |
— | — | @@ -577,13 +766,6 @@ |
578 | 767 | */ |
579 | 768 | setThumbnail: function( selector, width, height, isLightBox ) { |
580 | 769 | 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 | | - }; |
588 | 770 | |
589 | 771 | /** |
590 | 772 | * This callback will add an image to the selector, using in-browser scaling if necessary |
— | — | @@ -596,36 +778,14 @@ |
597 | 779 | _this.ui.setStatus( 'mwe-upwiz-thumbnail-failed' ); |
598 | 780 | return; |
599 | 781 | } |
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 ); |
613 | 783 | // add the image to the DOM, finally |
614 | 784 | $j( selector ) |
615 | 785 | .css( { background: 'none' } ) |
616 | 786 | .html( |
617 | 787 | $j( '<a/></a>' ) |
618 | 788 | .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 ) |
630 | 790 | ); |
631 | 791 | placed = true; |
632 | 792 | }; |
— | — | @@ -656,7 +816,6 @@ |
657 | 817 | } |
658 | 818 | } |
659 | 819 | ); |
660 | | - |
661 | 820 | }, |
662 | 821 | |
663 | 822 | /** |
Index: trunk/extensions/UploadWizard/resources/mw.fileApi.js |
— | — | @@ -26,6 +26,8 @@ |
27 | 27 | return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge; |
28 | 28 | } |
29 | 29 | |
| 30 | + |
| 31 | + |
30 | 32 | }; |
31 | 33 | |
32 | 34 | } )( 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 |
1 | 15 | + native |