Inexplicable behavior of paint(Graphics g)

W

Wolfgang

I have the simplest program, first placing an image in a panel, then
painting a simple ball on top of it (just a red dot); see appended
code. Since the loading of the image takes unusually long (seconds),
I put in a static variable 'numberOfTimesBallWasDrawn' to see what
happens. If I just draw the ball, and no image, everything is normal,
the ball is drawn once. If I draw an image, and then the ball,
however, the ball gets drawn 640 times (sixhundredandfourty),
according to variable 'numberOfTimesBallWasDrawn'. Why is that? Can
anybody enlighten me about what's going on here?

Thanks for your help and advice.
Wolfgang

//==== simple code drawing image and ball======
import java.awt.*;
import java.util.*;
import javax.swing.*;

public class MainFrame extends JFrame {
static int numberOfTimesBallWasDrawn = 0;

public MainFrame() {
JPanelToDrawOn ap = new JPanelToDrawOn();
setSize(300, 300);
getContentPane().add(ap);
setVisible(true);
}

public static void main(String[] args) {
MainFrame ap = new MainFrame();
}
}

class JPanelToDrawOn extends JPanel {
Image image = Toolkit.getDefaultToolkit().getImage("c:/temp/deepfield.gif");
Ball ball = new Ball();

public void paint(Graphics g) {
// THE IMAGE IS THE CULPRIT causing 640 times drawing
g.drawImage(image, 0, 0, 300, 300, this);
ball.draw(g);
}
}

class Ball {
public void draw(Graphics g) {
System.err.println(MainFrame.numberOfTimesBallWasDrawn++);
g.setColor(Color.red);
g.fillOval(30, 30, 20, 20);
}
}
 
A

Andrew Hobbs

Wolfgang said:
I have the simplest program, first placing an image in a panel, then
painting a simple ball on top of it (just a red dot); see appended
code. Since the loading of the image takes unusually long (seconds),
I put in a static variable 'numberOfTimesBallWasDrawn' to see what
happens. If I just draw the ball, and no image, everything is normal,
the ball is drawn once. If I draw an image, and then the ball,
however, the ball gets drawn 640 times (sixhundredandfourty),
according to variable 'numberOfTimesBallWasDrawn'. Why is that? Can
anybody enlighten me about what's going on here?

Thanks for your help and advice.
Wolfgang

For a start, since you are using Swing, override the paintComponent(Graphics
g) method rather than the paint(Graphics g) method. OK in AWT but in Swing
this method is used internally by the painting routines.

Secondly, the culprit is not so much the drawImage() method alone as a
combination of drawImage() and the System.err.println(""). (It would be
better to stick to convention and use System.out.println("")). The
drawImage() method loads the image but then has to scale the image to fit
the panel. This does take a short while. In the meantime each call to
System.err.println("") is itself triggering another call to the paint method
if the image is not ready. Once the scaled image is ready then it works OK
and only draws once. These cyclic method calls are in fact slowing the
image generation down.

It is not a good idea to put in such methods (eg input, output, disk access,
or which involve overly long computational methods) in a paint method.

Andrew


--
********************************************************
Andrew Hobbs PhD

MetaSense Pty Ltd - www.metasense.com.au
12 Ashover Grove
Carine W.A.
Australia 6020

61 8 9246 2026
metasens AntiSpam @iinet dot net dot au


*********************************************************


//==== simple code drawing image and ball======
import java.awt.*;
import java.util.*;
import javax.swing.*;

public class MainFrame extends JFrame {
static int numberOfTimesBallWasDrawn = 0;

public MainFrame() {
JPanelToDrawOn ap = new JPanelToDrawOn();
setSize(300, 300);
getContentPane().add(ap);
setVisible(true);
}

public static void main(String[] args) {
MainFrame ap = new MainFrame();
}
}

class JPanelToDrawOn extends JPanel {
Image image = Toolkit.getDefaultToolkit().getImage("c:/temp/deepfield.gif");
Ball ball = new Ball();

public void paint(Graphics g) {
// THE IMAGE IS THE CULPRIT causing 640 times drawing
g.drawImage(image, 0, 0, 300, 300, this);
ball.draw(g);
}
}

class Ball {
public void draw(Graphics g) {
System.err.println(MainFrame.numberOfTimesBallWasDrawn++);
g.setColor(Color.red);
g.fillOval(30, 30, 20, 20);
}
}
 
W

Wolfgang

Originally I used both paintComponent() and System.out.println(), and
I put those back in (instead of paint() and System.err.println()).
The behavior remains exactly the same.

Why would each call to System.out.println("") trigger another call to
the paint method if the image is not ready? System.out.println()
should print on the console (rather than the Graphics object) once
when the ball is drawn, and why should that happen 640 times? Perhaps
I'm not getting one of the subtleties of the event handling involved
witht he paint method. Could somebody please explain?

Thanks,
Wolfgang
 
A

A. Bolmarcich

I have the simplest program, first placing an image in a panel, then
painting a simple ball on top of it (just a red dot); see appended
code. Since the loading of the image takes unusually long (seconds),
I put in a static variable 'numberOfTimesBallWasDrawn' to see what
happens. If I just draw the ball, and no image, everything is normal,
the ball is drawn once. If I draw an image, and then the ball,
however, the ball gets drawn 640 times (sixhundredandfourty),
according to variable 'numberOfTimesBallWasDrawn'. Why is that? Can
anybody enlighten me about what's going on here?

What is going on is what you (unknowingly) requested that the image
be redrawn whenever more of it is obtained by the getImage method. In
the following line of your paint method

g.drawImage(image, 0, 0, 300, 300, this);

the last argument "this" is an ImageObserver whose imageUpdate method
is invoked as more of the image becomes available. A JPanel inherits
the default implementation of the imageUpdate method from Component.
This default implementation invokes repaint on itself (the JPanel).
 
S

S Manohar

I'm no expert, but...
Try overriding the imageUpdate() method of your class (inherited from
the ImageConsumer interface). This method is called 640 times as your
image gradually loads, informing you of its progress. Maybe a
superclass of your class is telling the window to repaint each time
this happens!
 
W

Wolfgang

What is going on is what you (unknowingly) requested that the image
be redrawn whenever more of it is obtained by the getImage method. In
the following line of your paint method

g.drawImage(image, 0, 0, 300, 300, this);

the last argument "this" is an ImageObserver whose imageUpdate method
is invoked as more of the image becomes available. A JPanel inherits
the default implementation of the imageUpdate method from Component.
This default implementation invokes repaint on itself (the JPanel).

That's beginning to make sense. Thank you. Looking in the jdk 1.4
API documentation, I find 13 methods
drawImage(Image,..,ImageObserver). There is but one additonal
drawImage(...) method without an ImageObserver, but this one uses a
BufferedImage.

So what do you suggest I do if I want to avoid the 640-fold redrawing?

Thanks,
Wolfgang

I have simplified the program further, but the rusults are the same:
//**************************************************************************
import java.awt.*;
import java.util.*;
import javax.swing.*;

public class MainFrame extends JFrame {
static int numberOfTimesBallWasDrawn = 0;

public MainFrame() {
JPanelToDrawOn ap = new JPanelToDrawOn();
setSize(300, 300);
getContentPane().add(ap);
setVisible(true);
}

public static void main(String[] args) {
MainFrame ap = new MainFrame();
}
}

class JPanelToDrawOn extends JPanel {
Image image =
Toolkit.getDefaultToolkit().getImage("c:/temp/deepfield.gif");

public void paint(Graphics g) {
g.drawImage(image, 0, 0, 300, 300, this);
System.out.println("test " +
MainFrame.numberOfTimesBallWasDrawn++);
g.setColor(Color.red);
g.fillOval(30, 30, 20, 20);
}
}
 
A

Andrew Hobbs

Wolfgang said:
That's beginning to make sense. Thank you. Looking in the jdk 1.4
API documentation, I find 13 methods
drawImage(Image,..,ImageObserver). There is but one additonal
drawImage(...) method without an ImageObserver, but this one uses a
BufferedImage.

So what do you suggest I do if I want to avoid the 640-fold redrawing?
My mistake above. I was thinking of something else when I said that.

The 'problem' you are seeing is inherent in the methods. As the others have
pointed out the updateImage() method is called as the image is getting
loaded and drawn in the offscreen buffer, which triggers the redrawing each
time. This however is not normally important since painting the screen is a
relatively low priority and the VM consolidates all the repaints until it
has time to do them. In this case the program has finished well before the
paint() method has completed cycling. If you want to prove it to yourself
try running the program at the bottom which is a modified form of yours with
a couple of extra reporting methods.

If you leave out the print from within the paint() method and just increment
the counter, it appears that the paint() method is only called once. (As
reported by the print command in main() method.) However close the window
after everything is done and the true final value of
numberOfTimesBallWasDrawn is shown by the closing method and you will see
that it has actually been called hundreds of times by the program (via the
imageUpdate() method, which itself has been called almost the same number of
times as the paint()).

This rather indefinite behaviour is why it is not a good idea to put
anything inside a paint() or paintComponent() method that is not directly
related to painting your object.

If you want to get around this (possibly because you have an expensive paint
method that would slow everything down too much) I would suggest keeping
tabs on the image and prevent anything happening until it is fully loaded,
using a MediaTracker. Something like this does it well. Just call the
loadImage() method when you instantiate your JPanel. If you insert this in
your own program you should find that it only gets called once. (Still not
a good idea to put print calls in paint() methods).

Cheers

Andrew


--
********************************************************
Andrew Hobbs PhD

MetaSense Pty Ltd - www.metasense.com.au
12 Ashover Grove
Carine W.A.
Australia 6020

61 8 9246 2026
metasens AntiSpam @iinet dot net dot au


*********************************************************


class JPanelToDrawOn extends JPanel {
Image image;
Ball ball = new Ball();

public void loadImage() {
image =
Toolkit.getDefaultToolkit().getImage("C:\\Java\\Documents\\Logo2.gif");
waitForImage(image, this);
}

public boolean waitForImage(Image image, Component c) {
MediaTracker tracker = new MediaTracker(c);
tracker.addImage(image, this);
try {
tracker.waitForAll();
} catch(InterruptedException ie) {}
return(!tracker.isErrorAny());
}


****************************************************************

import java.awt.*;
import java.util.*;
import javax.swing.*;

public class MainFrame extends JFrame {
static int numberOfTimesBallWasDrawn = 0;
static int updates = 0;


public MainFrame() {
JPanelToDrawOn ap = new JPanelToDrawOn();
setSize(300, 300);
getContentPane().add(ap);

// A simple method to get the values when you are ready.

addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.out.println("Number of balls drawn = " +
numberOfTimesBallWasDrawn + " Updates = " + updates);
System.exit(0);
}
});
setVisible(true);
}

public static void main(String[] args) {
MainFrame ap = new MainFrame();

// Called when the main method has finished. Note that it is very early
on in the piece.

System.out.println("Number of balls drawn at end of main = " +
numberOfTimesBallWasDrawn);
System.out.println("Updates = " + updates);
}
}

class JPanelToDrawOn extends JPanel {
Image image =
Toolkit.getDefaultToolkit().getImage("c:/temp/deepfield.gif");
Ball ball = new Ball();

public void paint(Graphics g) {
// THE IMAGE IS THE CULPRIT causing 640 times drawing
g.drawImage(image, 0, 0, 300, 300, this);
ball.draw(g);
}
}

class Ball {
public void draw(Graphics g) {

// Use EITHER of these calls to see the apparent effect of
System.out.println calls.

// MainFrame.numberOfTimesBallWasDrawn++;
// System.err.println(MainFrame.numberOfTimesBallWasDrawn++);

g.setColor(Color.red);
g.fillOval(30, 30, 20, 20);
}

// This is simply a method to count the number of times imageUpdate is
called.

public boolean imageUpdate(Image img, int flags, int x, int y, int width,
int height) {
MainFrame.updates++;
return super.imageUpdate(img, flags, x, y, width, height);
}

}
 
A

Andrew Hobbs

Oooops

The method in my previous email should be

public boolean waitForImage(Image image, Component c) {
MediaTracker tracker = new MediaTracker(c);
tracker.addImage(image, 0);
try {
tracker.waitForAll();
} catch(InterruptedException ie) {}
return(!tracker.isErrorAny());
}

Otherwise it will not compile.

cheers

Andrew


--
********************************************************
Andrew Hobbs PhD

MetaSense Pty Ltd - www.metasense.com.au
12 Ashover Grove
Carine W.A.
Australia 6020

61 8 9246 2026
metasens AntiSpam @iinet dot net dot au


*********************************************************
 
A

A. Bolmarcich

That's beginning to make sense. Thank you. Looking in the jdk 1.4
API documentation, I find 13 methods
drawImage(Image,..,ImageObserver). There is but one additonal
drawImage(...) method without an ImageObserver, but this one uses a
BufferedImage.

So what do you suggest I do if I want to avoid the 640-fold redrawing?

First, try using a value of null for the ImageObserver argument to
drawImage. If the drawImage method complains because the argument
is null, go back to using this as the ImageObserver argument and
include the following method in your extension of JPanel.

public boolean imageUpdate(Image img, int flags, int x, int y,
int width, int height)
{
return false;
}
 
S

S Manohar

Try overriding imageUpdate:
I have simplified the program further, but the rusults are the same:
//**************************************************************************
import java.awt.*;
import java.util.*;
import javax.swing.*;

public class MainFrame extends JFrame {
static int numberOfTimesBallWasDrawn = 0;

public MainFrame() {
JPanelToDrawOn ap = new JPanelToDrawOn();
setSize(300, 300);
getContentPane().add(ap);
setVisible(true);
}

public static void main(String[] args) {
MainFrame ap = new MainFrame();
}
}

class JPanelToDrawOn extends JPanel {
Image image =
Toolkit.getDefaultToolkit().getImage("c:/temp/deepfield.gif");

public void paint(Graphics g) {
g.drawImage(image, 0, 0, 300, 300, this);
System.out.println("test " +
MainFrame.numberOfTimesBallWasDrawn++);
g.setColor(Color.red);
g.fillOval(30, 30, 20, 20);
}
/**
* Override imageUpdate to return true when our image is complete,
* or false otherwise. You could also use this to display a
* progress/status indicator for image loading.
*/
public boolean imageUpdate(Image i, int info, int x, int y, int w, int h){
if(i==image){
if((info & ALLBITS)>0){
repaint(); //repaint once image fully loaded
return false; //no more updates for this image
}
else return true; //inform of updates, but don't repaint now
} else return super.imageUpdate(i,info,x,y,w,h);
//default handler for other images
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top