diff --git a/pom.xml b/pom.xml
index e314922..ce5abd5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,11 @@
thumbnailator
[0.4, 0.5)
+
+ net.sourceforge.jtransforms
+ jtransforms
+ 2.4.0
+
diff --git a/src/main/resources/default.properties b/src/main/resources/default.properties
index 1888b7c..341e43b 100644
--- a/src/main/resources/default.properties
+++ b/src/main/resources/default.properties
@@ -24,7 +24,7 @@ image.dhash.tolerence=8
#set to false if hashing images is taking too long
image.phash.use=true
image.phash.weight=1.0
-image.phash.precision=16
+image.phash.precision=32
image.phash.tolerence=8
#Default Thumbnail Settings
diff --git a/src/main/scala/com/sothr/imagetools/hash/PHash.scala b/src/main/scala/com/sothr/imagetools/hash/PHash.scala
index b00435c..7a697d2 100644
--- a/src/main/scala/com/sothr/imagetools/hash/PHash.scala
+++ b/src/main/scala/com/sothr/imagetools/hash/PHash.scala
@@ -1,10 +1,63 @@
package com.sothr.imagetools.hash
+import edu.emory.mathcs.jtransforms.dct.FloatDCT_2D
+import grizzled.slf4j.Logging
+
/**
* Created by dev on 1/22/14.
*/
-object PHash extends PerceptualHasher {
+object PHash extends PerceptualHasher with Logging {
def getHash(imageData: Array[Array[Int]]): Long = {
- return 0L
+ //convert the imageData into a FloatArray
+ val width = imageData.length
+ val height = imageData(0).length
+
+ val imageDataFloat:Array[Array[Float]] = Array.ofDim[Float](height, width)
+ for (row <- 0 until height) {
+ for (col <- 0 until width) {
+ imageDataFloat(row)(col) = imageData(row)(col).toFloat
+ }
+ }
+
+ //perform transform on the data
+ val dct:FloatDCT_2D = new FloatDCT_2D(height,width)
+ dct.forward(imageDataFloat, true)
+
+ //extract the DCT data
+ val dctDataWidth:Int = width / 4
+ val dctDataHeight:Int = height / 4
+
+ //calculate the mean
+ var total = 0.0f
+ for (row <- 0 until dctDataHeight) {
+ for (col <- 0 until dctDataWidth) {
+ total += imageDataFloat(row)(col)
+ }
+ }
+ val mean = total / (dctDataHeight * dctDataWidth)
+
+ //calculate the hash
+ var hash = 0L
+ for (row <- 0 until dctDataHeight by 2) {
+ //process each column
+ for (col <- 0 until dctDataWidth by 1) {
+ hash <<= 1
+ val pixel = imageDataFloat(row)(col)
+ //If the current pixel is at or above the mean, store it as a one, else store it as a zero
+ if (pixel >= mean) hash |= 1 else hash |= 0
+ }
+
+ if ((row +1) < dctDataWidth) {
+ val nextRow = row + 1
+ //process each column
+ for (col <- (dctDataWidth - 1) to 0 by -1) {
+ hash <<= 1
+ val pixel = imageDataFloat(nextRow)(col)
+ if (pixel >= mean) hash |= 1 else hash |= 0
+ }
+ }
+ }
+ debug(s"Computed PHash: $hash from ${dctDataWidth * dctDataHeight} pixels")
+ hash
}
}
diff --git a/src/test/resources/default.properties b/src/test/resources/default.properties
index ffa4755..0752b0b 100644
--- a/src/test/resources/default.properties
+++ b/src/test/resources/default.properties
@@ -24,7 +24,7 @@ image.dhash.tolerence=8
#set to false if hashing images is taking too long
image.phash.use=true
image.phash.weight=1.0
-image.phash.precision=16
+image.phash.precision=32
image.phash.tolerence=8
#Default Thumbnail Settings
diff --git a/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala b/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala
index 5581ac5..efc6f8a 100644
--- a/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala
+++ b/src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala
@@ -101,4 +101,158 @@ class HashServiceTest extends BaseTest {
assert(HashService.areDhashSimilar(mediumHash,smallHash))
}
+ def ahashTestCase(filePath:String):Long = {
+ val sample = new File(filePath)
+ val image = ImageIO.read(sample)
+ HashService.getAhash(image)
+ }
+
+ test("Benchmark AHash") {
+ info("Benchmarking AHash")
+ info("AHash Large Image 3684x2736")
+ val largeTime1 = getTime { ahashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime2 = getTime { ahashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime3 = getTime { ahashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime4 = getTime { ahashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime5 = getTime { ahashTestCase(TestParams.LargeSampleImage1) }
+ val largeMean = getMean(largeTime1, largeTime2, largeTime3, largeTime4, largeTime5)
+ info(s"The mean time of 5 tests for large was: $largeMean ms")
+ info("AHash Medium Image 1824x1368")
+ val mediumTime1 = getTime { ahashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime2 = getTime { ahashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime3 = getTime { ahashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime4 = getTime { ahashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime5 = getTime { ahashTestCase(TestParams.MediumSampleImage1) }
+ val mediumMean = getMean(mediumTime1, mediumTime2, mediumTime3, mediumTime4, mediumTime5)
+ info(s"The mean time of 5 tests for medium was: $mediumMean ms")
+ info("AHash Small Image 912x684")
+ val smallTime1 = getTime { ahashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime2 = getTime { ahashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime3 = getTime { ahashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime4 = getTime { ahashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime5 = getTime { ahashTestCase(TestParams.SmallSampleImage1) }
+ val smallMean = getMean(smallTime1, smallTime2, smallTime3, smallTime4, smallTime5)
+ info(s"The mean time of 5 tests for small was: $smallMean ms")
+ assert(true)
+ }
+
+ test("Calculate AHash Large Sample Image 1") {
+ debug("Starting 'Calculate AHash Large Sample Image 1' test")
+ val sample = new File(TestParams.LargeSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getAhash(image)
+ debug(s"Testing that $hash = 36070299219713907L")
+ assert(hash == 36070299219713907L)
+ }
+
+ test("Calculate AHash Medium Sample Image 1") {
+ debug("Starting 'Calculate AHash Medium Sample Image 1' test")
+ val sample = new File(TestParams.MediumSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getAhash(image)
+ debug(s"Testing that $hash = 36070299219713907L")
+ assert(hash == 36070299219713907L)
+ }
+
+ test("Calculate AHash Small Sample Image 1") {
+ debug("Starting 'Calculate AHash Small Sample Image 1' test")
+ val sample = new File(TestParams.SmallSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getAhash(image)
+ debug(s"Testing that $hash = 36070299219713907L")
+ assert(hash == 36070299219713907L)
+ }
+
+ test("AHash Of Large, Medium, And Small Sample 1 Must Be Similar") {
+ val largeHash = ahashTestCase(TestParams.LargeSampleImage1)
+ val mediumHash = ahashTestCase(TestParams.MediumSampleImage1)
+ val smallHash = ahashTestCase(TestParams.SmallSampleImage1)
+ assert(HashService.areAhashSimilar(largeHash,mediumHash))
+ assert(HashService.areAhashSimilar(largeHash,smallHash))
+ assert(HashService.areAhashSimilar(mediumHash,smallHash))
+ }
+
+ def phashTestCase(filePath:String):Long = {
+ val sample = new File(filePath)
+ val image = ImageIO.read(sample)
+ HashService.getPhash(image)
+ }
+
+ test("Benchmark PHash") {
+ info("Benchmarking PHash")
+ info("PHash Large Image 3684x2736")
+ val largeTime1 = getTime { phashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime2 = getTime { phashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime3 = getTime { phashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime4 = getTime { phashTestCase(TestParams.LargeSampleImage1) }
+ val largeTime5 = getTime { phashTestCase(TestParams.LargeSampleImage1) }
+ val largeMean = getMean(largeTime1, largeTime2, largeTime3, largeTime4, largeTime5)
+ info(s"The mean time of 5 tests for large was: $largeMean ms")
+ info("PHash Medium Image 1824x1368")
+ val mediumTime1 = getTime { phashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime2 = getTime { phashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime3 = getTime { phashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime4 = getTime { phashTestCase(TestParams.MediumSampleImage1) }
+ val mediumTime5 = getTime { phashTestCase(TestParams.MediumSampleImage1) }
+ val mediumMean = getMean(mediumTime1, mediumTime2, mediumTime3, mediumTime4, mediumTime5)
+ info(s"The mean time of 5 tests for medium was: $mediumMean ms")
+ info("PHash Small Image 912x684")
+ val smallTime1 = getTime { phashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime2 = getTime { phashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime3 = getTime { phashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime4 = getTime { phashTestCase(TestParams.SmallSampleImage1) }
+ val smallTime5 = getTime { phashTestCase(TestParams.SmallSampleImage1) }
+ val smallMean = getMean(smallTime1, smallTime2, smallTime3, smallTime4, smallTime5)
+ info(s"The mean time of 5 tests for small was: $smallMean ms")
+ assert(true)
+ }
+
+ test("Calculate PHash Large Sample Image 1") {
+ debug("Starting 'Calculate PHash Large Sample Image 1' test")
+ val sample = new File(TestParams.LargeSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getPhash(image)
+ debug(s"Testing that $hash = -9154589787976242949L")
+ assert(hash == -9154589787976242949L)
+ }
+
+ test("Calculate PHash Medium Sample Image 1") {
+ debug("Starting 'Calculate PHash Medium Sample Image 1' test")
+ val sample = new File(TestParams.MediumSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getPhash(image)
+ debug(s"Testing that $hash = -9154589787976242949L")
+ assert(hash == -9154589787976242949L)
+ }
+
+ test("Calculate PHash Small Sample Image 1") {
+ debug("Starting 'Calculate PHash Small Sample Image 1' test")
+ val sample = new File(TestParams.SmallSampleImage1)
+ debug(s"Testing File: ${sample.getAbsolutePath} exists: ${sample.exists}")
+ val image = ImageIO.read(sample)
+ debug(s"Image: width: ${image.getWidth} height: ${image.getHeight}")
+ val hash = HashService.getPhash(image)
+ debug(s"Testing that $hash = -9154589787976242949L")
+ assert(hash == -9154589787976242949L)
+ }
+
+ test("PHash Of Large, Medium, And Small Sample 1 Must Be Similar") {
+ val largeHash = phashTestCase(TestParams.LargeSampleImage1)
+ val mediumHash = phashTestCase(TestParams.MediumSampleImage1)
+ val smallHash = phashTestCase(TestParams.SmallSampleImage1)
+ assert(HashService.arePhashSimilar(largeHash,mediumHash))
+ assert(HashService.arePhashSimilar(largeHash,smallHash))
+ assert(HashService.arePhashSimilar(mediumHash,smallHash))
+ }
+
}