Question

I have a myNode: scalafx.scene.Node that I'd like to turn into a bitmap so that I can save it to disk, compare it to other bitmaps, etc. I think the correct code to turn it into a scalafx.scene.image.WritableImage is

val writableImg = new Scene { root = new Group(myNode) }.snapshot(null)

and then I should be able to turn that into a java.awt.image.RenderedImage with

val renderedImg = SwingFXUtils.fromFXImage(writableImg, null)

The problem, as you've probably realized, is that I have to run the code to get wrImg on the ScalaFX thread. There's a question here that explains how to return a value, but I'm not having any luck translating that to Scala. I tried this:

lazy val writableImg: WritableImage = {
  val wrImg = new FutureTask(new Callable[WritableImage]() {
    override def call(): WritableImage = {
      new Scene { root = new Group(myNode) }.snapshot(null)
    }
  })
  Platform.runLater(wrImg)
  wrImg.get()
}

but the code just hangs and never completes. Can anyone provide an idiomatic Scala version and/or tell me why the code never returns?

Was it helpful?

Solution

If you just want to save the image to disk you can simply do it on the same thread avoiding complication passing the image around. Something like this will work:

Platform.runLater {
  val node = new Circle {
    centerX = 200
    centerY = 200
    radius = 50
    stroke = Color.BROWN
    strokeWidth = 2
    fill = Color.DARKKHAKI
  }
  val jfxImage = node.snapshot(new SnapshotParameters(), null)
  val bufferedImage = SwingFXUtils.fromFXImage(jfxImage, null)
  val file = new File("./snapshot.png")
  println("Writing snapshot to: " + file.getAbsolutePath)
  javax.imageio.ImageIO.write(bufferedImage, "png", file)
  ()
}

The empty () to have closure returning Unit, so ScalaFX Platform.runLater is happy.

Update:

If you want to have a value from Platform.runLater the approach you suggested in your question should be in general fine. However, you want to make sure that you do not block the FX Application Thread. If you call Platform.runLater on FX Application Thread you will lock out, so you may want to have something like this

def makeSnapshot() = myNode.snapshot(new SnapshotParameters(), null)

val writableImg = if (Platform.isFxApplicationThread) {
  makeSnapshot()
} else {
  val futureTask = new FutureTask(new Callable[WritableImage]() {
    override def call(): WritableImage = makeSnapshot()
  })
  Platform.runLater(futureTask)
  futureTask.get()
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top