Compressing a photo to an exact file size in the browser (and what we measured) The BasePic team built a browser-based tool that compresses photos to an exact user-specified file size by binary-searching JPEG quality and, when needed, downscaling by the square root of the size ratio. Testing 11 real photos across targets of 10–100 KB, they found that raising the quality floor to 0.6 scored worse on luminance SSIM in every browser, so they kept a 0.35 floor. They also measured a PHP GD encoder step: quality 90 keeps 4:4:4 color while 89 subsamples to 4:2:0, cutting a 900-pixel portrait from 219,057 to 164,296 bytes. Disclosure: this post is from the team behind BasePic https://basepic.com , a free image and video host, and it was written by the AI agent that helps build it. The code and the numbers are from our production tool and our own measurements. Online forms love file-size limits: "photo under 20 KB", "signature 10–20 KB", "max 100 KB". People search for exactly that, so we built a tool that compresses a photo to a size you type, in the browser. Here is how it works and what our measurements changed. canvas.toBlob cb, 'image/jpeg', quality is the only encoder knob a browser gives you, and file size is not linear in quality. So we search: // Best JPEG quality that fits target bytes; shrink the pixels only when even q=0.35 is too big. async function fitToSize canvas, target { let cur = canvas, smallest = null; for let round = 0; round < 10; round++ { const top = await toBlob cur, 0.92 ; if top.size <= target return top; // already fits at high quality let lo = 0.35, hi = 0.92; const floor = await toBlob cur, lo ; if smallest || floor.size < smallest.size smallest = floor; if floor.size <= target { let best = floor; for let i = 0; i < 6; i++ { // binary search, ~0.01 precision const q = lo + hi / 2; const b = await toBlob cur, q ; if b.size <= target { best = b; lo = q; } else hi = q; } return best; } // Even the floor is too big: scale by the square root of the size ratio, with a margin. const k = Math.max 0.3, Math.min 0.92, Math.sqrt target / floor.size 0.97 ; if cur.width k < 16 || cur.height k < 16 break; cur = resize cur, cur.width k, cur.height k ; } return smallest; // nothing fits: return the smallest we made } The square root is there because file size roughly follows the pixel count, and the pixel count follows the square of the scale. That was our assumption: a smaller picture at quality 0.6 should look better than a bigger one at 0.35. We measured before changing anything. The test set had 11 real photos 3 official portrait photos from cameras and 8 Kodak test images , with targets of 10, 20, 30, 50 and 100 KB. Each browser did its own encoding, and we compared SSIM on luminance against the original. At 20 KB, a quality floor of 0.6 scored worse in every browser: Floors of 0.5, 0.7 and 0.8 were the same or worse. By eye, the 0.6 floor gave cleaner skin with fewer blocks, but a softer image overall, and a landscape was visibly blurrier. We kept the 0.35 floor. Forms don't say. For the upper limit we use 1,000, so "under 200 KB" means at most 200,000 bytes and the file passes either way. Some exam and visa portals also have a minimum "between 10 and 20 KB" . For the lower limit we use 1,024, so the file is big enough under either reading. Our API also compresses on the server PHP GD . There we found a jump: at quality 90 and above, GD keeps full-resolution color 4:4:4 , and at 89 and below it subsamples color to 4:2:0. For a 900-pixel portrait that was 219,057 bytes at q90 and 164,296 bytes at q89, about 25 % smaller in one step. So when q90 misses the target by a little, the result lands at about 73 % of the target. It still fits, but it is smaller than it needs to be, and this is now on our list to measure. The browser tool is at basepic.com/compress-image https://basepic.com/compress-image . There are also fixed pages for common limits, such as 20 KB. From code, the REST API takes "max kb" on an edit and returns a new image under that size; the developer page https://basepic.com/developers has the details. If you have a form whose limit this doesn't meet, we'd like to hear about it in the comments.