4

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?

Community
  • 1
  • 1
Todd O'Bryan
  • 2,234
  • 17
  • 30
  • OK, this is weird...`myNode` depended on a `lazy val`. I took out the `lazy` part and now things are working. Is that to be expected, or did I run into a bug with the way `lazy val`s are handled? – Todd O'Bryan Mar 24 '14 at 03:39

1 Answers1

2

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()
}
Jarek
  • 1,513
  • 9
  • 16
  • You're assuming that I don't need to save the image for use later, which I do. In the library, students should be able to create an image, create new images based on it, display it, and save any of the images they've created to disk. The whole point is to be able to pass images around. – Todd O'Bryan Mar 25 '14 at 17:29
  • I didn't realize the subtlety that you can't use Platform.runLater() in the FX Application Thread. Thanks for that insight!!! I'll use the `Platform.isFxApplicationThread` guard and see if that helps. That may also be the answer to the SBT question that I have an open bounty on. – Todd O'Bryan Mar 26 '14 at 11:53