Skip to content

Instantly share code, notes, and snippets.

@atwong
Created October 2, 2018 21:23
Show Gist options
  • Save atwong/3da16d729b495f36ad5d09c68611172a to your computer and use it in GitHub Desktop.
Save atwong/3da16d729b495f36ad5d09c68611172a to your computer and use it in GitHub Desktop.
var Jimp = require("Jimp");
/*
* Compute Perceptual Hash of an Image
* Javascript equivalent of https://github.com/bjlittle/imagehash
*
* These hashes differ by ~1-3 Hamming distance from original python implementation
*
*/
// Copied from https://gist.github.com/bellbind/6fee86bd8027b57991f9
function dct2d(mat, m, n) {
// 0.0-1.0 2D matrix
var isqrt2 = 1 / Math.sqrt(2);
var px = Math.PI / m;
var py = Math.PI / n;
var r = [];
for (var ry = 0; ry < n; ry++) {
for (var rx = 0; rx < m; rx++) {
var c = Math.pow(isqrt2, (rx === 0) + (ry === 0));
var t = 0;
for (var y = 0; y < n; y++) {
for (var x = 0; x < m; x++) {
var v = mat[y * m + x];
t += v * Math.cos(px * (x + 0.5) * rx) * Math.cos(py * (y + 0.5) * ry);
}
}
r.push(c * t / 4);
}
}
return r;
};
function phashQ(imgUrl, hashsize = 8, hfreq_fact = 4) {
const LUMASCALE = [0.2989, 0.5870, 0.1140]; //ITU-R 601-2 luma RGB -> greyscale transform
const OCTLEN = 8;
var imgsize = hashsize * hfreq_fact;
var midpoint = Math.floor((hashsize * hashsize) / 2);
var noctets = Math.floor((hashsize * hashsize) / OCTLEN);
return new Promise((resolve, reject) => {
Jimp.read(imgUrl).then(img => {
var pixels = [];
img.resize(imgsize, imgsize).scan(0, 0, imgsize, imgsize, function(x, y, idx) {
pixels.push(
this.bitmap.data[idx] * LUMASCALE[0] +
this.bitmap.data[idx + 1] * LUMASCALE[1] +
this.bitmap.data[idx + 2] * LUMASCALE[2]);
});
var coef = dct2d(pixels, imgsize, imgsize);
var lofreqcoef = coef.filter((v, idx) => (Math.floor(idx / imgsize) < hashsize) && ((idx % imgsize) < hashsize));
var median = lofreqcoef.slice().sort((a, b) => a - b)[midpoint];
var bits = new Uint8Array(noctets);
for (var i=0; i<noctets; i++) {
bits[i] = lofreqcoef.slice(i * OCTLEN, (i + 1) * OCTLEN).
reduce((acc, val, idx) => acc |= ((val > median) ? 01 : 00) << (OCTLEN - idx - 1), 00);
}
resolve(bits.reduce((acc, val) => acc += val.toString(16).padStart(2, "0"), ""));
});
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment