views:

755

answers:

7

I have made a custom component (derived from JComponent) which represents a draggable Bezier-curve.
(looks like a hanging cable, someone might know it from Bender or Cubase)

My problem is: The curve may become really long, let's say from top left to bottom right corners of the desktop.

This makes Swing's repaint functionality inefficient: The area of the curve is perhaps few hundred pixels, but the area of the component (being mostly 'transparent') is millions of pixels big.

My subjection impression is:
The longer the curve, the more flicker I get when dragging it.

I hope I made myself clear about the problem.

Perhaps it would help when I somehow could choose by myself, which regions of the component needs repainting at all.

EDIT:
Such a mess! I'm profiling the application using Netbeans, which helps to find inefficient code normally, but this Swing framework is making hundreds of nested calls! I just can't figure out, what is slow and why.
By the way, disabling super.paint(...) or super.paintComponent(...) doesn't help.

+1  A: 

You can redraw a smaller portion of the screen using repaint(Rectangle r)

http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)

Then you mention flicker. Since you are using swing, which uses double buffering your flickering must be coming from something else. Are you clearing the screen in paintComponent(...)? I.e. call to fillRect(...)? Don't do that, it's not needed (IIRC).

wires
Thank you, I will try this. No, I'm not clearing anything by myself, no idea where the flicker comes from.
ivan_ivanovich_ivanoff
+2  A: 

There is no efficient way to create lots of small clip rectangles for a diagonal structure which leaves you with two strategies to avoid flickering:

  • Double buffering. This needs an enormous amount of memory but the memory copy is very fast (it usually happens in the time the "electron beam" goes back from lower right to upper left ... if there was still a beam in your LCD).

  • Don't call super.paint() (which draws or "erases" the background) and draw the curve a second time with the background color to erase it.

For more details, see this document.

[EDIT] If fillRect() wasn't abstract, you could set a break point :) Set a break point in paint(), check who calls it and whether the background got cleared at that time. It should be since rendering would be completely wrong. Then set break points further up in the call chain.

Aaron Digulla
Avoiding calling super.paint(...) is a good idea
ivan_ivanovich_ivanoff
No, doesn't help :(
ivan_ivanovich_ivanoff
Don't forget to override update(). The document I mentioned contains also lots of information how to improve rendering speed.
Aaron Digulla
+5  A: 

Check out Filthy Rich Clients by Chet Haase and Romain Guy. They address these very optimizations among others along the way to producing responsive and graphically impressive UI.

Martin OConnor
+1  A: 

Which method do yo use to paint your curve? paint or paintComponent?

adrian.tarau
It's paintComponent, but pain gives same results; BTW, I don't know who downvoted you, wasn't me.
ivan_ivanovich_ivanoff
I wasn't I, but someone splitting hairs would say that you're supposed to downvote when and answer 'is not helpful' (check the alt text). I would say this answer belongs as a comment, and if the user had enough rep to leave a comment I would have downvoted him/her. But they don't so I didn't.
Malfist
I can't add comments yet, so ...
adrian.tarau
then i'll give you a an upvote to get you back to even. that's a good reason to add a question as an answer, i guess. But next time, add more info to support why you're asking the question.
John Gardner
I've just started with StackOverflow so please excuse my mistake :)
adrian.tarau
+2  A: 

Doing all of your bezier mathematics on the paint thread everytime the component is refreshed is (as you've gathered) a bad idea. Does your curve change often? If not then why not paint it to a BufferedImage as and when it changes, and change your paint() code to simply draw the buffered image to the component instead.

class CurveComponent extends JComponent {
    private BufferedImage image;

    @Override
    public void paintComponent( Graphics g ) {
        if ( image == null ) {
            return;
        }
        g.drawImage( image, 0, 0, this );
    }

    private void updateCurve() {
        image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
        Graphics g = image.getGraphics();
        // draw the curve onto image using g.
        g.dispose();
    }
}

Only call updateCurve() when you need to and all that expensive mathematics won't be needlessly repeated. Painting should be pretty responsive, even for a fullscreen window. drawImage() will be doing a straightforward memory copy and should be lightning fast.

banjollity
Actually, I do nothing: the bezier mathematics is done by Java'sCubicCurve2D ;)
ivan_ivanovich_ivanoff
And the curve changes all the time, it is never dragged by whole, but always by dragging one of the two endpoints.
ivan_ivanovich_ivanoff
+2  A: 

Try writing a tiny test app, which consists of nothing except what you need to reproduce this problem. This will make profiling easier. Then post that app here, so we can take a look at possible solutions.

I found your question interesting so I wrote a test app myself. This draws a Bezier curve which is continually resized as you drag. I created a gradient background to ensure this works well with a nasty background. I get good performance and low flicker, although I use top-notch machine.

It pays to read "Filthy Rich Clients" to learn all the tricks of writing custom Swing components that perform really well.

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;

public class CustomComponent extends JComponent {

    private Point2D start = new Point2D.Double(0, 0);
    private Point2D end = new Point2D.Double(300, 200);

    private CustomComponent() {
        this.setOpaque(true);
        final MouseAdapter mouseAdapter = new MouseAdapter() {

            @Override
            public void mouseDragged(MouseEvent e) {
                    setEnd(e.getPoint());
            }

        };
        this.addMouseListener(mouseAdapter);
        this.addMouseMotionListener(mouseAdapter);
    }

    public void setStart(Point2D start) {
        this.start = start;
        repaint();
    }

    public void setEnd(Point2D end) {
        this.end = end;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {

        final Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // draw gradient background
        final int width = getWidth();
        final int height = getHeight();
        g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
        g2.fillRect(0, 0, width, height);

        // draw Bezier curve
        final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
        g2.setColor(Color.BLACK);
        g2.draw(shape);

        g2.drawString("Click and drag to test for flickering", 100, 20);
    }

    public static void main(String[] args) {
        final CustomComponent component = new CustomComponent();
        final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
        component.setPreferredSize(size);

        final JFrame frame = new JFrame();
        frame.add(component);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

}

Some things to note:

  • only overwrite paintComponent(Graphics g), not the other paintXXX() methods
  • set custom component to opaque if possible
  • only use repaint() to request repainting. Never directly order a repaint directly in your code. This lets Swing handle it well.
Steve McLeod
A: 

My solution was a partial re-design:

Now I don't represent each "cable"-element by a component.
Now, cables are just dummy objects (with no involved JComponent).
The repaint takes place "globally", on the content pane of the parent JFrame.

Now it's efficient, and flickers less.

ivan_ivanovich_ivanoff