A DESCRIPTION OF THE REQUEST :
Given a JFXPanel that has a Canvas component that is drawn on using GraphicContext.strokeLine() etc. methods. Rendering of the panel is visibly inferior in some cases than that of the pure JavaFX counterpart.
JUSTIFICATION :
We want to write a component that renders on FX Canvas and can be used in JFXPanel to provide Swing compatibility at the same time.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Rendering quality in JFXPanel should be the same as in the FX-counterpart
ACTUAL -
(given in the description)
---------- BEGIN SOURCE ----------
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.IOException;
/**
*
*/
public class ImprovedGraphicsExample {
private JFrame frame;
private ImprovedGraphicsExample() {
ImprovedJFXPanel p = new ImprovedJFXPanel();
frame = createFrame("Improved rendering for JFXPanel", p, 320, 220);
Platform.runLater(new Runnable() {
@Override
public void run() {
p.init();
}
});
}
private JFrame createFrame(String title, JComponent component, int width, int height) {
final Dimension dimension = new Dimension(width, height);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(dimension);
frame.setSize(dimension);
frame.add(component);
frame.setTitle(title);
frame.pack();
return frame;
}
private void show() {
frame.setVisible(true);
}
public static void main(String[] args) throws IOException, InterruptedException {
ImprovedGraphicsExample app = new ImprovedGraphicsExample();
SwingUtilities.invokeLater(app::show);
}
///////////////////////////////////////
private class ImprovedJFXPanel extends JFXPanel {
private final double internalScale = 1;
private Canvas canvas;
ImprovedJFXPanel() {
}
void init() {
Scene scene = createScene();
setScene(scene);
}
private Scene createScene() {
canvas = new Canvas();
canvas.setWidth(getWidth());
canvas.setHeight(getHeight());
scaleCanvas();
Group root = new Group();
root.getChildren().add(canvas);
drawLine();
return new Scene(root);
}
private void drawLine() {
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.save();
graphicsContext.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);
graphicsContext.setStroke(Color.BLACK);
graphicsContext.setLineWidth(1.2);
double x1 = 0;
double y1 = -40;
int count = 6;
for (int i = 0; i < count; i++) {
double d = 2.2 * i;
double x2 = 60 * Math.cos(d * Math.PI/(2 * count));
double y2 = 60 * Math.sin(d * Math.PI/(2 * count));
graphicsContext.strokeLine(x1, y1, x2, y2);
}
graphicsContext.restore();
}
@Override
public void paintComponent(Graphics g) {
double s = 1 / internalScale;
// scale-down what was scaled-up in FX graphics
((Graphics2D) g).scale(s, s);
super.paintComponent(g);
}
// scale up canvas for FX
private void scaleCanvas() {
canvas.setScaleX(internalScale);
canvas.setScaleY(internalScale);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Above code exemplifies a workaround as well. If internalScale is changed to 2 rendering improves. Essentially, rendering process is the following:
- scale GraphicsContext
- draw sample
- scale-down Graphics2D
Given a JFXPanel that has a Canvas component that is drawn on using GraphicContext.strokeLine() etc. methods. Rendering of the panel is visibly inferior in some cases than that of the pure JavaFX counterpart.
JUSTIFICATION :
We want to write a component that renders on FX Canvas and can be used in JFXPanel to provide Swing compatibility at the same time.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Rendering quality in JFXPanel should be the same as in the FX-counterpart
ACTUAL -
(given in the description)
---------- BEGIN SOURCE ----------
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.io.IOException;
/**
*
*/
public class ImprovedGraphicsExample {
private JFrame frame;
private ImprovedGraphicsExample() {
ImprovedJFXPanel p = new ImprovedJFXPanel();
frame = createFrame("Improved rendering for JFXPanel", p, 320, 220);
Platform.runLater(new Runnable() {
@Override
public void run() {
p.init();
}
});
}
private JFrame createFrame(String title, JComponent component, int width, int height) {
final Dimension dimension = new Dimension(width, height);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setPreferredSize(dimension);
frame.setSize(dimension);
frame.add(component);
frame.setTitle(title);
frame.pack();
return frame;
}
private void show() {
frame.setVisible(true);
}
public static void main(String[] args) throws IOException, InterruptedException {
ImprovedGraphicsExample app = new ImprovedGraphicsExample();
SwingUtilities.invokeLater(app::show);
}
///////////////////////////////////////
private class ImprovedJFXPanel extends JFXPanel {
private final double internalScale = 1;
private Canvas canvas;
ImprovedJFXPanel() {
}
void init() {
Scene scene = createScene();
setScene(scene);
}
private Scene createScene() {
canvas = new Canvas();
canvas.setWidth(getWidth());
canvas.setHeight(getHeight());
scaleCanvas();
Group root = new Group();
root.getChildren().add(canvas);
drawLine();
return new Scene(root);
}
private void drawLine() {
GraphicsContext graphicsContext = canvas.getGraphicsContext2D();
graphicsContext.save();
graphicsContext.translate(canvas.getWidth() / 2, canvas.getHeight() / 2);
graphicsContext.setStroke(Color.BLACK);
graphicsContext.setLineWidth(1.2);
double x1 = 0;
double y1 = -40;
int count = 6;
for (int i = 0; i < count; i++) {
double d = 2.2 * i;
double x2 = 60 * Math.cos(d * Math.PI/(2 * count));
double y2 = 60 * Math.sin(d * Math.PI/(2 * count));
graphicsContext.strokeLine(x1, y1, x2, y2);
}
graphicsContext.restore();
}
@Override
public void paintComponent(Graphics g) {
double s = 1 / internalScale;
// scale-down what was scaled-up in FX graphics
((Graphics2D) g).scale(s, s);
super.paintComponent(g);
}
// scale up canvas for FX
private void scaleCanvas() {
canvas.setScaleX(internalScale);
canvas.setScaleY(internalScale);
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Above code exemplifies a workaround as well. If internalScale is changed to 2 rendering improves. Essentially, rendering process is the following:
- scale GraphicsContext
- draw sample
- scale-down Graphics2D