Skip to content

stabilityprotocol/monolith-phash-algo

Repository files navigation

@stabilityprotocol.com/phash

Perceptual hash (pHash) implementation that runs in Node and the browser with zero dependencies. Written in TypeScript with full type definitions.

Features

  • 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

Live Demo

Try the interactive examples - Upload images and see perceptual hashing in action!

Install

npm install @stabilityprotocol.com/phash

Quick Start

Browser - Compare Two Images

import { 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%

Browser - Hash from Image Element

import { fromImage } from "@stabilityprotocol.com/phash";

const img = document.querySelector("img");
const hash = fromImage(img);

console.log(hash);
// > "a4f3c2d1e5b67890"

Node.js - Hash from RGBA Buffer

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"

CDN Usage (No Build Step)

<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>

API Reference

Hashing Functions

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

Comparison Functions

Function Description
hammingDistance(hash1, hash2) Count differing bits between two hashes (0-64)

Options

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;
}

Understanding Results

Hamming Distance Guide

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

Example: Find Duplicates in a Collection

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;
}

How It Works

The perceptual hash algorithm:

  1. Resize - Scale image to a square (default 64x64) using bilinear interpolation
  2. Grayscale - Convert to grayscale using luminosity method (ITU-R BT.601)
  3. DCT - Apply 2D Discrete Cosine Transform to extract frequency components
  4. Reduce - Extract top-left DCT coefficients (default 8x8) containing low-frequency data
  5. Threshold - Compute median of DCT values (excluding DC component)
  6. Hash - Create binary hash where each bit = 1 if value > median, else 0
  7. Encode - Convert binary to hexadecimal string

Similar images produce similar hashes because they share low-frequency visual patterns.

Development

# Install dependencies
npm install

# Build the library
npm run build

# Run tests
npm test

# Lint code
npm run lint

# Serve examples locally
npx serve examples

Release Process

This project uses semantic-release for automated versioning and publishing.

Commit Message Format

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

License

MIT

About

Perceptual hash (pHash) implementation that runs in Node and the browser with no dependencies.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors