diff --git a/cli/pom.xml b/cli/pom.xml index a5e716d..a88316e 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 @@ -88,9 +88,12 @@ process-resources - - - + + + diff --git a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java index 44bd8c4..9275ec4 100644 --- a/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java +++ b/cli/src/main/java/com/sothr/imagetools/cli/AppCLI.java @@ -18,10 +18,9 @@ import scala.collection.immutable.List; */ class AppCLI { - private static Logger logger; - private static final String HEADER = "Process images and search for duplicates and similar images heuristically"; private static final String FOOTER = "Please report issues to..."; + private static Logger logger; public static void main(String[] args) { try { diff --git a/engine/pom.xml b/engine/pom.xml index 2401ed9..ea231f3 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 @@ -119,6 +119,10 @@ com.jsuereth scala-arm_${scala.binary.version} + + com.twelvemonkeys.imageio + imageio-jpeg + @@ -192,10 +196,12 @@ - + - + diff --git a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java index b0acb9d..f798b40 100644 --- a/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java +++ b/engine/src/main/java/com/sothr/imagetools/engine/AppConfig.java @@ -18,24 +18,20 @@ import java.io.File; public class AppConfig { - private static Logger logger; - public static CacheManager cacheManager; - // Logging defaults private static final String LOGSETTINGSFILE = "./logback.xml"; - private static Boolean configuredLogging = false; - // Properties defaults private static final String DEFAULTPROPERTIESFILE = "application.conf"; private static final String USERPROPERTIESFILE = "user.conf"; + // General Akka Actor System + private static final ActorSystem appSystem = ActorSystem.create("ITActorSystem"); + public static CacheManager cacheManager; + public static FXMLLoader fxmlLoader = null; + private static Logger logger; + private static Boolean configuredLogging = false; private static Boolean loadedProperties = false; - // Cache defaults private static Boolean configuredCache = false; - - // General Akka Actor System - private static final ActorSystem appSystem = ActorSystem.create("ITActorSystem"); - // The Main App private static Stage primaryStage = null; @@ -47,11 +43,13 @@ public class AppConfig { primaryStage = newPrimaryStage; } - public static FXMLLoader fxmlLoader = null; - - public static FXMLLoader getFxmlLoader() { return fxmlLoader; } + public static FXMLLoader getFxmlLoader() { + return fxmlLoader; + } - public static void setFxmlLoader(FXMLLoader loader) { fxmlLoader = loader; } + public static void setFxmlLoader(FXMLLoader loader) { + fxmlLoader = loader; + } public static ActorSystem getAppActorSystem() { return appSystem; @@ -101,7 +99,7 @@ public class AppConfig { String message = fromFile ? "From File" : "From Defaults"; logger.info(String.format("Configured Logger %s", message)); logger.info(String.format("Detected Version: %s of Image Tools", PropertiesService.getVersion().toString())); - logger.info(String.format("Running on %s, %s, %s",PropertiesService.OS(), PropertiesService.OS_VERSION(),PropertiesService.OS_ARCH())); + logger.info(String.format("Running on %s, %s, %s", PropertiesService.OS(), PropertiesService.OS_VERSION(), PropertiesService.OS_ARCH())); } //Only configure logging from the default file once diff --git a/engine/src/main/resources/hibernate.cfg.xml b/engine/src/main/resources/hibernate.cfg.xml index 84f210f..4de017a 100644 --- a/engine/src/main/resources/hibernate.cfg.xml +++ b/engine/src/main/resources/hibernate.cfg.xml @@ -24,7 +24,8 @@ thread - org.hibernate.cache.ehcache.EhCacheRegionFactory + org.hibernate.cache.ehcache.EhCacheRegionFactory + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory true diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala index 3d9c9e1..3db2a56 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala @@ -31,37 +31,6 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { engineSimilarityController ! SetNewListener(listenerRef) } - def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { - debug(s"Looking for images in directory: $directoryPath") - val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() - // make sure the engine is listening - engineProcessingController ! EngineStart - for (file <- imageFiles) { - engineProcessingController ! EngineProcessFile(file) - } - engineProcessingController ! EngineNoMoreFiles - var doneProcessing = false - while (!doneProcessing) { - val f = engineProcessingController ? EngineIsProcessingFinished - val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] - result match { - case true => - doneProcessing = true - debug("Processing Complete") - case false => - debug("Still Processing") - //sleep thread - Thread.sleep(5000L) - //val future = Future { blocking(Thread.sleep(5000L)); "done" } - } - } - val f = engineProcessingController ? EngineGetProcessingResults - val result = Await.result(f, timeout.duration).asInstanceOf[List[Image]] - images ++= result - images.toList - } - //needs to be rebuilt def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { debug(s"Looking for similar images in directory: $directoryPath") @@ -116,6 +85,37 @@ class ConcurrentEngine extends Engine with grizzled.slf4j.Logging { info(s"Finished processing ${images.size} images. Found $similarCount similar images") cleanedSimilarImages.toList } + + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { + debug(s"Looking for images in directory: $directoryPath") + val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + // make sure the engine is listening + engineProcessingController ! EngineStart + for (file <- imageFiles) { + engineProcessingController ! EngineProcessFile(file) + } + engineProcessingController ! EngineNoMoreFiles + var doneProcessing = false + while (!doneProcessing) { + val f = engineProcessingController ? EngineIsProcessingFinished + val result = Await.result(f, timeout.duration).asInstanceOf[Boolean] + result match { + case true => + doneProcessing = true + debug("Processing Complete") + case false => + debug("Still Processing") + //sleep thread + Thread.sleep(5000L) + //val future = Future { blocking(Thread.sleep(5000L)); "done" } + } + } + val f = engineProcessingController ? EngineGetProcessingResults + val result = Await.result(f, timeout.duration).asInstanceOf[List[Image]] + images ++= result + images.toList + } } @@ -158,13 +158,6 @@ class ConcurrentEngineProcessingController extends Actor with ActorLogging { var listener = context.actorOf(Props[DefaultLoggingEngineListener], name = "ProcessedEngineListener") - def setListener(newListener: ActorRef) = { - //remove the old listener - this.listener ! PoisonPill - //setup the new listener - this.listener = newListener - } - override def preStart() = { log.info("Staring the controller for processing images") log.info("Using {} actors to process images", numOfRouters) @@ -182,9 +175,11 @@ class ConcurrentEngineProcessingController extends Actor with ActorLogging { case _ => log.info("received unknown message") } - override def postStop() = { - super.postStop() + def setListener(newListener: ActorRef) = { + //remove the old listener this.listener ! PoisonPill + //setup the new listener + this.listener = newListener } def startEngine() = { @@ -250,6 +245,11 @@ class ConcurrentEngineProcessingController extends Actor with ActorLogging { throw e } } + + override def postStop() = { + super.postStop() + this.listener ! PoisonPill + } } class ConcurrentEngineProcessingActor extends Actor with ActorLogging { @@ -313,13 +313,6 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { var listener = context.actorOf(Props[DefaultLoggingEngineListener], name = "SimilarityEngineListener") - def setListener(newListener: ActorRef) = { - //remove the old listener - this.listener ! PoisonPill - //setup the new listener - this.listener = newListener - } - override def preStart() = { log.info("Staring the controller for processing similarities between images") log.info("Using {} actors to process image similarities", numOfRouters) @@ -337,9 +330,11 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { case _ => log.info("received unknown message") } - override def postStop() = { - super.postStop() + def setListener(newListener: ActorRef) = { + //remove the old listener this.listener ! PoisonPill + //setup the new listener + this.listener = newListener } def startEngine() = { @@ -412,6 +407,11 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging { throw e } } + + override def postStop() = { + super.postStop() + this.listener ! PoisonPill + } } class ConcurrentEngineSimilarityActor extends Actor with ActorLogging { diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala index 0e2ea3b..74a141f 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/SequentialEngine.scala @@ -29,26 +29,6 @@ class SequentialEngine extends Engine with Logging { this.similarityListener = listenerRef } - def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { - debug(s"Looking for images in directory: $directoryPath") - val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() - val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) - val directory: File = new File(directoryPath) - var count = 0 - for (file <- imageFiles) { - count += 1 - if (count % 25 == 0) { - //info(s"Processed ${count}/${imageFiles.size}") - processedListener ! ScannedFileCount(count, imageFiles.size) - } - val image = ImageService.getImage(file) - if (image != null) { - images += image - } - } - images.toList - } - def getSimilarImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[SimilarImages] = { debug(s"Looking for similar images in directory: $directoryPath") val images = getImagesForDirectory(directoryPath, recursive, recursiveDepth) @@ -88,4 +68,24 @@ class SequentialEngine extends Engine with Logging { info(s"Finished processing ${images.size} images. Found $similarCount similar images") allSimilarImages.toList } + + def getImagesForDirectory(directoryPath: String, recursive: Boolean = false, recursiveDepth: Int = 500): List[Image] = { + debug(s"Looking for images in directory: $directoryPath") + val images: mutable.MutableList[Image] = new mutable.MutableList[Image]() + val imageFiles = getAllImageFiles(directoryPath, recursive, recursiveDepth) + val directory: File = new File(directoryPath) + var count = 0 + for (file <- imageFiles) { + count += 1 + if (count % 25 == 0) { + //info(s"Processed ${count}/${imageFiles.size}") + processedListener ! ScannedFileCount(count, imageFiles.size) + } + val image = ImageService.getImage(file) + if (image != null) { + images += image + } + } + images.toList + } } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala index 186b1c8..f9daab1 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dao/HibernateUtil.scala @@ -17,6 +17,10 @@ object HibernateUtil extends Logging { private val sessionFactory: SessionFactory = buildSessionFactory() private var serviceRegistry: ServiceRegistry = null + def getSessionFactory: SessionFactory = { + sessionFactory + } + private def buildSessionFactory(): SessionFactory = { try { // Create the SessionFactory from hibernate.cfg.xml @@ -34,9 +38,5 @@ object HibernateUtil extends Logging { } } - def getSessionFactory: SessionFactory = { - sessionFactory - } - } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala b/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala index e73e038..7314fa0 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/dto/ImageHashDTO.scala @@ -8,12 +8,12 @@ import grizzled.slf4j.Logging @Table(name = "ImageHash") class ImageHashDTO(var ahash: Long, var dhash: Long, var phash: Long, var md5: String) extends Serializable with Logging { - def this() = this(0l, 0l, 0l, "") - @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Int = _ + def this() = this(0l, 0l, 0l, "") + def getId: Int = id def setId(newId: Int) = { diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala index 33073da..baf6397 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/hash/HashService.scala @@ -117,6 +117,12 @@ object HashService extends Logging { if (distance <= tolerence) true else false } + def areImageHashesSimilar(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Boolean = { + val weightedHammingMean = getWeightedHashSimilarity(imageHash1, imageHash2) + val weightedToleranceMean = getWeightedHashTolerence + if (weightedHammingMean <= weightedToleranceMean) true else false + } + def getWeightedHashSimilarity(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Float = { //ahash val aHashTolerance = PropertiesService.aHashTolerance @@ -196,10 +202,4 @@ object HashService extends Logging { weightedTolerance } - def areImageHashesSimilar(imageHash1: ImageHashDTO, imageHash2: ImageHashDTO): Boolean = { - val weightedHammingMean = getWeightedHashSimilarity(imageHash1, imageHash2) - val weightedToleranceMean = getWeightedHashTolerence - if (weightedHammingMean <= weightedToleranceMean) true else false - } - } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala index 9db8b53..1bfb438 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/Image.scala @@ -10,18 +10,21 @@ import grizzled.slf4j.Logging @Table(name = "Image") class Image(val image: String, val thumbnail: String, val size: (Int, Int), val imageHashes: ImageHashDTO = null) extends Serializable with Logging { - def this() = this("", "", (0, 0), null) - @Id var imagePath: String = image - - def getImagePath: String = imagePath - - def setImagePath(path: String) = { - imagePath = path + var thumbnailPath: String = thumbnail + var width: Int = size._1 + var height: Int = size._2 + var hashes: ImageHashDTO = imageHashes + @transient + var imageSize: (Int, Int) = { + new Tuple2(width, height) } + @transient + var imageName: String = "" + var imageType: ImageType = ImageType.SingleFrameImage - var thumbnailPath: String = thumbnail + def this() = this("", "", (0, 0), null) def getThumbnailPath: String = thumbnailPath @@ -29,40 +32,24 @@ class Image(val image: String, val thumbnail: String, val size: (Int, Int), val thumbnailPath = path } - var width: Int = size._1 - def getWidth: Int = width def setWidth(size: Int) = { width = size } - var height: Int = size._2 - def getHeight: Int = height def setHeight(size: Int) = { height = size } - var hashes: ImageHashDTO = imageHashes - def getHashes: ImageHashDTO = hashes def setHashes(newHashes: ImageHashDTO) = { hashes = newHashes } - @transient - var imageSize: (Int, Int) = { - new Tuple2(width, height) - } - - @transient - var imageName: String = "" - - var imageType: ImageType = ImageType.SingleFrameImage - def getName: String = { if (this.imageName.length < 1) { this.imageName = this.getImagePath.split('/').last @@ -70,6 +57,12 @@ class Image(val image: String, val thumbnail: String, val size: (Int, Int), val this.imageName } + def getImagePath: String = imagePath + + def setImagePath(path: String) = { + imagePath = path + } + def isSimilarTo(otherImage: Image): Boolean = { //debug(s"Checking $imagePath for similarities with ${otherImage.imagePath}") HashService.areImageHashesSimilar(this.hashes, otherImage.hashes) diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala index b4f7ecc..cb4db2b 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/ImageService.scala @@ -18,6 +18,32 @@ object ImageService extends Logging { val imageCache = AppConfig.cacheManager.getCache("images") private val imageDAO = new ImageDAO() + def getImage(file: File): Image = { + try { + val image = lookupImage(file) + if (image != null) { + debug(s"${file.getAbsolutePath} was already processed") + return image + } else { + debug(s"Processing image: ${file.getAbsolutePath}") + val bufferedImage = ImageIO.read(file) + val hashes = HashService.getImageHashes(bufferedImage, file.getAbsolutePath) + var thumbnailPath = lookupThumbnailPath(hashes.md5) + if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.md5) + val imageSize = { + (bufferedImage.getWidth, bufferedImage.getHeight) + } + val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, hashes) + debug(s"Created image: $image") + return saveImage(image) + } + } catch { + case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}... ${ioe.getMessage}") + case ex: Exception => error(s"Error processing ${file.getAbsolutePath}... ${ex.getMessage}", ex) + } + null + } + private def lookupImage(file: File): Image = { var image: Image = null var found = false @@ -54,46 +80,16 @@ object ImageService extends Logging { image } - def getImage(file: File): Image = { - try { - val image = lookupImage(file) - if (image != null) { - debug(s"${file.getAbsolutePath} was already processed") - return image - } else { - debug(s"Processing image: ${file.getAbsolutePath}") - val bufferedImage = ImageIO.read(file) - val hashes = HashService.getImageHashes(bufferedImage, file.getAbsolutePath) - var thumbnailPath = lookupThumbnailPath(hashes.md5) - if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.md5) - val imageSize = { - (bufferedImage.getWidth, bufferedImage.getHeight) - } - val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, hashes) - debug(s"Created image: $image") - return saveImage(image) - } - } catch { - case ioe: IOException => error(s"Error processing ${file.getAbsolutePath}... ${ioe.getMessage}") - case ex: Exception => error(s"Error processing ${file.getAbsolutePath}... ${ex.getMessage}", ex) - } - null - } - - def deleteImage(image: Image) = { - debug(s"Attempting to delete all traces of image: ${image.getImagePath}") - try { - val imageFile = new File(image.imagePath) - //try to delete the file - imageFile.delete() - //purge the file from the database and cache - this.imageCache.remove(imageFile.getAbsolutePath) - this.imageDAO.delete(image) - } catch { - case se: SecurityException => error(s"Unable to delete file: ${image.getImagePath} due to a security exception", se) - case ise: IllegalStateException => error(s"Unable to delete file: ${image.getImagePath} due to an illegal state exception", ise) - case he: HibernateException => error(s"Unable to delete file: ${image.getImagePath} due to a hibernate exception", he) + def lookupThumbnailPath(md5: String): String = { + var thumbPath: String = null + if (md5 != null) { + //check for the actual file + val checkPath = calculateThumbPath(md5) + if (new File(checkPath).exists) thumbPath = checkPath + } else { + error("Null md5 passed in") } + thumbPath } def calculateThumbPath(md5: String): String = { @@ -110,18 +106,6 @@ object ImageService extends Logging { path } - def lookupThumbnailPath(md5: String): String = { - var thumbPath: String = null - if (md5 != null) { - //check for the actual file - val checkPath = calculateThumbPath(md5) - if (new File(checkPath).exists) thumbPath = checkPath - } else { - error("Null md5 passed in") - } - thumbPath - } - def getThumbnail(image: BufferedImage, md5: String): String = { //create thumbnail val thumb = resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced = false) @@ -129,7 +113,7 @@ object ImageService extends Logging { val path = calculateThumbPath(md5) // save thumbnail to path try { - ImageIO.write(thumb, "jpg", new File(path)) + ImageIO.write(thumb, "png", new File(path)) debug(s"Wrote thumbnail to $path") } catch { case ioe: IOException => error(s"Unable to save thumbnail to $path", ioe) @@ -138,37 +122,6 @@ object ImageService extends Logging { path } - /** - * Get the raw data for an image - */ - def getImageData(image: BufferedImage): Array[Array[Int]] = { - convertTo2DWithoutUsingGetRGB(image) - } - - /** - * Quickly convert an image to grayscale - * - * @param image image to convert to greyscale - * @return - */ - def convertToGray(image: BufferedImage): BufferedImage = { - //debug("Converting an image to grayscale") - val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY) - - //create a color conversion operation - val op = new ColorConvertOp( - image.getColorModel.getColorSpace, - grayImage.getColorModel.getColorSpace, null) - - //convert the image to grey - val result = op.filter(image, grayImage) - - //val g = image.getGraphics - //g.drawImage(image,0,0,null) - //g.dispose() - result - } - def resize(image: BufferedImage, size: Int, forced: Boolean = false): BufferedImage = { //debug(s"Resizing an image to size: ${size}x${size} forced: $forced") if (forced) { @@ -178,6 +131,29 @@ object ImageService extends Logging { } } + def deleteImage(image: Image) = { + debug(s"Attempting to delete all traces of image: ${image.getImagePath}") + try { + val imageFile = new File(image.imagePath) + //try to delete the file + imageFile.delete() + //purge the file from the database and cache + this.imageCache.remove(imageFile.getAbsolutePath) + this.imageDAO.delete(image) + } catch { + case se: SecurityException => error(s"Unable to delete file: ${image.getImagePath} due to a security exception", se) + case ise: IllegalStateException => error(s"Unable to delete file: ${image.getImagePath} due to an illegal state exception", ise) + case he: HibernateException => error(s"Unable to delete file: ${image.getImagePath} due to a hibernate exception", he) + } + } + + /** + * Get the raw data for an image + */ + def getImageData(image: BufferedImage): Array[Array[Int]] = { + convertTo2DWithoutUsingGetRGB(image) + } + /** * Convert a buffered image into a 2d pixel data array * @@ -256,4 +232,28 @@ object ImageService extends Logging { } result } + + /** + * Quickly convert an image to grayscale + * + * @param image image to convert to greyscale + * @return + */ + def convertToGray(image: BufferedImage): BufferedImage = { + //debug("Converting an image to grayscale") + val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY) + + //create a color conversion operation + val op = new ColorConvertOp( + image.getColorModel.getColorSpace, + grayImage.getColorModel.getColorSpace, null) + + //convert the image to grey + val result = op.filter(image, grayImage) + + //val g = image.getGraphics + //g.drawImage(image,0,0,null) + //g.dispose() + result + } } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala index 1030b5b..de38840 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/image/SimilarImages.scala @@ -9,15 +9,6 @@ import grizzled.slf4j.Logging */ class SimilarImages(val rootImage: Image, val similarImages: List[Image]) extends Logging { - protected def getPrettySimilarImagesList: String = { - val sb = new StringBuilder() - for (image <- similarImages) { - sb.append(image.imagePath) - sb.append(System.lineSeparator()) - } - sb.toString() - } - override def hashCode: Int = { val prime = 7 var result = prime * 1 + rootImage.hashCode @@ -33,4 +24,13 @@ class SimilarImages(val rootImage: Image, val similarImages: List[Image]) extend $getPrettySimilarImagesList""".stripMargin } + protected def getPrettySimilarImagesList: String = { + val sb = new StringBuilder() + for (image <- similarImages) { + sb.append(image.imagePath) + sb.append(System.lineSeparator()) + } + sb.toString() + } + } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala index ff7fece..18999bc 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/PropertiesService.scala @@ -11,17 +11,14 @@ import grizzled.slf4j.Logging */ object PropertiesService extends Logging { - private var defaultConf: Config = null - private var userConf: Config = null + //OS information + val OS = System.getProperty("os.name", "UNKNOWN") + val OS_VERSION = System.getProperty("os.version", "UNKNOWN") + val OS_ARCH = System.getProperty("os.arch", "UNKNOWN") private val newUserConf: Properties = new Properties() - private var version: Version = null private val configRenderOptions = ConfigRenderOptions.concise().setFormatted(true) - - def getVersion: Version = this.version - //specific highly used properties var TimingEnabled: Boolean = false - //ahash var aHashPrecision = 0 var aHashTolerance = 0 @@ -37,11 +34,11 @@ object PropertiesService extends Logging { var pHashTolerance = 0 var pHashWeight = 0.0f var usePhash = false + private var defaultConf: Config = null + private var userConf: Config = null + private var version: Version = null - //OS information - val OS = System.getProperty("os.name", "UNKNOWN") - val OS_VERSION = System.getProperty("os.version", "UNKNOWN") - val OS_ARCH = System.getProperty("os.arch", "UNKNOWN") + def getVersion: Version = this.version /* * Load the properties file from the specified location @@ -79,16 +76,21 @@ object PropertiesService extends Logging { info("Loaded Special Properties") } - private def cleanAndPrepareNewUserProperties(): Properties = { - //insert special keys here - newUserConf.setProperty(PropertyEnum.PreviousVersion.toString, version.parsableToString()) - //remove special keys here - newUserConf.remove(PropertyEnum.Version.toString) - newUserConf - } - - private def getCleanedMergedUserConf: Config = { - ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf + def get(key: String, defaultValue: String = null): String = { + var result: String = defaultValue + //check the latest properties + if (newUserConf.containsKey(key)) { + result = newUserConf.getProperty(key) + } + //check the loaded user properties + else if (userConf.hasPath(key)) { + result = userConf.getString(key) + } + //check the default properties + else if (defaultConf.hasPath(key)) { + result = defaultConf.getString(key) + } + result } def saveConf(location: String) = { @@ -101,6 +103,18 @@ object PropertiesService extends Logging { out.close() } + private def getCleanedMergedUserConf: Config = { + ConfigFactory.parseProperties(cleanAndPrepareNewUserProperties()) withFallback userConf + } + + private def cleanAndPrepareNewUserProperties(): Properties = { + //insert special keys here + newUserConf.setProperty(PropertyEnum.PreviousVersion.toString, version.parsableToString()) + //remove special keys here + newUserConf.remove(PropertyEnum.Version.toString) + newUserConf + } + def has(key: String): Boolean = { var result = false if (newUserConf.containsKey(key) @@ -111,23 +125,6 @@ object PropertiesService extends Logging { result } - def get(key: String, defaultValue: String = null): String = { - var result: String = defaultValue - //check the latest properties - if (newUserConf.containsKey(key)) { - result = newUserConf.getProperty(key) - } - //check the loaded user properties - else if (userConf.hasPath(key)) { - result = userConf.getString(key) - } - //check the default properties - else if (defaultConf.hasPath(key)) { - result = defaultConf.getString(key) - } - result - } - def set(key: String, value: String) = { newUserConf.setProperty(key, value) } diff --git a/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala index be5038a..06a2d64 100644 --- a/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala +++ b/engine/src/main/scala/com/sothr/imagetools/engine/util/Version.scala @@ -72,14 +72,6 @@ class Version(val versionString: String) extends Logging { } } - def parsableToString(): String = { - s"$major.$minor.$patch-$buildTag-$buildNumber-$buildHash" - } - - override def toString: String = { - s"$major.$minor.$patch-$buildTag build:$buildNumber code:$buildHash" - } - override def hashCode(): Int = { val prime: Int = 37 val result: Int = 255 @@ -89,4 +81,12 @@ class Version(val versionString: String) extends Logging { hash += buildTag.hashCode prime * result + hash } + + def parsableToString(): String = { + s"$major.$minor.$patch-$buildTag-$buildNumber-$buildHash" + } + + override def toString: String = { + s"$major.$minor.$patch-$buildTag build:$buildNumber code:$buildHash" + } } diff --git a/engine/src/test/resources/hibernate.cfg.xml b/engine/src/test/resources/hibernate.cfg.xml index 0e71575..a1a6f1c 100644 --- a/engine/src/test/resources/hibernate.cfg.xml +++ b/engine/src/test/resources/hibernate.cfg.xml @@ -21,7 +21,8 @@ thread - org.hibernate.cache.ehcache.EhCacheRegionFactory + org.hibernate.cache.ehcache.EhCacheRegionFactory + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory true diff --git a/gui/pom.xml b/gui/pom.xml index 0da165e..0e88b6f 100644 --- a/gui/pom.xml +++ b/gui/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 @@ -92,9 +92,12 @@ process-resources - - - + + + diff --git a/gui/src/main/java/com/sothr/imagetools/ui/App.java b/gui/src/main/java/com/sothr/imagetools/ui/App.java index 97774e8..0f7bb32 100644 --- a/gui/src/main/java/com/sothr/imagetools/ui/App.java +++ b/gui/src/main/java/com/sothr/imagetools/ui/App.java @@ -12,8 +12,11 @@ import javafx.stage.Stage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; import java.io.IOException; import java.net.URL; +import java.util.Iterator; import java.util.List; /** @@ -21,9 +24,8 @@ import java.util.List; */ public class App extends Application { - private static Logger logger; - private static final String MAINGUI_FXML = "fxml/mainapp/MainApp.fxml"; + private static Logger logger; public static void main(String[] args) { AppConfig.configureApp(); @@ -54,6 +56,12 @@ public class App extends Application { //store the primary stage globally for reference in popups and the like AppConfig.setPrimaryStage(primaryStage); try { + //Confirm we have the plugin for JPEG color fixes + Iterator readers = ImageIO.getImageReadersByFormatName("JPEG"); + while (readers.hasNext()) { + logger.info("Image Reader Plugin: [{}] Available", new Object[]{readers.next()}); + } + FXMLLoader loader = new FXMLLoader(); URL location = ResourceLoader.get().getResource(MAINGUI_FXML); loader.setLocation(location); diff --git a/gui/src/main/resources/fxml/mainapp/MainApp.fxml b/gui/src/main/resources/fxml/mainapp/MainApp.fxml index 13703f0..4e6afb4 100644 --- a/gui/src/main/resources/fxml/mainapp/MainApp.fxml +++ b/gui/src/main/resources/fxml/mainapp/MainApp.fxml @@ -6,97 +6,136 @@ - + - + - + - + - - + + - + - + - + - + - -