Resize or compress first? The order changes the result
A downscale is an average, and averaging pixels that already carry compression error bakes that error in. Here is the arithmetic of a five-to-one reduction, and when the other order wins.
You have a photograph that is too big to send, a resizer open in the next tab and a compressor open behind it. Which runs first? The answer is resize, and the usual reason given for it is the wrong one. It is not that resizing is cheaper, and it is not that encoders happen to behave better on small files. It is that a downscale is an average, and averaging something that has already been through a lossy encoder averages the encoder's mistakes along with the picture.
A downscale is an average
Every pixel in a resized image is a weighted sum of a neighbourhood of pixels in the source. Nearest neighbour takes one of them. Bilinear takes a small window. Lanczos takes a wide one. The output is a blend, and a blend has no way to distinguish the parts of its inputs that came from the scene from the parts that came from an encoder.
A JPEG encoder cuts the image into eight-by-eight blocks, transforms each block into frequencies, and quantises those frequencies — the high ones hard, the low ones gently. What survives that process is not the original block. It is the original block plus whatever the quantiser left behind: a slight step at the block boundary, a faint ripple around a hard edge, a slow drift of colour across a smooth sky. Those artefacts are in the pixels the decoder hands you. They are real numbers in real channels, and the resampler has no idea they are not scenery.
So when you compress first and resize second, here is what happens. The block boundaries, which were a high-frequency detail the next encoder could cheaply throw away, get averaged into their neighbours and become gentle gradients. Gradients are low-frequency. The next encoder's quantiser protects low frequencies, because that is where the picture is. It spends bits preserving the shape of an error, and the error was never the picture. Ringing around an edge loses its position and becomes a soft halo with nothing behind it.
The arithmetic of a five-to-one downscale
Take a 4000 by 3000 photograph — a phone camera file, twelve million pixels. It is going to be shown in an 800 by 600 slot. The width ratio is 4000 divided by 800, which is 5. The height ratio is 3000 divided by 600, which is also 5. The area ratio is the square of that: 25. The file carries 12,000,000 pixels, the slot shows 480,000 of them, and 24 out of every 25 pixels the decoder reconstructs are thrown away before anybody looks at them. Four fifths of the width and 96 percent of the pixels were paid for in bytes and spent on nothing.
The bytes are worth pricing. Suppose the file is 6 MB, which is a normal size for a twelve-megapixel camera JPEG. Six million bytes spread over twelve million pixels is about half a byte per pixel. Resize to 800 by 600 and hold the encoder to the same half-byte-per-pixel rate: 480,000 pixels times 0.5 is 240,000 bytes, a bit under 235 KiB. That comparison understates the win, because a smaller image is also easier for an encoder to spend its budget on — fewer coefficients to code, less high-frequency noise to discard.
The order decides what the encoder is asked to protect. Compress first and the encoder is told to preserve detail at 4000 pixels wide, and then a resampler averages nearly all of that detail into its neighbours. Resize first and the encoder works at the size that will actually be displayed, so its bits go to the detail that survives to the screen.
Here is the size of the neighbourhoods involved. Downscaling by five stretches the filter kernel by five, because the kernel has to reach across the source pixels that one output pixel replaces:
scale = 4000 / 800 = 5 nearest support 1 source pixel window 1 x 1 = 1 pixel bilinear radius 1, stretched 5x window 10 x 10 = 100 pixels bicubic radius 2, stretched 5x window 20 x 20 = 400 pixels lanczos-3 radius 3, stretched 5x window 30 x 30 = 900 pixels
That last row is the mechanism in one number. Each output pixel of a Lanczos downscale is a weighted sum of nine hundred input pixels. If each of those nine hundred carries a little quantisation error from an earlier encode, all nine hundred errors go into the average. Nearest neighbour is the one filter that does not mix — it picks a single pixel and keeps it whole — which is exactly why it is no help here. It copies one already-corrupted pixel instead of averaging thirty.
What each filter is for
Filter choice matters more than most people expect, and none of the common ones is unconditionally best. The table below is the version I would hand to someone who has to pick one today.
| Filter | What it is good for | How it fails |
|---|---|---|
| Nearest neighbour | Pixel art, sprite sheets, indexed images, deliberate chunky magnification | Samples rather than averages, so it drops entire pixels and shreds photographic detail into jaggies |
| Bilinear | Fast previews, small magnifications where speed matters | Softer than everything else; two-pixel-wide detail turns into mush |
| Bicubic | The default for photographs when you want no surprises | Slightly soft next to Lanczos, and a mild halo on very hard edges |
| Lanczos (a=2, a=3) | Downscaling photographs where retained sharpness is the point | Rings: a bright halo and a dark trough beside hard edges, most visible on text and thin lines |
| Box / area average | Exact integer reductions, and chain-halving to build preview levels | Only the true average at whole-number ratios; softer than Lanczos at everything else |
Nearest neighbour is on this list for completeness only. It belongs on pixel art and screenshots of pixel art, and nowhere else. If a filter dialog defaults to it because it is the fastest, that default is a bug.
One big step or several small ones
A single five-to-one Lanczos pass beats four passes of roughly 1.5x, and the reason is compounding. Every intermediate pass writes its result back to eight bits per channel, so each one adds a rounding error that the next pass reads as data. Each pass also applies a kernel, and applying a blur kernel twice softens more than applying it once. Four passes means four roundings and four softenings stacked on top of each other, for a result that is measurably softer than doing it in one go.
There is one exception, and it is the one that makes mipmaps work. A chain of exact halvings — 4000 to 2000 to 1000 to 500 — is not the same case, because each step averages exactly four pixels with no fractional sample positions, and a box average is exactly the area average when the ratio is a whole number. Halving repeatedly is close to lossless in quality terms. Chaining arbitrary ratios is not. So the rule is: halve as far as the ratio allows, then finish with a single good filter for whatever is left over.
One more thing the resampler is quietly deciding: the space it averages in. Averaging sRGB-encoded values is averaging the wrong quantity, because light does not add up that way. Edges between light and dark come out slightly soft and slightly wrong in value. Careful resamplers convert to linear light, filter, and convert back. If a downscale looks duller than the source in a way you cannot explain, this is often the reason, and a photograph with high-contrast edges is where it shows most.
When compressing first is right
The exception is about the link, not about the pixels. If a 40 MB file has to move over a slow connection, or a receiving service refuses uploads over a fixed cap, the compress-first order exists to produce an intermediate small enough to travel. That is a legitimate use and its cost is one extra lossy generation — which is cheap, provided you keep the original and derive everything from that original rather than from the intermediate. The moment you treat the intermediate as your master, you have thrown away the source and kept the error.
The second case is a downscale small enough that averaging barely happens. At a 1.1x reduction most output pixels are close to copies of one input pixel, the windows in the table above collapse to a few samples, and the encoder sees something very like the original. Below about 1.3x, choose the order on bytes rather than on quality, because the quality difference is not there to find.
The third case is content rather than ratio. A screenshot, a diagram, or a photograph with a lot of text in it is full of hard edges, which is precisely what Lanczos rings on. A halo around a letterform is more offensive than a little softness, so for that material use bicubic or a box average and leave Lanczos in the drawer. Upscaling is the mirror image: nearest for pixel art, bicubic or Lanczos for everything else.
Is a resize even needed
Half the time the right move is to skip the resize entirely. A file that is only slightly wider than its slot is not costing you much, and a second generation of lossy encoding costs you something. The table below takes the ratio as source width divided by display width and says what I would do.
| Source width | Display width | Ratio | What I would do |
|---|---|---|---|
| 4000 px | 800 px | 5.0x | Resize first, without hesitating |
| 2400 px | 1200 px | 2.0x | Resize first; the file is four times bigger than it needs to be |
| 1200 px | 600 px | 2.0x | Resize first, unless the image is already small in bytes |
| 900 px | 600 px | 1.5x | Resize only if the byte count matters; expect a modest gain |
| 700 px | 600 px | 1.17x | Leave it alone; the second generation costs more than the pixels saved |
| 600 px | 600 px | 1.0x | Nothing to resize. Compress, or better, re-export at higher quality if it looks bad |
| 300 px | 600 px | 0.5x | You are upscaling. Go and find a larger source instead |
The column that decides the verdict is the third one, and it is easy to get wrong because display width is rarely what you think. Measure it in device pixels: a 600-pixel CSS slot on a screen with a device pixel ratio of 2 wants a 1200-pixel file, which moves the 1200-pixel row to a ratio of 1.0 and the 900-pixel row below the threshold entirely. Check the ratio against the real target before you spend a generation of quality on a resize nobody needed.
The order I use
In practice the sequence is short. Crop and straighten first, because both change the pixel dimensions and doing them after a resize means a second resize. Resize to twice the CSS width of the slot, capped at the source width so you never upscale by accident. Compress that result at a quality high enough that the loss is invisible at the final size. Then look at the output at 100 percent, and remember that if the page will show it at half that width, you are inspecting detail no viewer will ever receive.
The rule of thumb I would keep is the ratio, measured in device pixels. Below 1.3, leave the dimensions alone and spend your effort on the encoder. Between 1.3 and 2, decide on bytes. Above 2, resize first, always, and pick bicubic or Lanczos depending on whether the image has hard edges in it.
The mistake worth avoiding is the repeated one: resizing, saving, noticing a problem, resizing again next week and saving over the result. Each JPEG write is a generation. Three passes at a quality setting of 90 is not 90 percent of the original three times over — it is the same coefficients requantised three times, and the artefacts that survive are the ones that compound. Keep the original file and rebuild from it every time, and the question at the top of this piece only has to be answered once per output.
Where the tools fit. The Image Resizer is the first step in this sequence: it takes the width and height you actually need, lets you choose the resampling filter, and does the reduction in a single pass rather than chaining browser-default steps. Feeding it a size that matches the slot is the whole trick.
The Image Compressor is the second step, and it is much easier to judge once the dimensions are already final, because the quality slider is then trading against a file size that has already come down. The work happens in the page you already have open, so you can run the same file through at several settings and compare the results directly.
The short version
Resize first. Downscaling averages neighbouring pixels, so if the file has already been compressed the average includes each pixel's compression error, and the next encoder then spends bits preserving the shape of that error. A 4000-pixel photograph in an 800-pixel slot is losing 96 percent of its pixels to the display, which means four fifths of the detail the first encoder protected was never going to be seen. Pick a filter for the content — bicubic as the safe default, Lanczos when sharpness matters and there is no text, nearest only for pixel art — do the reduction in one pass, and check the ratio in device pixels before you decide a resize is needed at all.