How can I repaint efficiently when using big custom component in Swing? - java

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.

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.

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.

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.

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.

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).

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

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.

just use getVisibleRect(); inside paintComponent(Graphics g) to get the area you actually need to redraw

Related

Repaint all components of a JFrame/JPanel

I'm working on some animation where I have a certain number of dots wandering around my JFrame and based on their distance they should be connected by lines of different strengths.
The base code for moving the dots works and actually I also had them displayed correctly in the beggining but I had some issues where the movement was stuttering (probably due to the repaint process). At that point the Window class handled the entire repaint procedure.
After reading some posts around here I adapted my code according to the github page linked in this post to use the individual Dots as JComponents and have them being repainted individually. However, now the problem is that although I still have 100 Dots as components on my JPanel, only one of them is being painted (however, the stuttering is gone at least). I also see that all components are being iterated and their repaint method is being called but they just don't display.
This is my paintComponent method in the Dot class:
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setPaint(new Color(0, 0, 0));
Ellipse2D.Double circle = new Ellipse2D.Double(x - 10 / 2, y - 10 / 2, 10, 10);
g2d.fill(circle);
}
And this is what my repaintTimer looks like:
final Timer repaintTimer = new Timer(20, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for(Component component : window.getContentPane().getComponents()) {
component.repaint();
}
recalculateDots();
}
});
repaintTimer.start();
The result I get is something like this:
I tried some things of which I thought that it could solve the problem but nothing really helped and I'm really confused as to why this is happening. I'd appreciate any help very much because this issue doesn't make any sense for me.

PaintComponent Java Slow

I have been developing on my Mac a JAVA Applications . The logic is as follows:
A Server sends to a client application some orders to Draw basic shapes
The client applications draws the the basic shapes into a Jpanel
Every Time a Shape arrives the program calls repaint()
public void paintShape(Shape p)
{
//this.paintComponent(this.getGraphics());
arrayofShapes.add(p);
this.repaint();
//this.updateUI();
//this.update(this.getGraphics());
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g2d = (Graphics2D) g.create();
g2d.setStroke(new BasicStroke(2));
g2d.setColor(pickedColor);
for(final Shape p : arrayofShapes)
{
g2d.draw(p);
}
//g2d.dispose();
}
Everything works smoothly(on real time) , so I decided to test the same application on a Windows computer. The result is a laggy application. These are the conclusion that I have reached.
RepaintManager is accumulating repaint() calls. I see how the shapes arrive at destination but in some cases more than 5 repaint calls are accumulated into one, which make the application very lagged/not real Time.
I have tried instead of calling repaint every time a shape arrives to do it with a Timer every few milliseconds, the result is the same. Code :
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
repaint();
}
};
Timer displayTimer = new Timer(5, listener);
displayTimer.start();
In addition i have tested some random code that allows you to paint with the mouse, same logic as mine with paintComponent. In this case it work smoothly without sense of lag.... Example: http://javagraphics.blogspot.com.es/2010/06/shapes-implementing-freehand-pencil.html
I do not understand why paintComponent is so slow on my Windows Computer(same Jar). What could be affecting the performance of my program?
I have read all the answers regarding paint Components but any of them has solved this issue.
Any advice on how could I solve the problem and actually archive Real-Time?
Thank you in advance
Update Videos:
Mac Video:https://youtu.be/OhNXdGXoQpk real Time no problem handling heavy load
Windows Video https://youtu.be/yol2miHudZc clearly laggy
I apologize for the low quality
Update BufferedImage:
After introducing the BufferedImage the result is still a slow painting Application. It creates another problem, since one of the orders is to delete all shapes, it adds some complexity since I have to do a :
g2d.clearRect(0, 0, screenSize.width, screenSize.height);
HW/OS/JavaVersion
Windows
Processor i5-4300u 2.5ghz
Ram 12gb
Java version 1.7.0_71
MAC
Processor i7 2.9ghz
Ram 8gb
Java version 1.7.0_67
Java VisualVM
Video of live VisualVM:https://youtu.be/cRNX4b3rlZk
I do not see anything strange that could explain why the lag occurs but I'm far from being an expert(Again sorry for low quality)
Thank you for all your responses
There's no need to create() a new graphics context each time; just cast g to Graphics2D. This is safe on all concrete implementations. This also obviates the need to dispose() of the created context. As noted here, preserve any context variables that may be critical for later painting.
public void paintComponent(Graphics g) {
super.paintComponent(g);
g2d = (Graphics2D) g;
Stroke oldStroke = g2d.getStroke();
g2d.setStroke(new BasicStroke(2));
g2d.setColor(pickedColor);
for(final Shape p : arrayofShapes) {
g2d.draw(p);
}
g2d.setStroke(oldStroke);
}
Also, compare the profiles on both platforms to look for disparities. For reference, the example cited here comfortably handles selections containing hundreds of shapes on either platform.
I would recommend that you do static drawing to a BufferedImage, and then draw the BufferedImage in your paintComponent method.
e.g.,
private BufferedImage bufferedImage = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_ARGB);
public void paintShape(Shape p) {
Graphics2D g2 = bufferedImage.createGraphics();
g2d.setStroke(MY_STROKE); // make this a constant
g2d.setColor(pickedColor);
g2d.draw(p);
g2d.dispose();
this.repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (bufferedImage != null) {
g2.drawImage(bufferedImage, 0, 0, null);
}
// do dynamic drawing such as drawing of moving sprites here
}
After more than two days of debugging I have found out that the problem has nothing to do with paintComponent()
With the same server generating random shapes
At the Windows app, some shapes are received at the same time which is impossible since I am sending shapes every 15 ms. That's why it accumulates shapes(result =lag).
On the other Hand at the Mac app, every shape has different time reception (result = real Time)
Thank you for the kind responses, and sorry for inconvenience I may have cause

Translate/Rotate/Move a graphics object without messing up the whole screen

I'm coding a GUI that will be doing some graphics translations/rotations, etc.
My problem is that when I try to translate my graphics,
(a) The entire screen translates instead of my one little painted area
(b) The old paint stays there, leaving a big paint blob instead of a translated image
(c) If I use the clearRect method to allow me to avoid (b), the entire screen goes white and (a) is still a problem
my DrawPanel class (I called it "LaunchTubeImage" for whatever reason)
private class LaunchTubeImage extends JPanel {
private Color colour;
public LaunchTubeImage(Color color) {
super();
this.colour = color;
}
public void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,
tubeImage.getSize().height - 50);
g.setColor(colour);
g.clearRect(0,0,getWidth(),getHeight());
g.fillRect(tubeImage.getSize().width/2,
tubeImage.getSize().height - 100 , 10, 50);
}
}
where this is called in my code
tubeImage = new LaunchTubeImage(Color.MAGENTA);
angle.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
tubeImage.repaint();
}
});
Case 1: Comment out clearRect in that 1st block of code I posted
http://i58.tinypic.com/2d1l5w2_th.png
Black background as desired. Not rotated yet. Looks good so far.
http://oi60.tinypic.com/1zw1sm.jpg
Rotated it with my JSpinner... you see that the previous location was not removed (and note how my buttons randomly doubled and put themselves at the top of the screen).
Case 2: Keeping in the clearRect method
oi57.tinypic.com/2s84307.jpg
Layout is fine so far, but I wanted the background to be black
oi57.tinypic.com/4rde8x.jpg
Yay! It rotated. But note the weird behavior of that random "15" that appeared in my top right corner
oi58.tinypic.com/vymljm.jpg
And finally... when I resize the window you see that my entire screen was rotated - not just the pink image I wanted to rotate
Tips/fixes/advice? Thanks!! I hope I've provided enough information
(P.s. if you insist on us asking clear/useful questions.... then DON'T limit the number of images you can post... :/ )
The first line of an overridden paintComponent method should usually be super.paintComponent(g). On a JPanel, this will cause the drawing area to be cleared with the background color. If you want to clear the background with a different color, you can do this by manually filling a rectangle (clearRect is discouraged, see the JavaDoc), but of course, this has to be done before applying any transform.
So your method should probably look like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colour);
g.fillRect(0,0,getWidth(),getHeight());
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,tubeImage.getSize().height - 50);
gg.fillRect(tubeImage.getSize().width/2,tubeImage.getSize().height - 100 , 10, 50);
}

how do i stop repeat calls to java paint() replacing the last one?

I create a window with a panel with custom panel class and paint circles on it based on array data. How do I get both sets of circles to stay on screen?
import java.awt.Color;
import java.awt.*;
import javax.swing.*;
class DataPaint extends JPanel {
static int offsetY = 0;
int leftX = 20;
int[] guessColours;
Color purple = new Color(155, 10, 255);
Color pink = new Color(255, 125, 255);
Color[] Colours = { Color.blue, Color.cyan, Color.green, Color.orange,
pink, purple, Color.red, Color.yellow };
public DataPaint() {
setBackground(Color.WHITE);
}
public void paintClues(int[] guessColours) {
this.guessColours = guessColours;
offsetY += 30;
}
// naive attempt to make it work !!!!
// what is diff between paintComponent and paint?
public void update(Graphics g) {
paintComponent(g);
}
// paint circles based on array data
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (int i = 0; i < guessColours.length; i++) {
g2.setPaint(Colours[guessColours[i]]);
g2.fillOval(leftX + (i * 30), offsetY, 20, 20);
}
}
// create window with panel and paint circles on it based on array data
public static void main(String args[]) {
JFrame frame = new JFrame("data paint");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
DataPaint panel = new DataPaint();
frame.add(panel);
frame.setVisible(true);
int[] cols = { 2, 4, 5, 3, 6 };
int[] cols2 = { 1, 3, 7, 3, 4 };
// the second call replaces the first call on the panel?
panel.paintClues(cols);
panel.paintClues(cols2);
}
}
You don't stop it. The way Swing, and most GUI frameworks, work is that the paintComponent method or its analogue is always supposed to draw everything from scratch.
There are several reasons for this. One is is that if the window were resized, or any other sort of layout change occurred, or if the data-set being drawn changed in a complex way, you will need to be able to redraw anyway. Also, some window systems do not even store what is drawn in the window permanently, so you need to be able to redraw if your window is covered then uncovered. It is possible to have a component which has a permanent image that you can draw into, but that is not the usual way to do things and is less efficient unless you're writing, say, a paint program.
Change your data structures so that you keep all the information, and write your paintComponent so that it draws everything you want on screen every time it is called.
(There are refinements to make this efficient for partial updates of complex graphics, but you don't need to worry about those yet, as this is such a simple case. If you did need to do this, you would ask Swing to repaint a small region of your component (such as with JComponent.repaint(Rectangle r); it would then automatically prohibit drawing to areas outside that region while it calls your paintComponent method. That works fine to prevent flicker and save some filling work; then if it really matters for efficiency, inside paintComponent you compare that clip region (Graphics.getClip()) to what you're drawing and skip everything that doesn't intersect. But you really shouldn't worry about that yet for this simple case. First get your code working and using Swing correctly, then optimize if it matters.)
In your particular example, which I take it is intended to be a Mastermind game (you should mention that up front to help us read your code), put cols and cols2 into an int[][] field of DataPaint, and then use a loop inside of paintComponent to read every sub-array in that array and paint all of them.

Jlabel with Image Fade Out Strange Effect

Hope this question could emphasize more about the fading out effect of Jlabel (swing).
Certainly, yes... I already follow some guide and some answers given from This Link Thread, but mine is quite a bit different. It's not just only A text inside the JLabel, there's an image i put on.
I proceed to follow on the Thread Located out of stackoverflow. And yet, it gives me a fade out effect. But there's horrible thing occured; the white background.
How to solve this out?
I share the interface here...
The First screenshot taken here is the earlier phase when the fade out have not occured yet. While,
The Second screenshot taken here is the unwanted result i mentioned.
Tobe honest, I used the Trident Library to do animatiing;
So, whenever the user click over the image it will execute this code;
Timeline tm = new Timeline(jll_btnProceed);
tm.addPropertyToInterpolate("intensity", 1.0f, 0.0f);
tm.setDuration(1000);
tm.play();
and... the JLabel itself, I used to override it using this source code;
/**
*
* #author Gumuruh
*/
public class JLabelFader extends JLabel {
private float intensity = 1.0f;
public void setIntensity(float intensity) {
this.intensity = intensity;
this.setOpaque(false);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
final Composite oldComposite = g2.getComposite();
g2.setComposite(AlphaComposite.SrcOver);
final Color c = getBackground();
final Color color = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (255 * (1.0f - intensity)));
g2.setColor(color);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setComposite(oldComposite);
}
}
My hand and my head can't stop for making this trully solved. Thus I tried to follow up some example from the Java Filthy Rich Client ebook and then applying the source code given below, but first I need to COMMENT the protected void paint(Graphic g) method written above, and simply adding this source code;
private BufferedImage buttonImage = null;
public void paint(Graphics g) {
// Create an image for the button graphics if necessary
if (buttonImage == null || buttonImage.getWidth() != getWidth()
|| buttonImage.getHeight() != getHeight()) {
buttonImage = getGraphicsConfiguration().
createCompatibleImage(getWidth(), getHeight());
}
Graphics gButton = buttonImage.getGraphics();
gButton.setClip(g.getClip());
// Have the superclass render the button for us
super.paint(gButton);
// Make the graphics object sent to this paint() method translucent
Graphics2D g2d = (Graphics2D) g;
AlphaComposite newComposite =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, intensity);
g2d.setComposite(newComposite);
// Copy the button's image to the destination graphics, translucently
g2d.drawImage(buttonImage, 0, 0, null);
}
in which at the end... giving me nice fade out effect. But At first, it gave me the 2nd horrible effect which is BLACK BACKGROUND rendered first. Can't believe me?? Okay, Here is First screen shot AFTER applying code from ebook. and here is the nice fade out effect result.
If there's somebody telling me;
"YOUR PNG IS NOT TRANSPARENT!".
Please, dont say like that. Because, I followed the creation of PNG inside the Photoshop nicely using this Tut.
Now, My head's spinned, but my heart laughed and can't handle it over. OMG. Geeezzz....!
And the New Stories begun...
* UPDATED FROM HERE AND BELOW *
Ehm, depply thank you very much to our friend called... MKorbel,
from his thread given at this link. Providing a clear example of fading out effect the Swing JButton and I tried to implement it into my JLabel, and violaaa...!!
IT works.
Let's give a big clap for MKorbel. :D
SO anyway, how could I fix the earlier code? Pretty simple, just COMMENT the Paint() method, and use again the paintComponent() method and it should be overriden with the new source code below;
#Override
public void paintComponent(java.awt.Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, intensity));
if (rectangularLAF && isBackgroundSet()) {
Color c = getBackground();
g2.setColor(c);
g.fillRect(0, 0, getWidth(), getHeight());
}
super.paintComponent(g2);
g2.dispose();
}
Now the JLabel become easy to be changed with its intensity -variable.
Fading out and Fading in is accomplished.
Okay. Now everything seems "OKAY". BUt hold a moment, there's something strangely occured again here. Have you noticed it? Hmmm....
I tried to give a Mouse Event (Hover On) into the JLabel that we override the paintComponent() method discussed earlier.With this line of code;
myJLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
Logically, it should change the Cursor when Hover ON. But, Here comes another strange effect. (Really sorry, but this is stil the continue of the main case). The strange effect now is the Mouse Cursor can't be changed when we Hover On the Jlabel. It still can't change the Cursor. Seems the paintComponent() method effecting the way Cursor react. Is that true?

Categories