mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-11-28 01:50:09 +00:00
Introduce image size bucketing
See the conversation on mediawiki.org: http://ur1.ca/g14jv We're trying this out as one option in combatting slow thumbnail load times. It may or may not work very well. Bug: 56695 Change-Id: If1211fdff87c0782c7355d654415bfd29d63d84a
This commit is contained in:
parent
f18208025c
commit
f96d5498ca
|
@ -85,6 +85,22 @@
|
|||
urls = [],
|
||||
viewer = this;
|
||||
|
||||
/**
|
||||
* @property {number[]}
|
||||
* @private
|
||||
* List of acceptable image sizes...used to bucket
|
||||
*/
|
||||
this.imageWidthBuckets = [
|
||||
320,
|
||||
640,
|
||||
800,
|
||||
1024,
|
||||
1280,
|
||||
1920,
|
||||
2560,
|
||||
2880
|
||||
];
|
||||
|
||||
/**
|
||||
* @property {mw.Api}
|
||||
* @private
|
||||
|
@ -125,7 +141,8 @@
|
|||
$links.data( 'filePageLink', filePageLink );
|
||||
|
||||
// Create a LightboxImage object for each legit image
|
||||
thisImage = viewer.createNewImage( $thumb.prop( 'src' ), filePageLink, fileTitle, index );
|
||||
thisImage = viewer.createNewImage( $thumb.prop( 'src' ), filePageLink, fileTitle, index, thumb );
|
||||
|
||||
urls.push( thisImage );
|
||||
|
||||
// Register callback that launches modal image viewer if valid click
|
||||
|
@ -183,17 +200,69 @@
|
|||
* @param {string} filePageLink Link to the File: page
|
||||
* @param {mw.Title} fileTitle Represents the File: page
|
||||
* @param {number} index Which number file this is
|
||||
* @param {HTMLImageElement} thumb The thumbnail that represents this image on the page
|
||||
* @returns {mw.LightboxImage}
|
||||
*/
|
||||
MMVP.createNewImage = function ( fileLink, filePageLink, fileTitle, index ) {
|
||||
MMVP.createNewImage = function ( fileLink, filePageLink, fileTitle, index, thumb ) {
|
||||
var thisImage = new mw.LightboxImage( fileLink );
|
||||
thisImage.filePageLink = filePageLink;
|
||||
thisImage.filePageTitle = fileTitle;
|
||||
thisImage.index = index;
|
||||
thisImage.thumbnail = thumb;
|
||||
|
||||
return thisImage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the next highest image size given a target size.
|
||||
* Searches the bucketed sizes configured in the class.
|
||||
* @param {number} target
|
||||
* @return {number}
|
||||
*/
|
||||
MMVP.findNextHighestImageSize = function ( target ) {
|
||||
var i, bucket,
|
||||
buckets = this.imageWidthBuckets,
|
||||
len = buckets.length;
|
||||
|
||||
for ( i = 0; i < len; i++ ) {
|
||||
bucket = buckets[i];
|
||||
|
||||
if ( bucket >= target ) {
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
|
||||
// If we failed to find a high enough size...good luck
|
||||
return bucket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the API arguments for various calls to the API to find sized thumbnails.
|
||||
* @param {mw.LightboxInterface} ui
|
||||
* @returns {number}
|
||||
*/
|
||||
MMVP.getImageSizeApiArgs = function ( ui ) {
|
||||
var requestedWidth, calculatedMaxWidth,
|
||||
thumb = ui.currentImage.thumbnail,
|
||||
density = $.devicePixelRatio(),
|
||||
targetWidth = density * ui.$imageWrapper.width(),
|
||||
targetHeight = density * ui.$imageWrapper.height();
|
||||
|
||||
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 );
|
||||
} else {
|
||||
// Simple case, ratio tells us we're limited by width
|
||||
requestedWidth = this.findNextHighestImageSize( targetWidth );
|
||||
}
|
||||
|
||||
return {
|
||||
requested: requestedWidth,
|
||||
target: calculatedMaxWidth || targetWidth
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles clicks on legit image links.
|
||||
*
|
||||
|
@ -236,19 +305,19 @@
|
|||
*/
|
||||
MMVP.resize = function ( ui ) {
|
||||
var viewer = this,
|
||||
density = $.devicePixelRatio(),
|
||||
filename = ui.currentImageFilename;
|
||||
filename = ui.currentImageFilename,
|
||||
apiArgs = {
|
||||
action: 'query',
|
||||
format: 'json',
|
||||
titles: filename,
|
||||
prop: 'imageinfo',
|
||||
iiprop: 'url'
|
||||
},
|
||||
|
||||
this.api.get( {
|
||||
action: 'query',
|
||||
format: 'json',
|
||||
titles: filename,
|
||||
prop: 'imageinfo',
|
||||
iiprop: 'url',
|
||||
iiurlwidth: Math.floor( density * ui.$imageWrapper.width() ),
|
||||
iiurlheight: Math.floor( density * ui.$imageWrapper.height() )
|
||||
} ).done( function ( data ) {
|
||||
viewer.loadResizedImage( ui, data );
|
||||
targetWidth = this.getImageSizeApiArgs( ui, apiArgs );
|
||||
|
||||
this.api.get( apiArgs ).done( function ( data ) {
|
||||
viewer.loadResizedImage( ui, data, targetWidth );
|
||||
} );
|
||||
};
|
||||
|
||||
|
@ -259,8 +328,9 @@
|
|||
*
|
||||
* @param {mw.LightboxInterface} ui lightbox that got resized
|
||||
* @param {Object} data information regarding the new resized image
|
||||
* @param {number} targetWidth
|
||||
*/
|
||||
MMVP.loadResizedImage = function ( ui, data ) {
|
||||
MMVP.loadResizedImage = function ( ui, data, targetWidth ) {
|
||||
var imageInfo, innerInfo, rpid, viewer, image;
|
||||
|
||||
// Replace image only if data was returned.
|
||||
|
@ -276,6 +346,7 @@
|
|||
innerInfo = imageInfo.imageinfo[0];
|
||||
|
||||
image.onload = function () {
|
||||
image.width = targetWidth;
|
||||
viewer.profileEnd( rpid );
|
||||
ui.replaceImageWith( image );
|
||||
this.updateControls();
|
||||
|
@ -669,15 +740,18 @@
|
|||
|
||||
mdpid = this.profileStart( 'metadata-fetch' );
|
||||
|
||||
this.fetchImageInfo( image.filePageTitle, function ( imageInfo ) {
|
||||
this.fetchImageInfo( image.filePageTitle, function ( imageInfo, res, size ) {
|
||||
var pid,
|
||||
innerInfo = imageInfo.imageinfo[0],
|
||||
imageEle = new Image();
|
||||
imageEle = new Image(),
|
||||
targetWidth = size;
|
||||
|
||||
viewer.profileEnd( mdpid );
|
||||
|
||||
imageEle.onload = function () {
|
||||
imageEle.width = targetWidth;
|
||||
viewer.profileEnd( pid );
|
||||
|
||||
viewer.lightbox.iface.replaceImageWith( imageEle );
|
||||
viewer.lightbox.iface.$imageDiv.removeClass( 'empty' );
|
||||
viewer.updateControls();
|
||||
|
@ -733,7 +807,7 @@
|
|||
|
||||
if ( viewer.imageInfo[filename] ) {
|
||||
// Give back the information we have
|
||||
cb( viewer.imageInfo[filename], viewer.repoInfo );
|
||||
cb( viewer.imageInfo[filename], viewer.repoInfo, targetWidth );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -754,26 +828,29 @@
|
|||
|
||||
var imageInfo,
|
||||
filename = fileTitle.getPrefixedText(),
|
||||
density = $.devicePixelRatio(),
|
||||
apiArgs = {
|
||||
action: 'query',
|
||||
format: 'json',
|
||||
titles: filename,
|
||||
prop: 'imageinfo',
|
||||
iiprop: iiprops.join( '|' ),
|
||||
iiurlwidth: Math.floor( density * this.lightbox.iface.$imageWrapper.width() ),
|
||||
iiurlheight: Math.floor( density * this.lightbox.iface.$imageWrapper.height() ),
|
||||
// Short-circuit, don't fallback, to save some tiny amount of time
|
||||
iiextmetadatalanguage: mw.config.get( 'wgUserLanguage', false ) || mw.config.get( 'wgContentLanguage', 'en' )
|
||||
},
|
||||
viewer = this;
|
||||
viewer = this,
|
||||
|
||||
widths = this.getImageSizeApiArgs( this.ui ),
|
||||
targetWidth = widths.target,
|
||||
requestedWidth = widths.requested;
|
||||
|
||||
apiArgs.iiurlwidth = requestedWidth;
|
||||
|
||||
if ( this.imageInfo[filename] === undefined ) {
|
||||
// Fetch it in the same API query as the image info
|
||||
fetchImageInfoCallback();
|
||||
} else {
|
||||
this.fetchRepoInfo( function ( res ) {
|
||||
cb( viewer.imageInfo[filename], res );
|
||||
cb( viewer.imageInfo[filename], res, targetWidth );
|
||||
} );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -228,7 +228,7 @@
|
|||
link.trigger( 'click' );
|
||||
} );
|
||||
|
||||
QUnit.test( 'Validate new LightboxImage object has sane constructor parameters', 4, function ( assert ) {
|
||||
QUnit.test( 'Validate new LightboxImage object has sane constructor parameters', 5, function ( assert ) {
|
||||
var viewer,
|
||||
fname = 'valid',
|
||||
imgSrc = '/' + fname + '.jpg/300px-' + fname + '.jpg',
|
||||
|
@ -236,13 +236,25 @@
|
|||
|
||||
createGallery( imgSrc );
|
||||
|
||||
mw.MultimediaViewer.prototype.createNewImage = function ( fileLink, filePageLink, fileTitle, index ) {
|
||||
mw.MultimediaViewer.prototype.createNewImage = function ( fileLink, filePageLink, fileTitle, index, thumb ) {
|
||||
assert.ok( fileLink.match( imgRegex ), 'Thumbnail URL used in creating new image object' );
|
||||
assert.strictEqual( filePageLink, '', 'File page link is sane when creating new image object' );
|
||||
assert.strictEqual( fileTitle.title, fname, 'Filename is correct when passed into new image constructor' );
|
||||
assert.strictEqual( index, 0, 'The only image we created in the gallery is set at index 0 in the images array' );
|
||||
assert.strictEqual( thumb.outerHTML, '<img src="' + imgSrc + '">', 'The image element passed in is the thumbnail we want.' );
|
||||
};
|
||||
|
||||
viewer = new mw.MultimediaViewer();
|
||||
} );
|
||||
|
||||
QUnit.test( 'We get sane image sizes when we ask for them', 5, function ( assert ) {
|
||||
var viewer = new mw.MultimediaViewer();
|
||||
|
||||
assert.strictEqual( viewer.findNextHighestImageSize( 200 ), 320, 'Low target size gives us lowest possible size bucket' );
|
||||
assert.strictEqual( viewer.findNextHighestImageSize( 320 ), 320, 'Asking for a bucket size gives us exactly that bucket size' );
|
||||
assert.strictEqual( viewer.findNextHighestImageSize( 320.00001 ), 640, 'Asking for greater than an image bucket definitely gives us the next size up' );
|
||||
assert.strictEqual( viewer.findNextHighestImageSize( 2000 ), 2560, 'The image bucketing also works on big screens' );
|
||||
assert.strictEqual( viewer.findNextHighestImageSize( 3000 ), 2880, 'The image bucketing also works on REALLY big screens' );
|
||||
} );
|
||||
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
Loading…
Reference in a new issue