1

I'm attempting to double buffer an image containing a polygon in the method paint() using AWT. Using an Image object for the buffering process, I set the image background to black, draw the polygon to the image and then draw the buffered image to the screen. I then call repaint() in order to render the image again.

Unfortunately, I'm still receiving artifacts when repainting the image. What am I doing incorrectly?

EDIT: As a side note, I'm using Java 8. EDIT 2: I'm calling repaint() in paint() because I need to continuously buffer the image. The polygon is meant to translate across the screen based off user input.

import java.applet.Applet;
import java.awt.*;

public class DoubleBuffer extends Applet {
    int xSize = 900;
    int ySize = 600;

    Image bufferImage;
    Graphics bufferG;

    @Override
    public void init() {
        this.setSize(xSize, ySize);

        //Double buffering related variables
        bufferImage = this.createImage(xSize, xSize);
        bufferG = bufferImage.getGraphics();
    }

    //BUFFERING DONE HERE
    @Override
    public void paint(Graphics g){
        //drawing images to external image first (buffering)
        bufferG.setColor(Color.BLACK);
        bufferG.fillRect(0,0,xSize,ySize);
        bufferG.setColor(Color.WHITE);
        bufferG.drawRect(100, 100, 100, 100);

        //draw the image and call repaint
        g.drawImage(bufferImage, 0, 0, this);
        repaint();
    }
}
C Goldsworthy
  • 334
  • 2
  • 12
  • 1
    Any reason to *not* use lightweight Components (eg JPanel) which are by default double buffered? (I would recommend *not* calling `repaint` in the `paint` method) – copeg May 20 '15 at 17:28
  • As strange as this sounds, I'm stuck with using Heavyweight components (i.e. just the window) due to certain API constraints I'm following for a job (please bear with me). If all else fails, I'll go ahead and use a lightweight component though. I'm about to embark on a little more research, but what's wrong with using repaint() in paint()? Where else should I re-render my image? – C Goldsworthy May 20 '15 at 17:36
  • P.S. this is an education related job in which image rendering forms a small part - I have little experience in this area sadly. – C Goldsworthy May 20 '15 at 17:40
  • 1
    You should render within the `paint` method (or `paintComponent` for lightweight components), but calling `repaint` within either method causes it to continuously paint again, and again, and again, and again, and...should only be necessary to call `repaint` if something has changed. – copeg May 20 '15 at 17:41
  • Ahh, it's worth mentioning that this polygon is also meant to be continuously transforming based off user input. Thus, there will be constant change in the window and `repaint()` must be called. I'll edit the question. – C Goldsworthy May 20 '15 at 17:51
  • 3
    Use a `Timer` if you want it to repaint at time intervals, or call repaint only when the user interacts/alters a value. – copeg May 20 '15 at 17:56
  • Calling `repaint()` from `actionPerformed()` (called by a Timer) resolves this issue! I'm now curious as to why the artifacts still occurred - I'll find out on my own. – C Goldsworthy May 20 '15 at 18:00

1 Answers1

2

The problem is that you are not overriding update which is the method which will be called in response to repaint requests. For heavy-weight components, the default implementation of update will first clear the component to the background color (may default to white) then invoke your paint method.

As pointed out by others, you shouldn’t call repaint from within a paint method. You should use a Timer.

After cleaning up, the entire class will look like:

public class DoubleBuffer extends Applet {
    int xSize = 900;
    int ySize = 600;

    Image bufferImage;
    Graphics bufferG;
    Timer timer=new Timer(200, ev->repaint());

    @Override
    public void init() {
        this.setSize(xSize, ySize);
    }

    @Override
    public void addNotify() {
        super.addNotify();
        //Double buffering related variables
        bufferImage = this.createImage(xSize, xSize);
        bufferG = bufferImage.getGraphics();
        timer.start();
    }

    @Override
    public void removeNotify() {
        super.removeNotify();
        bufferImage = null;
        bufferG = null;
        timer.stop();
    }


    //BUFFERING DONE HERE
    @Override
    public void paint(Graphics g){
        //drawing images to external image first (buffering)
        bufferG.setColor(Color.BLACK);
        bufferG.fillRect(0,0,xSize,ySize);
        bufferG.setColor(Color.WHITE);
        bufferG.drawRect(100, 100, 100, 100);

        //draw the image and call repaint
        g.drawImage(bufferImage, 0, 0, this);
    }

    @Override
    public void update(Graphics g) {
        // now not clearing the background as we expect paint() to fill the entire area
        this.paint(g);
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • It's worth noting that simply having `repaint()` called from `actionPerformed()` (triggered by a Timer) served as a solution for myself, as I stated in the comments of the question. Regardless, I'm also having `update()` overridden, as an additional (offline) source backs up your claim. – C Goldsworthy May 20 '15 at 19:10
  • 1
    @c4goldsw: that might be a system-dependent side effect. Due to the unpredictable timing, the duplicated fill operations might happen so fast that you don’t notice it. On my machine, using a `Timer` greatly reduced the flickering but did not eliminate it entirely. So overriding `update` was required and I guess, there might be systems/Java versions where not overriding `update` might have an even bigger impact. – Holger May 21 '15 at 07:19
  • I was mistaken - when I first reported copeg's solution as working, I had the timer executing at a very slow rate (once per second). Increasing the rate at which a timer 'ticks' causes artifacts, so overriding and modifying update() is in fact necessary. – C Goldsworthy May 21 '15 at 13:59
  • A final question: what is the issue with having `repaint()` being called in `paint()`? [According to this website](http://www.scs.ryerson.ca/~mes/courses/cps530/programs/threads/Repaint/index.html), "`repaint()` merely requests the AWT thread to call update(). It then returns immediately". This could mean that the next call of paint could execute and finish before the current call of paint does, but the image is drawn before `repaint()` is called. – C Goldsworthy May 21 '15 at 14:01
  • 2
    You don’t have any control over how `repaint` is scheduled. Calling it from within `paint` may cause anything, ranging from a 100% CPU consuming painting frequency up to a complete ignorance due to the painting already being in-progress. It *could* happen to work on one system, but not noticing the problem actually makes things worse. – Holger May 21 '15 at 17:01