Browse Source

Started work on hashing and added a sample image for testing. First new unit test is broken due to array indexing issues. Configured the pom further for testing configuration. Added a few properties to support different levels or precision depending on the hash.

master
Drew Short 11 years ago
parent
commit
24a1b32bad
  1. 3
      .gitignore
  2. 18
      pom.xml
  3. BIN
      src/includes/sample/sample_01_large.jpg
  4. 4
      src/main/resources/default.properties
  5. 10
      src/main/scala/com/sothr/imagetools/hash/AHash.scala
  6. 30
      src/main/scala/com/sothr/imagetools/hash/DHash.scala
  7. 36
      src/main/scala/com/sothr/imagetools/hash/HashService.scala
  8. 10
      src/main/scala/com/sothr/imagetools/hash/PHash.scala
  9. 10
      src/main/scala/com/sothr/imagetools/hash/PerceptualHasher.scala
  10. 81
      src/main/scala/com/sothr/imagetools/image/ImageService.scala
  11. 4
      src/main/scala/com/sothr/imagetools/util/PropertiesEnum.scala
  12. 3
      src/test/java/com/sothr/imagetools/AppTest.java
  13. 25
      src/test/resources/default.properties
  14. 11
      src/test/scala/com/sothr/imagetools/BaseTest.scala
  15. 19
      src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala

3
.gitignore

@ -31,3 +31,6 @@ version.info
# Copied README.md and LICENSE files # Copied README.md and LICENSE files
src/includes/README.md src/includes/README.md
src/includes/LICENSE src/includes/LICENSE
# Image Tools Log Files
*.err

18
pom.xml

@ -101,6 +101,24 @@
</excludes> </excludes>
</resource> </resource>
</resources> </resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
<include>**/*.info</include>
</includes>
</testResource>
<testResource>
<directory>src/test/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.info</exclude>
</excludes>
</testResource>
</testResources>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<plugin> <plugin>

BIN
src/includes/sample/sample_01_large.jpg

After

Width: 3648  |  Height: 2736  |  Size: 5.0 MiB

4
src/main/resources/default.properties

@ -6,13 +6,17 @@ version=${project.version}
#images must be 90% similar #images must be 90% similar
image.differenceThreshold=0.90 image.differenceThreshold=0.90
#control generation of hashes for new images. #control generation of hashes for new images.
image.hash.precision=64
image.ahash.use=true image.ahash.use=true
image.ahash.weight=0.70 image.ahash.weight=0.70
image.ahash.precision=8
image.dhash.use=true image.dhash.use=true
image.dhash.weight=0.85 image.dhash.weight=0.85
image.dhash.precision=8
#set to false if hashing images is taking too long #set to false if hashing images is taking too long
image.phash.use=true image.phash.use=true
image.phash.weight=1.0 image.phash.weight=1.0
image.phash.precision=16
#Default Thumbnail Settings #Default Thumbnail Settings
#Directory where to store thumbnails #Directory where to store thumbnails

10
src/main/scala/com/sothr/imagetools/hash/AHash.scala

@ -0,0 +1,10 @@
package com.sothr.imagetools.hash
/**
* Created by dev on 1/22/14.
*/
object AHash extends PerceptualHasher {
def getHash(imageData: Array[Array[Int]]): Long = {
return 0L
}
}

30
src/main/scala/com/sothr/imagetools/hash/DHash.scala

@ -0,0 +1,30 @@
package com.sothr.imagetools.hash
/**
* Created by dev on 1/22/14.
*/
object DHash extends PerceptualHasher {
def getHash(imageData: Array[Array[Int]]): Long = {
val width = imageData.length
val height = imageData(0).length
var hash = 0L
for (row <- 0 until width) {
//println(f"Row: $row%d")
var previousPixel = imageData(row)(0)
var previousLocation = (row, 0)
//process each column
for (col <- 0 until height) {
//println(f"Column: $col%d")
hash <<= 1
val pixel = imageData(row)(col)
//binary or the current bit based on whether the value
//of the current pixel is greater or equal to the previous pixel
if (pixel >= previousPixel) hash |= 1 else hash |= 0
previousPixel = pixel
previousLocation = (row, col)
}
}
hash
}
}

36
src/main/scala/com/sothr/imagetools/hash/HashService.scala

@ -3,6 +3,10 @@ package com.sothr.imagetools.hash
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import com.sothr.imagetools.dto.ImageHashDTO import com.sothr.imagetools.dto.ImageHashDTO
import com.sothr.imagetools.util.{PropertiesEnum, PropertiesService} import com.sothr.imagetools.util.{PropertiesEnum, PropertiesService}
import com.sothr.imagetools.ImageService
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.io.File
/** /**
* A service that exposes the ability to construct perceptive hashes from an * A service that exposes the ability to construct perceptive hashes from an
@ -20,16 +24,16 @@ object HashService extends Logging {
var phash:Long = 0L var phash:Long = 0L
//Get Image Data //Get Image Data
var imageData:Array[Array[Int]] = null
val image = ImageIO.read(new File(imagePath))
if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") { if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") {
ahash = getAhash(imageData)
ahash = getAhash(image)
} }
if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") { if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") {
dhash = getDhash(imageData)
dhash = getDhash(image)
} }
if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") { if (PropertiesService.get(PropertiesEnum.UseAhash.toString) == "true") {
phash = getPhash(imageData)
phash = getPhash(image)
} }
val hashes = new ImageHashDTO(ahash, dhash, phash) val hashes = new ImageHashDTO(ahash, dhash, phash)
@ -38,16 +42,28 @@ object HashService extends Logging {
return hashes return hashes
} }
def getAhash(imageData:Array[Array[Int]]):Long = {
return 0L
def getAhash(image:BufferedImage):Long = {
debug("Generating an AHash")
val grayImage = ImageService.convertToGray(image)
val resizedImage = ImageService.resize(grayImage, PropertiesService.get(PropertiesEnum.AhashPrecision.toString).toInt, true)
val imageData = ImageService.getImageData(resizedImage)
AHash.getHash(imageData)
} }
def getDhash(imageData:Array[Array[Int]]):Long = {
return 0L
def getDhash(image:BufferedImage):Long = {
debug("Generating an DHash")
val grayImage = ImageService.convertToGray(image)
val resizedImage = ImageService.resize(grayImage, PropertiesService.get(PropertiesEnum.DhashPrecision.toString).toInt, true)
val imageData = ImageService.getImageData(resizedImage)
DHash.getHash(imageData)
} }
def getPhash(imageData:Array[Array[Int]]):Long = {
return 0L
def getPhash(image:BufferedImage):Long = {
debug("Generating an PHash")
val grayImage = ImageService.convertToGray(image)
val resizedImage = ImageService.resize(grayImage, PropertiesService.get(PropertiesEnum.PhashPrecision.toString).toInt, true)
val imageData = ImageService.getImageData(resizedImage)
PHash.getHash(imageData)
} }
} }

10
src/main/scala/com/sothr/imagetools/hash/PHash.scala

@ -0,0 +1,10 @@
package com.sothr.imagetools.hash
/**
* Created by dev on 1/22/14.
*/
object PHash extends PerceptualHasher {
def getHash(imageData: Array[Array[Int]]): Long = {
return 0L
}
}

10
src/main/scala/com/sothr/imagetools/hash/PerceptualHasher.scala

@ -0,0 +1,10 @@
package com.sothr.imagetools.hash
/**
* Created by dev on 1/22/14.
*/
trait PerceptualHasher {
def getHash(imageData:Array[Array[Int]]):Long
}

81
src/main/scala/com/sothr/imagetools/image/ImageService.scala

@ -1,14 +1,91 @@
package com.sothr.imagetools package com.sothr.imagetools
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import java.awt.image.{DataBufferByte, BufferedImage}
import net.coobird.thumbnailator.Thumbnails
object ImageService extends Logging { object ImageService extends Logging {
/** /**
* Get the raw data for an image * Get the raw data for an image
*/ */
def getImageData(path:String):Array[Array[Int]] = {
return null
def getImageData(image:BufferedImage):Array[Array[Int]] = {
return convertTo2DWithoutUsingGetRGB(image)
}
/**
* Quickly convert an image to grayscale
*
* @param image
* @return
*/
def convertToGray(image:BufferedImage):BufferedImage = {
val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY)
val g = image.getGraphics
g.drawImage(image,0,0,null)
g.dispose()
grayImage
}
def resize(image:BufferedImage, size:Int, forced:Boolean=false):BufferedImage = {
if (forced) {
Thumbnails.of(image).forceSize(size,size).asBufferedImage
} else {
Thumbnails.of(image).size(size,size).asBufferedImage
}
}
/**
* Convert a buffered image into a 2d pixel data array
*
* @param image
* @return
*/
private def convertTo2DWithoutUsingGetRGB(image:BufferedImage):Array[Array[Int]] = {
val pixels = image.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData
val width = image.getWidth
val height = image.getHeight
val hasAlphaChannel = image.getAlphaRaster != null
val result = Array.ofDim[Int](height,width)
if (hasAlphaChannel) {
val pixelLength = 4
var row = 0
var col = 0
for (pixel <- 0 until pixels.length by pixelLength) {
var argb:Int = 0
argb += (pixels(pixel) & 0xff) << 24 //alpha
argb += (pixels(pixel + 1) & 0xff) //blue
argb += (pixels(pixel + 2) & 0xff) << 8 //green
argb += (pixels(pixel + 3) & 0xff) << 16 //red
result(row)(col) = argb
col += 1
if (col == width) {
col = 0
row += 1
}
}
} else {
val pixelLength = 3
var row = 0
var col = 0
for (pixel <- 0 until pixels.length by pixelLength) {
var argb:Int = 0
argb += -16777216; // 255 alpha
argb += (pixels(pixel) & 0xff) //blue
argb += (pixels(pixel + 1) & 0xff) << 8 //green
argb += (pixels(pixel + 2) & 0xff) << 16 //red
result(row)(col) = argb
col += 1
if (col == width) {
col = 0
row += 1
}
}
}
result
} }
} }

4
src/main/scala/com/sothr/imagetools/util/PropertiesEnum.scala

@ -5,12 +5,16 @@ object PropertiesEnum extends Enumeration {
val Version = Value("version") val Version = Value("version")
//default image settings //default image settings
val ImageDifferenceThreshold = Value("image.differenceThreshold") val ImageDifferenceThreshold = Value("image.differenceThreshold")
val HashPrecision = Value("image.hash.precision")
val UseAhash = Value("image.ahash.use") val UseAhash = Value("image.ahash.use")
val AhashWeight = Value("image.ahash.weight") val AhashWeight = Value("image.ahash.weight")
val AhashPrecision = Value("image.ahash.precision")
val UseDhash = Value("image.dhash.use") val UseDhash = Value("image.dhash.use")
val DhashWeight = Value("image.dhash.weight") val DhashWeight = Value("image.dhash.weight")
val DhashPrecision = Value("image.dhash.precision")
val UsePhash = Value("image.phash.use") val UsePhash = Value("image.phash.use")
val PhashWeight = Value("image.phash.weight") val PhashWeight = Value("image.phash.weight")
val PhashPrecision = Value("image.phash.precision")
//Default Thumbnail Settings //Default Thumbnail Settings
val ThumbnailDirectory = Value("thumbnail.directory") val ThumbnailDirectory = Value("thumbnail.directory")
val ThumbnailSize = Value("thumbnail.size") val ThumbnailSize = Value("thumbnail.size")

3
src/test/java/com/sothr/imagetools/AppTest.java

@ -7,8 +7,7 @@ import junit.framework.TestSuite;
/** /**
* Unit test for simple App. * Unit test for simple App.
*/ */
public class AppTest
extends TestCase
public class AppTest extends TestCase
{ {
/** /**
* Create the test case * Create the test case

25
src/test/resources/default.properties

@ -0,0 +1,25 @@
#Default Properties File
#Image Tools version: ${project.version}
version=${project.version}
#Default Image Settings
#images must be 90% similar
image.differenceThreshold=0.90
#control generation of hashes for new images.
image.hash.precision=64
image.ahash.use=true
image.ahash.weight=0.70
image.ahash.precision=8
image.dhash.use=true
image.dhash.weight=0.85
image.dhash.precision=8
#set to false if hashing images is taking too long
image.phash.use=true
image.phash.weight=1.0
image.phash.precision=16
#Default Thumbnail Settings
#Directory where to store thumbnails
thumbnail.directory=./cache/thumbnails/
#Size of the thumbnail to generate and store
thumbnail.size=128

11
src/test/scala/com/sothr/imagetools/BaseTest.scala

@ -1,5 +1,12 @@
package com.sothr.imagetools package com.sothr.imagetools
import org.scalatest.{FunSuite,Matchers,OptionValues,Inside,Inspectors}
import org.scalatest.{FunSuite,Matchers,OptionValues,Inside,Inspectors,BeforeAndAfter}
abstract class BaseTest extends FunSuite with Matchers with OptionValues with Inside with Inspectors
abstract class BaseTest extends FunSuite with Matchers with OptionValues with Inside with Inspectors with BeforeAndAfter {
before {
AppConfig.configLogging()
AppConfig.loadProperties()
}
}

19
src/test/scala/com/sothr/imagetools/hash/HashServiceTest.scala

@ -0,0 +1,19 @@
package com.sothr.imagetools.hash
import com.sothr.imagetools.BaseTest
import javax.imageio.ImageIO
import java.io.File
/**
* Created by dev on 1/23/14.
*/
class HashServiceTest extends BaseTest {
test("Calculate DHash") {
val sample = new File("./target/sample/sample_01_large.jpg")
val image = ImageIO.read(sample)
val hash = HashService.getDhash(image)
assert(hash == 0L)
}
}
Loading…
Cancel
Save