About this image resizer
A small, dependency-free tool for one job: changing an image's pixel dimensions without sending the image anywhere. No account, no upload, nothing kept after you close the tab.
Why it exists
Resizing an image is one of the most common things anyone does with a photo before it goes anywhere else — a form field, a listing, an email, a page that will only ever display it at 800 pixels wide. Doing it well does not need a desktop application or an account with a service that keeps a copy of what you upload; it needs a canvas, a resampling filter, and correct arithmetic for the width and height. This page exists so that arithmetic and that decode path are documented rather than left as a black box.
How it is implemented
The pipeline has three steps, all inside the tab:
- Decode. The picked or dropped file is passed to
createImageBitmap(), which decodes it off the main thread where the browser supports it. If that call is unavailable or rejects the file, the tool falls back to loading it into a plain<img>element and reading its natural dimensions once it firesload. - Resample. A
<canvas>is sized to the target width and height,imageSmoothingEnabledandimageSmoothingQuality: 'high'are set, anddrawImage()draws the decoded bitmap scaled to that size in one call. The browser's own resampler does the interpolation. - Export.
canvas.toBlob()encodes the canvas contents to the chosen format (PNG, JPEG or WebP) and, for the lossy formats, the chosen quality. The resultingBlobbacks a localblob:object URL that an invisible, programmatically clicked<a download>triggers as a file save — then the URL is revoked.
The dimension arithmetic — turning a typed width into a locked height, a percentage into a width and height pair, clamping a target to at least 1 pixel, never producing NaN on a blank field — lives in its own file with no DOM or canvas code in it at all, specifically so it can be exercised outside a browser.
How the dimension maths was tested
The pure logic module was run under Node with a small assertion script before this page shipped, independent of any browser:
| Case | Input | Result |
|---|---|---|
| Aspect-locked width edit | 1600×900, width→800 | height = 450 |
| Aspect-locked height edit | 1600×900, height→300 | width = 533 (rounded from 533.33) |
| Percentage scale | 1600×900 at 50% | 800×450 |
| Floor at 1px | tiny width/height targets | clamps to 1, never 0 or negative |
| Blank input | empty string, null, non-numeric text | returns null, never NaN |
| Round-trip | 1920×1080, width→1280→height→width | returns to 1280 exactly |
All 36 assertions passed, including format helpers (MIME type and file extension per format, lossy/lossless classification) and the human-readable file-size formatter. What Node cannot exercise is the browser side: actual image decoding, canvas resampling quality, and real Blob sizes depend on the browser's own image and canvas implementation, which is why the page also flags upscaling and EXIF orientation as caveats rather than claiming to solve them.
How the site is built
- Static HTML, one small stylesheet, two small scripts. No framework, no build step, no bundler.
- Nothing leaves your device. The picked image is decoded, resized and exported entirely inside your browser tab; there is no upload endpoint anywhere in this tool for it to reach.
- No CDN, no web font, no remote image. The favicon and header mark are inline SVG. Once the page has loaded, resizing an image needs no further network activity at all.
- System font stack. Your operating system's UI font, so nothing has to download and nothing shifts as it loads.
- Dark mode follows your system setting via
prefers-color-scheme.
Scope and honesty about limits
This tool resizes one image at a time to an exact width/height or percentage, and exports it as PNG, JPEG or WebP. It does not crop, rotate, straighten, batch-process multiple files, or add real detail when you enlarge an image past its native resolution — see the upscaling and EXIF caveats on the main page. It also does not guarantee an exact output file size; quality sliders influence size, but the final byte count depends on what is actually in the image.
If you find a case where the resized output looks wrong compared to what a browser's own canvas should produce, that is worth reporting — see contact.