4

This is my first question to Stackoverflow, so be gentle with me :)

I recently discovered JavaFX as a new approach to design a GUI for Java applications. Currently I am working on a project, that requires to draw up to 500x500 (250.000) objects on a Canvas, which can be from 1x1px to 20x20px big (all have the same size). Sadly the performance isn't very good. Drawing a field of 500x500 rectangles takes like 10 seconds. I made an example application to demonstrate the problem.

Why is the performance so bad? Since the drawing takes place in the UI thread, the UI is blocked until the drawing is finished. I have 2 aproaches:

  1. Make drawing fast.
  2. Draw to a buffer object in a separate thread and display the result on the canvas.

My experiments with writing to a BufferedImage before drawing that image on the canvas failed hard (canvas stayed empty).

Thanks for taking your time to help research this topic :)

sample.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.GridPane?>

<AnchorPane prefHeight="275.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="sample.Controller">
  <children>
    <ScrollPane fx:id="scrollPane" content="$null" prefHeight="237.0" prefWidth="300.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="38.0" />
    <Button layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onAction="#draw" text="Draw" />
  </children>
</AnchorPane>

Controller.java:

package sample;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ScrollPane;
import javafx.scene.paint.Color;

import java.util.Random;

public class Controller {

    @FXML
    private ScrollPane scrollPane;

    Canvas canvas;
    @FXML
    protected void draw(ActionEvent event) {
    canvas = new Canvas(500,500);
    scrollPane.setContent(canvas);
    GraphicsContext gc = canvas.getGraphicsContext2D();
        Random r = new Random();

        for(int i = 0; i<=500; i++) {
            for(int j = 0; j<=500; j++) {
                if (r.nextBoolean()) {
                    gc.setFill(Color.BLACK);
                } else {
                    gc.setFill(Color.BLUE);
                }
                gc.fillRect(i, j, i+1, j+1);
            }
        }
    }
}

Main.java:

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        ScrollPane scrollPane = (ScrollPane) root.lookup("#scrollPane");
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}
rgettman
  • 176,041
  • 30
  • 275
  • 357
Tim
  • 409
  • 1
  • 6
  • 15
  • I think I might know what might be the problem, see `gc.fillRect(i, j, i+1, j+1);` the Size is varying depending on i & j. So lets say if you remove i & j, its drawing pretty fast at my end every pixel. `gc.fillRect(i, j, 1, 1);` – zIronManBox Jun 15 '15 at 09:22
  • Other way is to layer up the canvas, use different canvas for background, for foreground, updating object1, updating object2, there by stacking multiple canvas. – zIronManBox Jun 15 '15 at 09:24
  • How did you overcome this problem? – zIronManBox Jun 15 '15 at 09:24

1 Answers1

4

I suspect the root cause may have to do with creating a canvas context every draw call. If you instead populate the canvas only once and retain a reference to it, you may see improvement. Stated otherwise:

protected void draw(...) { canvas = new Canvas(...); }

Should be moved to the initialization.

public void start(...) { ... /* Other initialization. */ ... ; canvas = new Canvas(...); }

Everything else can stay the same.

EDIT 1: I know the question is dated, but it's a highly ranked result on Google, and should be good for prosperity.

EDIT 2: On further investigation, I think this has more to do with the speed of the draw call than the actual acquisition of the canvas. The link Java Hardware Acceleration has a little bit more information. Summarily, I'd look into forcing hardware acceleration. You can check if it's active for your calls with incremental additions of someCanvas.getGraphicsConfiguration() + .getBufferCapabilities() + .isPageFlipping().

Community
  • 1
  • 1
  • i just figured i asked this question a while back, i will take a look on the effect next week :) – Tim Apr 26 '15 at 19:42