The usual advice for sharing a graphic is to export it as JPEG because JPEG is small. I build small export tools at work, and red text is where that advice keeps looking wrong to me. The edges come out with a muddy rim. I wanted numbers instead of a hunch, so I drew a test poster in code and exported it every way I could think of. Then I plotted file size against how clean the text edges stayed. A 124-color PNG came out smaller than every JPEG and cleaner than all of them. Every lossy file was dirtier than it, and all but one were bigger too.
The poster is 1600×900, a sale-style layout with a yellow headline on a red background and a white card below it carrying lines of red text at several sizes. No photos and no third-party assets. The source PNG is 126,373 bytes and has 746 distinct colors, but only a handful of them are real design colors. The rest are anti-aliasing shades along the glyph edges. I ran everything on an Apple M4 with 16 GB. Browser exports came from a Chromium 149 open-source build through canvas.toBlob. For comparison I also used Pillow 11.3, cwebp 1.6.0 and the sips tool that ships with macOS for AVIF. To score each file I measured the mean color difference (CIE76 ΔE) inside a 2 px band around the glyph edges, because flat areas barely change and would hide the problem. A ΔE above about 5 is commonly treated as a visible fringe. That is a rule of thumb, not something I measured.
Here is what the numbers looked like. PNG-8 with 124 colors came out at 48,481 bytes with ΔE 0.13, and lossless WebP from cwebp at 49,432 bytes with ΔE 0. JPEG from the browser at quality 0.80 to 0.99 ran from 118 KB to 265 KB while ΔE only fell from 8.11 to 6.35. Paying about 150 KB more bought an improvement of 1.76. The one file smaller than the PNG-8 was AVIF at q60, 46,832 bytes, and its ΔE of 7.88 was among the worst of the lot. Browser WebP landed in the middle at 86 KB and ΔE 6.56 for quality 0.9.
Why lossy formats smear red edges
Every lossy file on the chart uses 4:2:0 chroma subsampling. The encoder splits the image into brightness and two color channels, then stores the color channels at half width and half height. That leaves color at a quarter of the resolution. Text outlines mostly live in the brightness channel. Black on white survives because the difference is almost all brightness. Pure red at (215, 0, 15) has low brightness, so against white, yellow or dark backgrounds a lot of the edge information sits in the color channels that just got downsampled. Lossy WebP is 4:2:0 by spec. The sips AVIF files are also 4:2:0, though AVIF itself supports 4:4:4 and sips simply didn't use it. Browser JPEG stays 4:2:0 in this Chromium build until quality rounds to 100. JPEG at 4:4:4 from Pillow does much better (quality 75 gives 169,640 bytes at ΔE 4.47), but it is still more than three times the size of the PNG-8.
The lossless side wins for a boring reason. This image is big flat areas of a few colors. Palette PNG and lossless WebP can describe that almost for free. A lossy encoder spends most of its bits trying to approximate sharp color steps and still misses.
Does it hold on a real image
All of that was on a poster I drew myself, so I repeated the test on a real one: a ready-made 2026 National Day holiday notice template I found online (a template image from Gaoding Design, a Chinese template site). It is a 1242×2688 truecolor PNG with a red title area, red text on an off-white card, and a calendar of small red dates, with gradients, illustrations and lanterns behind everything. The source has 169,747 distinct colors, against 746 on my poster. I cropped out the site's small logo and a QR code for the figures.
This is the whole template. The two red boxes mark where I zoomed in: the "10月8日返岗" line (back to work on October 8) on the middle card, and the calendar cells for October 1 to 3. The next figure uses only the calendar crop.
This is the calendar crop showing "1 廿一", "2 廿二" and "3 廿三" at 5×, with no labels on the image. Top to bottom the rows are the source, browser WebP at 0.9, cwebp at quality 90 with sharp YUV, and lossless WebP. Look at the small characters 廿一 and 廿二 under the numbers, which are lunar dates. In the second row their strokes turn darker and brownish instead of the source's clean red. The sharp YUV row gets some of the red back but is still slightly dirty. The lossless row can't be told apart from the source.
The lossy half of the story held. In the calendar area browser WebP at 0.9 scored ΔE 4.67, and cwebp with sharp YUV brought that down to 3.71. Browser JPEG grew 3.2 times in size between quality 0.80 and 0.99, while the edge ΔE on the card text only fell from 6.56 to 4.48. The lossless half did not hold. Lossless WebP came out at 2,359,014 bytes, 2.5 times the 950,062-byte JPEG at 0.9, and the source PNG itself is 3,339,216 bytes. My guess is that the gradients and illustrations leave a palette or pixel-copy coder nothing cheap to reuse. So "lossless is smaller and cleaner" only holds for flat graphics made of a few colors plus text.
What I export now
For the PNG-8 point I used ImgIng (https://imging.ai/) in the same Chromium 149 build with default settings, running the Chinese-language version of the site. It flagged my drawn poster as line art on its own and went straight to PNG-8, ending at 124 colors and 62% smaller than the source. That 62% is a flat-graphics result. On the holiday template it did not pick PNG-8 at all. It classified the image as a screenshot or UI and defaulted to WebP at quality 84, 82% smaller than the source, with an edge ΔE of 5.12. That is about the same as plain lossy WebP, so it did not rescue the red text there. Its UI calls that result lossless and small. When I diffed it, 2.03% of pixels had changed, by at most 6 levels. You can't see it, but it isn't pixel-exact either. I also checked its JPG export with the slider at 100. The file was byte-identical to Chromium's own toBlob at 0.99, so it is 4:2:0 as well.
My rule for flat graphics with text is to try palette PNG or lossless WebP before any lossy format, and only then compare. On templates full of gradients and illustrations lossless is the biggest option, so I go lossy and prefer a 4:4:4 JPEG where I can set it. On the template, quality 75 at 4:4:4 was 680,616 bytes at ΔE 4.29, cleaner than browser 0.99 (2,004,878 bytes, 4.48) at about a third of the size. I haven't tested photos, and a poster with a large photo on it is a different problem that I won't guess about. If you do export red text as JPEG, zoom to 400% and look at the edge before you ship it.

Top comments (0)