Image Hash
Generates hazehash, blurhash and thumbhash for raster images — compact strings a page turns into a blurred preview of the picture while the real image is still loading — and, if asked, a dominant color and a ready-made tiny preview image.
mediatoolz image-hash [paths...] [options]Unlike every other command here, this one is a generator, not a check: it reads images and produces data. It writes nothing to disk unless you name an output (-o, --per-file, --out-dir); without one, the result goes to stdout, ready to pipe or paste, and --dry-run shows it as a table instead.
Real output — one image, blurhash only, straight to stdout:
mediatoolz image-hash imgs/red.jpg -t blurhash{
"imgs/red.jpg": {
"width": 200,
"height": 100,
"blurhash": "L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ"
}
}Every entry carries the image's real width and height (EXIF orientation applied), so a page can reserve the right box before anything loads.
What goes in
[paths...] takes image files, directories, or a mix. Several paths can be separate arguments or one comma-separated argument:
mediatoolz image-hash hero.jpg logo.png
mediatoolz image-hash hero.jpg,logo.png,public/imgA path that really exists with a comma in its name is taken as one path, not split. A path that doesn't exist is reported as a problem; the rest still run.
Besides paths, an argument can be an http:// or https:// URL: the image is downloaded (30 second timeout, 100 MB cap) and keyed by its URL. A URL containing a comma is not split. --files-from <file> adds paths and URLs from a file, one per line (blank lines and lines starting with # are ignored); --files-from - reads the list from stdin, so git ls-files '*.png' | mediatoolz image-hash --files-from - works.
A directory contributes only the images directly inside it. -r/--recursive walks its subdirectories too. --ext sets which extensions are picked up from directories (default: .jpg,.jpeg,.png,.webp,.gif,.avif,.tif,.tiff, also matched in upper case, e.g. .JPG). A file named explicitly is always tried, whatever its extension. node_modules, dist, .git and the rest of the toolbox's usual noise directories are skipped, plus whatever the project's .gitignore and --ignore add.
What is generated
-t/--type takes a list of what to generate, separated by commas or spaces (PowerShell passes a,b as one argument a b, which works too): hazehash, blurhash, thumbhash, color, preview. both (the default) means blurhash,thumbhash and all means everything, hazehash included.
Each image is first scaled down so its longest side is at most --size pixels (default 100, the most thumbhash accepts), with EXIF rotation applied and transparency kept. All the hashes are computed from that same small copy, so a large photo costs about the same as a small one. A hazehash made here can therefore differ a little from hazehash encode of the original file, which looks at the full-size pixels; both rebuild the same kind of preview.
- hazehash is a base64url string such as
Ed7UwRWKKv5znndNd2Ba284jhm2TLgpUMa0UkQ(38 characters for a photo at the default budget). At the same size it is the most accurate of the three, and it keeps the aspect ratio and, only when the image has transparent pixels, the alpha channel.--budget <bytes>is the most one hash may take, from 7 to 48 (default28; 16–48 is the range the format is tuned for). It is a ceiling, not a fixed size: a flat or smooth image needs fewer bytes, and a solid red picture is just the 7-byte header,FGez7WAAAA. See HazeHash for decoding it on a page. - blurhash is a short string like
L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ.--components 4x3sets the horizontal and vertical detail (each 1–9); more components give a finer blur and a longer string. - thumbhash is stored as base64. It keeps the aspect ratio and the alpha channel, which blurhash does not.
- color is the dominant color as
#rrggbb: the most common color of the scaled copy, ignoring transparent pixels. An image with no opaque pixel has no color and the field is left out. - preview is a tiny PNG as a
data:image/png;base64,…URI, decoded from the thumbhash, ready to put straight into asrcor a CSSbackground. It is a few hundred bytes.
mediatoolz image-hash imgs --dry-run -t all --components auto┌───────────────┬─────────┬────────────┬──────────────────────────────┬──────────────────────────────┬─────────┬──────────────────────────────┐
│ File │ Size │ HazeHash │ BlurHash │ ThumbHash │ Color │ Preview │
├───────────────┼─────────┼────────────┼──────────────────────────────┼──────────────────────────────┼─────────┼──────────────────────────────┤
│ imgs/blue.png │ 120×300 │ CuvTNlAAAA │ T704c9gSfQf:fRfQfQfQfQf:fRfQ │ 3xEBAwB4h3d3f3iIAIUHmIg= │ #0077ff │ data:image/png;base64,iVBOR… │
│ imgs/red.jpg │ 200×100 │ FGez7WAAAA │ L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ │ 1fsDBICHh4h3h4d3iHD3iXifiA== │ #fe0000 │ data:image/png;base64,iVBOR… │
└───────────────┴─────────┴────────────┴──────────────────────────────┴──────────────────────────────┴─────────┴──────────────────────────────┘
Hashed 2 images.
(dry run — nothing written)The hazehash budget is a ceiling. The same image, hashed at 16 bytes and at the default 28:
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 16
mediatoolz image-hash imgs/sub/green.webp -t hazehash --budget 28{
"imgs/sub/green.webp": {
"width": 64,
"height": 64,
"hazehash": "EG4WmXAAAGwAxjYZmMAQEQ"
}
}{
"imgs/sub/green.webp": {
"width": 64,
"height": 64,
"hazehash": "EDr2mXAAANjAAGMDNjY"
}
}With 16 bytes the encoder spends all of them (22 characters); with 28 this image needs only 14 (19 characters). A budget smaller than an image needs is not an error, but an image with transparency cannot fit in under 9 bytes, and such an image is reported as a problem.
--components auto picks the blurhash components from the aspect ratio instead of a fixed 4x3: 4x3 for a 16:9 photo, 3x4 for a portrait one, 4x4 for a square. A portrait photo hashed with 4x3 is blurred unevenly; auto avoids that.
Images that are animated (GIF, animated WebP) are hashed from their first frame. CMYK images are converted to sRGB first. A file that is cut off or not an image is reported as a problem instead of being hashed from whatever could be read, and an image larger than the pixel limit (sharp's default, about 268 million pixels) is refused with a message naming --max-pixels, which raises the limit or, as 0, removes it.
Keys in the output
The file path that identifies an image in the output is relative to --cwd. Applications usually look hashes up by the URL an image is served from, so two options reshape the key:
--key-base <dir>makes keys relative to that directory instead: with--key-base public,public/img/hero.jpgbecomesimg/hero.jpg.--key-prefix <text>puts text in front of every key: add--key-prefix /and it becomes/img/hero.jpg.
Both only change the keys; files are still read and written where they are. (In Git Bash on Windows, an argument starting with / is rewritten into a Windows path — set MSYS_NO_PATHCONV=1 or use //.) A URL input keeps its URL as the key.
Where it goes
stdout
With no output option, the formatted result of all images is printed to stdout and nothing is written. Problems go to stderr, so a pipe still receives clean data.
One file
-o <file> writes everything into a single file, creating missing directories:
mediatoolz image-hash imgs -r -t thumbhash -f csv -o hashes.csvfile,width,height,thumbhash
imgs/blue.png,120,300,3xEBAwB4h3d3f3iIAIUHmIg=
imgs/red.jpg,200,100,1fsDBICHh4h3h4d3iHD3iXifiA==
imgs/sub/green.webp,64,64,EXsABwC/iYjfioireJiIiJh3BgIhEFYOOne file per image
--per-file writes a file next to each image; --out-dir <dir> writes them into a directory instead, mirroring the folder structure under the path you gave (and implies --per-file). The name is the full image file name plus a suffix and an extension:
mediatoolz image-hash imgs -r -f plain --per-fileHashed 3 images.
Wrote 6 files:
imgs/blue.png.blurhash.txt
imgs/blue.png.thumbhash.txt
imgs/red.jpg.blurhash.txt
imgs/red.jpg.thumbhash.txt
imgs/sub/green.webp.blurhash.txt
imgs/sub/green.webp.thumbhash.txtWith --format plain each hash gets its own file holding only the hash text, with no newline, so a program can read it as is. With any other format one file per image holds everything requested, and {type} becomes hash when more than one type is included.
--suffix replaces the default .{type}; {type} stands for hazehash, blurhash, thumbhash, or hash. --out-ext replaces the extension, which otherwise follows the format. A custom suffix and extension:
mediatoolz image-hash imgs -r -t blurhash -f plain --out-dir hashes --suffix .bh --out-ext hashWrote 3 files:
hashes/blue.png.bh.hash
hashes/red.jpg.bh.hash
hashes/sub/green.webp.bh.hashExisting output files are overwritten. Two images that would produce the same output file (say, two explicitly named files with the same name from different folders, both going into one --out-dir) are reported as a problem instead of silently clobbering each other.
Preview as a table
--dry-run writes nothing and shows the result as a table instead — handy for a quick look before committing to files, and for copying a single hash:
mediatoolz image-hash imgs -r -t blurhash --dry-run┌─────────────────────┬─────────┬──────────────────────────────┐
│ File │ Size │ BlurHash │
├─────────────────────┼─────────┼──────────────────────────────┤
│ imgs/blue.png │ 120×300 │ L704c9gSfQgSf:fRfQfRfQfQfQfQ │
│ imgs/red.jpg │ 200×100 │ L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ │
│ imgs/sub/green.webp │ 64×64 │ L207Z?hWfjhVhWf*fjfjfQfQfQfQ │
└─────────────────────┴─────────┴──────────────────────────────┘
Hashed 3 images.
(dry run — nothing written)The table has a column per requested hash, the real size, and the file path, which is shortened from the left if the terminal is too narrow. Combined with -o, --per-file or --out-dir, it also lists the files that would be written, and still writes none of them. --format has no effect on the table. With --json, the full report is printed as usual.
Keeping an output file up to date
Re-hashing every image on each run is wasteful, and a hash file nobody remembers to regenerate goes stale. Four options deal with that.
--cache [file] remembers each image's modification time and size, and the settings used, in a JSON file (default .mediatoolz-image-hash-cache.json in --cwd; add it to .gitignore). An image that has not changed is taken from the cache, and sharp is not even loaded when every image is cached. The report says how many came from the cache:
Hashed 3 images, 3 from the cache.A change to the file, to --size or --components, or a newly requested type, recomputes that image. --dry-run and --check never write the cache.
--update merges into the existing -o file instead of replacing it: images hashed now overwrite their entries, and entries for images not mentioned this run are kept. That makes it possible to hash only a new folder into a shared file, and hashes of other types already in the file stay. It works with json, ts, js and csv, on a file this command generated; a file reformatted by Prettier can no longer be read back. --prune, with --update, drops entries whose image no longer exists on disk:
mediatoolz image-hash public/img -r -o hashes.json --update --pruneHashed 2 images.
Wrote 1 file:
out/h.json
Removed 1 entry whose image no longer exists.--check is for CI. It generates everything as usual, compares it with what is on disk, writes nothing and exits 1 when an output file is missing or differs, 0 when everything is current. It needs the output to compare against (-o, --per-file or --out-dir) and cannot be combined with --dry-run or --update.
mediatoolz image-hash public/img -r -o hashes.json --checkHashed 3 images.
1 of 1 output file out of date:
out/h.json — differs from the images
(regenerate them by running the same command without --check)Combine --cache with --check to keep a CI run fast.
Output formats
-f/--format is one of:
json(default) — an object keyed by file path relative to--cwd, each value holdingwidth,heightand the requested hashes. One file per image holds just that object.plain— just the hash text. Aggregated into stdout or-o, one image per line:path, then each hash, tab-separated.csv— a header row, thenfile,width,heightand one column per hash. Fields containing a comma or a quote are quoted.ts— a module you can import:export const imageHashes = { … } as const. One file per image usesexport default.--namechanges the constant name.js— the same withoutas const.
mediatoolz image-hash imgs -r -t blurhash -f ts --name placeholdersexport const placeholders = {
"imgs/blue.png": {
"width": 120,
"height": 300,
"blurhash": "L704c9gSfQgSf:fRfQfRfQfQfQfQ"
},
"imgs/red.jpg": {
"width": 200,
"height": 100,
"blurhash": "L6T9R{,YfQ,Y|cjtfQjtfQfQfQfQ"
},
"imgs/sub/green.webp": {
"width": 64,
"height": 64,
"blurhash": "L207Z?hWfjhVhWf*fjfjfQfQfQfQ"
}
} as constThe file is written in the plain JSON layout; run Prettier over it if your project formats generated code.
The sharp library
Decoding images needs sharp, a native library with prebuilt binaries for the common platforms. It comes with mediatoolz: npm installs the binary for your platform together with the package, so normally there is nothing to do.
The binary can be missing when the package was installed with --omit=optional, or on a platform sharp has no binary for. Then, when image-hash first needs it:
- if
sharpis installed in the project (--cwd), or from an earlier run, it is used as is; - otherwise, in a terminal, the command asks whether to install it into
~/.mediatoolz/depsand, on yes, runsnpm installthere and carries on; on no, it stops without doing anything; - without a terminal (CI, a pipe) it stops with a message unless
-y/--yeswas given, which agrees to the install up front.
The managed copy lives under your home directory, not in any project, so a project's package.json and node_modules are never touched.
Options
[paths...]
Image files and/or directories; several allowed, comma-separated too.
--cwd <path>
Root paths are resolved against, and the base for the file paths in the output. Default: the current directory.
-r, --recursive
Also walk subdirectories of every given directory.
--ext <list>
Comma-separated image extensions to pick up from directories.
--ignore <glob>
Extra ignore pattern (repeatable), on top of the built-in defaults.
--no-respect-gitignore
Don't also honor the project's .gitignore.
--files-from <file>
Read more paths and URLs from a file, one per line; - reads stdin. Blank lines and lines starting with # are ignored.
-t, --type <list>
What to generate, comma-separated: hazehash, blurhash, thumbhash, color, preview. both is blurhash,thumbhash, all is everything. Default: both.
--budget <bytes>
Hazehash only: the most one hash may take, in bytes and header included, from 7 to 48. 16–48 is the range the format is tuned for. Default: 28. A smaller budget makes shorter hashes with less detail; see What is generated. Changing it makes --cache recompute the images.
--components <XxY|auto>
Blurhash components, each side 1–9, or auto to pick them from the aspect ratio. Default: 4x3.
--size <px>
Longest side an image is scaled down to before hashing, 1–100. Default: 100.
--max-pixels <n>
Refuse images with more pixels than this; 0 removes the limit. Default: sharp's limit, about 268 million.
-f, --format <format>
json, plain, csv, ts or js — the format of the generated hashes. Default: json. For the full report (entries, written files, problems) use --json instead.
--name <identifier>
Exported constant name for --format ts/js. Default: imageHashes.
--key-base <dir>
Make the file keys in the output relative to this directory instead of --cwd.
--key-prefix <text>
Put this text in front of every file key, for example /.
-o, --out <file>
Write everything into one file instead of stdout. Cannot be combined with --per-file/--out-dir.
--update
Merge into the existing -o file instead of replacing it. Works with json, ts, js and csv.
--prune
With --update, drop entries whose image no longer exists.
--per-file
Write one file per image, next to the image.
--out-dir <dir>
Write the per-image files into this directory, mirroring the folder structure. Implies --per-file.
--suffix <text>
Per-image file name suffix after the image file name; {type} is hazehash, blurhash, thumbhash or hash. Default: .{type}. With --format plain and several types, it must contain {type}.
--out-ext <ext>
Per-image file extension, with or without the dot. Default by format: .json, .txt (plain), .csv, .ts, .js.
--check
Write nothing; exit 1 if the output files are missing or differ from the images. Needs -o, --per-file or --out-dir.
--cache [file]
Skip images that did not change since the last run, remembered in this file. Default file: .mediatoolz-image-hash-cache.json.
--concurrency <n>
Images processed in parallel. Default: 4.
--dry-run
Write nothing — show the result as a table instead.
-y, --yes
Install the missing sharp library without asking.
Examples:
mediatoolz image-hash public/img # both hashes, JSON on stdout
mediatoolz image-hash a.jpg,b.png,photos -r -t blurhash -f csv -o hashes.csv
mediatoolz image-hash public/img -r -t hazehash --budget 24 -o hashes.json # hazehash of 24 bytes at most
mediatoolz image-hash public/img -r -f plain --per-file # photo.jpg.blurhash.txt + photo.jpg.thumbhash.txt
mediatoolz image-hash public/img -r -f ts -t thumbhash -o src/placeholders.ts --name placeholders
mediatoolz image-hash public/img -r -t all --dry-run # hashes, dominant color and preview as a table
mediatoolz image-hash public/img -r -o hashes.json --cache --update --prune # incremental, kept in sync
mediatoolz image-hash public/img -r -o hashes.json --check # CI: fail if hashes.json is stale
mediatoolz image-hash public/img -r --key-base public --key-prefix / -o src/hashes.json
git ls-files '*.png' | mediatoolz image-hash --files-from - -f csv
mediatoolz image-hash https://example.com/a.jpg -t color
mediatoolz image-hash public/img -r --json # the full report, hashes includedProblems and exit codes
An image that can't be read (a corrupt or truncated file, an unsupported format, too many pixels, a missing path, a failed download) is reported with its path and the reason in plain words, and the rest of the images still run. In a terminal, a counter shows progress while a large set is hashed; it goes to stderr, so it never mixes into the data. The exit code is 0 when everything was hashed (and, with --check, every output is current), 1 when at least one problem was reported or an output is out of date, and 2 for a mistake in the options (a bad --type, --out together with --per-file) or when sharp is missing and could not be installed.
--json prints the full report instead: every entry, the files written, and the problems. image-hash generates data rather than diagnosing a project, so it has no place in a sweep like the full-check of devtoolz.
If the project uses vue-image-kit, its own placeholders command writes the result straight into <VImage> templates and a manifest; image-hash is the framework-agnostic one, for any project and any consumer of the hashes.