What a blur placeholder is for
An image that has not loaded yet leaves a hole. Fill it with nothing and the text below jumps when the picture arrives; fill it with a grey box and the page looks broken rather than loading. A blur placeholder fills it with the picture's own colours, in roughly the right places, so the layout is settled before a single byte of the real file has arrived.
LEHV6nWB2yk8pyo0adR*.7kCMdnj — twenty-eight characters holding the low frequencies of a photograph, and nothing else
That is short enough to sit in a database column beside the image's URL, to travel in the same JSON response as the rest of the record, and to be rendered from server-side markup with no extra request — which is the whole trick, because a placeholder that needs its own request arrives after the thing it was standing in for. This tool does both directions: give it a picture and it produces the hash, the base64 blurDataURL that next/image wants, and the snippet to use either one; give it a hash somebody else made and it paints it back.
How the encoding works
The picture is decoded to pixels, converted from sRGB into linear light, and projected onto a small grid of cosine basis functions — the same transform JPEG uses, stopped early. Each basis function has a coefficient per colour channel: how much of that particular wave the picture contains.
Only the first few coefficients are kept. Four across and three down means twelve of them, and everything finer than that is thrown away — which is exactly the point, because what is left is the blur.
Those coefficients are then quantised hard and written in base83. The alphabet is 83 characters chosen because every one of them survives being written inside a JSON string, an HTML attribute and a URL query with no escaping. That is why a hash can be pasted anywhere without a second thought.
The average colour gets four characters of its own and full precision, because it is the one value the eye will notice being wrong. Every other coefficient gets two characters, expressed as a fraction of a scale that is itself stored in the hash.
Working in linear light rather than in sRGB matters more than it sounds. Averaging gamma-encoded values darkens the result — a blur of a bright picture comes out muddy — so the conversion happens on the way in and again on the way out.
Every control on this page
Controls that cannot affect what you are doing are disabled rather than left live and quietly ignored, and the hint underneath each one says why it went grey.
| Control | What it does | Reach for it when |
|---|---|---|
| Start from | Switches between making a hash from a picture and painting a hash you already have. | Always — it decides which of the two inputs below is shown. |
| Image | Decodes a picture in this tab and keeps it, so changing a setting costs a transform rather than a re-read. | You have the picture and want the string. |
| BlurHash string | Takes a hash and paints it. Waits for you to stop typing before redrawing. | You have a string in a database and want to know what it looks like. |
| Detail across | How many cosine terms are kept left to right, 1 to 9. Each one adds two characters. Fitted to the picture until you set it yourself. | The fit is not what you want — you are matching an existing hash's shape, or trading detail for a shorter string. |
| Detail down | The same, top to bottom. | Same reason, the other way. Keep the pair near the picture's proportions. |
| Shape | Chooses the proportions the preview is painted at. Live only when decoding. | You are looking at a bare hash and know what shape the real picture is. |
| Placeholder size | Sets the longest edge of the PNG inside the data URI, from 16 to 64 pixels. | The data URI is longer than you want, or the blur looks blocky rather than soft. |
| Punch | Multiplies every coefficient except the average, pulling the colours further apart. | The placeholder looks washed out next to the real picture. |
| Snippet tabs | Switches between the next/image, react-blurhash and plain CSS forms of the same result. | You know where this is going and want it in the right shape. |
Two controls change meaning with the mode. The detail counts are yours to set when encoding and are read out of the hash when decoding, because the first character of every hash records them. The shape is the opposite: the picture you dropped in already has proportions, so the control only opens up when there is no picture to ask.
Punch is the one setting that does not touch the hash. It is applied while painting, which means it is also a decision your reader's browser makes rather than one you bake in — react-blurhash takes it as a prop, and the PNG this page writes has it baked in because a PNG has no coefficients left to scale. Change punch and the hash above it stays exactly the same string.
Choosing how much detail to keep
The two detail numbers are the only real quality knob, and the trade is straightforward: each extra component costs two characters and buys one more cosine term in that direction. Drop a picture in and both are chosen for you from its proportions; change either one and the pair stays where you put it.
That fit matters more than it sounds, and it is why the old flat default of four by three is not used here. A 16:9 photograph given a four-by-three grid spends a third of its coefficients on vertical detail that is not there and starves the direction that is; the same picture at seven by four resolves three separate rock formations where the smaller grid showed one red band. The pairs are matched on the ratio of the two numbers, so a portrait gets the mirror image of what its landscape rotation would.
Nine by nine is the format's ceiling — the size flag is a single base83 character holding both numbers, so a tenth column has nowhere to go — and at 166 characters it is well past the point of usefulness. If you find yourself wanting that much detail, what you want is a real thumbnail, not a hash.
Detail beyond about six by six also stops being visible once the placeholder is blurred by the browser scaling it up, which it always is. The picture is 32 pixels on its longest edge and it is being drawn across a card.
A hash has no shape
This surprises people, and it is worth being explicit about: a BlurHash records coefficients and nothing else. No width, no height, no aspect ratio. The same string is a perfectly good 16:9 banner and a perfectly good square avatar, and it will paint happily at either.
That is why this page asks you for a shape when you paste a hash, and why it does not when you drop in a picture. It also means the shape is your application's problem at render time: store the real image's dimensions alongside the hash, or the placeholder will be the right colours in the wrong box and the layout will still shift.
The same freedom is what makes the size control cheap, and it is why the preview here is not the PNG. A hash is a continuous function sampled onto a grid, so painting it at 384 pixels rather than 32 costs nothing but arithmetic — there is no source resolution to run out of. Stretching the 32-pixel PNG across a card instead would show you bilinear interpolation between 32 samples rather than the curve they came from, which hides most of what raising the detail bought you. The PNG stays small because it is inlined into your HTML; the preview does not have to be.
Using the result
For next/image, take the blurDataURL. Next's blur placeholder reads a data URI and has no idea what BlurHash is, which is why this tool writes a PNG as well as a string. Store the hash if you want to re-render at other sizes later; ship the data URI if you just want it working.
For react-blurhash, take the hash. It decodes in the reader's browser onto a canvas, so nothing is inlined into your HTML — cheaper markup, slightly later paint, and punch becomes a prop you can change without regenerating anything.
For anything else, the CSS form is a background image and two lines. It works in an email template, a static site generator, a server-rendered template with no JavaScript at all.
Whichever you pick, generate at upload time and store the result, not on every request. The transform is fast but it is not free, and the answer never changes for a given picture.
BlurHash, ThumbHash and a tiny JPEG
The oldest approach is a genuinely small JPEG — twenty pixels wide, quality 20, inlined as a data URI. It is simple, it needs no library, and it costs a few hundred bytes of markup per image. Its weakness is that it is still an image file, with a header and a decoder, and it cannot be stored as a short string in a text column.
ThumbHash is the closer relative: the same idea with a better transform, roughly the same length, and it carries the aspect ratio and an alpha channel, both of which BlurHash lacks. If you are starting fresh and nothing in your stack expects BlurHash, it is worth a look.
BlurHash's advantage is that it arrived first and is everywhere — Wolt's original, react-blurhash, plus ports in Swift, Kotlin, Python, Rust, PHP and Go. A hash written by this page is read by all of them, which is the reason this implementation is checked against the reference one rather than only against itself.
Limits, and why they are where they are
Twenty-five megabytes per file and a forty-megapixel ceiling. Both are about memory in one browser tab rather than bandwidth, because nothing here is uploaded — a decoded picture costs four bytes per pixel whatever the file size.
Before the transform runs, the picture is scaled down so its longest edge is 128 pixels. Encoding costs width × height × components, so a camera original at nine by nine components would be hundreds of millions of operations and a frozen tab. Nothing survives the downscale that could have survived the transform: at most 81 cosine coefficients come out the other end.
The hash also loses everything that was not colour. No EXIF, no colour profile, no transparency — BlurHash has no alpha channel, so a picture with a transparent background is encoded as though the transparent parts were black.
Privacy
No picture you drop here is uploaded, and no server is involved. The image is decoded by the browser, the transform is arithmetic in this tab, and the PNG behind the data URI is written by a WebAssembly build of OxiPNG fetched once from this site.
Nothing is stored either. Close the page and the picture, the hash and the data URI are gone with it — so copy what you want before you leave.
