I run two small tool sites, and on one of them people upload photos to a shared gallery. Storage and bandwidth come out of my own pocket, so the upload form has a 200 KB cap, and I'd been putting off the client-side compression that would save people from a bare "file too large". Phones hand you 12 or 24 MP photos now. Before building anything I wanted to know what that cap actually costs a 24 MP photo. I measured on an Apple M4 / 16 GB with a Chromium 149 open-source build, using only the browser's own canvas encoders. The 12 and 24 MP images are photo-like textures I generated with a script, not real photos. The real-photo numbers come from a 3840×2160 landscape that ships with the OS as a wallpaper.
1. Lowering quality alone doesn't get there
At JPEG quality 0.30, the 24 MP image was still 785 KB and the 8.3 MP real photo was 315 KB. WebP at 0.30 left the 24 MP image at 540 KB. I had assumed that if you keep turning quality down you eventually hit any size. You don't, or at least not before the picture is ruined. For a cap this tight you have to throw pixels away.
2. Pixels: what size survives 200 KB
My approach was to encode once at quality 0.50, and if that's over the cap, shrink both sides by sqrt(target / size) × 0.97, redraw and try again. It took one or two rounds. The 24 MP JPEG went from 6000×4000 down to 2262×1508, ending at quality 0.51 and 195,424 bytes. The WebP kept 2805×1870, at quality 0.60 and 201,806 bytes. On the real photo JPEG kept 2306×1297 and WebP 3193×1796. JPEG needed a third round there because the first estimate still landed at 217 KB. The 0.50 floor and the 0.97 margin are numbers I picked, not a standard. For viewing in a gallery, 2262 pixels wide is plenty. For someone who wanted to crop the photo later, it's a real loss.
I used ImgIng (https://imging.ai/) on the same M4 Mac, Chromium 149, to see how a finished compressor handles this. Its image compressor has no target-size field at all. You get a quality slider with a recommended value based on the image content, plus resizing. That made me less sure a "fit under N KB" box is what my users want. They may be better off seeing the size and choosing.
3. Time: WebP makes people wait
In every row the green WebP bar is 12 to 15 times the blue JPEG bar, and the gap in milliseconds grows with pixel count. That ratio is what you multiply by the number of encodes in your search. The shrink-then-search run above took 204 ms in total for the 24 MP JPEG and 2.45 s for the WebP. A plain quality search on the full 24 MP image costs about 0.5 s with JPEG and about 7.9 to 8.8 s with WebP. The page stayed usable. During seven back-to-back WebP encodes (7.6 s in total) the longest gap between 16 ms timer callbacks was 19 ms. It's waiting time, not a frozen tab. Moving the encode into a Worker didn't make it faster either (1146 ms against 1165 ms). So for my site the answer is a progress bar, a cancel button, and shrinking first so each encode has fewer pixels to chew on.
4. Quality: the same 200 KB, two formats
On a 1920×1080 version of the real photo, each format pushed to the highest quality that stays under 200 KB, JPEG landed at quality 70 with a PSNR of 35.43 dB and SSIM 0.9726. WebP landed at quality 81 with 37.18 dB and 0.9784. WebP gets you 1.75 dB and, from item 2, a bigger image. You pay for both with the wait in item 3.
5. Which 200 KB?
The JPEG in item 4 was 204,214 bytes. That passes if 200 KB means 200 × 1024 and fails by 2% if it means 200,000. The WebP in item 2 has the same problem. If the frontend counts one way and the server the other, users get "fine on the client, rejected by the server". I don't know how many sites have this mismatch. I only know it's one constant apart.
For my gallery I'm going with JPEG, shrinking before searching, and one byte limit defined in one place that both sides read. Before you set a size cap on your own form, encode your biggest expected photo once at your lowest acceptable quality. If that single encode is already over the cap, plan for resizing from day one.
Top comments (0)