← Writing
The Editorial · Engineering

Compressing any image to WebP — and how images actually work

Issue 001Jun 18, 202612 min read

I built a small tool that takes any image, shrinks it, and converts it to WebP — usually 25–80% smaller with no visible loss. Here's how it works, plus a from-scratch primer on what an image really is, so the whole pipeline makes sense.

The issue

The tool is simple to use and boring by design: drop in a JPEG, PNG, or HEIC, and it hands back a WebP that's a fraction of the size with no difference your eye can catch. But to understand why that's even possible, you have to start with what an image actually is underneath the file.

An image is just a grid of pixels, and each pixel is a few numbers. A normal colour photo stores three channels — red, green, blue — at 8 bits each, so every pixel is 24 bits, or 32 if you add an alpha channel for transparency. Multiply that out: a 4000×3000 photo is 12 million pixels, which is roughly 36 MB of raw, uncompressed data. Nobody ships 36 MB, which is the entire reason image formats exist — they're all just clever ways to store that grid in far fewer bytes.

There are two families of compression. Lossless (PNG, WebP-lossless) rebuilds the original pixels exactly — great for logos, screenshots, and anything with sharp edges or text, but bulky for photographs. Lossy (JPEG, WebP-lossy) throws away information your eyes barely register and keeps the file tiny. The trick is choosing what to discard.

The biggest lever is human vision itself: our eyes are far more sensitive to brightness (luminance) than to colour (chrominance). So lossy formats convert RGB into a luma + colour representation and then store the colour at half resolution — '4:2:0 chroma subsampling.' You instantly drop a chunk of the data and almost nobody notices. On top of that, JPEG slices the image into 8×8 blocks and uses a discrete cosine transform to keep the broad shapes while rounding off the fine, high-frequency detail.

WebP, Google's format, is the modern upgrade. It does both lossy and lossless, supports transparency and animation, and typically lands 25–35% smaller than an equivalent-quality JPEG (and much smaller than PNG for photos). That combination — smaller files, alpha support, near-universal browser support — is why I convert everything to it by default.

The pipeline itself is four steps. Decode the incoming file into raw pixels; optionally resize so nothing exceeds a sensible max dimension (a 6000px-wide image on a 1200px layout is pure waste); re-encode to WebP at a quality target around 78–82, which is the sweet spot on the size-versus-quality curve; then compare the result to the original and keep whichever is smaller. On the server I lean on libvips (via sharp) because it's fast and memory-light; in the browser the same thing is doable with a canvas or a WASM encoder.

A few gotchas worth knowing. Keep PNG when an image is mostly flat colour, text, or hard edges — WebP-lossy will smear them. Preserve the alpha channel or you'll get black boxes where transparency used to be. Respect EXIF orientation before you resize, or phone photos come out sideways. And strip metadata (location, camera data) on the way out — it's privacy you don't need to ship and bytes you don't want.

All issues