Rendimiento de JavaFx Gui vs Swing
performance (1)
El ejemplo Swing aplana la imagen a 600 2 = 360,000 píxeles. Por el contrario, el ejemplo de JavaFX traza casi 2,4 millones de polígonos superpuestos cuando finalmente se representa. Tenga en cuenta que su ejemplo JavaFX mide tanto el tiempo para componer el fractal como el tiempo para representarlo en el gráfico de escena .
Si desea conservar los trazos que comprenden el fractal, componga el resultado en un
Canvas
, como se muestra
aquí
.
Si una
Image
plana es suficiente, redacte el resultado en una imagen
BufferedImage
,
BufferedImage
en una
Image
JavaFX y muéstrela en un
ImageView
, como se muestra a continuación.
El resultado de JavaFX es más de un segundo
más rápido
que el ejemplo de Swing en mi hardware.
Debido a que
SwingFXUtils.toFXImage
realiza una
copia
, una
Task<Image>
segundo plano podría continuar actualizando una única imagen
BufferedImage
mientras publica los resultados de la
Image
provisional a través de
updateValue()
.
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* @see https://stackoverflow.com/q/44136040/230513
*/
public class BufferedImageTest extends Application {
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 final int numberOfLevels = 13;
private int countTriangles;
@Override
public void start(Stage stage) {
stage.setTitle("BufferedImageTest");
StackPane root = new StackPane();
Scene scene = new Scene(root);
root.getChildren().add(new ImageView(createImage()));
stage.setScene(scene);
stage.show();
}
private Image createImage() {
BufferedImage bi = new BufferedImage(
PANEL_WIDTH, PANEL_HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setPaint(Color.white);
g.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
Point top = new Point(PANEL_WIDTH / 2, TOP_GAP);
Point left = new Point(SIDE_GAP, TOP_GAP + TRI_HEIGHT);
Point right = new Point(SIDE_GAP + TRI_WIDTH, TOP_GAP + TRI_HEIGHT);
g.setColor(Color.red);
long startTime = System.currentTimeMillis();
drawTriangle(g, numberOfLevels, top, left, right);
g.setPaint(Color.black);
g.drawString("Number of triangles: " + countTriangles, 15, 15);
g.drawString("Time : " + (System.currentTimeMillis() - startTime) + " ms", 15, 35);
g.drawString("Levels: " + numberOfLevels, 15, 50);
WritableImage image = SwingFXUtils.toFXImage(bi, null);
g.dispose();
return image;
}
private void drawTriangle(Graphics2D g, int levels, Point top, Point left, Point right) {
if (levels < 0) {
return;
}
countTriangles++;
Polygon tri = new Polygon();
tri.addPoint(top.x, top.y);
tri.addPoint(left.x, left.y);
tri.addPoint(right.x, right.y);
g.drawPolygon(tri);
// Get the midpoint on each edge in the triangle
Point p12 = midpoint(top, left);
Point p23 = midpoint(left, right);
Point p31 = midpoint(right, top);
// recurse on 3 triangular areas
drawTriangle(g, levels - 1, top, p12, p31);
drawTriangle(g, levels - 1, p12, left, p23);
drawTriangle(g, levels - 1, p31, p23, right);
}
private Point midpoint(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
public static void main(String[] args) {
launch(args);
}
}
Escribí dos programas simples, ambos dibujan el mismo Triángulo de Sierpinski:
Un programa se implementó con swing y otro con javafx.
Hay una diferencia de rendimiento muy significativa, la implementación de swing es consistentemente mucho más rápida:
(En este caso de prueba: Balancee durante 1 segundo. Javafx durante 12 segundos)
¿Es de esperar o hay algo muy mal con mi implementación de Java?
Implementación de swing
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleSrpnskTriSw {
private Triangles triPanel;
SimpleSrpnskTriSw(int numberOfLevels){
JFrame frame = new JFrame("Sierpinski Triangles (swing)");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
triPanel = new Triangles();
frame.add(triPanel, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setVisible(true);
triPanel.draw(numberOfLevels);
}
class Triangles extends JPanel{
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;
boolean working;
private int numberOfLevels = 0;
Triangles() {
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_HEIGHT));
startTime = System.currentTimeMillis();
countTriangles = 0;
working = true;
draw();
}
void draw(int numLevels) {
numberOfLevels = numLevels;
working = true;
draw();
}
void draw() {
startTime = System.currentTimeMillis();
countTriangles = 0;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
repaint();
}
});
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setFont(new Font("Ariel", Font.PLAIN, 14));
if(working) {
g.setColor(getBackground());
g.fillRect(0,0,PANEL_WIDTH,PANEL_HEIGHT);
g.setColor(getForeground());
g.drawString("Working.........", 15, 15);
working = false;
return;
}
if(numberOfLevels <= 0 ) {
return;
}
Point top = new Point(PANEL_WIDTH/2, TOP_GAP);
Point left = new Point(SIDE_GAP, TOP_GAP+ TRI_HEIGHT);
Point right = new Point(SIDE_GAP + TRI_WIDTH, TOP_GAP+ TRI_HEIGHT);
BufferedImage bi = getBufferedImage(top, left, right);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(bi,0,0, this);
g.drawString("Number of triangles: "+ countTriangles, 15, 15);
g.drawString("Time : "+ (System.currentTimeMillis()- startTime)+ " mili seconds", 15, 35);
g.drawString("Levels: "+ numberOfLevels, 15, 50);
}
private BufferedImage getBufferedImage(Point top, Point left, Point right) {
BufferedImage bi = new BufferedImage(PANEL_WIDTH,PANEL_HEIGHT,
BufferedImage.TYPE_INT_ARGB);
drawTriangle(bi, numberOfLevels, top, left, right);
return bi;
}
private void drawTriangle(BufferedImage bi, int levels, Point top, Point left, Point right) {
if(levels < 0) {
return ;
}
countTriangles++;
Graphics g = bi.getGraphics();
g.setColor(Color.RED);
Polygon tri = new Polygon();
tri.addPoint(top.x, top.y); //use top,left right rather than fixed points
tri.addPoint(left.x, left.y);
tri.addPoint(right.x, right.y);
g.drawPolygon(tri);
// Get the midpoint on each edge in the triangle
Point p12 = midpoint(top, left);
Point p23 = midpoint(left, right);
Point p31 = midpoint(right, top);
// recurse on 3 triangular areas
drawTriangle(bi, levels - 1, top, p12, p31);
drawTriangle(bi, levels - 1, p12, left, p23);
drawTriangle(bi, levels - 1, p31, p23, right);
}
private Point midpoint(Point p1, Point p2) {
return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
}
public static void main(String[] args) {
new SimpleSrpnskTriSw(13);
}
}
Implementación de JavaFx
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.image.WritableImage;
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 SimpleSrpnskTriFx 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)) {
try {
int num = Integer.parseInt(args[0]);
numberOfLevels = num ;
} catch (NumberFormatException ex) {
ex.printStackTrace();
return;
}
}
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 Canvas backgroundCanvas;
private GraphicsContext gc;
Triangles(){
setPrefSize(PANEL_WIDTH, PANEL_HEIGHT);
canvas = getCanvas();
backgroundCanvas = getCanvas();
gc = backgroundCanvas.getGraphicsContext2D();
getChildren().add(canvas);
draw(numberOfLevels);
}
void draw(int numberLevels) {
Platform.runLater(new Runnable() {
@Override
public void run() {
canvas.getGraphicsContext2D().fillText("Working....",5,15);
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) {
copyCanvas();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.fillText("Number of triangles: "+ countTriangles,5,15);
gc.fillText("Time : "+ (System.currentTimeMillis()- startTime )+ " mili seconds", 5,35);
gc.fillText("Levels: "+ numberOfLevels,5,55);
}
}
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 copyCanvas() {
WritableImage image = backgroundCanvas.snapshot(null, null);
canvas.getGraphicsContext2D().drawImage(image, 0, 0);
}
/**
*/
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");
}
}