Back to portfolioBack

Image filters

Image
...
Rust
...
JavaScript
...

Image ..., Rust ..., JavaScript ...

Notes

What you are looking at

A photograph and the same photograph after a filter, split by a line you can drag. The filter runs twice, once in Rust compiled to WebAssembly and once in JavaScript, on identical pixels. The default image is the graphic portrait from the home page: black and cream swirls shaped into a bearded face.

The times in the ledger are a single run, the last one. If the two outputs differ by more than 1 in any channel the ledger says so and shows no speedup. Image sizes are read from the decoded image: uploads are decoded with the long edge capped at 4096 px, then drawn at the Size you choose before the filter runs.

How it works

A convolution replaces each pixel with the weighted sum of the pixels around it. The weights are the kernel grid in the rail: sharpen puts a large positive weight in the middle and negative ones around it, so a pixel that differs from its neighbours is pushed further away. The sum is divided by the divisor, and Normalise sets the divisor so that a flat area keeps its brightness.

Greyscale, sepia, invert, brightness and contrast are one 4x5 colour matrix applied to each pixel. Edges runs the Sobel kernel and its transpose and keeps the magnitude. Blur is a Gaussian applied along rows and then along columns. At the edges of the image both engines repeat the nearest pixel, round with floor(x + 0.5) and never change alpha.

The Rust time includes copying the pixels into WebAssembly memory and back out, because that copy is part of what calling Rust from JavaScript costs.

Size sweep

The sweep runs the current filter on square images of 256, 512, 1024, 2048 and 4096 px with the shared benchmark harness, in a worker of its own, and plots the median time against megapixels on log axes. It runs only when you ask, and it reports whatever it measures.

Next Cryptography