Enlarge with the filter named and cited, and the alignment measured: six published kernels matching Pillow to within half a level, premultiplied alpha, and an unsharp mask whose colour space is a choice.
Initializing in your browser…
Resize with the filter named (Lanczos, Mitchell, Catmull-Rom, bilinear, box or nearest) and the option to average LIGHT rather than stored bytes, which is worth 1.24 stops on a detailed picture.
Turn an image into a data URL whose payload is decoded again and compared byte for byte with the file, with the media type taken from the file's own signature rather than its extension and the size cost of embedding stated in full.
Compare two images with a perceptual engine (pixelmatch) that ignores anti-aliasing, or an exact byte engine. Groups changes into regions with bounding boxes, reports SSIM, and handles size mismatches with an explicit policy.
A 64 by 64 test image enlarged four times, scored against Pillow’s implementation of each filter name.
Input
up-small.png · 4x · three named filters
RMSE against Pillow’s own filter of the same name
before after bilinear 14.10 0.10 bicubic 15.63 0.30 lanczos 16.00 0.49 closest match bilinear its own name alignment error -1.50 px 0.0000 px
All three of the old filters were closest to BILINEAR, including the one labelled Lanczos, because a half-pixel misalignment swamped the difference between the kernels. The mapping was `x * srcWidth / dstWidth`, which aligns the two grids at their corners and shifts the picture by half of (scale minus one): exactly -0.5, -1.5 and -3.5 pixels at 2x, 4x and 8x when measured. Centre alignment brings each filter back to its own name.
Enlarge an image with the filter named and cited, and its alignment measured on the settings in force. Six published kernels, an unsharp mask whose colour space is a choice you can see the effect of, premultiplied alpha so a cut-out keeps no dark rim, and PNG, JPEG or WebP output read back from its own bytes.
A sharp, correctly aligned enlargement with transparency preserved and no rim.
Nearest neighbour, which replicates each pixel exactly and keeps the blocks square.
A larger pixel count from the same picture, with the filter named so the result is reproducible elsewhere.
The same picture under six published kernels, with what each one is for stated beside it.
Lanczos 3 at 4x with sharpening off. The alignment panel reports 0.0000 pixels of error, so the enlarged logo sits exactly where the original did rather than a pixel and a half to one side.
Nearest neighbour at 3x, which replicates every source pixel exactly nine times, checked in the test suite with zero deviation.
Enlarging an image is interpolation: deciding what colour to put between the pixels that exist. Every filter here is a published rule for making that decision, and two things about it are now checked rather than claimed.
The first is which filter it is. The tool used to offer bilinear, bicubic and Lanczos and implement all three itself. Measured against Pillow's implementation of the same three names on the same picture, they scored RMSE 14.10, 15.63 and 16.00, and the closest match for ALL THREE was Pillow's bilinear: the one labelled Lanczos was nearer to bilinear (15.28) than to Lanczos (16.00). They now go through this project's resampler, whose six kernels each carry the paper they come from, and the same measurement gives 0.10, 0.30 and 0.49 against their own namesakes, with each one closest to the filter it is named after.
The second is alignment, and it is what made the first measurement so bad. A resampler maps destination pixel CENTRES to source pixel centres, which is `(x + 0.5) * srcWidth / dstWidth - 0.5`. The arithmetic tools reach for first, `x * srcWidth / dstWidth`, aligns the two grids at their CORNERS and shifts the whole picture by half of (scale minus one): a quarter of a pixel at 2x, 1.5 pixels at 4x, 3.5 at 8x. Measured on the shipped implementation, the error was exactly -0.5, -1.5 and -3.5 at those three factors, which is the prediction to the digit. That shift swamps the difference between one filter and another, which is why three different kernels all came out looking like bilinear. The page now measures it on the settings in force: one bright pixel is enlarged and the centre of mass of the result is compared with where the arithmetic says it should be, and the answer is 0.0000 pixels of error for every filter at every factor.
Sharpening was a fixed three by three kernel applied to the stored bytes. It is now a proper unsharp mask, with an amount, a Gaussian radius and a threshold below which a difference is left alone, and with the SPACE as a setting. That matters because byte 128 carries 0.216 of white's light rather than half of it, so an overshoot that is symmetric in stored bytes is wildly asymmetric in light. At 100 percent on a step from byte 100 to byte 160, the two spaces give 69 and 173 in linear light against 82 and 178 on the stored bytes, and the page prints both so the choice is visible rather than inherited.
Alpha is premultiplied before filtering and divided out afterwards. Without that, a white cut-out beside transparent black picks up the black: measured on the shipped implementation, a visible pixel lost 191 levels of brightness at the boundary, which is the dark rim people see around an enlarged logo. It is now zero.
The output goes through `canvas.toBlob` rather than `toDataURL`, so a large enlargement no longer passes the whole file through a base64 string a third larger than itself, and the format is read back off the blob's own first bytes so the extension names what the file IS. PNG, JPEG and WebP are offered, and each is probed at load: a canvas returns a PNG for a type it cannot write and says nothing, so anything this browser cannot encode is disabled rather than silently substituted.
One thing this page will not do is invent detail. Nothing here is a neural network. A blurry photograph enlarged eight times is a larger blurry photograph, and the page says so.
No. Interpolation decides what to put between existing pixels; it invents nothing. Nothing here is a neural network, so a blurry photograph enlarged eight times is a larger blurry photograph. What a good filter buys you is edges that stay straight and a picture that is not shifted.
A measurement, taken on the settings in force. One bright pixel is enlarged and the centre of mass of the result is compared with where the arithmetic says it should be. It reads 0.0000 here. The mapping tools commonly use instead aligns the grids at their corners and shifts the whole picture by half of (scale minus one), which is 1.5 pixels at 4x.
Lanczos 3 for a photograph. Catmull-Rom or Mitchell if the ringing at hard edges bothers you, since Mitchell and Netravali chose their coefficients to trade ringing against blur. Nearest neighbour for pixel art, and box for flat graphics. Each one names the paper it comes from on the page.
Because it changes the answer. Byte 128 carries 0.216 of white's light rather than half of it, so an overshoot that is symmetric in stored bytes is very unequal in light. At 100 percent on a step from 100 to 160 the two settings give 69 and 173 against 82 and 178, and the page prints both.
Yes, in PNG and WebP. The colour channels are premultiplied by alpha before filtering and divided out afterwards, which is what stops an enlarged cut-out picking up the colour of the transparent pixels beside it. JPEG has no alpha channel at all and will composite it away.
Until the browser runs out of canvas. The page shows the pixel count and the memory the enlargement needs before you commit to it, and warns when a factor pushes past what most browsers will hold.
Images are decoded, edited, and exported entirely inside this browser tab. No originals, exports, or metadata are uploaded.