Browse Source

Working on some cleanup. Moved setup logic for the image tile into it's own class. Reduced the logic in the factory. Need to work on context menus and selectable items in the pane next. Might have to be done with a custom TilePane that allows selection.

master
Drew Short 10 years ago
parent
commit
b5512f02f3
  1. 1
      cli/pom.xml
  2. 1
      engine/pom.xml
  3. 4
      engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala
  4. 3
      engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala
  5. 3
      engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala
  6. 1
      gui/pom.xml
  7. 5
      gui/src/main/resources/fxml/mainapp/MainApp.fxml
  8. 68
      gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTile.scala
  9. 62
      gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTileFactory.scala
  10. 20
      gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala

1
cli/pom.xml

@ -9,7 +9,6 @@
<relativePath>../parent</relativePath> <relativePath>../parent</relativePath>
</parent> </parent>
<groupId>com.sothr.imagetools</groupId>
<artifactId>ImageTools-CLI</artifactId> <artifactId>ImageTools-CLI</artifactId>
<version>0.1.0</version> <version>0.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>

1
engine/pom.xml

@ -9,7 +9,6 @@
<relativePath>../parent</relativePath> <relativePath>../parent</relativePath>
</parent> </parent>
<groupId>com.sothr.imagetools</groupId>
<artifactId>ImageTools-Engine</artifactId> <artifactId>ImageTools-Engine</artifactId>
<version>0.1.2</version> <version>0.1.2</version>
<packaging>jar</packaging> <packaging>jar</packaging>

4
engine/src/main/scala/com/sothr/imagetools/engine/ConcurrentEngine.scala

@ -234,7 +234,7 @@ class ConcurrentEngineProcessingController extends Actor with ActorLogging {
*/ */
def checkForResults() = { def checkForResults() = {
try { try {
listener ! SubmitMessage(s"Finished Processing ${processed}/${processed} images")
listener ! SubmitMessage(s"Finished Processing $processed/$processed images")
processorsFinished = 0 processorsFinished = 0
toProcess = 0 toProcess = 0
processed = 0 processed = 0
@ -396,7 +396,7 @@ class ConcurrentEngineSimilarityController extends Actor with ActorLogging {
*/ */
def checkForResults() = { def checkForResults() = {
try { try {
listener ! SubmitMessage(s"Finished Scanning ${processed}/${processed} images")
listener ! SubmitMessage(s"Finished Scanning $processed/$processed images")
processorsFinished = 0 processorsFinished = 0
toProcess = 0 toProcess = 0
processed = 0 processed = 0

3
engine/src/main/scala/com/sothr/imagetools/engine/hash/AHash.scala

@ -3,6 +3,9 @@ package com.sothr.imagetools.engine.hash
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
/** /**
* Speedy AHash Perceptual Hashing
* Uses the average value of the pixels as a baseline
*
* Created by dev on 1/22/14. * Created by dev on 1/22/14.
*/ */
object AHash extends PerceptualHasher with Logging { object AHash extends PerceptualHasher with Logging {

3
engine/src/main/scala/com/sothr/imagetools/engine/hash/PHash.scala

@ -4,6 +4,9 @@ import edu.emory.mathcs.jtransforms.dct.FloatDCT_2D
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
/** /**
* Complex perceptual hash
* Uses FFT to get
*
* Created by dev on 1/22/14. * Created by dev on 1/22/14.
*/ */
object PHash extends PerceptualHasher with Logging { object PHash extends PerceptualHasher with Logging {

1
gui/pom.xml

@ -9,7 +9,6 @@
<relativePath>../parent</relativePath> <relativePath>../parent</relativePath>
</parent> </parent>
<groupId>com.sothr.imagetools</groupId>
<artifactId>ImageTools-GUI</artifactId> <artifactId>ImageTools-GUI</artifactId>
<version>0.1.0</version> <version>0.1.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>

5
gui/src/main/resources/fxml/mainapp/MainApp.fxml

@ -65,6 +65,11 @@
<bottom> <bottom>
<FlowPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="60.0" prefWidth="220.0" BorderPane.alignment="CENTER"> <FlowPane maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="60.0" prefWidth="220.0" BorderPane.alignment="CENTER">
<children> <children>
<CheckBox fx:id="doRecursiveProcessing" mnemonicParsing="false" text="Recursive">
<FlowPane.margin>
<Insets bottom="5.0" />
</FlowPane.margin>
</CheckBox>
<Button maxWidth="1.7976931348623157E308" minWidth="220.0" mnemonicParsing="false" onAction="#showAllImages" text="Show All Images"> <Button maxWidth="1.7976931348623157E308" minWidth="220.0" mnemonicParsing="false" onAction="#showAllImages" text="Show All Images">
<FlowPane.margin> <FlowPane.margin>
<Insets bottom="5.0" /> <Insets bottom="5.0" />

68
gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTile.scala

@ -1,22 +1,76 @@
package com.sothr.imagetools.ui.component package com.sothr.imagetools.ui.component
import java.io.FileInputStream
import javafx.event.EventHandler
import javafx.geometry.Pos
import javafx.scene.control.{Tooltip, Label}
import javafx.scene.image.{ImageView}
import javafx.scene.input.MouseEvent
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
import com.sothr.imagetools.engine.image.Image
import grizzled.slf4j.Logging
import resource._
/** /**
* ImageTile class that is a special VBox * ImageTile class that is a special VBox
* *
* Created by drew on 8/22/14. * Created by drew on 8/22/14.
*/ */
class ImageTile extends VBox {
var imageData: Image = null
class ImageTile(thumbnailWidth: Integer, image: com.sothr.imagetools.engine.image.Image) extends VBox with Logging {
val imageData: com.sothr.imagetools.engine.image.Image = image
val preferedTileSize = (thumbnailWidth + 32).toDouble
//set tile size
this.setPrefSize(preferedTileSize, preferedTileSize)
this.setMinSize(preferedTileSize, preferedTileSize)
this.setMaxSize(preferedTileSize, preferedTileSize)
def getImageData: Image = {
imageData
this.setAlignment(Pos.TOP_CENTER)
this.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler[MouseEvent] {
override def handle(event: MouseEvent): Unit = {
if (event.isPrimaryButtonDown) {
//double click
if (event.getClickCount == 2) {
// Look into http://stackoverflow.com/questions/228477/how-do-i-programmatically-determine-operating-system-in-java
// for proper multi-platform opening support
//Desktop.getDesktop.open(new File(image.getImagePath))
} else {
}
} else if (event.isSecondaryButtonDown) {
//right click context menu
} }
}
})
def setImageData(image: Image) = {
this.imageData = image
// Image
val genImageView = new ImageView()
debug(s"Getting thumbnail from: ${image.getThumbnailPath}")
managed(new FileInputStream(image.getThumbnailPath)) acquireAndGet {
thumbSource =>
val thumbnail = new javafx.scene.image.Image(thumbSource)
genImageView.setImage(thumbnail)
if (thumbnail.getHeight > thumbnail.getWidth) {
genImageView.setFitHeight(128.0)
} else {
genImageView.setFitWidth(128.0)
}
}
genImageView.setPreserveRatio(true)
this.getChildren.add(genImageView)
//Label
val imageLabel = new Label()
imageLabel.setText(s"${image.getHeight}x${image.getWidth}")
imageLabel.setWrapText(true)
//Tooltip
val tooltip = new Tooltip()
tooltip.setText(s"${image.getName}")
imageLabel.setTooltip(tooltip)
this.getChildren.add(imageLabel)
def getImageData: com.sothr.imagetools.engine.image.Image = {
imageData
} }
} }

62
gui/src/main/scala/com/sothr/imagetools/ui/component/ImageTileFactory.scala

@ -1,15 +1,9 @@
package com.sothr.imagetools.ui.component package com.sothr.imagetools.ui.component
import java.awt.Desktop
import java.io.{File, FileInputStream}
import javafx.event.EventHandler
import javafx.geometry.{Insets, Pos}
import javafx.scene.control.{Label, Tooltip}
import javafx.scene.image.{Image, ImageView}
import javafx.scene.input.MouseEvent
import javafx.geometry.Insets
import com.sothr.imagetools.engine.util.PropertiesService
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import resource._
/** /**
* Created by drew on 8/6/14. * Created by drew on 8/6/14.
@ -19,58 +13,10 @@ import resource._
object ImageTileFactory extends Logging { object ImageTileFactory extends Logging {
def get(image: com.sothr.imagetools.engine.image.Image): ImageTile = { def get(image: com.sothr.imagetools.engine.image.Image): ImageTile = {
val imageTile = new ImageTile()
imageTile.setImageData(image)
//set tile size
imageTile.setPrefSize(160.0d, 160.0d)
imageTile.setMinSize(160.0d, 160.0d)
imageTile.setMaxSize(160.0d, 160.0d)
val thumbnailWidth = PropertiesService.get("app.thumbnail.size","128").toInt
val imageTile = new ImageTile(thumbnailWidth, image)
//set padding //set padding
imageTile.setPadding(new Insets(2, 2, 2, 2)) imageTile.setPadding(new Insets(2, 2, 2, 2))
//imageTile.setSpacing(5.0d)
imageTile.setAlignment(Pos.TOP_CENTER)
imageTile.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler[MouseEvent] {
override def handle(event: MouseEvent): Unit = {
if (event.isPrimaryButtonDown) {
//double click
if (event.getClickCount == 2) {
Desktop.getDesktop.open(new File(image.getImagePath))
} else {
}
} else if (event.isSecondaryButtonDown) {
//right click context menu
}
}
})
// Image
val genImageView = new ImageView()
debug(s"Getting thumbnail from: ${image.getThumbnailPath}")
managed(new FileInputStream(image.getThumbnailPath)) acquireAndGet {
thumbSource =>
val thumbnail = new Image(thumbSource)
genImageView.setImage(thumbnail)
if (thumbnail.getHeight > thumbnail.getWidth) {
genImageView.setFitHeight(128.0)
} else {
genImageView.setFitWidth(128.0)
}
}
genImageView.setPreserveRatio(true)
imageTile.getChildren.add(genImageView)
//Label
val imageLabel = new Label()
imageLabel.setText(s"${image.getHeight}x${image.getWidth}")
imageLabel.setWrapText(true)
//Tooltip
val tooltip = new Tooltip()
tooltip.setText(s"${image.getName}")
imageLabel.setTooltip(tooltip)
imageTile.getChildren.add(imageLabel)
imageTile imageTile
} }

20
gui/src/main/scala/com/sothr/imagetools/ui/controller/AppController.scala

@ -1,13 +1,12 @@
package com.sothr.imagetools.ui.controller package com.sothr.imagetools.ui.controller
import java.io.{File, IOException} import java.io.{File, IOException}
import java.util.ArrayList
import java.util.Scanner import java.util.Scanner
import javafx.application.Platform import javafx.application.Platform
import javafx.event.ActionEvent import javafx.event.ActionEvent
import javafx.fxml.FXML import javafx.fxml.FXML
import javafx.scene.control._ import javafx.scene.control._
import javafx.scene.layout.{VBox, TilePane, AnchorPane}
import javafx.scene.layout.{AnchorPane, TilePane, VBox}
import javafx.scene.text.{Text, TextAlignment} import javafx.scene.text.{Text, TextAlignment}
import javafx.scene.web.WebView import javafx.scene.web.WebView
import javafx.scene.{Group, Node, Scene} import javafx.scene.{Group, Node, Scene}
@ -15,17 +14,17 @@ import javafx.stage.{DirectoryChooser, Stage, StageStyle}
import javafx.util.Callback import javafx.util.Callback
import akka.actor._ import akka.actor._
import com.sothr.imagetools.engine._
import com.sothr.imagetools.engine.image.{Image, SimilarImages} import com.sothr.imagetools.engine.image.{Image, SimilarImages}
import com.sothr.imagetools.engine.util.{PropertiesService, ResourceLoader} import com.sothr.imagetools.engine.util.{PropertiesService, ResourceLoader}
import com.sothr.imagetools.engine._
import com.sothr.imagetools.ui.component.ImageTileFactory import com.sothr.imagetools.ui.component.ImageTileFactory
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import org.markdown4j.Markdown4jProcessor import org.markdown4j.Markdown4jProcessor
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._ import scala.concurrent._
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
import ExecutionContext.Implicits.global
/** /**
* Main Application controller * Main Application controller
@ -48,6 +47,7 @@ class AppController extends Logging {
// Others // Others
@FXML var progressBar: ProgressBar = null @FXML var progressBar: ProgressBar = null
@FXML var paginator: Pagination = null @FXML var paginator: Pagination = null
@FXML var doRecursiveProcessing: CheckBox = null
// Engine // Engine
val engine: Engine = new ConcurrentEngine() val engine: Engine = new ConcurrentEngine()
@ -164,9 +164,9 @@ class AppController extends Logging {
@FXML @FXML
def showAllImages(event: ActionEvent) = { def showAllImages(event: ActionEvent) = {
resetPaginator() resetPaginator()
imageTilePane.getChildren.setAll(new ArrayList[Node]())
imageTilePane.getChildren.setAll(new java.util.ArrayList[Node]())
val f: Future[List[Image]] = Future { val f: Future[List[Image]] = Future {
engine.getImagesForDirectory(currentDirectory)
engine.getImagesForDirectory(currentDirectory, recursive = doRecursiveProcessing.isSelected)
} }
f onComplete { f onComplete {
@ -187,10 +187,10 @@ class AppController extends Logging {
@FXML @FXML
def showSimilarImages(event: ActionEvent) = { def showSimilarImages(event: ActionEvent) = {
resetPaginator() resetPaginator()
imageTilePane.getChildren.setAll(new ArrayList[Node]())
imageTilePane.getChildren.setAll(new java.util.ArrayList[Node]())
val f: Future[List[SimilarImages]] = Future { val f: Future[List[SimilarImages]] = Future {
engine.getSimilarImagesForDirectory(currentDirectory)
engine.getSimilarImagesForDirectory(currentDirectory, recursive = doRecursiveProcessing.isSelected)
} }
f onComplete { f onComplete {
@ -234,9 +234,9 @@ class AppController extends Logging {
def showPage(pageIndex: Integer) = { def showPage(pageIndex: Integer) = {
val itemsPerPage = PropertiesService.get("app.ui.thumbsPerPage", "50").toInt val itemsPerPage = PropertiesService.get("app.ui.thumbsPerPage", "50").toInt
val startIndex = pageIndex * itemsPerPage val startIndex = pageIndex * itemsPerPage
val endIndex = if ((startIndex + itemsPerPage) > this.currentImages.size) this.currentImages.length else (startIndex + itemsPerPage)
val endIndex = if ((startIndex + itemsPerPage) > this.currentImages.size) this.currentImages.length else startIndex + itemsPerPage
//clear and populate the scrollpane //clear and populate the scrollpane
imageTilePane.getChildren.setAll(new ArrayList[Node]())
imageTilePane.getChildren.setAll(new java.util.ArrayList[Node]())
val images = this.currentImages.slice(startIndex, endIndex) val images = this.currentImages.slice(startIndex, endIndex)
Platform.runLater(new Runnable() { Platform.runLater(new Runnable() {
override def run() { override def run() {

Loading…
Cancel
Save