Skip to content

Encoding Files in Node.js ​

The helpers in hazehash/node read an image file or a buffer, decode it with sharp, and encode the pixels. They are the way to hash images on disk without writing the pixel-reading code yourself.

ts
import { encodeFile, encodeFileToString, encodeFileDetailed } from 'hazehash/node'

sharp is an optional peer dependency (0.33 or newer), so install it where you use these helpers:

bash
npm install --save-dev sharp

Without it the helpers throw a PlaceholderError with the code InvalidInput and a message that explains how to install sharp. The core entry points never need it.

The image is converted to sRGB and rotated according to its EXIF orientation before it is encoded, so the hash matches what a browser shows. Every helper takes the same two arguments:

pathOrBuffer ​

string | Uint8Array

A path to an image file, or the bytes of an encoded image (JPEG, PNG, WebP, AVIF, GIF and the other formats sharp reads).

options ​

EncodeOptions · optional

The same options as encode(): budget, profile, alpha, analysisSize and weights.

Getting a string ​

encodeFileToString(pathOrBuffer, options?)

Promise<string>

Resolves to the hash as base64url.

ts
import { encodeFileToString } from 'hazehash/node'

const hash = await encodeFileToString('photo.jpg', { budget: 24 })
// "Ec7UwRWKIv5znmt3XYLUF5zBSYmAo0Wr"

Getting the bytes ​

encodeFile(pathOrBuffer, options?)

Promise<Uint8Array>

Resolves to the raw bytes of the hash, for a binary column.

ts
import { encodeFile } from 'hazehash/node'

const bytes = await encodeFile('photo.jpg')
bytes.length // 28

Getting the size as well ​

encodeFileDetailed(pathOrBuffer, options?)

Promise<EncodedFile>

Resolves to the bytes, the string and the pixel size of the image as displayed, which is its size after the EXIF rotation. It is the function behind the hazehash encode command, and the way to store the real dimensions next to the hash.

ts
import { encodeFileDetailed } from 'hazehash/node'

const { hash, bytes, width, height } = await encodeFileDetailed('photo.jpg')
// hash: "Ed7UwRWKKv5znm6a7sC1tziHDNpMuCikxrYpIg", width: 1280, height: 959

The result has this shape:

ts
interface EncodedFile {
  bytes: Uint8Array // the hash bytes
  hash: string // the hash as base64url
  width: number // pixel size as displayed
  height: number
}

Hashing a folder ​

The helpers hash one image per call. For a folder, loop over the files; the encoder itself takes a few milliseconds, so the time goes into decoding the image:

ts
import { readdir, writeFile } from 'node:fs/promises'
import { extname, join } from 'node:path'
import { encodeFileToString } from 'hazehash/node'

const dir = './images'
const hashes: Record<string, string> = {}

for (const name of await readdir(dir)) {
  if (['.jpg', '.jpeg', '.png', '.webp'].includes(extname(name).toLowerCase())) {
    hashes[name] = await encodeFileToString(join(dir, name), { budget: 24 })
  }
}

await writeFile('hashes.json', JSON.stringify(hashes, null, 2))

For a one-off job the command does the same without code: npx hazehash encode ./images -f json -o hashes.json. In Nuxt, the module generates and caches the hashes during the build.