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