Merge "Fix resize issues (Part III, Fin)"

This commit is contained in:
jenkins-bot 2014-03-05 00:16:16 +00:00 committed by Gerrit Code Review
commit 44b9fa127a
9 changed files with 200 additions and 87 deletions

View file

@ -124,7 +124,7 @@
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;
return Math.round( ( sampleWidth / sampleHeight ) * boundingHeight );
} else {
// simple case, ratio tells us we're limited by width
return boundingWidth;
@ -156,16 +156,19 @@
TWCP.calculateWidths = function( boundingWidth, boundingHeight, sampleWidth, sampleHeight ) {
var cssWidth,
cssHeight,
screenPixelWidth,
bucketedWidth;
bucketedWidth,
ratio = sampleHeight / sampleWidth;
cssWidth = this.calculateFittingWidth( boundingWidth, boundingHeight, sampleWidth, sampleHeight );
cssHeight = Math.round( cssWidth * ratio );
screenPixelWidth = cssWidth * this.devicePixelRatio;
bucketedWidth = this.findNextBucket( screenPixelWidth );
return new mw.mmv.model.ThumbnailWidth( cssWidth, screenPixelWidth, bucketedWidth );
return new mw.mmv.model.ThumbnailWidth( cssWidth, cssHeight, screenPixelWidth, bucketedWidth );
};
mw.mmv.ThumbnailWidthCalculator = ThumbnailWidthCalculator;

View file

@ -187,12 +187,7 @@
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
*/
MMVP.setImage = function ( ui, thumbnail, image, imageWidths ) {
// we downscale larger images but do not scale up smaller ones, that would look ugly
if ( thumbnail.width > imageWidths.css ) {
image.width = imageWidths.css;
}
ui.canvas.setImageAndMaxDimensions( image );
ui.canvas.setImageAndMaxDimensions( thumbnail, image, imageWidths );
this.updateControls();
};
@ -282,7 +277,7 @@
return;
}
viewer.displayPlaceholderThumbnail( imageInfo, image, $initialImage, imageWidths );
viewer.displayPlaceholderThumbnail( imageInfo, $initialImage, imageWidths );
} );
metadataPromise = this.fetchSizeIndependentLightboxInfo(
@ -365,17 +360,16 @@
/**
* Display the blurred thumbnail from the page
* @param {mw.mmv.model.Image} imageInfo
* @param {mw.mmv.LightboxImage} imageRawMetadata Raw metadata of image
* @param {jQuery} $initialImage The thumbnail from the page
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
*/
MMVP.displayPlaceholderThumbnail = function ( imageInfo, imageRawMetadata, $initialImage, imageWidths ) {
MMVP.displayPlaceholderThumbnail = function ( imageInfo, $initialImage, imageWidths ) {
// If the actual image has already been displayed, there's no point showing the blurry one
if ( this.realThumbnailShown ) {
return;
}
this.blurredThumbnailShown = this.lightbox.iface.canvas.setThumbnailForDisplay(
this.blurredThumbnailShown = this.lightbox.iface.canvas.maybeDisplayPlaceholder(
imageInfo, $initialImage, imageWidths );
};

View file

@ -24,12 +24,13 @@
* overcomplicated Hungarian notation :)
* @class mw.mmv.model.ThumbnailWidth
* @constructor
* @param {number} css width in CSS pixels
* @param {number} cssWidth width in CSS pixels
* @param {number} cssHeight height in CSS pixels
* @param {number} screen width in screen pixels
* @param {number} real width in real pixels
*/
function ThumbnailWidth( css, screen, real ) {
if ( !css || !screen || !real ) {
function ThumbnailWidth( cssWidth, cssHeight, screen, real ) {
if ( !cssWidth || !cssHeight || !screen || !real ) {
throw 'All parameters are required and cannot be empty or zero';
}
@ -38,7 +39,14 @@ function ThumbnailWidth( css, screen, real ) {
* into UI code like $element.width(x).
* @property {number}
*/
this.css = css;
this.cssWidth = cssWidth;
/**
* Height of the thumbnail on the screen, in CSS pixels. This is the number which can be plugged
* into UI code like $element.height(x).
* @property {number}
*/
this.cssHeight = cssHeight;
/**
* Width of the thumbnail on the screen, in device pixels. On most devices this is the same as

View file

@ -72,6 +72,12 @@
oo.inheritClass( Canvas, mw.mmv.ui.Element );
C = Canvas.prototype;
/** Maximum blownup factor tolerated */
Canvas.MAX_BLOWUP_FACTOR = 11;
/** Blowup factor threshold at which blurring kicks in */
Canvas.BLUR_BLOWUP_FACTOR_THRESHOLD = 2;
/**
* Clears everything.
*/
@ -98,9 +104,11 @@
/**
* Sets contained image and also the max dimensions. Called while resizing the viewer.
* Assumes set function called before.
* @param {mw.mmv.model.Thumbnail} thumbnail thumbnail information
* @param {HTMLImageElement} imageEle
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
*/
C.setImageAndMaxDimensions = function( imageEle ) {
C.setImageAndMaxDimensions = function( thumbnail, imageEle, imageWidths ) {
var $image = $( imageEle );
function makeMaxMatchParent ( $image ) {
@ -110,6 +118,11 @@
} );
}
// we downscale larger images but do not scale up smaller ones, that would look ugly
if ( thumbnail.width > imageWidths.cssWidth ) {
imageEle.width = imageWidths.cssWidth;
}
if ( this.$image.is( imageEle ) ) { // http://bugs.jquery.com/ticket/4087
// We may be changing the width of the image when we resize, we should also
// update the max dimensions otherwise the image is not scaled properly
@ -129,8 +142,11 @@
C.attach = function() {
var canvas = this;
// TODO: Try to use Element.handleEvent() instead !
if ( !this.resizeListener ) {
this.resizeListener = function () { canvas.resizeCallback(); };
this.resizeListener = function () {
canvas.$mainWrapper.trigger( $.Event( 'mmv-resize') );
};
window.addEventListener( 'resize', this.resizeListener );
}
};
@ -147,64 +163,79 @@
}
};
/**
* Resize callback
* @protected
*/
C.resizeCallback = function() {
this.$mainWrapper.trigger( $.Event( 'mmv-resize') );
};
/**
* @method
* Set page thumbnail for display, resizing and bluring if necessary.
* Sets page thumbnail for display if blowupFactor <= MAX_BLOWUP_FACTOR. Otherwise thumb is not set.
* The image gets also blured to avoid pixelation if blowupFactor > BLUR_BLOWUP_FACTOR_THRESHOLD.
* We set SVG files to the maximum screen size available.
* Assumes set function called before.
*
* @param {mw.mmv.model.Image} imageInfo
* @param {jQuery} $initialImage The page thumbnail
* @param {jQuery} $imagePlaceholder Image placeholder to be displayed while the real image loads.
* @param {mw.mmv.model.ThumbnailWidth} imageWidths
* @returns {boolean} Whether the image was blured or not
*/
C.setThumbnailForDisplay = function ( imageInfo, $initialImage, imageWidths ) {
var ratio,
targetWidth,
C.maybeDisplayPlaceholder = function ( imageInfo, $imagePlaceholder, imageWidths ) {
var targetWidth,
targetHeight,
blowupFactor,
blurredThumbnailShown = false;
blurredThumbnailShown = false,
maxSizeFileExtensions = {
'svg' : true,
};
// If the image is smaller than the screen we need to adjust the placeholder's size
if ( imageInfo.width < imageWidths.css ) { // This assumes imageInfo.width in CSS units
targetWidth = imageInfo.width;
targetHeight = imageInfo.height;
} else {
ratio = $initialImage.height() / $initialImage.width();
targetWidth = imageWidths.css;
targetHeight = Math.round( imageWidths.css * ratio );
}
// There are some file types (SVG for example) for which there is no concept
// of initial size. For these cases we force a max canvas resize and no bluring.
if ( maxSizeFileExtensions[ this.imageRawMetadata.filePageTitle.getExtension().toLowerCase() ] ) {
$imagePlaceholder.width( imageWidths.cssWidth );
$imagePlaceholder.height( imageWidths.cssHeight );
this.set( this.imageRawMetadata, $imagePlaceholder.show() );
blowupFactor = targetWidth / $initialImage.width();
// If the placeholder is too blown up, it's not worth showing it
if ( blowupFactor > 11 ) {
return blurredThumbnailShown;
}
$initialImage.width( targetWidth );
$initialImage.height( targetHeight );
// Assume natural thumbnail size¸
targetWidth = imageInfo.width;
targetHeight = imageInfo.height;
// If the image is bigger than the screen we need to resize it
if ( imageInfo.width > imageWidths.cssWidth ) { // This assumes imageInfo.width in CSS units
targetWidth = imageWidths.cssWidth;
targetHeight = imageWidths.cssHeight;
}
blowupFactor = targetWidth / $imagePlaceholder.width();
// If the placeholder is too blown up, it's not worth showing it
if ( blowupFactor > Canvas.MAX_BLOWUP_FACTOR ) {
return blurredThumbnailShown;
}
$imagePlaceholder.width( targetWidth );
$imagePlaceholder.height( targetHeight );
// Only blur the placeholder if it's blown up significantly
if ( blowupFactor > 2 ) {
// We have to apply the SVG filter here, it doesn't work when defined in the .less file
// We can't use an external SVG file because filters can't be accessed cross-domain
// We can't embed the SVG file because accessing the filter inside of it doesn't work
$initialImage.addClass( 'blurred' ).css( 'filter', 'url("#gaussian-blur")' );
if ( blowupFactor > Canvas.BLUR_BLOWUP_FACTOR_THRESHOLD ) {
this.blur( $imagePlaceholder );
blurredThumbnailShown = true;
}
this.set( this.imageRawMetadata, $initialImage.show() );
this.set( this.imageRawMetadata, $imagePlaceholder.show() );
return blurredThumbnailShown;
};
/**
* Blur image
* @param {jQuery} $image Image to be blurred.
*/
C.blur = function( $image ) {
// We have to apply the SVG filter here, it doesn't work when defined in the .less file
// We can't use an external SVG file because filters can't be accessed cross-domain
// We can't embed the SVG file because accessing the filter inside of it doesn't work
$image.addClass( 'blurred' ).css( 'filter', 'url("#gaussian-blur")' );
};
/**
* Animates the image into focus
*/

View file

@ -1,4 +1,3 @@
@import "mediawiki.mixins";
@import "mmv.mixins";
.mlb-image {

View file

@ -91,7 +91,7 @@
100, 'fit calculation correct when same aspect ratio' );
} );
QUnit.test( 'calculateWidths() test', 6, function ( assert ) {
QUnit.test( 'calculateWidths() test', 9, function ( assert ) {
var boundingWidth = 100,
boundingHeight = 200,
thumbnailWidthCalculator = new mw.mmv.ThumbnailWidthCalculator( {
@ -102,21 +102,24 @@
// 50x10 image in 100x200 box - image size should be 100x20, thumbnail should be 128x25.6
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 50, 10 );
assert.strictEqual( widths.css, 100, 'css width is correct when limited by width' );
assert.strictEqual( widths.cssWidth, 100, 'css width is correct when limited by width' );
assert.strictEqual( widths.cssHeight, 20, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 128, 'real width is correct when limited by width' );
// 10x100 image in 100x200 box - image size should be 20x200, thumbnail should be 32x320
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 10, 100 );
assert.strictEqual( widths.css, 20, 'css width is correct when limited by height' );
assert.strictEqual( widths.cssWidth, 20, 'css width is correct when limited by height' );
assert.strictEqual( widths.cssHeight, 200, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 32, 'real width is correct when limited by height' );
// 10x20 image in 100x200 box - image size should be 100x200, thumbnail should be 128x256
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 10, 20 );
assert.strictEqual( widths.css, 100, 'css width is correct when same aspect ratio' );
assert.strictEqual( widths.cssWidth, 100, 'css width is correct when same aspect ratio' );
assert.strictEqual( widths.cssHeight, 200, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 128, 'real width is correct when same aspect ratio' );
} );
QUnit.test( 'calculateWidths() test with non-standard device pixel ratio', 6, function ( assert ) {
QUnit.test( 'calculateWidths() test with non-standard device pixel ratio', 9, function ( assert ) {
var boundingWidth = 100,
boundingHeight = 200,
thumbnailWidthCalculator = new mw.mmv.ThumbnailWidthCalculator( {
@ -127,17 +130,20 @@
// 50x10 image in 100x200 box - image size should be 100x20, thumbnail should be 256x51.2
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 50, 10 );
assert.strictEqual( widths.css, 100, 'css width is correct when limited by width' );
assert.strictEqual( widths.cssWidth, 100, 'css width is correct when limited by width' );
assert.strictEqual( widths.cssHeight, 20, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 256, 'real width is correct when limited by width' );
// 10x100 image in 100x200 box - image size should be 20x200, thumbnail should be 64x640
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 10, 100 );
assert.strictEqual( widths.css, 20, 'css width is correct when limited by height' );
assert.strictEqual( widths.cssWidth, 20, 'css width is correct when limited by height' );
assert.strictEqual( widths.cssHeight, 200, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 64, 'real width is correct when limited by height' );
// 10x20 image in 100x200 box - image size should be 100x200, thumbnail should be 256x512
widths = thumbnailWidthCalculator.calculateWidths( boundingWidth, boundingHeight, 10, 20 );
assert.strictEqual( widths.css, 100, 'css width is correct when same aspect ratio' );
assert.strictEqual( widths.cssWidth, 100, 'css width is correct when same aspect ratio' );
assert.strictEqual( widths.cssHeight, 200, 'css height is correct when limited by width' );
assert.strictEqual( widths.real, 256, 'real width is correct when same aspect ratio' );
} );
}( mediaWiki ) );

View file

@ -237,7 +237,7 @@
viewer.setImage = $.noop;
viewer.lightbox = { iface : { canvas : {
setThumbnailForDisplay : function() { return true; }
maybeDisplayPlaceholder : function() { return true; }
} } };
viewer.displayPlaceholderThumbnail( undefined, undefined, undefined);

View file

@ -133,15 +133,18 @@
}
} );
QUnit.test( 'ThumbnailWidth constructor sanity check', 4, function ( assert ) {
QUnit.test( 'ThumbnailWidth constructor sanity check', 5, function ( assert ) {
var cssWidth = 23,
cssHeight = 29,
screenWidth = 42,
realWidth = 123,
thumbnailWidth = new mw.mmv.model.ThumbnailWidth( cssWidth, screenWidth, realWidth );
thumbnailWidth = new mw.mmv.model.ThumbnailWidth(
cssWidth, cssHeight, screenWidth, realWidth );
assert.strictEqual( thumbnailWidth.css, cssWidth, 'Url is set correctly' );
assert.strictEqual( thumbnailWidth.screen, screenWidth, 'Width is set correctly' );
assert.strictEqual( thumbnailWidth.real, realWidth, 'Height is set correctly' );
assert.strictEqual( thumbnailWidth.cssWidth, cssWidth, 'Width is set correctly' );
assert.strictEqual( thumbnailWidth.cssHeight, cssHeight, 'Height is set correctly' );
assert.strictEqual( thumbnailWidth.screen, screenWidth, 'Screen width is set correctly' );
assert.strictEqual( thumbnailWidth.real, realWidth, 'Real width is set correctly' );
try {
thumbnailWidth = new mw.mmv.model.ThumbnailWidth( cssWidth, screenWidth );

View file

@ -52,31 +52,46 @@
assert.ok( canvas.$imageDiv.hasClass( 'empty' ), 'Canvas is not visible.' );
} );
QUnit.test( 'setImageAndMaxDimensions()', 6, function( assert ) {
QUnit.test( 'setImageAndMaxDimensions()', 8, function( assert ) {
var $qf = $( '#qunit-fixture' ),
canvas = new mw.mmv.ui.Canvas( $qf ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.png' ),
image = new Image(),
$imageElem = $( image ),
image2 = new Image(),
$currentImage;
thumbnailWidth = 10,
screenWidth = 100,
$currentImage,
originalWidth;
// Need to call set() before using setImageAndMaxDimensions()
canvas.set( imageRawMetadata, $imageElem );
originalWidth = image.width;
// Call with the same image
canvas.setImageAndMaxDimensions( image );
canvas.setImageAndMaxDimensions(
{ width: thumbnailWidth },
image,
{ cssWidth: screenWidth }
);
assert.strictEqual( image.width, originalWidth, 'Image width was not modified.' );
assert.strictEqual( canvas.$image, $imageElem, 'Image element still set correctly.' );
assert.strictEqual( canvas.$image.css( 'maxHeight' ), canvas.$imageDiv.height() + 'px', 'MaxHeight set correctly.' );
assert.strictEqual( canvas.$image.css( 'maxWidth' ), canvas.$imageDiv.width() + 'px', 'MaxHeight set correctly.' );
$currentImage = canvas.$image;
// Call with a new image
canvas.setImageAndMaxDimensions( image2 );
// Call with a new image bigger than screen size
thumbnailWidth = 150;
canvas.setImageAndMaxDimensions(
{ width: thumbnailWidth },
image2,
{ cssWidth: screenWidth }
);
assert.notStrictEqual( canvas.$image, $currentImage, 'Image element set correctly.' );
assert.strictEqual( image2.width, screenWidth, 'Image width was trimmed correctly.' );
assert.notStrictEqual( canvas.$image, $currentImage, 'Image element switched correctly.' );
assert.strictEqual( canvas.$image.css( 'maxHeight' ), canvas.$imageDiv.height() + 'px', 'MaxHeight set correctly.' );
assert.strictEqual( canvas.$image.css( 'maxWidth' ), canvas.$imageDiv.width() + 'px', 'MaxHeight set correctly.' );
} );
@ -96,22 +111,58 @@
assert.ok( ! canvas.resizeListener, 'resize listener has been removed.' );
} );
QUnit.test( 'setThumbnailForDisplay: placeholder big enough that it doesn\'t need blurring, actual image bigger than the lightbox', 5, function ( assert ) {
QUnit.test( 'maybeDisplayPlaceholder: Max area for SVG files', 5, function ( assert ) {
var $image,
blurredThumbnailShown,
$qf = $( '#qunit-fixture' ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.svg' ),
canvas = new mw.mmv.ui.Canvas( $qf );
imageRawMetadata.filePageTitle = {
getExtension: function() { return 'svg'; },
};
canvas.imageRawMetadata = imageRawMetadata;
canvas.set = function () {
assert.ok ( true, 'Placeholder is shown');
};
$image = $( '<img>' ).width( 10 ).height( 5 );
blurredThumbnailShown = canvas.maybeDisplayPlaceholder(
{ width : 200, height : 100 },
$image,
{ cssWidth : 300, cssHeight: 150 }
);
assert.strictEqual( $image.width(), 300, 'Placeholder width was set to max' );
assert.strictEqual( $image.height(), 150, 'Placeholder height was set to max' );
assert.ok( ! $image.hasClass( 'blurred' ), 'Placeholder is not blurred' );
assert.ok( ! blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'maybeDisplayPlaceholder: placeholder big enough that it doesn\'t need blurring, actual image bigger than the lightbox', 5, function ( assert ) {
var $image,
blurredThumbnailShown,
$qf = $( '#qunit-fixture' ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.png' ),
canvas = new mw.mmv.ui.Canvas( $qf );
imageRawMetadata.filePageTitle = {
getExtension: function() { return 'png'; },
};
canvas.imageRawMetadata = imageRawMetadata;
canvas.set = function () {
assert.ok ( true, 'Placeholder shown');
};
$image = $( '<img>' ).width( 200 ).height( 100 );
blurredThumbnailShown = canvas.setThumbnailForDisplay(
blurredThumbnailShown = canvas.maybeDisplayPlaceholder(
{ width : 1000, height : 500 },
$image,
{ css : 300 }
{ cssWidth : 300, cssHeight: 150 }
);
assert.strictEqual( $image.width(), 300, 'Placeholder has the right width' );
@ -120,22 +171,28 @@
assert.ok( ! blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'setThumbnailForDisplay: big-enough placeholder that needs blurring, actual image bigger than the lightbox', 5, function ( assert ) {
QUnit.test( 'maybeDisplayPlaceholder: big-enough placeholder that needs blurring, actual image bigger than the lightbox', 5, function ( assert ) {
var $image,
blurredThumbnailShown,
$qf = $( '#qunit-fixture' ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.png' ),
canvas = new mw.mmv.ui.Canvas( $qf );
imageRawMetadata.filePageTitle = {
getExtension: function() { return 'png'; },
};
canvas.imageRawMetadata = imageRawMetadata;
canvas.set = function () {
assert.ok ( true, 'Placeholder shown');
};
$image = $( '<img>' ).width( 100 ).height( 50 );
blurredThumbnailShown = canvas.setThumbnailForDisplay(
blurredThumbnailShown = canvas.maybeDisplayPlaceholder(
{ width : 1000, height : 500 },
$image,
{ css : 300, real : 300 }
{ cssWidth : 300, cssHeight : 150 }
);
assert.strictEqual( $image.width(), 300, 'Placeholder has the right width' );
@ -144,22 +201,28 @@
assert.ok( blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'setThumbnailForDisplay: big-enough placeholder that needs blurring, actual image smaller than the lightbox', 5, function ( assert ) {
QUnit.test( 'maybeDisplayPlaceholder: big-enough placeholder that needs blurring, actual image smaller than the lightbox', 5, function ( assert ) {
var $image,
blurredThumbnailShown,
$qf = $( '#qunit-fixture' ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.png' ),
canvas = new mw.mmv.ui.Canvas( $qf );
imageRawMetadata.filePageTitle = {
getExtension: function() { return 'png'; },
};
canvas.imageRawMetadata = imageRawMetadata;
canvas.set = function () {
assert.ok ( true, 'Placeholder shown');
};
$image = $( '<img>' ).width( 100 ).height( 50 );
blurredThumbnailShown = canvas.setThumbnailForDisplay(
blurredThumbnailShown = canvas.maybeDisplayPlaceholder(
{ width : 1000, height : 500 },
$image,
{ css : 1200 }
{ cssWidth : 1200, cssHeight : 600 }
);
assert.strictEqual( $image.width(), 1000, 'Placeholder has the right width' );
@ -168,22 +231,28 @@
assert.ok( blurredThumbnailShown, 'Placeholder state is correct' );
} );
QUnit.test( 'setThumbnailForDisplay: placeholder too small to be displayed, actual image bigger than the lightbox', 4, function ( assert ) {
QUnit.test( 'maybeDisplayPlaceholder: placeholder too small to be displayed, actual image bigger than the lightbox', 4, function ( assert ) {
var $image,
blurredThumbnailShown,
$qf = $( '#qunit-fixture' ),
imageRawMetadata = new mw.mmv.LightboxImage( 'foo.png' ),
canvas = new mw.mmv.ui.Canvas( $qf );
imageRawMetadata.filePageTitle = {
getExtension: function() { return 'png'; },
};
canvas.imageRawMetadata = imageRawMetadata;
canvas.set = function () {
assert.ok ( false, 'Placeholder shown when it should not');
};
$image = $( '<img>' ).width( 10 ).height( 5 );
blurredThumbnailShown = canvas.setThumbnailForDisplay(
blurredThumbnailShown = canvas.maybeDisplayPlaceholder(
{ width : 1000, height : 500 },
$image,
{ css : 300, real : 300 }
{ cssWidth : 300, cssHeight : 150 }
);
assert.strictEqual( $image.width(), 10, 'Placeholder has the right width' );