import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Screen; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.geometry.Pos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; /** * This demo program uses a thread to compute an image "in the background". * As rows of pixels in the image are computed, they are copied to the * screen. (The image is a small piece of the famous Mandelbrot set, which * is used just because it takes some time to compute. There is no need * to understand what the image means.) The user starts the computation by * clicking a "Start" button. A separate thread is created and is run at * a lower priority, which will make sure that the GUI thread will get a * chance to run to repaint the display as necessary. All changes to the * GUI from the animation thread are made by calling Platform.runLater(). */ public class BackgroundComputationDemo extends Application { public static void main(String[] args) { launch(args); } //----------------------------------------------------------------------------- private Runner runner; // the thread that computes the image private volatile boolean running; // used to signal the thread to abort private Button startButton; // button the user can click to start or abort the thread private Canvas canvas; // the canvas where the image is displayed private GraphicsContext g; // the graphics context for drawing on the canvas private Color[] palette; // the color palette, containing the colors of the spectrum int width, height; // the size of the canvas /** * Set up the GUI and event handling. The canvas will be 1200-by-1000 pixels, * if that fits comfortably on the screen; otherwise, size will be reduced to fit. * This method also makes the color palette, containing colors in spectral order. */ public void start(Stage stage) { palette = new Color[256]; for (int i = 0; i < 256; i++) palette[i] = Color.hsb(360*(i/256.0), 1, 1); int screenWidth = (int)Screen.getPrimary().getVisualBounds().getWidth(); int screenHeight = (int)Screen.getPrimary().getVisualBounds().getHeight(); width = Math.min(1200,screenWidth - 50); height = Math.min(1000, screenHeight - 120); canvas = new Canvas(width,height); g = canvas.getGraphicsContext2D(); g.setFill(Color.LIGHTGRAY); g.fillRect(0,0,width,height); startButton = new Button("Start!"); startButton.setOnAction( e -> doStartOrStop() ); HBox bottom = new HBox(startButton); bottom.setStyle("-fx-padding: 6px; -fx-border-color:black; -fx-border-width: 2px 0 0 0"); bottom.setAlignment(Pos.CENTER); BorderPane root = new BorderPane(canvas); root.setBottom(bottom); root.setStyle("-fx-border-color:black; -fx-border-width: 2px"); Scene scene = new Scene(root); stage.setScene(scene); stage.setTitle("Demo: Background Computation in a Thread"); stage.setResizable(false); stage.show(); } /** * This method is called from the animation thread when one row of pixels needs * to be added to the image. * @param rowNumber the row of pixels whose colors are to be set * @param colorArray an array of colors, one for each pixel */ private void drawOneRow( int rowNumber, Color[] colorArray ) { for (int i = 0; i < width; i++) { // Color an individual pixel by filling in a 1-by-1 pixel // rectangle. Not the most efficient way to do this, but // good enough for this demo. g.setFill(colorArray[i]); g.fillRect(i,rowNumber,1,1); } } /** * This method is called when the user clicks the Start button. * If no thread is running, it sets the signaling variable, running, * to true and creates and starts a new thread. Note that * the thread is responsible for changing the text on the button. * Note that the priority of the thread is set to be one less * than the priority of the thread that calls this method, that * is of the JavaFX application thread. This means that the application * thread is run in preference to the computation thread. When there is an * event to be handled, such as updating the display or reacting to a * button click, the event-handling thread should wake up immediately * to handle the event. */ private void doStartOrStop() { if (running == false) { // create a thread and start it startButton.setDisable(true); // will be re-enabled by the thread g.setFill(Color.LIGHTGRAY); g.fillRect(0,0,width,height); runner = new Runner(); try { runner.setPriority( Thread.currentThread().getPriority() - 1 ); } catch (Exception e) { } running = true; // Set the signal before starting the thread! runner.start(); } else { // stop the thread startButton.setDisable(true); // will be re-enabled by the thread running = false; runner = null; } } /** * This class defines the thread that does the computation. The * run method computes the image one pixel at a time. After computing * the colors for each row of pixels, the colors are copied into the * image, and the part of the display that shows that row is repainted. * All modifications to the GUI are made using Platform.runLater(). * (Since the thread runs in the background, at lower priority than * the event-handling thread, the event-handling thread wakes up * immediately to repaint the display.) */ private class Runner extends Thread { double xmin, xmax, ymin, ymax; int maxIterations; Runner() { xmin = -1.6744096740931858; xmax = -1.674409674093473; ymin = 4.716540768697223E-5; ymax = 4.716540790246652E-5; maxIterations = 10000; } public void run() { try { Platform.runLater( () -> startButton.setDisable(false) ); Platform.runLater( () -> startButton.setText("Abort!") ); double x, y; double dx, dy; dx = (xmax-xmin)/(width-1); dy = (ymax-ymin)/(height-1); for (int row = 0; row < height; row++) { // Compute one row of pixels. final Color[] rgb = new Color[width]; y = ymax - dy*row; for (int col = 0; col < width; col++) { x = xmin + dx*col; int count = 0; double xx = x; double yy = y; while (count < maxIterations && (xx*xx + yy*yy) < 4) { count++; double newxx = xx*xx - yy*yy + x; yy = 2*xx*yy + y; xx = newxx; } if (count == maxIterations) rgb[col] = Color.BLACK; else rgb[col] = palette[count%palette.length]; if (! running) { // Check for the signal to abort the computation. return; } } final int rowNum = row; Platform.runLater( () -> drawOneRow(rowNum,rgb) ); } } finally { // Make sure the state is correct after the thread ends for any reason. Platform.runLater( () -> startButton.setText("Start Again") ); Platform.runLater( () -> startButton.setDisable(false) ); running = false; runner = null; } } } }