4

I want to display a relative long diagram. I used to work with javafx canvas, but sometimes I get a buffer overflow exception, when there are to many values drawn. I was looking for a different approach and found a way to draw a diagram with java.awt.graphics2d. The advantage with graphics2d is that the performance improved and I don't get an exception. The disadvantage is the quality of the diagram. With graphics2d the diagram doesn't look as smooth as the diagram in a canvas in javafx.

Here a two test application (one with javafx canvas and one with java.awt.graphics2d) where a sine wave is drawn.

How can I improve the quality of the graphics2d example, so that it looks like the canvas example? Or is there a different approach that I haven't thought of that would solve my problem?

Canvas Example

public class CanvasDraw extends Application
{
    private static final int PANEL_WIDTH = 400, PANEL_HEIGHT = 100;


    public static void main(String[] args)
    {
        launch(args);
    }


    @Override
    public void start(Stage stage)
    {
        stage.setTitle("CanvasDraw");
        StackPane root = new StackPane();
        Scene scene = new Scene(root);
        root.getChildren().add(createCanvas());
        stage.setScene(scene);
        stage.show();
    }


    private Canvas createCanvas()
    {
        Canvas canvas = new Canvas(PANEL_WIDTH, PANEL_HEIGHT);
        GraphicsContext gc = canvas.getGraphicsContext2D();
        drawSinus(gc);
        return canvas;
    }


    private void drawSinus(GraphicsContext gc)
    {
        double height = 50;
        double xFactor = 0.5;
        double yFactor = 50;
        for (int index = 0; index < 720; index++)
        {
            double x = index * xFactor;
            double y = Math.sin(Math.toRadians(index)) * yFactor + height;
            if (index == 0)
            {
                gc.moveTo(x, y);
            }
            else
            {
                gc.lineTo(x, y);
            }
        }
        gc.stroke();
    }
}

NEW Graphics2D example

public class Graphics2DDraw extends Application
{
    private static final int PANEL_WIDTH = 400, PANEL_HEIGHT = 100;


    public static void main(String[] args)
    {
        launch(args);
    }


    @Override
    public void start(Stage stage)
    {
        stage.setTitle("Graphics2DDraw");
        StackPane root = new StackPane();
        Scene scene = new Scene(root);
        root.getChildren().add(createImageView());
        stage.setScene(scene);
        stage.show();
    }


    private ImageView createImageView()
    {
        BufferedImage bi = new BufferedImage(PANEL_WIDTH, PANEL_HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = bi.createGraphics();
        g.setColor(Color.BLACK);
        BasicStroke bs = new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        g.setStroke(bs);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
        g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        drawSinus(g);
        return new ImageView(SwingFXUtils.toFXImage(bi, null));
    }


    private void drawSinus(Graphics2D g)
    {
        double height = 50;
        double xFactor = 0.5;
        double yFactor = 50;
        int length = 720;
        int lastX = 0;
        int lastY = 0;
        GeneralPath path = new GeneralPath();
        for (int index = 0; index < length; index++)
        {
            double x3 = index * xFactor;
            double y3 = Math.sin(Math.toRadians(index)) * yFactor + height;
            if (index == 0)
            {
                path.moveTo(x3, y3);
            }
            else
            {
                path.lineTo(x3, y3);
            }
        }
        g.draw(path);
        g.dispose();
    }
}

EDIT this is what the buffer overflow exception looks like:

java.nio.BufferOverflowException
    at com.sun.javafx.sg.prism.GrowableDataBuffer.ensureReadCapacity(GrowableDataBuffer.java:317)
    at com.sun.javafx.sg.prism.GrowableDataBuffer.getInt(GrowableDataBuffer.java:527)
    at com.sun.javafx.sg.prism.GrowableDataBuffer.getFloat(GrowableDataBuffer.java:563)
    at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:960)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:609)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:477)
    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:330)
    at com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:134)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
    at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalArgumentException: ALPHA color component is out of range
    at com.sun.pisces.PiscesRenderer.checkColorRange(PiscesRenderer.java:113)
    at com.sun.pisces.PiscesRenderer.setColor(PiscesRenderer.java:105)
    at com.sun.prism.sw.SWPaint.setColor(SWPaint.java:76)
    at com.sun.prism.sw.SWPaint.setPaintBeforeDraw(SWPaint.java:118)
    at com.sun.prism.sw.SWPaint.setPaintFromShape(SWPaint.java:86)
    at com.sun.prism.sw.SWGraphics.paintShape(SWGraphics.java:493)
    at com.sun.prism.sw.SWGraphics.drawLine(SWGraphics.java:545)
    at com.sun.javafx.sg.prism.NGCanvas.handleRenderOp(NGCanvas.java:1219)
    at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:1103)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:609)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:235)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:577)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:477)
    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:330)
    at com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:134)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
    at java.lang.Thread.run(Thread.java:748)

EDIT 2: I found a way to improve the quality of the graphics2d drawing. By setting the rendering hints (thank you Hylian Pickachu for the advice) for the graphics2d object, the sine curve looks smoother. It's not as sharp as the sine curve in the canvas example, but I think I can live with the result now.

Yupp
  • 315
  • 3
  • 18
  • 1
    Could you include an example stack trace of your buffer overflow exception, please? – Slaw Mar 12 '20 at 08:28
  • sorry I forgot to do that. This is what it looks like when there are too many values drawn into the canvas. I also have a test application (similar to the app in this link https://stackoverflow.com/questions/44136040/performance-of-javafx-gui-vs-swing) where the exception can be reproduced. If you want to, I can add that code as well – Yupp Mar 12 '20 at 10:31
  • 1
    Please do not add the entire code but rather an [mre]. Are you updating the gui from a thread ? – c0der Mar 12 '20 at 12:32
  • yes I'm updating the gui from a service, so the app does not freeze while updating the view – Yupp Mar 12 '20 at 12:40
  • Is the UI update executed on the _JavaFX Application Thread_? The error(s) you're getting don't mention your code at all which can be a good indicator of concurrency issues. – Slaw Mar 12 '20 at 14:37
  • Yes, when the thread is successful, the UI will be updated. If I'm not running the draw function in a thread, it takes too long until the view is ready. – Yupp Mar 13 '20 at 06:43
  • Cannot reproduce your issue with JavaFX using the code posted in the question... – fabian Mar 17 '20 at 17:26
  • I'm not getting the exception in this code. This one is to compare the quality of the drawn sine curve in javafx canvas to the one with awt graphics2d. I would like to know if it is possible to improve the quality of the graphics2d example to the one in the javafx canvas – Yupp Mar 18 '20 at 05:49
  • 1
    Did you try using RenderingHints? Link here:https://docs.oracle.com/javase/tutorial/2d/advanced/quality.html – Fluffy the Togekiss Mar 19 '20 at 17:58
  • Cool! Glad to help! I went ahead and restated my comment as an answer, as I do very much enjoy collecting fake internet points. – Fluffy the Togekiss Mar 20 '20 at 21:13

1 Answers1

1

One way to improve Graphics2D quality is by using RenderingHints. RenderingHints allow you to set the balance of rendering quality vs. rendering performance, which is a trade-off to consider, as you said that you did enjoy the performance boost from using Graphics2D over the Canvas. However, this approach will allow you to continue using Graphics2D and avoid crashes.