PNG, JPG, or WebP? Choosing the Right Format for Text Images
By Tu Luu · · · 13 min read
Most format advice online is written for photographs, which is why it gives you the wrong answer for text. An image full of sharp letter edges behaves almost exactly opposite to a photo of a sunset, and picking the format by habit is how you end up with muddy, grey-fringed lettering.
The one distinction that decides everything
Every image format is either lossless or lossy. Lossless means the pixels you get back are bit-for-bit the pixels you put in. Lossy means the encoder is allowed to throw information away to make the file smaller, and it decides what to discard based on assumptions about what human eyes ignore.
Those assumptions were built around photographs. Photos are full of gradual tonal shifts, so a lossy encoder can smooth out fine detail and nobody notices. Text is the opposite: it is defined almost entirely by hard, high-contrast edges. Every single letter is a boundary between two very different colours, and edges are precisely what lossy compression handles worst.
Why JPG ruins text specifically
JPG compresses by splitting the image into 8×8 pixel blocks and describing each block as a sum of wave patterns, then discarding the subtlest waves. Reproducing a crisp black-to-white transition requires a lot of those subtle waves. Take them away and the encoder approximates the edge with a ripple instead of a clean step.

You see that ripple as ringing: faint grey or coloured ghosting that echoes outward from every letter, most visible against flat backgrounds. At small text sizes the ripples from neighbouring letters overlap and the whole line takes on a dirty haze. Zoom into any JPG screenshot of a document and you will find it immediately.
JPG makes this worse by storing colour at half resolution in each direction (chroma subsampling). Coloured text on a coloured background — red on white, white on blue — gets its edges smeared before block compression even begins. This is why red text is the classic worst case for JPG.
The damage compounds. JPG is lossy every time it is saved. If you screenshot a JPG, crop it, and save again, you are compressing artefacts that are already there. Text images often get re-saved many times as they move between apps, and each round trip degrades them further. Lossless formats have no such generational loss.
Why PNG is the sane default for text
PNG is lossless, so a black letter stays exactly black and the pixel beside it stays exactly white. But the more interesting property is why PNG files stay small for this kind of image.
PNG compresses by finding repetition. A text image is mostly one flat background colour, which is about the most repetitive data possible, so it collapses dramatically. A 1200×600 PNG of a quote on a solid background often lands under 30 KB. The same image as a photograph would be hundreds of kilobytes.
This produces a result that surprises people: for text on a flat background, the lossless format is the smaller file. The usual trade-off does not apply. On the densest sample I measured — a 618×213 invoice listing at 14px — lossless WebP came to 4.9 KB against 103 KB for JPEG at quality 85. That is not a marginal win, it is twenty times.
It reverses on screenshots, though, and I only found that out by measuring it. The numbers and the reason are further down.
PNG also supports a full alpha channel, which is what lets you export text with a genuinely transparent background for overlays and watermarks. JPG has no concept of transparency at all — it will fill it with white or black.
Where WebP actually wins
WebP is often described as simply better than both, which oversells it. WebP has two separate modes and they behave very differently:
- Lossy WebP is a modernised JPG. It handles edges noticeably better than JPG and supports transparency, but it is still throwing information away and still softens fine lettering.
- Lossless WebP is a modernised PNG, and it genuinely does beat PNG on size. Across 29 samples the median saving was 28% for the same exact pixels — but the spread ran from 9% to 48%, and fewer than half the samples landed inside the 20–30% band this article used to quote.
So the honest summary is: lossless WebP is the technically superior choice for text images, and PNG is the choice that works everywhere. If you are publishing to your own website and control the pipeline, lossless WebP saves real bandwidth. If the file is going to be pasted into a chat app, dropped into a slide deck, emailed to a colleague, or opened by someone on an older system, PNG will not make you troubleshoot anything.
What I measured, and where I was wrong
Both of the size claims above used to sit here with no source behind them. I had repeated them because they are what everyone repeats. So I measured: 29 text images, six encoders, every byte count for the same exact pixels. The harness is in the repository as scripts/webp-benchmark.mjs, and the full table of results is checked in beside it, because a benchmark you cannot re-run is just a number to trust.
One claim survived, one did not, and the most useful finding was not either of them.
1. "20–30% smaller than PNG" — roughly right, much wider than stated
Median saving across all 29 samples was 28.3%, so the old figure was not wrong so much as falsely precise. The real range was 9% to 48%, and only 12 of 29 samples fell inside the band. The saving is largest on complex images and smallest on the flat two-colour text where PNG is already near-optimal.
The bigger catch is what "PNG" means. That 28% is against a PNG already squeezed with oxipng at maximum effort. Against the PNG a browser actually hands you, the median saving is 73% — because the browser's own PNG encoder is the weakest link in the chain, not the format.
2. "For text images the lossless format is usually smaller" — half wrong
| Kind of image | Lossless beat JPEG q85 |
|---|---|
| Rendered text on flat colour | 13 of 13 |
| Text over a background of rising complexity | 5 of 10 |
| Real UI screenshots | 0 of 4 |
Every screenshot lost, and lost clearly — JPEG at quality 85 came out around 1.4× smaller than the best lossless encoding of the same capture. The old advice in the decision table below, that screenshots are "the worst case for JPG", was simply not true of the four real screenshots on this site.
Finding the reason took three tries, and the first two were wrong. My first guess was the number of distinct colours, so I built a sweep holding the text fixed and varying only the background — and lossless kept winning up to 4,500 colours, which killed the theory. My second guess was that screenshots are mostly empty space, so I swept the percentage of the canvas covered in text from 5% to 100% — lossless won every single one, which killed that theory too.
What actually matters is whether the non-text area is flat or continuously varying. My first sweep used hard-edged bands, and a band of one colour compresses by repetition no matter how many bands the image contains; once I made the background a true per-column ramp, the crossover appeared immediately, between roughly 3,900 and 5,600 distinct colours. Real screenshots are full of soft shadows, subtle panel gradients and anti-aliased chrome — continuous variation everywhere, which is exactly what lossless compression cannot exploit and what JPEG discards cheaply.
So the rule worth carrying away is not about text at all: if the background is flat, lossless wins by a mile; if it varies smoothly, JPEG wins even though the image is full of text.
3. The finding I was not looking for: the browser's PNG encoder
Chrome's canvas.toBlob writes PNGs a median of 2.1× larger than the same pixels through oxipng, and on the dense invoice sample it was 32.8× larger — 167 KB where 5 KB was enough. Its WebP encoder is 2× off cwebp too. This matters here more than anywhere, because that encoder is what this site's own tools call when you press download.
And it applied to this page. The four screenshots in the guides on this site were carrying 42% to 57% more bytes than they needed to, so they are now lossless WebP — including the JPEG-artefact comparison above, whose pixels were checked byte for byte against the original before the PNG was deleted.
Note that this is not what the table below recommends for a screenshot with gradients in it: by size alone, JPEG would have won on three of the four. I did not take that trade, for two reasons worth being explicit about. One of these images exists specifically to show what JPEG does to lettering, so encoding it as JPEG would be absurd. And this benchmark measured bytes, not how the result looks — a size result is not licence to make a quality decision it never tested.
The practical version for anyone exporting images: the format argument matters less than running the file through an optimiser afterwards. Switching PNG to lossless WebP saves about 28%. Switching a browser-exported PNG to an optimised one saves about 50%, before you change format at all.
-o max with and without zopfli, and cwebp 1.6.0 at -lossless -z 9. Every lossless output was decoded outside the browser and compared byte for byte against the source — 145 of 145 matched exactly. That verification cannot be done through a canvas: reading pixels back with getImageData round-trips through premultiplied alpha and shifts anti-aliased edges by one to three levels, which looks exactly like encoder loss and is not. An earlier draft of this benchmark reported precisely that false result. No photographs were tested, and encode time — which for cwebp -z 9 is substantial — was not measured.A decision table you can actually use
| What the image contains | Use | Why |
|---|---|---|
| Text on a solid background | PNG | Crisp edges, and usually the smaller file anyway |
| Text needing transparency | PNG | Full alpha channel; JPG cannot do it at all |
| Screenshot of a document or plain UI | PNG or lossless WebP | Flat panels compress by repetition; nothing to lose |
| Screenshot with shadows, gradients or chrome | JPG q85 — measured, not assumed | Lossless lost all 4 real captures I tested, by about 1.4× |
| Text images for your own site | Lossless WebP | Same pixels as PNG, median 28% less bandwidth |
| Anything a browser exported | Run it through an optimiser | Median 2.1× larger than it needs to be, worst case 32.8× |
| A photo with a caption burned in | High-quality JPG or lossy WebP | Photo content dominates; keep quality ≥ 85 |
| Anything being archived or re-edited | PNG | No generational loss across repeated saves |
Formats worth mentioning briefly
GIF is lossless but capped at 256 colours. For plain two-colour text it technically works, and it fails as soon as you use anti-aliased edges or a gradient background, since those need far more than 256 shades. Its transparency is also binary — a pixel is fully opaque or fully invisible — which produces jagged letter edges. There is no reason to choose it over PNG today.
SVG is the genuinely best option when it fits, because it stores the text as vector instructions rather than pixels, so it stays razor sharp at any size and the file is tiny. The catch is that it only renders correctly where the font is available, and many platforms — most social networks among them — will not accept an SVG upload at all. That is exactly why rasterising to PNG remains the practical route for social graphics.
AVIF compresses better than WebP but is slower to encode and its lossless mode is less consistently supported. For text specifically, lossless WebP is the better-supported pick right now.
Three mistakes worth avoiding
Converting a JPG to PNG to "fix" it. Once compression artefacts are baked into the pixels, they are part of the image. PNG will faithfully preserve the damage. Always re-export from the original source instead.
Exporting text at display size. Whatever the format, generate at roughly twice the size you intend to display, so it stays sharp on high-density screens. Downscaling looks fine; upscaling never does.
Assuming the platform keeps your format. Most social networks re-encode every upload to their own lossy format regardless of what you send. You cannot prevent that, but uploading a clean lossless original means their encoder starts from undamaged pixels rather than compressing artefacts twice.
Try it yourself
The clearest way to understand any of this is to see it. Generate the same line of text twice with our Text to Image Converter, save one copy as PNG, run the other through any JPG compressor at moderate quality, then zoom both to 400%. The comparison near the top of this page is exactly that test already run — but doing it on your own text takes about ten seconds, and seeing the grey haze appear around your own letters is more convincing than being told about it.