A question about Images and Graphics

Hi all, first post here ;D

My question is, why does this work as expected:


public class AppTest extends Applet
{
	Image i;
	public void init()
	{
	}
	
	public void paint(Graphics g)
	{
		i = createImage(this.getWidth(), this.getHeight());
		Graphics b = i.getGraphics();
		try
		{
			b.drawImage(this.getImage(new URL(this.getCodeBase(), "dude.png")), 0, 0, null);
		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
		b.dispose();
		g.drawImage(i, 0, 0, null);
		repaint();
	}
}

But this doesn’t draw anything:


public class AppTest extends Applet
{
	Image i;
	public void init()
	{
		i = createImage(this.getWidth(), this.getHeight());
		Graphics b = i.getGraphics();
		try
		{
			b.drawImage(this.getImage(new URL(this.getCodeBase(), "dude.png")), 0, 0, null);
		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
		b.dispose();
	}
	
	public void paint(Graphics g)
	{
		g.drawImage(i, 0, 0, null);
		repaint();
	}
}

Both of your methods will actually create an infinite loop if you ever got them to draw.

You call repaint() in order to tell your screen to refresh itself, and along the way repaint() automatically calls paint().

[EDIT] I think I should mention that you are really doing this more or less very very wrong and you should spend some time looking at examples and reading tutorials. It’s clear to me that you don’t really grasp what you’re doing.

But here’s a simple diagram of what’s actually happening in your code.

In the first one:

  1. Applet is created, repaint() is automatically called once upon creation.
  2. Repaint() calls paintComponent(), update(), paint() and a lot of other stuff you don’t see.
  3. paint() runs your stuff: creates a new image, loads that image from a file, draws it, then calls repaint.
  4. Go back to #2. Repeat infinitely.

In the second one:

  1. Applet is created, repaint() is automatically called upon creation (see 1a), and after that’s finished a new image is created and loaded from a file.
    1a) Repaint() calls paintComponent(), update(), paint() and a lot of other stuff you don’t see.
    1b) paint() draws your image, which is null, then calls repaint.
    1c) Go back to #1a. Repeat infinitely.

Notice that in the second one “after that’s finished a new image is created and loaded from a file” never happens because repaint keeps going forever. The applet never gets initialized entirely.

Both methods are very wrong however because you’re causing an infinite loop, which will eat up processor and inhibit you from doing absolutely anything. You’ll want to use a separate timer or loop that separately calls repaint(), you absolutely do not want a derivative of repaint() to call it.

[/EDIT]

You are suffering from several problems, DemonPants has pointed out your call to repaint() in the paint method is bad - it will cause the EDT (EventDispatchThread) to be flooded with repaint events continually redrawing your Applet. While this behaviour isn’t inherently bad (it is essentially a primitive form of game loop), it probably isn’t what you are intending.

As to the reason why your 1st sample appears to work, and your 2nd does not - that is a little more complicated.
Lets start with this :-

Component.getImage(…) (which falls through to Toolkit.getImage(…) does return an Image object - however it is returned unloaded. This means it is not ready to be drawn to the screen.
The data for the Image only begins to be loaded the first time you try and use the Image (in your case, this is the first time you attempt to draw it). This loading process however occurs asynchronously, and until it is complete any attempt to draw the image will result in nothing happening.

This is the cause of your 2nd code example not working - your init() method is invoked only once, therefore your first attempt to copy the “dude.png” onto your “i Image” results in failure because the image has yet to be loaded.

As to the reason your 1st code example DOES work - this is yet more complex!
If we go back to:-

[quote]this.getImage(new URL(this.getCodeBase(), “dude.png”))
[/quote]
if you take a look at the javadoc for Toolkit.getImage(…) you will see the documentation mentions that it attempts to cache requests for the same image:

This cache is unwittingly the reason why your first code example works. As your first example repeatedly attempts to load the same image each repaint cycle, the Toolkits underlying cache will be returning you the same Image object. Eventually the Image’s loading will complete and the subsequent copy onto the “i Image” will be successful.

I’ll annotate your code to better explain the exact sequence of steps, so you can better see this:


public class AppTest extends Applet
{
	Image i;
	public void init() //  Called by the browser or applet viewer to inform this applet that it has been loaded into the system.
	{
               // once this method completes, a repaint event will be pushed onto the EDT (EventDispatchThread), so at some future point the paint(Graphics) method will be called.
	}
	
	public void paint(Graphics g) // called by the EDT each time it encounters a paint event in it's queue
	{
                // LABEL 1
		i = createImage(this.getWidth(), this.getHeight()); // creates an empty image
		Graphics b = i.getGraphics(); // gets the graphics context to draw onto the empty image
		try
		{
                        // attempts to get the image "dude.png" from the Toolkit's image cache
                        // if it isn't present, it will be added.
                        Image img = this.getImage(new URL(this.getCodeBase(), "dude.png"));
                        // attempts to draw the image
                        // if the image is completely loaded, it will be drawn, otherwise no drawing will occur.
                        // for images that are not completely loaded, the loading process will commence (if it hasn't already) 
			b.drawImage(img, 0, 0, null); 
		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
		b.dispose();
                // draws the (potencially empty) image to the screen
		g.drawImage(i, 0, 0, null);
                // pushes another repaint event onto the EDT queue - for the sake of simplicity, just imagine flow-of-control returns to the beginning of this paint(Graphics) method. (GOTO LABEL 1)
		repaint();
	}
}

Here is a modified version of your code, i’ve left the repaint() call at the end of the paint method, so you can see it allows for a primitive way of animating.


import java.applet.Applet;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.net.MalformedURLException;
import java.net.URL;

public class DudeApplet extends Applet {
    Image dude;
	public void init()
	{
		try {
			dude = getImage(new URL(this.getCodeBase(), "dude.png"));
			// this class provides a mechanism for waiting on a set of resources to load.
			MediaTracker imageLoader = new MediaTracker(this);
			// adds the dude png to the list of images it is tracking
			imageLoader.addImage(dude, 0);
			// now initiate the loading, and wait.
			imageLoader.waitForAll();
		} catch (MalformedURLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	 * Overridden because we don't want the default behaviour of clearing
	 * the Component to it's background color. (which by default is white)
	 * @see Component#update(Graphics)
	 */
	public void update(Graphics g) {
		paint(g);
	}
	
	public void paint(Graphics g)
	{
		try {
			// note this is a VERY BAD way of throttling,
			// it is halting the EDT for 100ms,
			// preventing it from processing any other user inputs. (keyboard, mouse etc etc)
			// you should replace this with a proper
			// game loop & frame rate throttling mechanism
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		g.setColor(Color.BLACK);
		g.fillRect(0, 0, getWidth(), getHeight());

		// redraw the dude image @ a random location
		g.drawImage(dude,
				(int)((getWidth()-dude.getWidth(null)) *Math.random()),
				(int)((getWidth()-dude.getWidth(null)) *Math.random()), null);
	
		// this is essentially a very primitive way of making a game loop. 
		repaint();
	}
}

Great explanation! Thanks guys - I guess I will be creating another thread for drawing so that it doesn’t interfere with the EDT while I’m at it.

Cheers :slight_smile: