cd /news/ai-tools/compressing-a-photo-to-an-exact-file… · home › topics › ai-tools › article
[ARTICLE · art-157104] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

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.

by read3 min views5 publishedOct 25, 2026

Disclosure: this post is from the team behind BasePic, 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. 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 has the details.

If you have a form whose limit this doesn't meet, we'd like to hear about it in the comments.

── more in #ai-tools 4 stories · sorted by recency
── more on @basepic 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/compressing-a-photo-…] indexed:0 read:3min 2026-10-25 · —