Gabriel Malenowitch
March 15, 2026 · Gabriel Malenowitch

How image compression works

images
performance
compression
web
frontend
optimization
webp

How image compression works

Images usually account for most of a web page’s size. A photo taken with a phone easily exceeds 4MB — and serving that directly to the user is a huge waste of bandwidth, memory, and loading time.

Image compression is the process of reducing that size without (or with minimal) perceptible quality loss. And there are several techniques for this, which are usually combined.


Lossy vs Lossless

First of all, it’s worth understanding the fundamental difference:

  • Lossless (without loss): the file gets smaller, but the image can be reconstructed pixel by pixel with 100% fidelity. Useful for logos, icons, screenshots.
  • Lossy (with loss): discards information that the human eye is unlikely to notice. Results in much smaller files, but the loss is irreversible. Useful for photos and images with gradients.

In practice, most images on the web benefit from lossy compression — the size reduction is significant and the quality loss is imperceptible to the user.


Technique 1: Resizing

The simplest and often most impactful technique. A 4000×3000px image on a screen that displays at most 800px in width is loading ~25x more pixels than necessary.

# Python / Pillow
nova_largura = img.width // 3
nova_altura = img.height // 3
img.resize((nova_largura, nova_altura), Image.Resampling.LANCZOS)

The LANCZOS filter (also called Sinc) is the most recommended for reducing size — it’s slower, but preserves sharpness better and avoids artifacts.


Technique 2: Format

The image format has a huge impact on the final size — sometimes greater than the quality level.

FormatTypeWhen to use
JPEGLossyPhotos, images with gradients
PNGLosslessImages with transparency, logos
WebPLossy + LosslessModern replacement for JPEG and PNG
AVIFLossy + LosslessBest compression available, less support
GIFLossless (256 colors)Simple animations

WebP is the sweet spot today: supported by all modern browsers, it offers ~25-35% smaller size than JPEG at the same quality, and supports transparency (unlike JPEG).

AVIF is even better at compression, but the encoding cost is high — the time to generate the file is significantly longer.

# Convert to WebP with Pillow
img.save('saida.webp', 'WEBP', quality=90, method=6)

Technique 3: Quality (quality)

The quality parameter controls how much information is discarded in lossy compression. It usually ranges from 0 to 100.

RangeResult
80–90Sweet spot — good quality, much smaller size
60–70Acceptable for thumbnails and previews
below 50Visible artifacts, very specific cases

There’s no universal value — it depends on the image’s content and the context of use.


Technique 4: Optimize flag

Regardless of the quality level, there’s an extra lossless optimization pass that many libraries support. It reanalyzes the generated file and tries to reduce it further without losing information.

img.save('saida.webp', 'WEBP', quality=90, optimize=True)

It’s slower, but the result is usually a few KBs smaller without any additional quality loss. It’s worth enabling whenever possible.


Technique 5: Compression method

Some libraries expose the compression “method” or “level” — basically, how much computational effort the encoder will spend trying to compress the file.

# method ranges from 0 (fast, less compression) to 6 (slow, more compression)
img.save('saida.webp', 'WEBP', quality=90, method=6)

Higher method = smaller file, but longer encoding time. For offline or batch processing, it’s worth using the highest level. For real-time compression in the browser, lower levels are preferable.


Technique 6: Color mode (RGBA → RGB)

Images can have different color modes:

  • RGB — 3 channels: red, green, blue
  • RGBA — 4 channels: RGB + alpha channel (transparency)

JPEG doesn’t support transparency. If you save as JPEG without converting first, the library will return an error or an unexpected result. Removing the alpha channel when it’s not needed also slightly reduces the file size.

if img.mode != 'RGB':
    img = img.convert('RGB')

Technique 7: Grayscale

If the image doesn’t need color, converting it to grayscale eliminates 2 of the 3 channels, reducing the size significantly.

img_cinza = img.convert('L')  # 'L' = luminance (grayscale)

Useful for black-and-white photos, scanned documents, and cases where color doesn’t add information.


Technique 8: Iterative compression

Instead of applying a fixed quality level, it’s possible to test multiple levels and choose the smallest file that still meets an acceptable quality threshold.

for quality in [90, 80, 70, 60, 50]:
    generate image with this quality
    if size <= target:
        use this result
        stop

This is the “maximum optimization” mode in Tiny Image — it automatically tests several levels and returns the smallest possible file within the defined criterion.


Combining the techniques

In practice, the best compression comes from combining several of these techniques:

  1. Resize to the maximum size that will be displayed
  2. Convert to WebP (or AVIF if encoding time isn’t a problem)
  3. Adjust quality for the context (80–90 for displayed images, less for thumbnails)
  4. Enable optimize for the extra lossless pass
  5. Use a high method if processing is offline or in batch
  6. Convert the color mode if necessary (RGBA → RGB for JPEG)
  7. Grayscale when color is irrelevant

A 4MB image can get down to less than 100KB with this set of techniques — without the user noticing any visual difference.

06
Comments

Leave a comment

Feedback, questions, opportunities — feel free.