11import sharp from "sharp" ;
22import { normalize , resolve } from "@std/path" ;
33
4+ /**
5+ * Options for {@link dhash}.
6+ */
47export type DHashOptions = {
58 /**
69 * Flip the bit convention for left/right comparisons.
@@ -13,11 +16,29 @@ export type DHashOptions = {
1316
1417const MASK_64 = ( 1n << 64n ) - 1n ;
1518
19+ /**
20+ * Invert a 64-bit dHash (bitwise NOT), preserving leading zeroes.
21+ *
22+ * This is useful when matching the opposite bit convention used by other
23+ * implementations.
24+ */
1625export const invertHash = ( hash : string ) : string => {
1726 const v = BigInt ( "0x" + hash ) ;
1827 return ( ( ~ v ) & MASK_64 ) . toString ( 16 ) . padStart ( 16 , "0" ) ;
1928} ;
2029
30+ /**
31+ * Compute the 64-bit difference hash (dHash) for an image.
32+ *
33+ * Algorithm: grayscale -> resize to 9x8 -> compare adjacent pixels left-to-right
34+ * to produce 64 bits -> return as 16-char hex string.
35+ *
36+ * Bit convention: by default, bit `1` means the intensity increases left-to-right
37+ * (`left < right`). Use `options.invert` to flip this.
38+ *
39+ * @param pathOrSrc File path (relative to `Deno.cwd()`) or raw image bytes.
40+ * @returns 16-character lowercase hex string.
41+ */
2142export const dhash = async (
2243 pathOrSrc : string | Uint8Array ,
2344 options : DHashOptions = { } ,
@@ -51,6 +72,11 @@ export const dhash = async (
5172 . padStart ( 16 , "0" ) ;
5273} ;
5374
75+ /**
76+ * Compare two hex hashes by computing the Hamming distance.
77+ *
78+ * Lower is more similar, higher is more different.
79+ */
5480export const compare = ( hash1 : string , hash2 : string ) : number => {
5581 if ( hash1 . length !== hash2 . length ) {
5682 throw new Error ( `
@@ -65,6 +91,12 @@ export const compare = (hash1: string, hash2: string): number => {
6591 return xor . toString ( 2 ) . split ( "1" ) . length - 1 ;
6692} ;
6793
94+ /**
95+ * Render a hash as an 8x8 ASCII/Unicode fingerprint.
96+ *
97+ * Always renders 64 bits (leading zeros preserved). Customize the output by
98+ * changing the "off/on" character pair via `chars`.
99+ */
68100export const toAscii = ( hash : string , chars = [ "░░" , "██" ] ) : string => {
69101 // Use BigInt to avoid precision loss; always render 64 bits (8x8).
70102 const bin = BigInt ( "0x" + hash ) . toString ( 2 ) . padStart ( 64 , "0" ) ;
@@ -78,6 +110,9 @@ export const toAscii = (hash: string, chars = ["░░", "██"]): string => {
78110 return out ;
79111} ;
80112
113+ /**
114+ * Convert a hash into an 8x8 PNG (returned as bytes).
115+ */
81116export async function raw ( hash : string ) : Promise < Uint8Array > {
82117 const bin = BigInt ( "0x" + hash ) . toString ( 2 ) . padStart ( 64 , "0" ) ;
83118
@@ -94,6 +129,11 @@ export async function raw(hash: string): Promise<Uint8Array> {
94129 return await image . png ( ) . toBuffer ( ) ;
95130}
96131
132+ /**
133+ * Save an 8x8 PNG fingerprint for a hash to disk.
134+ *
135+ * Note: `.png` is appended to `filePath`.
136+ */
97137export async function save ( hash : string , filePath : string ) : Promise < void > {
98138 const buffer = await raw ( hash ) ;
99139 await Deno . writeFile ( `${ filePath } .png` , buffer ) ;
0 commit comments