Aplicativo de lona do JavaFX

Eu peço para praticar JavaFX, eu criei um aplicativo simples que desenha Triângulos Sierpinski .

import javafx.application.Application; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.BorderPane; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.stage.Stage; public class SierpinskiTriangles extends Application { private final int PADDING = 5; private static int numberOfLevels; public static void launch(String... args){ numberOfLevels = 8; if((args != null) && (args.length > 0)) { int num = -1; try { num = Integer.parseInt(args[0]); } catch (NumberFormatException ex) { ex.printStackTrace(); return; } numberOfLevels = (num > 0) ? num : numberOfLevels; } Application.launch(args); } @Override public void start(Stage stage) { stage.setOnCloseRequest((ae) -> { Platform.exit(); System.exit(0); }); stage.setTitle("Sierpinski Triangles (fx)"); BorderPane mainPane = new BorderPane(); mainPane.setPadding(new Insets(PADDING)); Pane triPanel = new Triangles(); BorderPane.setAlignment(triPanel, Pos.CENTER); mainPane.setCenter(triPanel); Scene scene = new Scene(mainPane); stage.setScene(scene); stage.centerOnScreen(); stage.setResizable(false); stage.show(); } class Triangles extends AnchorPane{ private static final int PANEL_WIDTH =600, PANEL_HEIGHT = 600; private static final int TRI_WIDTH= 500, TRI_HEIGHT= 500; private static final int SIDE_GAP = (PANEL_WIDTH - TRI_WIDTH)/2; private static final int TOP_GAP = (PANEL_HEIGHT - TRI_HEIGHT)/2; private int countTriangles; private long startTime; private Point2D top, left, right; private Canvas canvas; private GraphicsContext gc; Triangles(){ setPrefSize(PANEL_WIDTH, PANEL_HEIGHT); canvas = getCanvas(); gc = canvas.getGraphicsContext2D(); getChildren().add(canvas); draw(numberOfLevels); } void draw(int numberLevels) { Platform.runLater(new Runnable() { @Override public void run() { clearCanvas(); setStartPoints(); startTime = System.currentTimeMillis(); countTriangles = 0; RunTask task = new RunTask(numberLevels, top, left, right); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } }); } private void drawTriangle( int levels, Point2D top, Point2D left, Point2D right) { if(levels < 0) {//add stop criteria return ; } gc.strokePolygon( //implementing with strokeLine did not make much difference new double[]{ top.getX(),left.getX(),right.getX() }, new double[]{ top.getY(),left.getY(), right.getY() },3 ); countTriangles++; //Get the midpoint on each edge in the triangle Point2D p12 = midpoint(top, left); Point2D p23 = midpoint(left, right); Point2D p31 = midpoint(right, top); // recurse on 3 triangular areas drawTriangle(levels - 1, top, p12, p31); drawTriangle(levels - 1, p12, left, p23); drawTriangle(levels - 1, p31, p23, right); } private void setStartPoints() { top = new Point2D(getPrefWidth()/2, TOP_GAP); left = new Point2D(SIDE_GAP, TOP_GAP + TRI_HEIGHT); right = new Point2D(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_WIDTH); } private Point2D midpoint(Point2D p1, Point2D p2) { return new Point2D((p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2); } private void updateGraphics(boolean success){ if(success) { gc.fillText("Number of triangles: "+ countTriangles,5,15); gc.fillText("Time : "+ (System.currentTimeMillis()- startTime)+ " mili seconds", 5,35); gc.fillText("Levels: "+ numberOfLevels,5,55); } System.out.println("Completed after: "+ (System.currentTimeMillis()- startTime)+ " mili seconds" +" Triangles: " + countTriangles +" Failed: "+ !success ); } private Canvas getCanvas() { Canvas canvas = new Canvas(); canvas.widthProperty().bind(widthProperty()); canvas.heightProperty().bind(heightProperty()); canvas.getGraphicsContext2D().setStroke(Color.RED); canvas.getGraphicsContext2D().setLineWidth(0.3f); return canvas; } private void clearCanvas() { gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); } class RunTask extends Task{ private int levels; private Point2D top, left; private Point2D right; RunTask(int levels, Point2D top, Point2D left, Point2D right){ this.levels = levels; this.top = top; this.left = left; this.right = right; startTime = System.currentTimeMillis(); countTriangles = 0; } @Override public Void call() { drawTriangle(levels,top, left, right); return null; } @Override protected void succeeded() { updateGraphics(true); super.succeeded(); } @Override protected void failed() { updateGraphics(false); } } } public static void main(String[] args) { launch("13"); } } 

A saída é conforme o esperado: triângulos

Os problemas que tenho:

uma. A impressão de tempo em updateGraphics() mostra muito antes (8 segundos na minha máquina) o desenho dos triângulos é concluído, portanto, não mede o processo completo. Como eu melhoro isso?

b. Na minha máquina, demora de 30 a 35 segundos até que o painel esteja completamente desenhado. Uma aplicação de swing semelhante demora 4 segundos. Pode sugerir que há algo fundamentalmente errado com minha implementação do javafx.

Sua Task invoca drawTriangle() em segundo plano para atualizar um Canvas . O GraphicsContext associado requer que “Uma vez que um nó do Canvas esteja anexado a uma cena, ele deve ser modificado no Thread de Aplicação do JavaFX.” Sua chamada profundamente recursiva bloqueia o segmento de aplicativo JavaFX, impedindo uma atualização de canvas pontual. Por outro lado, a implementação de System.out.println() sua plataforma pode permitir que ele seja relatado em tempo hábil. A disparidade de tempo é vista mesmo sem uma Task .

Felizmente para o Canvas , “Se não estiver anexado a nenhuma cena, pode ser modificado por qualquer thread, desde que seja usado apenas de um thread de cada vez.” Uma abordagem pode ser sugerida em uma tarefa que retorna resultados parciais . Crie uma Task nocional Task que atualize um Canvas desanexado em segundo plano. Periodicamente, talvez em cada nível de recursion, copie o Canvas e publique um instantâneo via updateValue() . O painel delimitador pode ouvir a propriedade de value da tarefa e atualizar um Canvas fechado por meio de drawImage() sem bloquear o segmento de aplicativo JavaFX.

Infelizmente, o instantâneo “Lança IllegalStateException se esse método for chamado em um encadeamento diferente do encadeamento de aplicativos JavaFX”.

Na alternativa mostrada abaixo, o CanvasTask estende a Task e publica um novo Canvas em cada iteração de um loop. O CanvasTaskTest envolvente recebe a propriedade value e substitui o Canvas anterior toda vez que um novo chega. O exemplo abaixo mostra uma série de trees fractais de profundidade crescente e o tempo necessário para compor cada uma. Observe que em um GraphicsContext , “Cada chamada envia os parâmetros necessários para o buffer, onde eles serão renderizados posteriormente na imagem do nó Canvas pelo encadeamento de renderização no final de um pulso.” Isso permite que o JavaFX aproveite o pipeline de renderização de uma plataforma, mas pode impor uma sobrecarga adicional para um grande número de traços. Na prática, dezenas de milhares de traços tornam a renderização imperceptível, enquanto milhões de traços sobrepostos podem ser supérfluos.

imagem

 import javafx.application.Application; import javafx.beans.value.ObservableValue; import javafx.concurrent.Task; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.StackPane; import javafx.stage.Stage; /** * @see https://stackoverflow.com/a/44056730/230513 */ public class CanvasTaskTest extends Application { private static final int W = 800; private static final int H = 600; @Override public void start(Stage stage) { stage.setTitle("CanvasTaskTest"); StackPane root = new StackPane(); Canvas canvas = new Canvas(W, H); root.getChildren().add(canvas); Scene scene = new Scene(root); stage.setScene(scene); stage.show(); CanvasTask task = new CanvasTask(); task.valueProperty().addListener((ObservableValue observable, Canvas oldValue, Canvas newValue) -> { root.getChildren().remove(oldValue); root.getChildren().add(newValue); }); Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } private static class CanvasTask extends Task { private int strokeCount; @Override protected Canvas call() throws Exception { Canvas canvas = null; for (int i = 1; i < 15; i++) { canvas = new Canvas(W, H); GraphicsContext gc = canvas.getGraphicsContext2D(); strokeCount = 0; long start = System.nanoTime(); drawTree(gc, W / 2, H - 50, -Math.PI / 2, i); double dt = (System.nanoTime() - start) / 1_000d; gc.fillText("Depth: " + i + "; Strokes: " + strokeCount + "; Time : " + String.format("%1$07.1f", dt) + " µs", 8, H - 8); Thread.sleep(200); // simulate rendering latency updateValue(canvas); } return canvas; } private void drawTree(GraphicsContext gc, int x1, int y1, double angle, int depth) { if (depth == 0) { return; } int x2 = x1 + (int) (Math.cos(angle) * depth * 5); int y2 = y1 + (int) (Math.sin(angle) * depth * 5); gc.strokeLine(x1, y1, x2, y2); strokeCount++; drawTree(gc, x2, y2, angle - Math.PI / 8, depth - 1); drawTree(gc, x2, y2, angle + Math.PI / 8, depth - 1); } } public static void main(String[] args) { launch(args); } } 
Intereting Posts