DEV Community

Cover image for How I Built a Privacy-First Image Processing Toolbox That Runs Entirely in the Browser
shengqiang wang
shengqiang wang

Posted on

How I Built a Privacy-First Image Processing Toolbox That Runs Entirely in the Browser

How I Built a Privacy-First Image Processing Toolbox That Runs Entirely in the Browser

I've always been frustrated by online image tools that quietly upload your photos to their servers. You drag in a family photo to resize it, and for all you know, it's now sitting on some random server. So I built my own solution: PictKit (pictkit.com), a free toolbox with 27 image tools where every operation stays in the browser.

In this post, I'll walk through the core techniques that make client-side image processing possible, using real problems I hit while building it.

The Architecture: Web Workers + OffscreenCanvas

The key to keeping image processing fast and non-blocking is moving heavy work off the main thread. The browser's main thread handles UI rendering. If you hog it with image decoding and encoding, the entire page freezes -- buttons don't click, scrolling stutters, everything feels broken.

Web Workers solve this. They run JavaScript in a completely separate thread with no access to the DOM. For image work, we pair them with OffscreenCanvas. As the name suggests, it's a canvas that doesn't exist anywhere on the page but supports all the same drawing operations as a regular canvas. The combination means you can decode an uploaded image, resize it, and re-encode it as WebP or JPEG, all without the main thread ever touching those bytes.

OffscreenCanvas.convertToBlob is the hidden gem here. Before this API existed, you had to transfer raw pixel data back to the main thread just to encode it. That transfer was a bottleneck for large images -- megabytes of pixel data have to be copied (or transferred, which zeros the source buffer) between threads. Now, the worker can do the entire pipeline internally and only send back the compressed blob, which is typically a fraction of the size. When I was optimizing the compression tool on pictkit.com to handle 50MB photos without the UI freezing, this single API change made the biggest difference.

createImageBitmap deserves a mention too. It handles image decoding off the main thread. Unlike the traditional approach of creating an img element and waiting for its load event, createImageBitmap returns a bitmap directly, decodes in parallel, and integrates seamlessly with canvas contexts. Every millisecond you save on the main thread is a millisecond the UI stays responsive.

Handling Aspect Ratio When Resizing

A subtle detail that a lot of online tools get wrong: when you give a max width and max height, you can either force the image to those exact dimensions (which stretches it), or you can maintain the aspect ratio and fit the image into a bounding box.

The correct approach is to calculate a uniform scale factor. Take the width ratio and height ratio, pick whichever is smaller, and apply it to both dimensions. This ensures the image never exceeds the target bounds and never gets distorted. Simple math, but surprising how many tools get the stretch behavior wrong by default. At pictkit.com, I made "keep aspect ratio" the default rather than an opt-in checkbox, because stretching is almost never what users actually want.

Binary Search for Target File Size

Users don't think in terms of "quality 0.72." They think "compress this to under 500KB." But quality-to-size isn't linear, so a fixed quality value is always guesswork.

The solution is binary search. Encode at quality 50, check the size. Too big? Try 25. Too small? Try 75. Within about ten iterations you converge on the quality value that produces the closest match to the target file size. A 5% tolerance lets you early-exit when you're close enough. This approach works because the quality-to-size curve is monotonic -- lower quality always produces smaller files -- so binary search is guaranteed to converge. I added this as a "target file size" mode on the compressor, and it consistently lands within 5% of the user's goal on the first attempt.

A Heads-Up About PNG Quality

Here's something that trips people up: browsers completely ignore the quality parameter when encoding PNG. PNG is lossless by definition, so the encoder doesn't have a quality slider. Calling canvas.toBlob with type image/png and quality 0.5 produces the exact same file as quality 1.0. If you genuinely need to compress PNG files, you need a library that does quantization and color palette reduction, like browser-image-compression, which is what I ended up integrating for the PNG compression path on the site.

SVG Security Without a Backend

PictKit also includes an SVG editor, which means users can paste arbitrary SVG code from anywhere on the internet. SVGs are a surprisingly dangerous format -- they support script tags, inline event handlers like onclick and onload, and even foreignObject which embeds arbitrary HTML. Browsers don't execute scripts in img tags, but they absolutely will if you inject SVG directly into the DOM via innerHTML.

DOMPurify handles this cleanly. It parses the SVG, strips anything dangerous, and returns sanitized markup. The important practice is being explicit about both allowlisted tags and forbidden ones. SVG has a huge surface area, and defense in depth -- allowing what you need while explicitly blocking what's dangerous -- is the safest posture.

The Reason This Matters

Every time you use an online image tool that processes files on a server, that server has a copy of your image. Maybe they delete it after processing. Maybe they don't. You have no way to know.

Client-side processing eliminates that trust question entirely. The file never leaves your machine. The implications go beyond personal privacy -- think about lawyers redacting sensitive documents, doctors reviewing medical images, or anyone working with confidential material. For them, server-side processing isn't just a privacy concern, it's a compliance violation. That's the entire reason I built this the way I did.

If you want to see all of this in action, the full toolbox is at pictkit.com -- compression, format conversion, color adjustment, watermarking, QR codes, and about 20 more tools, all running locally in the browser. Open source on GitHub too.

Top comments (0)