计时 JavaFX Canvas 应用程序 [英] Timing JavaFX Canvas Application

查看:28
本文介绍了计时 JavaFX Canvas 应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了练习 JavaFX,我构建了一个简单的应用程序来绘制

我遇到的问题:

一个.updateGraphics() 的时间打印输出显示在三角形绘制完成之前(在我的机器上为 8 秒)long,因此它没有测量完整的过程.我该如何改进?

B.在我的机器上,面板完全绘制需要 30-35 秒.一个类似的 Swing 应用程序需要 4 秒.这可能表明我的 javafx 实现存在根本性的错误.

解决方案

您的

import javafx.application.Application;导入 javafx.beans.value.ObservableValue;导入 javafx.concurrent.Task;导入 javafx.scene.Scene;导入 javafx.scene.canvas.Canvas;导入 javafx.scene.canvas.GraphicsContext;导入 javafx.scene.layout.StackPane;导入 javafx.stage.Stage;/*** @see https://stackoverflow.com/a/44056730/230513*/公共类 CanvasTaskTest 扩展应用程序 {私有静态最终整数 W = 800;私有静态最终 int H = 600;@覆盖公共无效开始(阶段阶段){stage.setTitle("CanvasTaskTest");StackPane root = new StackPane();Canvas canvas = new Canvas(W, H);root.getChildren().add(canvas);场景场景 = 新场景(根);stage.setScene(场景);舞台表演();CanvasTask 任务 = new CanvasTask();task.valueProperty().addListener((ObservableValue observable, Canvas oldValue, Canvas newValue) -> {root.getChildren().remove(oldValue);root.getChildren().add(newValue);});线程线程=新线程(任务);线程.setDaemon(真);线程开始();}私有静态类 CanvasTask 扩展了 Task{私人 intstrokeCount;@覆盖受保护的画布调用()抛出异常{画布 canvas = null;for (int i = 1; i <15; i++) {画布 = 新画布(宽,高);GraphicsContext gc = canvas.getGraphicsContext2D();中风计数 = 0;长开始 = System.nanoTime();drawTree(gc, W/2, H - 50, -Math.PI/2, i);double dt = (System.nanoTime() - start)/1_000d;gc.fillText("深度:" + i+ "; 笔画:" + strokeCount+ "; 时间 : " + String.format("%1$07.1f", dt) + " µs", 8, H - 8);线程睡眠(200);//模拟渲染延迟更新值(画布);}返回画布;}private void drawTree(GraphicsContext gc, int x1, int y1, double angle, int depth) {如果(深度== 0){返回;}int x2 = x1 + (int) (Math.cos(angle) * depth * 5);int y2 = y1 + (int) (Math.sin(angle) * depth * 5);gc.strokeLine(x1, y1, x2, y2);中风计数++;drawTree(gc, x2, y2, 角度 - Math.PI/8, 深度 - 1);drawTree(gc, x2, y2, 角度 + Math.PI/8, 深度 - 1);}}公共静态无效主(字符串 [] args){发射(参数);}}

I order to practice JavaFX, I built a simple app that draws Sierpinski Triangles.

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<Void>{

            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");
    }
}


The output is as expected :

The problems I have:

a. The time printout at updateGraphics() shows long before (8 seconds on my machine) the drawing of the triangles is completed, hence it doesn't measure the complete process. How do I improve it ?

b. On my machine it takes 30-35 seconds until the panel is completely drawn. A similar swing application takes on 4 seconds. It may suggest that there is something fundamentally wrong with my javafx implementation.

解决方案

Your Task invokes drawTriangle() in the background to update a Canvas. The associated GraphicsContext requires that "Once a Canvas node is attached to a scene, it must be modified on the JavaFX Application Thread." Your deeply recursive call blocks the JavaFX Application Thread, preventing a timely screen update. In contrast, your platform's implementation of System.out.println() may allow it to report in a timely way. The timing disparity is seen even without a Task at all.

Happily for Canvas, "If it is not attached to any scene, then it can be modified by any thread, as long as it is only used from one thread at a time." One approach might be suggested in A Task Which Returns Partial Results. Create a notional Task<Image> that updates a detached Canvas in the background. Periodically, perhaps at each level of recursion, copy the Canvas and publish a snapshot via updateValue(). The enclosing Pane can listen to the task's value property and update an enclosed Canvas via drawImage() without blocking the JavaFX Application Thread.

Sadly, snapshot "Throws IllegalStateException if this method is called on a thread other than the JavaFX Application Thread."

In the alternative shown below, CanvasTask extends Task<Canvas> and publishes a new Canvas on each iteration of a loop. The enclosing CanvasTaskTest listens to the value property and replaces the previous Canvas each time a new one arrives. The example below displays a series of fractal trees of increasing depth and the time needed to compose each. Note that in a GraphicsContext, "Each call pushes the necessary parameters onto the buffer where they will be later rendered onto the image of the Canvas node by the rendering thread at the end of a pulse." This allows JavaFX to leverage a platform's rendering pipeline, but it may impose an additional overhead for a large number of strokes. In practice, tens of thousands of strokes slow rendering imperceptibly, while millions of overlapping strokes may be superfluous.

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<? extends Canvas> 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<Canvas> {

        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);
    }

}

这篇关于计时 JavaFX Canvas 应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆