mediawiki-extensions-Multim.../resources/mmv/mmv.ThumbnailWidthCalculator.js
Gergő Tisza 06cc6cca1a Refactor thumbnail size calculation
* moves generic logic into ThumbnailSizeCalculator class
* moves UI-specific logic into interface class
* fixes bug where non-bucketed sizes were served on devices with
  non-standard pixel density
* fixes bug where bucketed size was compared to css size instead of screen size
  for resizing

Change-Id: I8ba3380b74fcc8fb0a6ecc3f3140627411851ad0
Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/196
2014-02-08 01:27:27 +00:00

174 lines
6.2 KiB
JavaScript

/*
* 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, $ ) {
var TWCP;
/**
* @class mw.mmv.ThumbnailWidthCalculator
*
* A helper class for bucketing image sizes.
* Bucketing helps to avoid cache fragmentation and thus speed up image loading:
* instead of generating potentially hundreds of different thumbnail sizes, we restrict
* ourselves to a short list of acceptable thumbnail widths, and only ever load thumbnails
* of that size. Final size adjustment is done in a thumbnail.
*
* See also the [Standardized thumbnail sizes RFC][1]
*
* [1]: https://www.mediawiki.org/wiki/Talk:Requests_for_comment/Standardized_thumbnails_sizes
*
* @constructor
* @param {Object} [options]
* @param {number[]} [options.widthBuckets] see {@link mw.mmv.ThumbnailWidthCalculator#widthBuckets}
* @param {number} [options.devicePixelRatio] see {@link mw.mmv.ThumbnailWidthCalculator#devicePixelRatio};
* will be autodetected if omitted
*/
function ThumbnailWidthCalculator( options ) {
options = $.extend( {}, this.defaultOptions, options );
if ( !options.widthBuckets.length ) {
throw 'No buckets!';
}
/**
* List of thumbnail width bucket sizes, in pixels.
* @property {number[]}
*/
this.widthBuckets = options.widthBuckets;
this.widthBuckets.sort( function( a, b ) { return a - b; } );
/**
* Screen pixel count per CSS pixel.
* @property {number}
*/
this.devicePixelRatio = options.devicePixelRatio;
}
TWCP = ThumbnailWidthCalculator.prototype;
/**
* The default list of image widths
* @static
* @property {Object}
*/
TWCP.defaultOptions = {
// default image widths
widthBuckets: [
320,
640,
800,
1024,
1280,
1920,
2560,
2880
],
// screen pixel per CSS pixel
devicePixelRatio: $.devicePixelRatio()
};
/**
* @method
* Finds the smallest bucket which is large enough to hold the target size
* (i. e. the smallest bucket whose size is equal to or greater than the target).
* If none of the buckets are large enough, returns the largest bucket.
* @param {number} target
* @return {number}
*/
TWCP.findNextBucket = function ( target ) {
var i, bucket,
buckets = this.widthBuckets;
for ( i = 0; i < buckets.length; i++ ) {
bucket = buckets[i];
if ( bucket >= target ) {
return bucket;
}
}
// If we failed to find a high enough size...good luck
return bucket;
};
/**
* @method
* @protected
* 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.
*
* This is for internal use, you should probably use calculateWidths() instead.
*
* @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 {number} the largest width so that the scaled version of the sample image fits
* into the bounding box (either horizontal or vertical edges touch on both sides).
*/
TWCP.calculateFittingWidth = function( boundingWidth, boundingHeight, sampleWidth, sampleHeight ) {
if ( ( boundingWidth / boundingHeight ) > ( sampleWidth / sampleHeight ) ) {
// we are limited by height; we need to calculate the max width that fits
return ( sampleWidth / sampleHeight ) * boundingHeight;
} else {
// simple case, ratio tells us we're limited by width
return boundingWidth;
}
};
/**
* @method
* 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.widthBuckets. The resulting thumbnail will be slightly larger than
* the bounding box so that it takes roughly the same amount of bandwidth and
* looks decent when resized by the browser.
* - 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, in CSS pixels
* @param {number} boundingHeight height of the bounding box, in CSS pixels
* @param {number} sampleWidth width of the sample image (in whatever - only used for aspect ratio)
* @param {number} sampleHeight height of the sample image (in whatever - only used for aspect ratio)
* @return {mw.mmv.model.ThumbnailWidth}
*/
TWCP.calculateWidths = function( boundingWidth, boundingHeight, sampleWidth, sampleHeight ) {
var cssWidth,
screenPixelWidth,
bucketedWidth;
cssWidth = this.calculateFittingWidth( boundingWidth, boundingHeight, sampleWidth, sampleHeight );
screenPixelWidth = cssWidth * this.devicePixelRatio;
bucketedWidth = this.findNextBucket( screenPixelWidth );
return new mw.mmv.model.ThumbnailWidth( cssWidth, screenPixelWidth, bucketedWidth );
};
mw.mmv.ThumbnailWidthCalculator = ThumbnailWidthCalculator;
}( mediaWiki, jQuery ) );