Gergő Tisza has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/111397

Change subject: Add Thumbnail model
......................................................................

Add Thumbnail model

Also refactor size calculation a bit - I found target/requested
harder to remember.

Change-Id: I4781cdd7004e9a8e36875c152e1d3a335a55b7d7
---
M MultimediaViewer.php
M resources/mmv/mmv.js
A resources/mmv/model/mmv.model.Thumbnail.js
M resources/mmv/provider/mmv.provider.ThumbnailInfo.js
M tests/qunit/mmv.model.test.js
M tests/qunit/mmv.test.js
M tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
7 files changed, 168 insertions(+), 41 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/MultimediaViewer 
refs/changes/97/111397/1

diff --git a/MultimediaViewer.php b/MultimediaViewer.php
index 95e0dbc..526f637 100644
--- a/MultimediaViewer.php
+++ b/MultimediaViewer.php
@@ -143,6 +143,16 @@
                ),
        ), $moduleInfo( 'mmv/model' ) );
 
+       $wgResourceModules['mmv.model.Thumbnail'] = array_merge( array(
+               'scripts' => array(
+                       'mmv.model.Thumbnail.js',
+               ),
+
+               'dependencies' => array(
+                       'mmv.model',
+               ),
+       ), $moduleInfo( 'mmv/model' ) );
+
        $wgResourceModules['mmv.provider'] = array_merge( array(
                'scripts' => array(
                        'mmv.provider.Api.js',
@@ -244,6 +254,7 @@
                        'mmv.model.FileUsage',
                        'mmv.model.Image',
                        'mmv.model.Repo',
+                       'mmv.model.Thumbnail',
                        'mmv.provider',
                        'mediawiki.language',
                        'mmv.multilightbox',
diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js
index b31e89d..ad38488 100755
--- a/resources/mmv/mmv.js
+++ b/resources/mmv/mmv.js
@@ -267,28 +267,50 @@
         * Gets the API arguments for various calls to the API to find sized 
thumbnails.
         * @param {mw.LightboxInterface} ui
         * @returns {Object}
-        * @returns {number} return.requested The width that should be 
requested from the API
-        * @returns {number} return.target The ideal width we would like to 
have - should be the width of the image element later.
+        * @returns {number} return.real The width that should be requested 
from the API
+        * @returns {number} return.css The ideal width we would like to have - 
should be the width of the image element later.
         */
        MMVP.getImageSizeApiArgs = function ( ui ) {
-               var requestedWidth, calculatedMaxWidth,
-                       thumb = ui.currentImage.thumbnail,
-                       targetWidth = ui.$imageWrapper.width(),
-                       targetHeight = ui.$imageWrapper.height();
+               var thumb = ui.currentImage.thumbnail;
 
-               if ( ( targetWidth / targetHeight ) > ( thumb.width / 
thumb.height ) ) {
-                       // Need to find width corresponding to highest height 
we can have.
-                       calculatedMaxWidth = ( thumb.width / thumb.height ) * 
targetHeight;
-                       requestedWidth = this.findNextHighestImageSize( 
calculatedMaxWidth );
+               return this.getThumbnailWidth( ui.$imageWrapper.width(), 
ui.$imageWrapper.height(),
+                       thumb.width, thumb.height );
+       };
+
+       /**
+        * Finds the largest width for an image so that it will still fit into 
a given bounding box,
+        * based on the size of a sample (some smaller version of the same 
image, like the thumbnail
+        * shown in the article) which is used to calculate the ratio.
+        *
+        * Returns two values, a CSS width which is the size in pixels that 
should be used so the image
+        * fits exactly into the bounding box, and a real width which should be 
the size of the
+        * downloaded image in pixels. The two will be different for two 
reasons:
+        * - images are bucketed for more efficient caching, so the real width 
will always be one of
+        *   the numbers in this.imageWidthBuckets
+        * - for devices with high pixel density (multiple actual pixels per 
CSS pixel) we want to use a
+        *   larger image so that there will be roughly one image pixel per 
physical display pixel
+        *
+        * @param {number} boundingWidth width of the bounding box
+        * @param {number} boundingHeight height of the bounding box
+        * @param {number} sampleWidth width of the sample image
+        * @param {number} sampleHeight height of the sample image
+        * @return {{css: number, real: number}} 'css' field will contain the 
width of the
+        *     thumbnail in CSS pixels, 'real' the actual image size that 
should be requested.
+        */
+       MMVP.getThumbnailWidth = function( boundingWidth, boundingHeight, 
sampleWidth, sampleHeight ) {
+               var cssWidth, bucketedWidth;
+               if ( ( boundingWidth / boundingHeight ) > ( sampleWidth / 
sampleHeight ) ) {
+                       // we are limited by height; we need to calculate the 
max width that fits
+                       cssWidth = ( sampleWidth / sampleHeight ) * 
boundingHeight;
                } else {
-                       // Simple case, ratio tells us we're limited by width
-                       requestedWidth = this.findNextHighestImageSize( 
targetWidth );
+                       // simple case, ratio tells us we're limited by width
+                       cssWidth = boundingWidth;
                }
+               bucketedWidth = this.findNextHighestImageSize( cssWidth );
 
                return {
-                       // Factor in pixel ratio so we get as many pixels as 
the device supports, see b/60388
-                       requested: requestedWidth * $.devicePixelRatio(),
-                       target: calculatedMaxWidth || targetWidth
+                       css: cssWidth,
+                       real: bucketedWidth * $.devicePixelRatio()
                };
        };
 
@@ -333,11 +355,13 @@
         */
        MMVP.resize = function ( ui ) {
                var viewer = this,
-                       fileTitle = this.currentImageFileTitle;
+                       fileTitle = this.currentImageFileTitle,
+                       imageWidth;
 
                if ( fileTitle ) {
-                       this.fetchImageInfo( fileTitle ).done( function ( 
imageData, repoInfo, targetWidth, requestedWidth ) {
-                               viewer.loadResizedImage( ui, imageData, 
targetWidth, requestedWidth );
+                       imageWidth = this.getImageSizeApiArgs( ui );
+                       this.fetchImageInfoWithThumbnail( fileTitle, 
imageWidth.real ).then( function( imageInfo ) {
+                                       viewer.loadResizedImage( ui, imageInfo, 
imageWidth.css, imageWidth.real );
                        } );
                }
 
@@ -736,17 +760,15 @@
         */
        MMVP.fetchImageInfo = function ( fileTitle ) {
                var widths = this.getImageSizeApiArgs( this.ui ),
-                       targetWidth = widths.target,
-                       requestedWidth = widths.requested;
+                       targetWidth = widths.css,
+                       requestedWidth = widths.real;
 
                return $.when(
                        this.fileRepoInfoProvider.get(),
                        this.imageInfoProvider.get( fileTitle ),
                        this.thumbnailInfoProvider.get( fileTitle, 
requestedWidth )
-               ).then( function( fileRepoInfoHash, imageInfo, thumbnailData ) {
-                       var thumbnailUrl = thumbnailData[0],
-                               thumbnailWidth = thumbnailData[1];
-                       imageInfo.addThumbUrl( thumbnailWidth, thumbnailUrl );
+               ).then( function( fileRepoInfoHash, imageInfo, thumbnail ) {
+                       imageInfo.addThumbUrl( thumbnail.width, thumbnail.url );
                        return $.Deferred().resolve( imageInfo, 
fileRepoInfoHash, targetWidth, requestedWidth );
                } );
        };
diff --git a/resources/mmv/model/mmv.model.Thumbnail.js 
b/resources/mmv/model/mmv.model.Thumbnail.js
new file mode 100644
index 0000000..7f31ba0
--- /dev/null
+++ b/resources/mmv/model/mmv.model.Thumbnail.js
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the MediaWiki extension MultimediaViewer.
+ *
+ * MultimediaViewer is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MultimediaViewer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MultimediaViewer.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+( function ( mw ) {
+       /**
+        * @class mw.mmv.model.Thumbnail
+        * Represents information about an image thumbnail
+        * @constructor
+        * @param {string} url URL to the thumbnail
+        * @param {number} width Width in pixels
+        * @param {number} height Height in pixels
+        */
+       function Thumbnail(
+               url,
+               width,
+               height
+       ) {
+               /** @property {string} url The URL to the original image */
+               this.url = url;
+
+               /** @property {number} width The width of the thumbnail in 
pixels */
+               this.width = width;
+
+               /** @property {number} height The height of the thumbnail in 
pixels */
+               this.height = height;
+       }
+
+       mw.mmv.model.Thumbnail = Thumbnail;
+}( mediaWiki ) );
diff --git a/resources/mmv/provider/mmv.provider.ThumbnailInfo.js 
b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
index 2affa77..6415aae 100644
--- a/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
+++ b/resources/mmv/provider/mmv.provider.ThumbnailInfo.js
@@ -32,16 +32,19 @@
 
        /**
         * @method
-        * Runs an API GET request to get the thumbnail info.
+        * Runs an API GET request to get the thumbnail info for the specified 
size.
+        * The thumbnail always has the same aspect ration as the full image.
+        * One of width or height can be null; if both are set, the API will 
return the largest
+        * thumbnail which fits into a width x height bounding box (or the 
full-sized image - whichever
+        * is smaller).
         * @param {mw.Title} file
-        * @param {number} width thumbnail width
-        * @return {jQuery.Promise<string, number>} a promise which resolves to 
the thumbnail URL and
-        *     the actual width of the thumbnail (which might be smaller than 
the requested width,
-        *     in case the size we requested was larger than the full image 
size).
+        * @param {number} width thumbnail width in pixels
+        * @param {number} height thumbnail height in pixels
+        * @return {jQuery.Promise<mw.mmv.model.Thumbnail>}
         */
-       ThumbnailInfo.prototype.get = function( file, width ) {
+       ThumbnailInfo.prototype.get = function( file, width, height ) {
                var provider = this,
-                       cacheKey = file.getPrefixedDb() + '|' + width;
+                       cacheKey = file.getPrefixedDb() + '|' + ( width || '-' 
) + '|' + ( height || '-' );
 
                if ( !this.cache[cacheKey] ) {
                        this.cache[cacheKey] = this.api.get( {
@@ -49,13 +52,25 @@
                                prop: 'imageinfo',
                                titles: file.getPrefixedDb(),
                                iiprop: 'url',
-                               iiurlwidth: width,
+                               iiurlwidth: width, // mw.Api will omit 
null/undefined parameters
+                               iiurlheight: height,
                                format: 'json'
                        } ).then( function( data ) {
                                return provider.getQueryPage( file, data );
                        } ).then( function( page ) {
                                if ( page.imageinfo && page.imageinfo[0] ) {
-                                       return $.Deferred().resolve( 
page.imageinfo[0].thumburl, page.imageinfo[0].thumbwidth );
+                                       var imageInfo = page.imageinfo[0];
+                                       if ( imageInfo.thumburl && 
imageInfo.thumbwidth && imageInfo.thumbheight ) {
+                                               return $.Deferred().resolve(
+                                                       new 
mw.mmv.model.Thumbnail(
+                                                               
imageInfo.thumburl,
+                                                               
imageInfo.thumbwidth,
+                                                               
imageInfo.thumbheight
+                                                       )
+                                               );
+                                       } else {
+                                               return $.Deferred().reject( 
'error in provider, thumb info not found' );
+                                       }
                                } else if ( page.missing === '' && 
page.imagerepository === '' ) {
                                        return $.Deferred().reject( 'file does 
not exist: ' + file.getPrefixedDb() );
                                } else {
diff --git a/tests/qunit/mmv.model.test.js b/tests/qunit/mmv.model.test.js
index aac1854..20bf0c4 100644
--- a/tests/qunit/mmv.model.test.js
+++ b/tests/qunit/mmv.model.test.js
@@ -112,4 +112,15 @@
                assert.strictEqual( dbRepo.getArticlePath(), 
'http://example.org/wiki/$1', 'DB article path is set correctly' );
                assert.strictEqual( apiRepo.getArticlePath(), 
'http://example.net/wiki/$1', 'API article path is set correctly' );
        } );
+
+       QUnit.test( 'Thumbnail constructor sanity check', 3, function ( assert 
) {
+               var width = 23,
+                       height = 42,
+                       url = 'http://example.com/foo.jpg',
+                       thumbnail = new mw.mmv.model.Thumbnail( url, width, 
height );
+
+               assert.strictEqual( thumbnail.url, url, 'Url is set correctly' 
);
+               assert.strictEqual( thumbnail.width, width, 'Width is set 
correctly' );
+               assert.strictEqual( thumbnail.height, height, 'Height is set 
correctly' );
+       } );
 }( mediaWiki ) );
diff --git a/tests/qunit/mmv.test.js b/tests/qunit/mmv.test.js
index 7e73e94..07b5164 100644
--- a/tests/qunit/mmv.test.js
+++ b/tests/qunit/mmv.test.js
@@ -466,8 +466,8 @@
 
                widths = viewer.getImageSizeApiArgs( ui );
 
-               assert.strictEqual( widths.target, 150/100*200, 'Correct target 
width was computed.' );
-               assert.strictEqual( widths.requested, 320 * 
$.devicePixelRatio(), 'Correct requested width was computed.' );
+               assert.strictEqual( widths.css, 150/100*200, 'Correct CSS width 
was computed.' );
+               assert.strictEqual( widths.real, 320 * $.devicePixelRatio(), 
'Correct real width was computed.' );
 
                // Fake viewport dimensions, width/height == 1.0, we are 
limited by width
                ui.$imageWrapper.height( 600 );
@@ -475,8 +475,8 @@
 
                widths = viewer.getImageSizeApiArgs( ui );
 
-               assert.strictEqual( widths.target, 600, 'Correct target width 
was computed.' );
-               assert.strictEqual( widths.requested, 640 * 
$.devicePixelRatio(), 'Correct requested width was computed.' );
+               assert.strictEqual( widths.css, 600, 'Correct CSS width was 
computed.' );
+               assert.strictEqual( widths.real, 640 * $.devicePixelRatio(), 
'Correct real width was computed.' );
 
                ui.unattach();
        } );
diff --git a/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js 
b/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
index 27b4145..84f5263 100644
--- a/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
+++ b/tests/qunit/provider/mmv.provider.ThumbnailInfo.test.js
@@ -25,7 +25,7 @@
                assert.ok( thumbnailInfoProvider );
        } );
 
-       QUnit.asyncTest( 'ThumbnailInfo get test', 5, function ( assert ) {
+       QUnit.asyncTest( 'ThumbnailInfo get test', 6, function ( assert ) {
                var apiCallCount = 0,
                        api = { get: function() {
                                apiCallCount++;
@@ -54,11 +54,12 @@
                        file = new mw.Title( 'File:Stuff.jpg' ),
                        thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
 
-               thumbnailInfoProvider.get( file, 100 ).then( function( 
thumnailUrl, thumbnailWidth ) {
-                       assert.strictEqual( thumnailUrl,
+               thumbnailInfoProvider.get( file, 100 ).then( function( 
thumbnail ) {
+                       assert.strictEqual( thumbnail.url,
                                
'https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Stuff.jpg/51px-Stuff.jpg',
                                'URL is set correctly' );
-                       assert.strictEqual( thumbnailWidth, 95, 'actual width 
is set correctly' );
+                       assert.strictEqual( thumbnail.width, 95, 'actual width 
is set correctly' );
+                       assert.strictEqual( thumbnail.height, 200, 'actual 
height is set correctly' );
                } ).then( function() {
                        assert.strictEqual( apiCallCount, 1 );
                        // call the data provider a second time to check caching
@@ -130,4 +131,28 @@
                        QUnit.start();
                } );
        } );
+
+       QUnit.asyncTest( 'ThumbnailInfo fail test 3', 1, function ( assert ) {
+               var api = { get: function() {
+                               return $.Deferred().resolve( {
+                                       query: {
+                                               pages: {
+                                                       '-1': {
+                                                               title: 
'File:Stuff.jpg',
+                                                               imageinfo: [
+                                                                       {}
+                                                               ]
+                                                       }
+                                               }
+                                       }
+                               } );
+                       } },
+                       file = new mw.Title( 'File:Stuff.jpg' ),
+                       thumbnailInfoProvider = new 
mw.mmv.provider.ThumbnailInfo( api );
+
+               thumbnailInfoProvider.get( file, 100 ).fail( function() {
+                       assert.ok( true, 'promise rejected when thumbnail info 
is missing' );
+                       QUnit.start();
+               } );
+       } );
 }( mediaWiki, jQuery ) );

-- 
To view, visit https://gerrit.wikimedia.org/r/111397
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4781cdd7004e9a8e36875c152e1d3a335a55b7d7
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/MultimediaViewer
Gerrit-Branch: master
Gerrit-Owner: GergÅ‘ Tisza <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to