Uploading a file before you can process it is a habit servers taught us. Count the things people actually do to images day to day — shrink them, change format, cut out a background, build a short animation, pull the text out — and most of it the browser can do on its own. We spent a while moving those in one at a time, and hit a boundary that isn't pretty. Up front: I own the client-side codec and model-loading side of this. The matting algorithm and the PDF engine aren't mine, so where those come up I'll stick to what I observed.
Why not put it on a server
Because we don't want to touch your images. Nearly every online image tool works the same way: upload, process, download. Once the file is on someone else's server, how long it stays, who can read it and whether it's really deleted all come down to their word. A privacy policy is a promise, and a promise can't be verified. Meanwhile the things people most need to process are exactly the things that shouldn't travel — IDs, contracts, unreleased product shots, anything with a face in it. Running in the browser sidesteps all of that: the file never leaves your machine, and unlike a promise, that claim can be checked. Open DevTools, go to the Network tab, process an image. No upload request. The preview's src is a blob: URL — an object in your own browser's memory that never touched the network. Ten seconds, and worth more than a page of policy text.
One thing gets confused with this constantly: the first time you use certain features, something does get downloaded to you — an AI model, a decoder for some format. That's the server sending to your browser, the opposite direction from your file going out. The Method column settles it: GET fetches in, POST sends out. The cost side is a side effect, not the reason. Since the work runs on your device, one more operation costs us nothing, which is why the thing can be free, unmetered, account-free and watermark-free. Server-side tools burn money on every call, so they have to meter it, gate it behind signup, push you toward a plan — that isn't stinginess, the arithmetic just doesn't work. The price we pay is real too: it leans on your hardware, and a handful of formats genuinely can't run locally. We never claim everything is upload-free.
What runs locally today
On the image side: compression (parameters picked from content — a screenshot and a photo get different settings), format conversion (reads a dozen-plus, common ones written locally), AI background removal (model fetched on demand, inference local), an animation workshop (GIF, APNG and animated WebP interconvert, with per-frame duration edits, deletion and reordering), multilingual OCR that keeps text positions and table row/column structure, an image editor, and AI upscaling. On the document side: PDF compression that keeps the text layer and links, PDF to image, extracting the original embedded images, content extraction, PDF to HTML, plus DOCX and PPTX to standalone offline HTML. All of it finishes on your device. It's at imging.ai. Two numbers from the part I work on: our PNG-8 quantizer hits 45.8dB at 256 colours on the reference sample where pngquant gets 43.4dB, and inter-frame differencing takes the published sample animation from 843KB to 350KB.
How we decide what's safe to run locally
We didn't start with that confidence. To answer "can this browser do this job" we used a UA string and a compatibility table — check the version, check the format, open the feature if it lines up. Then we got burned inside an app's embedded browser. That environment displayed WebP perfectly well, but when canvas.toBlob asked for WebP it quietly handed back a PNG: no error, extension still .webp, and the user wondered why the file hadn't shrunk. The fault was in the phrase "supports WebP" — one cell in a table, but several separate things at runtime: decoding in, encoding out via canvas, and whether what comes out is actually WebP. The table was right. Our conclusion wasn't.
The fix is dumb and it works: stop guessing, run every capability for real in the user's browser. Six of them, independent — input decode, canvas processing, output encode, threaded WASM, WebGPU, and writing to disk. Decode once for real, encode once for real, and only when both ends pass does the feature get marked "instant"; anything needing a WASM download is marked client-side; whatever fails both goes to the server. Those coloured chips in the UI are that probe's result — they don't say "we support this format", they say "this device, right now, can or can't". The side effect I didn't see coming was that the probe, not a product decision, ends up determining which features we dare ship locally. One thing we only found by measuring: several unsupported formats returned blobs of exactly the same size, 95 bytes — the same 1×1 fallback PNG. That number became our crude test for whether something had been silently downgraded.
What we couldn't move
HEIC writing, TIFF and JPEG 2000 go to the server: HEVC carries patents and the encoder size is prohibitive, so the local path is closed. The UI colour-codes them so you see it before you commit. HTML-to-PDF also submits a script-stripped snapshot to a same-origin headless Chromium after you click convert, so it isn't purely local either — that's stated on the page, no hedging. Which is why "your images aren't uploaded" is scoped here: it holds for the features that take the local path. Video compression opened a few days ago and I haven't run it yet.
One thing still unsolved
The probe costs something. On first load it really does run a decode and an encode; the sample image is tiny, but on a low-end machine you can feel those few hundred milliseconds. We tried caching the result, except a user switching browsers or updating their OS makes the cache wrong, so we currently cache with a short expiry and live with it. I've never been happy with this and don't have a better answer — probing every time costs experience, caching too long puts us back to guessing, and guessing is the problem we set out to fix.
Top comments (0)