Running an AI image upscaler & sharpener 100% in the browser (TensorFlow.js) A developer built two free browser-based tools for upscaling and sharpening images using TensorFlow.js, allowing the entire machine-learning process to run locally without uploading images to a server. The tools, an AI image upscaler and an AI photo sharpener, use ESRGAN model variants and a patch-based approach to manage memory, with options for speed and detail. I wanted to sharpen and upscale images without uploading them to some server . It turns out you can run the whole ML model right in the browser — the image never leaves the device. Here's what I learned shipping it as two free tools. The core is one call: new Upscaler { model } , then await upscaler.upscale img, { patchSize: 64, padding: 4, progress } . The patchSize option is the important one — more on that below. Memory. Running 4× on a large image tries to allocate a huge tensor and the tab dies. The fix is patchSize — process the image in tiles and stitch them back, so memory stays bounded regardless of input size. Model choice matters more than I expected. I benchmarked three ESRGAN variants on the same image: slim is fast ~2.5s on a small image but slightly soft, medium had visible tiling artifacts rejected , and thick was clearly the sharpest but ~3× slower. So I default to slim and offer thick as a "max detail" mode. Lightweight upscalers smooth the image. ESRGAN-slim enlarges cleanly but the result can look soft. A small unsharp-mask pass afterward restores the bite without an obvious "sharpened" halo. Sharpen vs upscale are different jobs. To make a sharpener that keeps the original size, I run the same model then draw the result back down to native dimensions — the AI detail survives the downscale, so you get a clearer image at the same size. That became the AI photo sharpener https://snapvi.app/sharpen-image ; the enlarge-2×/4× version is the AI image upscaler https://snapvi.app/image-upscaler . The trade-off is a one-time model download and slower runs on weak phones, which I gate with size caps and a fast/max toggle. Happy to answer anything about the TF.js side — what would you run in-browser next?