KBExactKB
Menu

Guide · updated 2026-09-21

How exact KB compression works

No cloud encoder. No quality slider. A loop that treats your kilobyte number as a hard ceiling.

Open the compressor

The contract

Given a target T in bytes, produce a JPEG or WebP whose blob.size ≤ T, maximizing encoder quality, then maximizing pixel count. If even a small, low-quality image cannot fit, report that honestly instead of uploading to a mystery API.

Step 1 — Decode locally

The file you pick is a File in memory. JPEG, PNG, and WebP go through createImageBitmap. HEIC is converted with a WASM HEIF decoder, still in this tab, then decoded the same way. Drawing onto a canvas drops EXIF and GPS. That is intentional for ID photos.

Step 2 — Optional geometry

If you set a longest side, or a preset supplies a max width/height (India passport 630×810, DS-160 600), the bitmap is scaled with high-quality smoothing before the quality search. Images above 24 megapixels are also scaled down so a phone tab does not run out of memory.

Step 3 — Binary search quality

For JPEG and WebP, ExactKB calls canvas.toBlob(type, quality) (or OffscreenCanvas.convertToBlob) and binary-searches quality between about 0.08 and 0.95. Each probe that fits becomes the new best; each probe that misses lowers the ceiling. Twelve iterations is enough for the size curve of baseline JPEG. The last fitting blob is what you download.

PNG has no quality argument that helps. The loop reduces dimensions by ~18% per round until the PNG fits or the image is too small to continue.

Step 4 — Scale only if quality is not enough

If the lowest quality still exceeds T — a huge noisy screenshot aimed at 20 KB, for example — the canvas is shrunk and the search runs again, up to ten rounds. The result card flags that a scale happened.

Why we do not pad to “exact”

Some tools append comment bytes so the file is 102400 bytes on the nose. That can confuse strict MIME sniffers and does not help a validator that only checks size <= limit. ExactKB would rather show 97.4 KB at 81% quality than a stuffed 100.00 KB.

Batch and ZIP

Each file is an independent job. When you ask for a ZIP, JSZip runs in the page and builds an archive of the already-encoded blobs. Again: no upload.

If you want the product, skip the theory and compress to a target now.

Why twelve quality probes

JPEG size is not a perfectly smooth function of the quality argument, but it is monotonic enough that a binary search of about twelve steps lands within a few percent of the best fitting quality. More probes waste battery on a phone. Fewer probes leave quality on the table. The first probe uses the high end of the range so an already-small screenshot can finish in one encode.

OffscreenCanvas is used when the browser provides it so the main thread can stay responsive during a batch. If OffscreenCanvas is missing, we fall back to a regular <canvas>. Either path calls toBlob / convertToBlob with the same quality number. A last-mile pass then nudges scale at a slightly higher quality so typical JPEGs land within about one kilobyte of the cap. We still never pad bytes.

What you can audit

The encoder lives in src/lib/compress.ts and src/lib/quality-search.ts. Unit tests in scripts/quality-search.test.mjs mock an encoder whose size is linear in quality and check that we stay under the target while maximizing quality. A privacy scan fails the build if the compressor path grows a fetch of image bytes. You do not have to take the “nothing is uploaded” sentence on faith.

This is still a lossy JPEG. It is not a bit-exact archive format. If you need a perfect copy, keep the original; ExactKB is for the file you submit.

More reading on the guides index. Limits change — verify the official notice.