DEV Community

Muhaymin Bin Mehmood
Muhaymin Bin Mehmood

Posted on Fully Autonomous

Re-Saving a JPEG at Quality 90 Can Make It Bigger. Measure Before You "Compress"

A common bug report for image tools: "I compressed my photo and the file got bigger." It sounds impossible. It isn't, and it's worth understanding, because the same mistake shows up in build pipelines and CMS plugins.

Quality is not "percent of the original"

When you save a JPEG at quality 90, the 90 doesn't mean "90% of the file you opened". It selects a set of quantization tables, which control how much detail the encoder throws away. The encoder doesn't know or care how the input was saved before.

So take a photo that was saved at quality 75, open it, and save it at quality 90. The encoder now spends more bits per block than the original did. It carefully preserves detail, including the blocky artefacts from the first save. The result looks no better than the q75 file, and it's larger.

Measure it yourself

This Node script takes any JPEG and re-encodes it at a few qualities, then saves it ten times in a row at quality 90:

// node resave.mjs photo.jpg
import { readFile } from "node:fs/promises"
import sharp from "sharp"

const original = await readFile(process.argv[2])
console.log("original".padEnd(12), (original.length / 1024).toFixed(0), "KB")

for (const quality of [95, 90, 80, 70]) {
  const out = await sharp(original).jpeg({ quality }).toBuffer()
  console.log(`q${quality}`.padEnd(12), (out.length / 1024).toFixed(0), "KB")
}

let gen = original
for (let i = 1; i <= 10; i++) gen = await sharp(gen).jpeg({ quality: 90 }).toBuffer()
console.log("10x q90".padEnd(12), (gen.length / 1024).toFixed(0), "KB")
Enter fullscreen mode Exit fullscreen mode

Here's one run on a synthetic test image (random noise, 1600×1200, originally saved at quality 75). Real photos will give different numbers, so treat this as a demonstration and run it on yours:

original     613 KB
q95          918 KB
q90          770 KB
q80          639 KB
q70          584 KB
10x q90      761 KB
Enter fullscreen mode Exit fullscreen mode

Three things to notice:

  • q90 and q95 made it bigger, by 26% and 50%.
  • Even q80 made it bigger. Only going below the original's quality actually saved bytes.
  • Ten saves at q90 didn't keep growing. After the first re-encode the size settles. The quality loss from repeated saves is real, but it's mostly in the first generations.

Run it on your own photos. You'll quickly learn where "compression" stops paying for that kind of image.

The rule every compressor needs

Whatever the settings, a compressor must never hand back a bigger file than it was given:

const blob = await encode(file, { type: "image/jpeg", quality })
const grew = blob.size >= file.size
const result = grew
  ? { blob: file, size: file.size, grew: true } // keep the original, say so
  : { blob, size: blob.size, grew: false }
Enter fullscreen mode Exit fullscreen mode

And tell the user when it happens. "Already well compressed, kept the original" is useful information. A silent "-0%" or a larger download is a bug.

What actually reduces a JPEG

If the quality is already low, re-encoding won't save much. These do:

  1. Fewer pixels. A 4000 px photo shown at 1200 px on a page wastes most of its bytes. Resizing usually saves more than any quality setting.
  2. Strip metadata. EXIF, embedded thumbnails and colour profiles can add tens of KB, and EXIF may include GPS location.
  3. Chroma subsampling (4:2:0). Most photo encoders use it by default; check yours does for photos.
  4. A modern format where you control the delivery, like WebP or AVIF on your own site.

And keep the original. Every lossy save throws detail away for good, so compress from the master copy, not from yesterday's compressed export.

Try it on your photos

BatchSet's free JPEG compressor follows these rules: you pick a quality, it shows the size change on every file, and when a re-encode would be bigger it keeps your original and says so. It runs in the browser, so the photos aren't uploaded.

This article was drafted with an AI assistant. The script was run before publishing; the numbers shown come from a synthetic test image, as stated.

Top comments (0)