Results 1 to 17 of 17
  1. #1
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default 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:

    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);
    	}
    and this is the result:



    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:

    Java Code:
    		if (circle_moved) {
    			CAJP.repaint(OldCircle);
    			CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, 
    
    CAJP.getCH()+2);
    		}
    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.

    Can anyone suggest what the issue might be? Do I really have to repaint everything any time something's changed?

  2. #2
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    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.

  3. #3
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    Quote Originally Posted by Fubarable
    1) Have you considered drawing the unchanging background graphics in a BufferedImage and displaying that BufferedImage in the paintComponent method?
    I have not tried that. I'll give it a try. Thanks.

    Quote Originally Posted by Fubarable
    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.
    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?

  4. #4
    paul pasciak is offline Senior Member
    Join Date
    Jul 2008
    Posts
    125
    Rep Power
    0

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

    Java Code:
      if (circle_moved) {
        CAJP.repaint(OldCircle);
        CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, 
        CAJP.getCH()+2);
      }
    Your snippet is not complete enough
    to convince me that this is being
    done.

  5. #5
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    Quote Originally Posted by paul pasciak View Post
    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.

    Java Code:
      if (circle_moved) {
        CAJP.repaint(OldCircle);
        CAJP.repaint(CAJP.getCX()-1, CAJP.getCY()-1, CAJP.getCW()+2, 
        CAJP.getCH()+2);
      }
    Your snippet is not complete enough
    to convince me that this is being
    done.
    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():

    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);
    }
    and the paintComponent method:

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

    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.

  6. #6
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    Quote Originally Posted by gib65 View Post
    However, this doesn't really solve my original problem (how to draw the walls only once without them dissappearing),
    well, where the heck is your buffered image?

  7. #7
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    Quote Originally Posted by Fubarable View Post
    well, where the heck is your buffered image?
    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).

  8. #8
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    Quote Originally Posted by gib65 View Post
    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.
    Don't need to be constant. Just need to not change for a finite period of time and you will have benefit.

    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).
    You don't understand. You create the BufferedImage as needed, you don't need to make and store infinite images.

  9. #9
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    Quote Originally Posted by Fubarable View Post
    You don't understand. You create the BufferedImage as needed, you don't need to make and store infinite images.
    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)?

  10. #10
    camickr is offline Senior Member
    Join Date
    Jul 2009
    Posts
    1,233
    Rep Power
    6

    Default

    Is there a way of piping the results of these method calls to an ImageBuffer
    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.

  11. #11
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    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?

  12. #12
    camickr is offline Senior Member
    Join Date
    Jul 2009
    Posts
    1,233
    Rep Power
    6

    Default

    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.

  13. #13
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    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?

  14. #14
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    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.

  15. #15
    gib65 is offline Member
    Join Date
    Jun 2010
    Posts
    86
    Rep Power
    0

    Default

    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.

  16. #16
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    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);
      }
    }

  17. #17
    Fubarable's Avatar
    Fubarable is offline Moderator
    Join Date
    Jun 2008
    Posts
    19,316
    Blog Entries
    1
    Rep Power
    26

    Default

    Quote Originally Posted by gib65 View Post
    OK, that should be all I need to know. Thanks for the help.
    You're welcome.

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

Similar Threads

  1. How can I do this more efficiently ?
    By chucklesotoole in forum New To Java
    Replies: 5
    Last Post: 06-04-2010, 02:28 PM
  2. Need help with synchronous painting
    By JavaRulez in forum Advanced Java
    Replies: 7
    Last Post: 05-09-2010, 08:34 AM
  3. Images don't painting
    By daniel.henrique in forum New To Java
    Replies: 2
    Last Post: 02-12-2010, 11:48 AM
  4. Objects painting themselves?
    By martypapa in forum Java 2D
    Replies: 19
    Last Post: 02-06-2010, 04:08 AM
  5. painting problem
    By hannes in forum New To Java
    Replies: 3
    Last Post: 01-17-2010, 11:44 AM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •