Perceptual hash (pHash) implementation that runs in Node and the browser with zero dependencies. Written in TypeScript with full type definitions.
- Zero dependencies - Pure TypeScript/JavaScript implementation
- Type-safe - Full TypeScript support with comprehensive type definitions
- Universal - Works in Node.js and browsers
- Multiple formats - ESM and CommonJS builds
- Well-documented - Complete JSDoc documentation for all functions
- Fast - DCT-based perceptual hashing algorithm
Try the interactive examples - Upload images and see perceptual hashing in action!
npm install @stabilityprotocol.com/phashimport { fromFile, hammingDistance } from "@stabilityprotocol.com/phash";
// Get hashes from file inputs
const hash1 = await fromFile(fileInput1.files[0]);
const hash2 = await fromFile(fileInput2.files[0]);
// Compare similarity (0 = identical, 64 = completely different)
const distance = hammingDistance(hash1, hash2);
const similarity = 100 - (distance / 64) * 100;
console.log(`Similarity: ${similarity.toFixed(1)}%`);
// > Similarity: 95.3%import { fromImage } from "@stabilityprotocol.com/phash";
const img = document.querySelector("img");
const hash = fromImage(img);
console.log(hash);
// > "a4f3c2d1e5b67890"import { fromRgba } from "@stabilityprotocol.com/phash";
import sharp from "sharp"; // or any image library
// Load image and get raw RGBA pixels
const { data, info } = await sharp("image.jpg")
.raw()
.ensureAlpha()
.toBuffer({ resolveWithObject: true });
const hash = fromRgba(data, info.width, info.height);
console.log(hash);
// > "a4f3c2d1e5b67890"<script type="module">
import { fromFile } from "https://unpkg.com/@stabilityprotocol.com/phash@latest/dist/index.js";
const input = document.querySelector('input[type="file"]');
input.addEventListener("change", async (e) => {
const hash = await fromFile(e.target.files[0]);
console.log("Hash:", hash);
});
</script>| Function | Description | Environment |
|---|---|---|
fromRgba(rgba, width, height, options?) |
Hash from raw RGBA pixel buffer | Node + Browser |
fromImageData(imageData, options?) |
Hash from ImageData object | Node + Browser |
fromImage(image, options?) |
Hash from HTMLImageElement | Browser |
fromFile(file, options?) |
Hash from File object (async) | Browser |
phash(input, width?, height?, options?) |
Universal hash function | Node + Browser |
| Function | Description |
|---|---|
hammingDistance(hash1, hash2) |
Count differing bits between two hashes (0-64) |
interface PhashOptions {
hashSize?: number; // Hash dimensions (default: 8 = 64-bit hash)
sampleSize?: number; // Resize before DCT (default: 64)
createCanvas?: (w: number, h: number) => HTMLCanvasElement | OffscreenCanvas;
}| Distance | Similarity | Interpretation |
|---|---|---|
| 0-5 | 92-100% | Nearly identical (resizes, minor edits) |
| 6-10 | 84-91% | Very similar (crops, color adjustments) |
| 11-20 | 69-83% | Somewhat similar |
| 21+ | <69% | Different images |
import { fromFile, hammingDistance } from "@stabilityprotocol.com/phash";
async function findDuplicates(files: File[], threshold = 10) {
// Hash all images
const hashes = await Promise.all(
files.map(async (file) => ({
name: file.name,
hash: await fromFile(file),
}))
);
// Find duplicates
const duplicates: { a: string; b: string; distance: number }[] = [];
for (let i = 0; i < hashes.length; i++) {
for (let j = i + 1; j < hashes.length; j++) {
const distance = hammingDistance(hashes[i].hash, hashes[j].hash);
if (distance <= threshold) {
duplicates.push({
a: hashes[i].name,
b: hashes[j].name,
distance,
});
}
}
}
return duplicates;
}The perceptual hash algorithm:
- Resize - Scale image to a square (default 64x64) using bilinear interpolation
- Grayscale - Convert to grayscale using luminosity method (ITU-R BT.601)
- DCT - Apply 2D Discrete Cosine Transform to extract frequency components
- Reduce - Extract top-left DCT coefficients (default 8x8) containing low-frequency data
- Threshold - Compute median of DCT values (excluding DC component)
- Hash - Create binary hash where each bit = 1 if value > median, else 0
- Encode - Convert binary to hexadecimal string
Similar images produce similar hashes because they share low-frequency visual patterns.
# Install dependencies
npm install
# Build the library
npm run build
# Run tests
npm test
# Lint code
npm run lint
# Serve examples locally
npx serve examplesThis project uses semantic-release for automated versioning and publishing.
| Type | Version Bump | Example |
|---|---|---|
fix: |
Patch (0.0.X) | fix: handle edge case in DCT |
feat: |
Minor (0.X.0) | feat: add WebP support |
feat!: |
Major (X.0.0) | feat!: change API signature |
MIT