Page MenuHomePhabricator

Compile a list of "canonical" thumbnail sizes
Closed, ResolvedPublic

Description

As the first part of FY 25/26 WE 5.4.7 Standardize thumbnail sizes, we want a list of canonical thumbnail sizes in use. This task is an attempt to produce that.

Starting with the easy bits, the set of thumbnail sizes that users can choose are set per-wiki as wgThumbLimits, with the default being set by wmgThumbsizeIndex:

// Do not add more exceptions.
// Thumbnails must be shared between wikis.
'wgThumbLimits' => [
	'default' => [ 120, 150, 180, 200, 220, 250, 300, 400 ],
	'+itwikiquote' => [ 360 ],
	'svwiki' => [ 120, 200, 250, 300, 360 ],
	# nlwiki uses 260 instead of 250 (T215106)
	'nlwiki' => [ 120, 150, 180, 200, 220, 260, 300, 400 ],
],
// Do not add more exceptions. Default should stay the same between wikis.
'wmgThumbsizeIndex' => [
	'default' => 5,
	'svwiki' => 2, // T18739
],

So the default is 250 everywhere except nlwiki where it's 260 (per T215106). UPDATE: after change 1201584 was merged, it is now 250 everywhere. In any case, whichever of these sizes the user chooses, they in fact download the thumb of greater-or-equal size in $wgThumbnailSteps and the browser scales it down.

The set of thumbnail sizes that are used by default are in wgThumbnailSteps (so MW will take other sizes (in all circumstances?) and serve the next-largest one of these instead):

$wgThumbnailSteps = [ 20, 40, 60, 120, 250, 330, 500, 960 ];

The set of thumbs that are generated on image upload is defined in wgUploadThumbnailRenderMap (I don't think wgThumbnailBuckets is used in our deployment, since we don't make size-based thumb buckets?)

// Thumbnail chaining
'wgThumbnailBuckets' => [
	'default' => [ 1280 ],
],
'wgThumbnailMinimumBucketDistance' => [
	'default' => 100,
],
// Thumbnail prerendering at upload time
'wgUploadThumbnailRenderMap' => [
	'default' => [ 320, 640, 800, 1280 ],
	'private' => [],
],

Users can choose the size of preview they see on file pages on commons from the list defined in ImageLimits:

public const ImageLimits = [
        'default' => [
                [ 320, 240 ],
                [ 640, 480 ],
                [ 800, 600 ],
                [ 1024, 768 ],
                [ 1280, 1024 ],
                [ 2560, 2048 ],
        ],
        'type' => 'list',
];

Here if they pick one of the smaller sizes, they get the next-largest from $wgThumbnailSteps scaled in-browser (i.e. 330 scaled to 320, 960 scaled to 800 or 640), above 960 they get the requested size.

Extension:MultimediaViewer has a set of MediaViewerThumbnailBucketSizes which are the set of thumbnail sizes it'll request and then scale according to how big the MediaViewer browser window is. UPDATE: T372165 has reduced this set to 500, 960, 1280, 2560 . These are not affected by $wgThumbnailSteps.

		"MediaViewerThumbnailBucketSizes": {
			"description": "Array of preselected widths used to generate thumbnail URLs to avoid arbitrary size requests (see also wgUploadThumbnailRenderMap).",
			"value": [
				320,
				800,
				1024,
				1280,
				1920,
				2560,
				2880
			],
			"merge_strategy": "provide_default"

It offers a different set of download size options, set in mmv.ui.utils.js. Unlike the previous, these are impacted by $wgThumbnailSteps, so a user asking for a "small 640x480" download will in fact get the 960px thumb(!) T410711 has been opened to both make these more configurable and to align the smaller size with $wgThumbnailSteps.

		const buckets = {
			small: { width: 640, height: 480 },
			medium: { width: 1280, height: 720 }, // HD ready = 720p
			large: { width: 1920, height: 1080 }, // Full HD = 1080p
			xl: { width: 3840, height: 2160 } // 4K = 2160p
		};

The size in image galleries is set as wgGalleryOptions, which is consistent everywhere except svwiki:

'wgGalleryOptions' => [
        'default' => [
                'imagesPerRow' => 0,
                'imageWidth' => 120,
                'imageHeight' => 120,
                'captionLength' => true,
                'showBytes' => true,
                'mode' => 'traditional',
        ],
        '+svwiki' => [
                'imageWidth' => 150,
                'imageHeight' => 150,
        ],
],

So much for the wikis, what about the mobile apps? The below has now been reviewed by relevant Android and iOS experts.

Android first, I find:

app/src/main/java/org/wikipedia/Constants.kt
    const val PREFERRED_CARD_THUMBNAIL_SIZE = 800
app/src/main/java/org/wikipedia/dataclient/Service.kt
        const val PREFERRED_THUMB_SIZE = 320
app/src/main/java/org/wikipedia/places/PlacesFragment.kt
        const val THUMB_SIZE = 160

The iOS app has a deprecated function in WMF Framework/WMFArticle+Extensions.h:

@property (nonatomic, nullable) NSURL *thumbnailURL; // Deprecated. Use imageURLForWidth:

which has a hardcoded value:

return [self imageURLForWidth:240]; //hardcoded to not rely on UIScreen in a model object

but I think elsewhere it uses values taken from the following enum (which can be scaled up x2 on retina devices so 120,240, etc.)

typedef NS_ENUM(NSInteger, WMFImageWidth) {
    /**
     *  The smallest image width we will show, e.g. in search cell thumbnails.
     *
     *  There's no guarantee about image aspect ratio, so we fetch a little more and use aspect fill.
     */
    WMFImageWidthExtraSmall = 60,
    /**
     *  The next-smallest thumbnail we'll show, e.g. in nearby cell thumbnails.
     */
    WMFImageWidthSmall = 120,
    /**
     *  A medium width, e.g. POTD & lead images.
     */
    WMFImageWidthMedium = 320,
    /**
     *  A slightly larger width, e.g. modal gallery.
     */
    WMFImageWidthLarge = 640,

    WMFImageWidthExtraLarge = 1280,
    
    WMFImageWidthExtraExtraLarge = 1920
};

There's also a bug discovered while looking at this (T408859) that for the talk page header it takes a static side length of 80 and asks for the retina scale (1,2,3), so 80, 160, 240; the expectation is that will be fixed to use the standard enum sizes mentioned above.

Finally(?) there's the Android Commons app, which sets const val THUMB_IMAGE_SIZE = "70px" and const val THUMB_HEIGHT_PX = 450 (which is unusual in being height rather than widge).

All told, our list stands as follows (ones noted below as "scaled" are always treated thus, other sizes are scaled on-wiki but not from e.g. mobile). The frequency at which these occur in requests is measured in T410304.
20
40
60
70
80
120
150 (250 scaled down)
160
180 (250 scaled down)
200 (250 scaled down)
220 (250 scaled down)
240
250
260 [no longer canonical]
300 (330 scaled down)
320 [pregenerated]
330
360 (500 scaled down)
400 (500 scaled down)
450 [height setting]
500
640 [pregenerated]
800 [pregenerated]
960
1024
1280 [pregenerated]
1920
2560
2880
3840

UPDATE we also found popups would scale a thumbnail and ignore the standard sizes (generateThumbnailData), fixed in T411013 and T411125 to use the standard $wgThumbnailSteps. ReaderExperiments' Image Browsing experiment was modified to use ThumbnailSteps. There is also MobileFrontend which is the mobile MediaViewer, in maintenance mode, which has not yet been investigated for thumbnail sizing issues.

Event Timeline

Updated in the light of review from Android and iOS folks - only change to our list of sizes is the addition of 80.

A further complication - some wikis (I've found at least fr and de) add a lang{fr,de,...} prefix to the thumb size, e.g. https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Armoiries_de_Marseille.svg/250px-Armoiries_de_Marseille.svg.png (I think this is done to all svgs, presumably because some of them can be translated?); for multi-page documents the page number gets inserted into the thumb path too.

Most of the sizes mentioned here get absorbed into thumbnail steps. For example, if someone picks 300px in svwiki for default thumb size, they will be shown 330px but with width attribute of 300px (so it gets resized in the browser). Now we need to separate this list into stuff that actually hit directly upload.wikimedia.org (which means they are going to hit thumbor and so on) or they go through mediawiki's ::transform() path which means they get absorbed into thumbnail steps.

On whether thumbnail steps transforms all, it doesn't. It returns the exact value in several cases:

  • If it's bigger than biggest size (960px) which is because it'd be unwise to create it for all sizes above 960px.
  • If the file is smaller than the given size.
  • Other edge cases.

I think we will end up with multiple lists (i.e.. we need a different lists for different use cases) but the consolidation of many thumb lists will at least give us a full list that we can start rate limiting any size requested out of it.

On the wikis that use non-default thumb sizes (nlwiki, svwiki). We should remove the exception for infrastructure reasons, We can nlwiki next week but the rest is more complicated than that because user preferences is stored as array index value in the database so changing the config will lead to people's preferences to move around (yeah, I know...)

The regex in rTHMBREXT wikimedia_thumbor/handler/images/images.py should be correct as far as the format & parameters of the thumb URLs, at least at the Thumbor level.

  • qlow generates a low-quality JPEG thumbnail, which as far as I know is no longer used in production.
  • lossy and lossless are used for certain formats (namely TIFF and webp) to choose the compression type.
  • page, as noted, is used for multi-page files, but is not necessarily required for them.
  • lang is used for SVG thumbnails. MediaWiki does attempt to detect translation markup in SVGs and only request lang-specific URLs when it expects a working translation. rMW includes/media/SvgHandler.php English is treated as the default (as far as I can tell, unconditionally), with the default/fallback text specified in the SVG being accessed through the und language code.
  • seek is a timestamp used for video thumbnails

WMF Thumbor URLs only include the width, not the height, for canonicalization purposes. The extension at the end of the thumb filename is checked, but not the filename itself: .../Example.png/120px-Foobarbaz.png is not currently redirected/rejected as invalid.

It's historically been easy for applications to generate their own thumb URLs, and fairly common given how slow the imageinfo API can be.

Change #1201584 had a related patch set uploaded (by Ladsgroup; author: Amir Sarabadani):

[operations/mediawiki-config@master] Remove nlwiki exception from thumb limits

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

Change #1201584 merged by jenkins-bot:

[operations/mediawiki-config@master] Remove nlwiki exception from thumb limits

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

Mentioned in SAL (#wikimedia-operations) [2025-11-10T13:19:05Z] <ladsgroup@deploy2002> Started scap sync-world: Backport for [[gerrit:1201584|Remove nlwiki exception from thumb limits (T408715)]]

Mentioned in SAL (#wikimedia-operations) [2025-11-10T13:43:36Z] <ladsgroup@deploy2002> ladsgroup: Backport for [[gerrit:1201584|Remove nlwiki exception from thumb limits (T408715)]] synced to the testservers (see https://wikitech.wikimedia.org/wiki/Mwdebug). Changes can now be verified there.

Mentioned in SAL (#wikimedia-operations) [2025-11-10T13:59:59Z] <ladsgroup@deploy2002> Finished scap sync-world: Backport for [[gerrit:1201584|Remove nlwiki exception from thumb limits (T408715)]] (duration: 40m 54s)

It's historically been easy for applications to generate their own thumb URLs, and fairly common given how slow the imageinfo API can be.

Even modern WMF extensions do this btw.. MediaSearch for instance, just gets a 'standard' thumb url, then when it needs a bigger size because of masonry layout reasons, it will find one size larger in thumbmlimits? and does a value replace on the original thumburl it was going to use.

There are also many gadgets that apply methodologies like this