Results 1 to 17 of 17
- 07-03-2010, 04:35 AM #1
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
question about painting efficiently
Hello,
I'm experimenting with painting in swing, and I'm having some difficulty doing it as efficiently as I can (or I think is possible).
Let me show my paintComponent() method as it stands now:
and this is the result:Java Code:public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLUE); if (W1.redraw) g.drawRect(W1.getX(), W1.getY(), Wall.LENGTH, Wall.THICKNESS); if (W2.redraw) g.drawRect(W2.getX(), W2.getY(), Wall.THICKNESS, Wall.LENGTH); g.setColor(Color.RED); g.drawOval(cx, cy, cw, ch); }

The difficulty I'm having is that I don't want to keep redrawing elements that don't need to be redrawn (I should say, in order to make sense of that, that I have a Timer object setup to call repaint() every 100 milliseconds). I'm doing this because the user can animate the red circle (you can hold down the arrow keys to move it around). This means that it must be consistently redrawn so long as the user holds down one of the arrow keys.
But the blue walls only need to be drawn once as a part of the first rendering of the JPanel they're on. That's why I have their drawRect() calls under an if statement (where W1.redraw and W2.redraw are booleans.
The problem is that since I have to set these booleans to false (elsewhere) once the walls have been drawn, the call to super.paintComponent() wipes them out completely (it seems to wipe out everything in the JPanel) and therefore only redraws the circle since that isn't under the condition of an if statement. But I can't just take the call to super.paintComponent() out, not only because I've been told to do this when working in swing (and to always use paintComponent() rather than paint()), but because if I comment it out, this turns out to be the result:

The circle is leaving a trail behind it as I move it around the JPanel. This is especially strange because my code that calls repaint() looks like this:
CAJP is my JPanel, getCX() get's the circle's X position, and so on. Note the use of OldCirlce, a Rectangle whose dimensions were set before the circle's position was changed. I pass that into repaint() first (which is supposed to mark the area defined by the Rectangle as dirty, meaning it should be erased before the circle is redrawn) and then draw my circle. But this seems to be having no effect. Not to mention that the background is being repainted grey instead of the black I'd like.Java Code:if (circle_moved) { CAJP.repaint(OldCircle); CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, CAJP.getCH()+2); }
Can anyone suggest what the issue might be? Do I really have to repaint everything any time something's changed?
-
1) Have you considered drawing the unchanging background graphics in a BufferedImage and displaying that BufferedImage in the paintComponent method?
2) The trail from the circle is almost always due to not calling super.paintComponent(g) as the first call of the paintComponent override. The call to super repaints the background.
- 07-03-2010, 08:00 PM #3
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
I have not tried that. I'll give it a try. Thanks.
Originally Posted by Fubarable
I figured that much. So then I'm stuck with redrawing the walls every time the circle moves, which seems like a waste. There should be a way of redrawing only those regions of the JPanel that need redrawing while leaving all other regions alone - is there not?
Originally Posted by Fubarable
- 07-03-2010, 08:28 PM #4
Senior Member
- Join Date
- Jul 2008
- Posts
- 125
- Rep Power
- 0
Not clear if the old circle erased.
It looks like the old circle is not erased.
If the following code is supposed to
erase the old circle, something should
be done to ensure that the old circle
is repainted in blue, then the new circle
is repainted in red.
Your snippet is not complete enoughJava Code:if (circle_moved) { CAJP.repaint(OldCircle); CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, CAJP.getCH()+2); }
to convince me that this is being
done.
- 07-03-2010, 11:38 PM #5
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
I'm not sure why it should be repainted blue seeing as how the background is black (or grey when super.paintComponent() is commented out), but I revised my code as follows:
the call to repaint():
and the paintComponent method:Java Code:// save the old values for x and y before changing them CAJP.oldx = CAJP.getCX(); CAJP.oldy = CAJP.getCY(); ... // repaint circle only if it has moved if (circle_moved) { CAJP.repaint(OldCircle); CAJP.repaint(CAJP.oldx-1, CAJP.oldy-1, CAJP.getCW()+2, CAJP.getCH()+2); CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, CAJP.getCH()+2); }
Obviously, for both calls to repaint(), both the old circle (painted black therefore erasing the old circle) and the new circle (painted red) are being drawn. This might seem redundant but I can understand the need for it. For the first call to repaint(), the region where the old circle existed is being worked on (old circle erased and part of the new circle being drawn). For the second call to repaint(), the region where the new circle exists is being worked on (part of old circle being erased (and it should have been totally erased at this point anyway) and in fact the new circle drawn in the first call to repaint() also being erased), and the new circle being totally drawn. The difference between these two regions is only a single line of pixels, but well written code is well written code.Java Code:public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.BLUE); if (W1.redraw) g.drawRect(W1.getX(), W1.getY(), Wall.LENGTH, Wall.THICKNESS); if (W2.redraw) g.drawRect(W2.getX(), W2.getY(), Wall.THICKNESS, Wall.LENGTH); g.setColor(Color.BLACK); g.drawOval(oldx, oldy, cw, ch); g.setColor(Color.RED); g.drawOval(cx, cy, cw, ch); }
However, this doesn't really solve my original problem (how to draw the walls only once without them dissappearing), and moreover it doesn't solve the problem of the circle leaving a trace when super.paintComponent() is commented out, as the following screen captures show:
without commenting out super.paintComponent():

commenting out super.paintComponent():

The first screen capture shows how my original problem isn't solved (the blue walls aren't there), whereas the second shows how painting the old circle the same color as the background doesn't get rid of the trail (in the latter case, I set the color of the old circle as follows: g.setColor(super.getBackground())). I don't know if painting the old circle the same color as the background is having any useful effect in the first screen capture since there was never a problem of the circle leaving a trail in that case to begin with.
In any case, leaving aside the fact that my original problem isn't solved, I'm curious as to what the call to repaint(Rectangle) is doing if it isn't erasing the old circle (I thought that's what it was for).
Thanks for the suggestions in any case. Any further suggestions will be greatly appreciated.
-
- 07-04-2010, 02:22 AM #7
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
Oh, I see what you were getting at. You're suggesting I make the walls part of my buffered image. I suppose that *could* work under other circumstances, but not this one. Although the walls will not be animating, they won't be constant either. I plan to make this into a game in which the walls make up a maze, and the maze will be generated randomly. So unless I make an infinite number of buffered images, each a different maze, I will need to draw the walls of the maze in the paintComponent() method (plus I'll probably program the game so that under rare circumstances the walls will animate).
-
Don't need to be constant. Just need to not change for a finite period of time and you will have benefit.
You don't understand. You create the BufferedImage as needed, you don't need to make and store infinite images.I plan to make this into a game in which the walls make up a maze, and the maze will be generated randomly. So unless I make an infinite number of buffered images, each a different maze, I will need to draw the walls of the maze in the paintComponent() method (plus I'll probably program the game so that under rare circumstances the walls will animate).
- 07-04-2010, 03:48 AM #9
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
Oh, I assumed (for some reason) that you could only created a BufferedImage by reading from an image file (JPG, GIF, BMP, etc.). Are you saying I can program the contents of a BufferedImage directly? How would I do that? I just breezed through this tutorial and I've looked at the BufferedImage API and I don't see an easy way to do this.
The only way I know how to draw anything in AWT and Swing are with the Graphics objects you get in the paint() (or paintComponent()) methods - things like g.drawOval(), g.drawRect(), g.fillOval(), g.setColor(), etc. Is there a way of piping the results of these method calls to an ImageBuffer rather than to a JPanel or something like that (or to the screen)?
- 07-04-2010, 04:27 AM #10
Senior Member
- Join Date
- Jul 2009
- Posts
- 1,143
- Rep Power
- 5
Use the BufferedImage getGraphics() method then you draw to the buffered image that same way you would draw to a panel in the paintComponent() method.Is there a way of piping the results of these method calls to an ImageBuffer
- 07-04-2010, 04:58 AM #11
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
Oh, yes, I see. I missed that part :o.
One last question if I may: how do I then use the BufferedImage as the background to my JPanel? The setBackground() method only seems to take in a Color object and doesn't seem to be overloaded with anything that takes an Image or a BufferedImage. There are several drawImage(Image,...) method in the Graphics class, but then I might as well use all those geometry operations (drawOval(), drawRect(), etc.) directly in the paintComponent() method rather than to draw into the BufferedImage... unless a call to drawImage(Image,...) is somehow more efficient... is it?
- 07-04-2010, 05:05 AM #12
Senior Member
- Join Date
- Jul 2009
- Posts
- 1,143
- Rep Power
- 5
The idea is you do the individual drawings onto the buffered image once. Then in the paintComponent method you just draw the image.
Custom Painting Approaches has an example of this.
- 07-04-2010, 06:22 PM #13
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
Thanks camickr,
I finally implemented an ImageBuffer and it seems to work. I don't know if I initialized the imageType properly though. I set it to TYPE_INT_ARGB because it seemed pretty standard. Is this usually the one to go with if I'm drawing to the ImageBuffer using the standard drawing tools? Also, when drawing the ImageBuffer, I set the ImageObserver to null. Is this recommended/safe?
-
I'm no pro at BufferedImage, but TYPE_INT_ARGB is fine to use, if you are going to be setting the alpha. If not, then you'll likely want to use something more similar to TYPE_INT_RGB. These guys are described in the API. Also, you'll want to be sure that you dispose the Graphics object that you extract from your BufferedImage after you're done painting to it, otherwise you could have unwanted build up of unused system resources.
- 07-04-2010, 08:13 PM #15
Member
- Join Date
- Jun 2010
- Posts
- 86
- Rep Power
- 0
OK, that should be all I need to know. Thanks for the help.
I'm going to mark this thread 'solved' but I'm not entirely sure the ImageBuffer approach is the one I'll be using in the end. It's good to know it works and I *might* use it in the end - and so I really appreciate all the help I've been given - but I'm going to see if there are other approaches out there. Therefore, I'm going to crosspost this here and see what the guys over at sun have to say.
Again, thanks for all your help.
-
Example code:
Java Code:import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import javax.swing.*; public class WallsEg { private static void createAndShowUI() { final WallsEgPanel panel = new WallsEgPanel(); JButton startAnimation = new JButton("Start Animation"); startAnimation.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { panel.startAnimation(); } }); JFrame frame = new JFrame("WallsEg"); frame.getContentPane().add(panel, BorderLayout.CENTER); frame.getContentPane().add(startAnimation, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { createAndShowUI(); } }); } } @SuppressWarnings("serial") class WallsEgPanel extends JPanel { private static final Dimension DISPLAY_SIZE = new Dimension(500, 400); private static final Color BACKGROUND = Color.black; private static final Color WALL_COLOR = Color.blue; private static final Color OVAL_COLOR = Color.red; private static final int OVAL_W = 50; private static final Stroke OVAL_STROKE = new BasicStroke(3); private static final int OVAL_X = 20; private static final int OVAL_Y = OVAL_X; private static final int TIMER_STEP = 15; protected static final int X_STEP = 1; protected static final int Y_STEP = 1; private BufferedImage wallsBImage; private int ovalX = OVAL_X; private int ovalY = OVAL_Y; private Timer timer; public WallsEgPanel() { wallsBImage = createWallsBImage(); setPreferredSize(DISPLAY_SIZE); } public void startAnimation() { if (timer != null && timer.isRunning()) { timer.stop(); } else { ovalX = OVAL_X; ovalY = OVAL_Y; repaint(); timer = new Timer(TIMER_STEP, new ActionListener() { public void actionPerformed(ActionEvent e) { int oldX = ovalX; int oldY = ovalY; ovalX += X_STEP; ovalY += Y_STEP; repaint(oldX - 10, oldY - 10, OVAL_W + 20, OVAL_W + 20); repaint(ovalX - 10, ovalY - 10, OVAL_W + 20, OVAL_W + 20); } }); timer.start(); } } private BufferedImage createWallsBImage() { int w = DISPLAY_SIZE.width; int h = DISPLAY_SIZE.height; BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); Graphics imgG = img.getGraphics(); imgG.setColor(BACKGROUND); imgG.fillRect(0, 0, w, h); imgG.setColor(WALL_COLOR); imgG.fillRect(20, 200, 10, 80); imgG.fillRect(200, 20, 80, 10); imgG.dispose(); return img; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (wallsBImage != null) { g.drawImage(wallsBImage, 0, 0, null); } Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setStroke(OVAL_STROKE); g2.setColor(OVAL_COLOR); g2.drawOval(ovalX, ovalY, OVAL_W, OVAL_W); } }
-
You're welcome.
Thanks for informing us about the cross-post, but don't forget to notify them (nevermind this time, as I've done it for you).I'm going to mark this thread 'solved' but I'm not entirely sure the ImageBuffer approach is the one I'll be using in the end. It's good to know it works and I *might* use it in the end - and so I really appreciate all the help I've been given - but I'm going to see if there are other approaches out there. Therefore, I'm going to crosspost this here and see what the guys over at sun have to say.
Again, thanks for all your help.
Similar Threads
-
How can I do this more efficiently ?
By chucklesotoole in forum New To JavaReplies: 5Last Post: 06-04-2010, 02:28 PM -
Need help with synchronous painting
By JavaRulez in forum Advanced JavaReplies: 7Last Post: 05-09-2010, 08:34 AM -
Images don't painting
By daniel.henrique in forum New To JavaReplies: 2Last Post: 02-12-2010, 11:48 AM -
Objects painting themselves?
By martypapa in forum Java 2DReplies: 19Last Post: 02-06-2010, 04:08 AM -
painting problem
By hannes in forum New To JavaReplies: 3Last Post: 01-17-2010, 11:44 AM


LinkBack URL
About LinkBacks
Reply With Quote

Bookmarks