Storing Hashes
A hash is meant to live next to the data it describes: in a database column, a CMS field, a JSON response or a build manifest. Compute it once when the image is added, and every reader gets a placeholder without downloading or decoding the image.
Text or bytes
A hash has two interchangeable forms: the raw bytes and the base64url string. They convert in both directions without loss, and every function that reads a hash accepts either.
- Text —
VARCHAR(40)fits any hash with the default budget (38 characters) and up to 48 bytes needsVARCHAR(64). It is easy to read, to log, to put in JSON and to send in an HTML attribute. - Bytes —
BYTEAorBLOBof 28 bytes is about 25% smaller than the text form. Use it when the column is large or the hash goes through a binary protocol.
import { encode, toBase64Url } from 'hazehash/encode'
import { toBytes } from 'hazehash'
const bytes = encode(image) // Uint8Array, store in a binary column
const text = toBase64Url(bytes) // or keep the text form
const back = toBytes(text) // the same bytes againIn Node.js, Buffer.from(bytes).toString('base64url') produces the same string as toBase64Url().
A hash is not a fixed size
The budget is a ceiling. A flat or smoothly shaded image needs fewer bytes than a detailed one, and an image with transparency adds two bytes of header, so hashes of different images have different lengths even with the same options. Size the column for the largest hash you allow (the budget), not for the length of one example.
Keeping the size next to the hash
The aspect ratio inside a hash has a resolution of about 9%, which is enough to draw a placeholder but not to lay a page out. Store the real width and height of the image next to the hash, and use them for the width and height of the box. encodeFileDetailed() returns both in one call:
const { hash, width, height } = await encodeFileDetailed(path)
await db.images.insert({ path, hash, width, height })Stability
- Deterministic. The same pixels and options always give the same bytes, so recomputing a hash gives the string you already have, and a hash can be used as a cache key or compared.
- Unchanging decoding. Version 1 decoding does not change between releases: a stored hash decodes to the same pixels in every version. The format has room for new versions, and a decoder rejects a version it does not know with
UnsupportedVersion; see Hash Format. - Truncation. A hash cut short is still valid and decodes with less detail, because the least important coefficients are at the end. Do not rely on this to save space (use a smaller budget instead), but it means a clipped value in a legacy column degrades gracefully instead of failing.
Changing the budget later
A hash made with one budget stays valid after you change the default: old and new hashes decode with the same function. To give old images the better quality of a larger budget, encode them again from the source images, in a script or with hazehash encode.