
9 Ways to Reduce Image File Size Without Losing Quality
Nine practical techniques to shrink image files while keeping them sharp: right-sizing, modern formats, quality tuning, metadata stripping, chroma subsampling, lossless optimizers, and CDN negotiation.
“Without losing quality” needs a definition. No lossy encoder is mathematically lossless, so the honest target is this: no difference a visitor can see at the size the image is displayed. Under that definition a 3 MB photograph routinely becomes a 90 KB file with nothing visible lost.
These nine techniques are ordered by how much they usually save. Apply the first three and most images are already 90% smaller. The rest close the remaining gap.
The Worked Example
Every technique below is measured against the same source file so the savings compound visibly.
Source: terrace.jpg, 4032×3024, straight from a phone, 3,850 KB.
| Step | Technique | Result | Running total |
|---|---|---|---|
| 0 | Original | 3,850 KB | — |
| 1 | Resize to 1600×1200 | 1,120 KB | −71% |
| 2 | Encode as AVIF | 178 KB | −95% |
| 3 | Tune quality to the knee | 132 KB | −96.6% |
| 4 | Strip metadata | 128 KB | −96.7% |
| 5 | Chroma subsampling 4:2:0 | 119 KB | −96.9% |
The final file is 119 KB and looks identical on screen to the 3,850 KB original.
1. Resize to the Size You Actually Display
Resolution is the largest single lever, and it is the one people skip. File size scales with the pixel count, so halving both dimensions removes about 75% of the data before any encoder runs.
Find the Right Width
The correct source width is the widest CSS box the image occupies, multiplied by the device pixel ratio you support.
| Displayed width | 1× target | 2× target (recommended cap) |
|---|---|---|
| 400 px thumbnail | 400 px | 800 px |
| 600 px article image | 600 px | 1200 px |
| 800 px product photo | 800 px | 1600 px |
| Full-width hero | 1440 px | 2400 px |
Going beyond 2× is wasted. The perceptual return above 2× is close to zero, while file size keeps climbing.
Do It
# ImageMagick: cap the long edge at 1600, never upscale
magick terrace.jpg -resize '1600x1600>' -quality 85 terrace-1600.jpg
# Sharp (Node)
sharp('terrace.jpg')
.resize({ width: 1600, withoutEnlargement: true })
.toFile('terrace-1600.jpg');
# Sirv: width as a URL parameter, no build step
https://demo.sirv.com/terrace.jpg?w=1600
Use a good resampling filter. Lanczos and Mitchell preserve detail far better than nearest-neighbour, and every tool above defaults to a good one. The image resizing guide covers filter choice and upscaling.
2. Switch to AVIF or WebP
At matched visual quality, modern formats simply need fewer bytes than JPEG and PNG. This is the highest-value change after resizing, and it requires no visual compromise.
Same photo, same visual quality
terrace.jpg resized to 1600×1200, encoded at settings that produce no visible difference.
What Each Format Buys You
| Format | Versus JPEG | Alpha | Animation | Browser support |
|---|---|---|---|---|
| WebP | 25–35% smaller | Yes | Yes | Universal since 2021 |
| AVIF | 40–60% smaller | Yes | Yes | Universal since Safari 16 (2022) |
| JPEG XL | 30–50% smaller | Yes | Yes | Safari 17+ only (Chrome and Firefox behind a flag) |
Serve Them Safely
<picture>
<source srcset="terrace.avif" type="image/avif">
<source srcset="terrace.webp" type="image/webp">
<img src="terrace.jpg" width="1600" height="1200" alt="Stone terrace at sunset">
</picture>
# AVIF
avifenc --min 0 --max 63 -a end-usage=q -a cq-level=30 -s 4 terrace.jpg terrace.avif
# WebP
cwebp -q 80 -m 6 terrace.jpg -o terrace.webp
An image CDN skips the markup entirely by reading the Accept header and returning the best format each browser supports. Sirv does this by default on every URL.
3. Tune Quality to the Knee, Not to a Habit
Most teams pick one quality number and use it forever. Every image has a knee: the point where file size drops steeply while visual difference stays invisible. Below the knee, artifacts appear fast. Above it, you pay for nothing.
Find Your Knee
#!/usr/bin/env bash
# Sweep quality and print the size ladder
for q in 95 90 85 80 75 70 65 60; do
cwebp -quiet -q $q input.jpg -o "/tmp/test-$q.webp"
printf "q%-3s %6s KB\n" "$q" "$(( $(stat -c%s "/tmp/test-$q.webp") / 1024 ))"
done
Open the outputs side by side at display size and pick the lowest quality with no visible change. Do it once per content category, not once per image.
Starting Points by Content
| Content | JPEG | WebP | AVIF |
|---|---|---|---|
| Photograph, busy detail | 82–85 | 78–82 | 55–63 |
| Photograph, smooth gradients (sky, skin) | 85–88 | 82–86 | 60–68 |
| Product on white background | 85–90 | 80–85 | 58–65 |
| Illustration, flat colour | Use lossless | -lossless |
--lossless |
| Screenshot with small text | Use lossless | -lossless |
70–80 |
Quality scales are not comparable between formats. AVIF q58 is roughly JPEG q85. Copying a JPEG number into an AVIF encoder produces either a bloated file or a mushy one.
Automate It With Butteraugli or SSIMULACRA2
For large catalogues, target a perceptual score instead of a fixed quality:
# Encode, then score. Keep lowering quality while the score stays under threshold.
ssimulacra2 original.png candidate.png
# Scores above ~80 are visually indistinguishable at 1:1
The compression fundamentals guide explains what the encoder is actually discarding at each step.
4. Strip Metadata
A phone photo carries EXIF, GPS coordinates, a JPEG thumbnail preview, maker notes, and sometimes an embedded colour profile. That is 20 KB to 100 KB per file that no browser renders.
# ExifTool: strip everything, restore orientation
exiftool -all= -tagsFromFile @ -Orientation -overwrite_original terrace.jpg
# ImageMagick
magick terrace.jpg -strip terrace-clean.jpg
# Sharp keeps nothing unless asked
sharp('terrace.jpg').jpeg({ quality: 82 }).toFile('out.jpg');
Keep Exactly Two Things
- Orientation, or portrait photos render sideways in some pipelines. Better still, apply the rotation and then drop the tag.
- The ICC profile, but only if the image is not sRGB. Converting to sRGB and dropping the profile is smaller and more predictable.
# Convert to sRGB, then drop the profile
magick wide-gamut.jpg -profile sRGB.icc -strip out.jpg
Stripping metadata also removes GPS coordinates from user uploads, which matters for privacy as much as for bytes. See the metadata and privacy guide.
5. Use Chroma Subsampling Deliberately
Human vision resolves brightness far more finely than colour. Chroma subsampling exploits that by storing colour at half resolution. On photographs it is free bytes. On text and sharp colour edges it causes visible fringing.
| Mode | Colour resolution | Size effect | Use for |
|---|---|---|---|
| 4:4:4 | Full | Baseline | Screenshots, text, logos, sharp red/blue edges |
| 4:2:2 | Half horizontal | −10% | Mixed content |
| 4:2:0 | Half both axes | −15–20% | Photographs (default) |
# JPEG, force 4:4:4 for a screenshot
magick screenshot.png -sampling-factor 1x1 -quality 90 screenshot.jpg
# JPEG, 4:2:0 for a photo (usually the default)
magick photo.jpg -sampling-factor 2x2 -quality 85 photo-out.jpg
# AVIF
avifenc --yuv 420 photo.png photo.avif # photos
avifenc --yuv 444 diagram.png diagram.avif # text and diagrams
The classic symptom of the wrong setting is red text on a white background turning into a smeared orange halo. If you see that, switch to 4:4:4.
6. Match the Format to the Content
Compression cannot rescue a bad format choice. A logo saved as a 200 KB PNG becomes a 3 KB SVG that is also infinitely scalable.
| Content | Best format | Typical size | Wrong choice costs |
|---|---|---|---|
| Logo, icon, chart, map | SVG | 2–15 KB | PNG at 3 DPRs: 60–300 KB |
| Photograph | AVIF, WebP | 60–200 KB | PNG: 1,500–3,000 KB |
| Screenshot, UI capture | WebP lossless, PNG | 80–250 KB | JPEG: text fringing |
| Flat illustration, ≤256 colours | SVG or PNG-8 | 10–40 KB | PNG-24: 2–4× |
| Photo with transparency | AVIF, WebP | 70–220 KB | PNG-24: 3–8× |
| Short animation | MP4, WebM | 120–300 KB | GIF: 5–20× |
SVG Is the Biggest Win Available
If the asset was drawn rather than photographed, it belongs in SVG. Then optimize the SVG itself:
npx svgo --multipass icon.svg -o icon.min.svg
SVGO typically removes 40% to 70% of an exported SVG: editor metadata, redundant precision, empty groups. Serve it gzipped or brotli-compressed and it shrinks again. The SVG best practices guide covers the safe plugin set.
7. Run a Lossless Optimizer as the Last Step
After lossy encoding, lossless optimizers rewrite the file’s internal structure. Nothing changes visually because nothing changes in the decoded pixels. They simply find a better encoding of the same data.
| Tool | Format | Typical extra saving |
|---|---|---|
jpegtran -optimize -progressive |
JPEG | 3–8% |
MozJPEG (cjpeg) |
JPEG | 8–15% versus libjpeg |
oxipng -o 4 --strip safe |
PNG | 10–30% |
gifsicle -O3 |
GIF | 10–25% |
svgo --multipass |
SVG | 40–70% |
# JPEG: progressive + optimized Huffman tables, pixel-identical
jpegtran -copy none -optimize -progressive -outfile out.jpg in.jpg
# PNG: try harder, keep the colour profile
oxipng -o 4 --strip safe in.png
# MozJPEG: better encoder, same input
cjpeg -quality 82 -progressive -optimize -outfile out.jpg in.ppm
Progressive JPEG deserves a note of its own. It renders a low-quality full-frame pass first, then refines. The file is usually slightly smaller than baseline and the perceived load is faster. There is no reason not to use it above roughly 10 KB.
The compression tools comparison benchmarks these against each other.
8. Change the Image, Not Just the Encoder
Some of the largest savings come from editing rather than encoding. An encoder must faithfully represent whatever you give it, including the parts nobody needs.
Crop to the Subject
A product photo with 40% empty background is 40% wasted pixels. Cropping to the subject reduces the pixel count and lets the remaining pixels carry more detail at the same file size.
Flatten Unnecessary Transparency
An image with an alpha channel that is fully opaque everywhere still pays for the channel. Flatten it onto a background and drop to a format without alpha.
magick input.png -background white -alpha remove -alpha off output.jpg
Reduce Noise Before Encoding
Sensor noise is high-frequency detail that no encoder can compress well. Light denoising before encoding can cut 15% to 25% off a low-light photograph with no perceived quality loss.
magick noisy.jpg -enhance -quality 85 clean.jpg
Simplify the Palette on Flat Art
# 256-colour PNG-8 for illustrations, often 60% smaller than PNG-24
pngquant --quality 65-90 --speed 1 illustration.png
For product catalogues, background removal does both jobs at once: it isolates the subject and it makes the background a single flat colour that compresses to almost nothing. Sirv AI Studio does this in batch, and the Sirv Studio API exposes it programmatically for automated pipelines.
9. Let a CDN Negotiate Per Request
Every technique above produces one file. A visitor on a 375-pixel phone in Safari and a visitor on a 27-inch monitor in Chrome need different files. Deciding at build time means guessing.
An image CDN decides at request time using the Accept header, the requested width, and the device pixel ratio.
https://demo.sirv.com/terrace.jpg?w=800 → 800px, AVIF to Chrome, WebP to older clients
https://demo.sirv.com/terrace.jpg?w=1600&q=80 → explicit quality
https://demo.sirv.com/terrace.jpg?w=400&format=webp → explicit format when you need it
What This Removes From Your Build
| Task | Build-time pipeline | Image CDN |
|---|---|---|
| Generate 4 widths × 3 formats | 12 files per image | 0 files |
| Add a new breakpoint | Rebuild everything | Change a URL |
| Support a new format | Update the pipeline | Automatic |
| Storage | 12× originals | 1× original |
Create a Sirv account if you want this without maintaining a build step. The image CDN comparison covers the alternatives and their pricing models.
Putting It Together
A complete Sharp pipeline that applies techniques 1, 2, 3, 4, and 5 in one pass:
import sharp from 'sharp';
const WIDTHS = [400, 800, 1200, 1600];
async function processImage(input, basename) {
const outputs = [];
for (const width of WIDTHS) {
const base = sharp(input)
.rotate() // apply EXIF orientation, then drop it
.resize({ width, withoutEnlargement: true }); // technique 1
outputs.push(
base.clone()
.avif({ quality: 58, chromaSubsampling: '4:2:0' }) // techniques 2, 3, 5
.toFile(`${basename}-${width}.avif`),
base.clone()
.webp({ quality: 80, effort: 6 })
.toFile(`${basename}-${width}.webp`),
base.clone()
.jpeg({ quality: 82, progressive: true, mozjpeg: true })
.toFile(`${basename}-${width}.jpg`)
);
}
return Promise.all(outputs); // metadata is dropped by default: technique 4
}
Summary
Savings by Technique
| # | Technique | Typical saving | Effort |
|---|---|---|---|
| 1 | Resize to displayed size | 50–90% | Low |
| 2 | AVIF or WebP | 25–60% | Low |
| 3 | Tune quality to the knee | 15–40% | Medium |
| 4 | Strip metadata | 20–100 KB per file | Low |
| 5 | Chroma subsampling | 10–20% | Low |
| 6 | Right format for the content | 2–50× on the wrong ones | Low |
| 7 | Lossless optimizer pass | 3–30% | Low |
| 8 | Crop, flatten, denoise | 15–40% | Medium |
| 9 | CDN negotiation per request | Removes the guesswork | Low |
Checklist
- ✅ No image exceeds 2× its largest displayed width
- ✅ AVIF and WebP are offered, with JPEG or PNG as fallback
- ✅ Quality is tuned per content category, not set once globally
- ✅ Metadata is stripped, orientation is applied first
- ✅ 4:2:0 on photographs, 4:4:4 on text and diagrams
- ✅ Drawn assets are SVG, not PNG
- ✅ A lossless pass runs last in the pipeline
- ✅ Images are cropped to the subject before encoding
- ✅ Delivery negotiates format and width per request
Techniques 1 through 3 do most of the work. Everything after that is the difference between a good result and a fully optimized one.
Related Resources
Related Guides
Understanding Image Compression: Lossy vs Lossless Deep Dive
Image Compression Tools Compared: TinyPNG vs Squoosh vs ShortPixel & More
Image Resizing for the Web: The Complete Guide
JPEG Optimization Mastery: The Definitive Guide
WebP vs AVIF vs JPEG XL: Which Format When?
12 Image Optimization Mistakes That Slow Down Your Website