167 lines
5.4 KiB
JavaScript
167 lines
5.4 KiB
JavaScript
/*
|
|
|
|
Copyright (c) 2011 Elliot Shepherd
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
// https://code.google.com/p/ironchef-team21/source/browse/ironchef_team21/src/ImagePHash.java
|
|
|
|
/*
|
|
* pHash-like image hash.
|
|
* Author: Elliot Shepherd (elliot@jarofworms.com
|
|
* Based On: http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html
|
|
*/
|
|
|
|
function ImagePHash(size, smallerSize) {
|
|
this.size = this.size || size;
|
|
this.smallerSize = this.smallerSize || smallerSize;
|
|
initCoefficients(this.size);
|
|
}
|
|
|
|
ImagePHash.prototype.size = 32;
|
|
ImagePHash.prototype.smallerSize = 8;
|
|
|
|
ImagePHash.prototype.distance = function(s1, s2) {
|
|
var counter = 0;
|
|
for (var k = 0; k < s1.length; k++) {
|
|
if (s1[k] != s2[k]) {
|
|
counter++;
|
|
}
|
|
}
|
|
return (counter / s1.length);
|
|
}
|
|
|
|
// Returns a 'binary string' (like. 001010111011100010) which is easy to do a hamming distance on.
|
|
ImagePHash.prototype.getHash = function(img) {
|
|
/* 1. Reduce size.
|
|
* Like Average Hash, pHash starts with a small image.
|
|
* However, the image is larger than 8x8; 32x32 is a good size.
|
|
* This is really done to simplify the DCT computation and not
|
|
* because it is needed to reduce the high frequencies.
|
|
*/
|
|
img = img.clone().resize(this.size, this.size);
|
|
|
|
/* 2. Reduce color.
|
|
* The image is reduced to a grayscale just to further simplify
|
|
* the number of computations.
|
|
*/
|
|
img.grayscale();
|
|
|
|
var vals = [];
|
|
|
|
for (var x = 0; x < img.bitmap.width; x++) {
|
|
vals[x] = [];
|
|
for (var y = 0; y < img.bitmap.height; y++) {
|
|
vals[x][y] = intToRGBA(img.getPixelColor(x, y)).b;
|
|
}
|
|
}
|
|
|
|
/* 3. Compute the DCT.
|
|
* The DCT separates the image into a collection of frequencies
|
|
* and scalars. While JPEG uses an 8x8 DCT, this algorithm uses
|
|
* a 32x32 DCT.
|
|
*/
|
|
var dctVals = applyDCT(vals, this.size);
|
|
|
|
/* 4. Reduce the DCT.
|
|
* This is the magic step. While the DCT is 32x32, just keep the
|
|
* top-left 8x8. Those represent the lowest frequencies in the
|
|
* picture.
|
|
*/
|
|
/* 5. Compute the average value.
|
|
* Like the Average Hash, compute the mean DCT value (using only
|
|
* the 8x8 DCT low-frequency values and excluding the first term
|
|
* since the DC coefficient can be significantly different from
|
|
* the other values and will throw off the average).
|
|
*/
|
|
var total = 0;
|
|
|
|
for (var x = 0; x < this.smallerSize; x++) {
|
|
for (var y = 0; y < this.smallerSize; y++) {
|
|
total += dctVals[x][y];
|
|
}
|
|
}
|
|
|
|
var avg = total / (this.smallerSize * this.smallerSize);
|
|
|
|
/* 6. Further reduce the DCT.
|
|
* This is the magic step. Set the 64 hash bits to 0 or 1
|
|
* depending on whether each of the 64 DCT values is above or
|
|
* below the average value. The result doesn't tell us the
|
|
* actual low frequencies; it just tells us the very-rough
|
|
* relative scale of the frequencies to the mean. The result
|
|
* will not vary as long as the overall structure of the image
|
|
* remains the same; this can survive gamma and color histogram
|
|
* adjustments without a problem.
|
|
*/
|
|
var hash = "";
|
|
|
|
var count = 0;
|
|
for (var x = 0; x < this.smallerSize; x++) {
|
|
for (var y = 0; y < this.smallerSize; y++) {
|
|
hash += (dctVals[x][y] > avg?"1":"0");
|
|
}
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
// DCT function stolen from http://stackoverflow.com/questions/4240490/problems-with-dct-and-idct-algorithm-in-java
|
|
|
|
function intToRGBA(i){
|
|
var rgba = {}
|
|
rgba.r = Math.floor(i / Math.pow(256, 3));
|
|
rgba.g = Math.floor((i - (rgba.r * Math.pow(256, 3))) / Math.pow(256, 2));
|
|
rgba.b = Math.floor((i - (rgba.r * Math.pow(256, 3)) - (rgba.g * Math.pow(256, 2))) / Math.pow(256, 1));
|
|
rgba.a = Math.floor((i - (rgba.r * Math.pow(256, 3)) - (rgba.g * Math.pow(256, 2)) - (rgba.b * Math.pow(256, 1))) / Math.pow(256, 0));
|
|
|
|
return rgba;
|
|
}
|
|
|
|
var c = [];
|
|
function initCoefficients(size) {
|
|
for (var i=1;i<size;i++) {
|
|
c[i]=1;
|
|
}
|
|
c[0]=1/Math.sqrt(2.0);
|
|
}
|
|
|
|
function applyDCT(f, size) {
|
|
var N = size;
|
|
|
|
var F = [];
|
|
for (var u=0;u<N;u++) {
|
|
F[u] = [];
|
|
for (var v=0;v<N;v++) {
|
|
var sum = 0;
|
|
for (var i=0;i<N;i++) {
|
|
for (var j=0;j<N;j++) {
|
|
sum+=Math.cos(((2*i+1)/(2.0*N))*u*Math.PI)*Math.cos(((2*j+1)/(2.0*N))*v*Math.PI)*(f[i][j]);
|
|
}
|
|
}
|
|
sum*=((c[u]*c[v])/4);
|
|
F[u][v] = sum;
|
|
}
|
|
}
|
|
return F;
|
|
}
|
|
|
|
module.exports = ImagePHash; |