Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

[java] Java 2D Graphics, Threads and Classes

Started by Instigator Jan 13, 2007 at 2:36 PM 10 replies 3.6k views
Original Post
Instigator
Instigator
Okay I know that to some of you this may seem like a lot to be working on all at once. As a matter of fact my abilities with 2D graphics in Java suck. This is why I started a simple graphics program to draw 2 rectangles to the screen. First of, I can't figure out why this is not working. Here's my code: ------------------------------ main.java -----------------------------

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

public class MainDr extends Applet implements Runnable
{
	Thread drawThread = new Thread();
	boolean running = false;
	Animate draw;
	
	public void init()
	{
		Animate draw = new Animate();
		setBackground(Color.darkGray);
		drawThread = new Thread(this);
		running = true;
		drawThread.start();
		System.out.println("done.");
	}
	
	public void destroy()
	{
		running = false;
		drawThread = null;	
	}
	
	public void run()
	{
		while (running)
		{
			try {
				drawThread.sleep(50);
			}
			catch (InterruptedException e){
				System.out.println(e);
			}		
			draw.repaint();
		}
	}
} 
-------------------------- Animate.java --------------------------------

import java.awt.*;
import java.applet.*;
import java.io.*;
import java.net.*;

public class Animate extends Applet {

	int x =60, y=60;
	Graphics screen;
	
	public Animate()
	{
		screen = getGraphics();
	}
	
	public void paint(Graphics g)
	{
		if (g == null)
		{
			g = getGraphics();
		}
		 g.setColor(Color.red);
		 x++;
		 y++;
		 
		 // This will draw a rectangle (xco,yco,xwidth,height);
		 g.drawRect(5,5,20,20); 
		 g.drawRect(x, y, 20, 20);
	}

}
---------------------------------------------------------------- EDIT**** Please check out my second post instead (its an update).. Before it would work sometimes while other times it wouldn't work. I figured out that I was compiling in the wrong way. SO now ... I get the following error all the time with the above code.
Quote:
java.lang.NullPointerException at MainDr.run(MainDr.java:56) at java.lang.Thread.run(Thread.java:619) at MainDr.init(MainDr.java:21) at sun.applet.AppletPanel.run(AppletPanel.java:417) at java.lang.Thread.run(Thread.java:619)
The second problem is that 'when' the program does work. The line 'draw.repaint();' isn't working. Since its a thread. I expect that the line will be called over and over again. and thus, moving the (x and y) cords so that you see a moving rectangle. It seems that the repaint() method isn't begin called. Thus, only repainting the screen if you were to (minimize/maximize) the screen. I have found many tutorials online on how to use the paint method. However, I have yet to come across a tutorial on "calling a paint method in another class", such as mine. How can this madness be done? Any help would be much appreciated!!!! [Edited by - Instigator on January 13, 2007 6:06:32 PM]
Megaman_22
Megaman_22
I'm no expert on this, but I would consider using an active renderer function instead of relying on repaint actually calling paint when you want it to.

I'm not real sure why you would want an applet to be part of another applet, but maybe that makes sense.
Eric Richards
Instigator
Instigator
Okay, thank you for your help. Yes, I do need to do it this way. So here's the update. I've found a new way to do this. Seems to be almost working except there's an error right as I called "draw.repaint()" The error states:

Quote:

Exception in thread "Thread-4" java.lang.NullPointerException
at MainDr.run(MainDr.java:38)
at java.lang.Thread.run(Thread.java:619)


Here's the new update Main.java (which calls the unchanged animate.java)

--------------------------------------------------------------------

import java.awt.*;import java.applet.*;public class MainDr extends Applet implements Runnable{	Thread drawThread = new Thread();	boolean running = false;	Animate draw;	int tempVal = 0;		public void init()	{		System.out.println("Starting right now.");		Animate draw = new Animate();		draw.init();		//setBackground(Color.darkGray);		drawThread = new Thread(this);		running = true;		//drawThread.start();		drawThread.start();		System.out.println("done.");	}		public void destroy()	{		running = false;		drawThread = null;		}		public void run()	{		Thread tempThread = Thread.currentThread();				while (drawThread == tempThread)		{			System.out.println ("in DrawThread");			draw.repaint(); // boom error <-----------------****----						try{				Thread.sleep(500);			}			catch (InterruptedException e) {				System.out.println( "whoops.");			}		}		// something else			}}


I'm so close yet so far... any ideas on what is causing this error?

[Edited by - Instigator on January 13, 2007 8:37:01 PM]
Whackjack
Whackjack
Classic example of variable hiding. You have a variable called draw that is of type Animate as an object field. You also have a method variable in init() that is called draw of the same type. You are initializing the method scope variable, while the object field variable remains null. This is why you are getting a NullPointerException.

Change:
Animate draw = new Animate();

to:
draw = new Animate();
Instigator
Instigator
I've searched through examples and example of 2D graphics programming trying to understand why I got the error. Now that you solved it I can't tell you how happy I am. :D... so thank you for being able to see that. When I wrote my code I guess that didn't go through my noobish mind. All this to say is that the syntax errors are gone, yet there's some logic error yet to be uncovered. I've spent all of my Saturday on this piece of ****, I'm not giving up though.

When I try to run the applet I can see that the thread is getting executed every 500 milliseconds (what I need). However, I don't see the rectangles appear on the applet. Thus, the 'draw.repaint()' method is getting called yet there's no drawing done to the screen.

Basically, this means that there's some logic error with the way I'm printing to the screen. I'm probably using the graphics objects wrong. Does anyone know what I missed? I feel like a complete idiot but when I see this once, I will forever remember it until long after Java passes away.

I pasted the entire code below to aviod conflicts with the above code sections that I previously posted:

==================== MainDr.java =====================
import java.awt.*;import java.applet.*;public class MainDr extends Applet implements Runnable{	Thread drawThread = new Thread();	boolean running = false;	Animate draw;	int tempVal = 0;		public void init()	{		System.out.println("Starting right now.");		draw = new Animate();		draw.init();		//setBackground(Color.darkGray);		drawThread = new Thread(this);		running = true;		//drawThread.start();		drawThread.start();		System.out.println("done.");	}		public void start(){		Animate draw = new Animate();		draw.init();				if (drawThread == null){			drawThread = new Thread(this);			drawThread.start();		}	}		public void destroy()	{		running = false;		drawThread = null;		}		public void run()	{		Thread tempThread = Thread.currentThread();				while (drawThread == tempThread)		{			System.out.println ("in DrawThread");			//draw.repaint();			Graphics screen = getGraphics();			draw.setScreen(screen);			try{				Thread.sleep(500);			}			catch (InterruptedException e) {				System.out.println( "whoops.");			}		}		// something else			}}

--------------------------------------------

================= Animate.java =============
import java.awt.*;import java.applet.*;import java.io.*;import java.net.*;public class Animate extends Applet {	Image offscreen;	Graphics buffGraphs;	Image yellowPic;	URL crtPath;	int x =60, y=60;	Graphics g;		public Animate()	{		//offscreen = createImage(200, 200);		//buffGraphs = offscreen.getGraphics();		//yellowPic = getImage(crtPath, "yellow.png");	}		public void init()	{		g = getGraphics();	}		public void setScreen(Graphics scrn)	{		g = scrn;	}		public void paint(Graphics g)	{		//g = getGraphics();		//g = screen;		if (g == null)		{			g = getGraphics();		}		//g.drawImage(yellowPic, 20,20,(this));		 g.setColor(Color.red);		 x++;		 y++;		 		 // This will draw a rectangle (xco,yco,xwidth,height);		 g.drawRect(5,5,20,20); 		 g.drawRect(x, y, 20, 20);		// screen = g;		//buffGraphs = 		//g.drawRect(20, 20, 50, 50);	}	}
Im_wasting_my_life
Im_wasting_my_life
Oops...sorry for not logging in.

Here is my post again, this time with the links:

Killer Game Programming in Java is a good book that covers 2D and 3D game programming in java. Many drafts of the chapters that were published in the book are available in pdf format on the website.

The pdf on the site for Chapter 2, here, describes an animation framework for using java 2D, including "active rendering."
Aldacron
Aldacron
Quote:
Original post by Instigator
When I try to run the applet I can see that the thread is getting executed every 500 milliseconds (what I need). However, I don't see the rectangles appear on the applet. Thus, the 'draw.repaint()' method is getting called yet there's no drawing done to the screen.


There are several problems with your code. You appear to have a misunderstanding of the Applet lifecycle, for one. Your Animate class should not be subclassing Applet at all. You only need one Applet instance. java.awt.Canvas would be a better choice. You are also needlessly initializing thing multiple times, declaring variables that have no purpose, hiding class members behind local variables of the sames name...

I normally don't do this, but I've rewritten your code into a functioning Applet. I suggest you run it in the AppletViewer that ships with the JDK (if you are using Eclipse, it can launch it for you automatically without an HTML file). This will allow you to experiment with the lifecycle methods via the menu.

A quick rundown of the Applet lifecycle:

init - called when the applet is first loaded. Called only once.

start - any threads the applet uses should start execution here. Can be called multiple times in the lifetime of an applet.

stop - terminate any running threads here. Can be called multiple time in the lifetime of an applet.

destroy - called when the applet is unloaded. Called only once. Normally you don't need to override this unless you are using system resources that you need to cleanup. Most of the time, you would handle that in the stop method anyway.

I've commented the code somewhat. I hope looking it over helps you.

// MainDR.javaimport java.applet.Applet;public class MainDR extends Applet implements Runnable {	// running should be declared volatile since it will be used as a flag	// by more than one thread.	private volatile boolean running = false;		private Thread drawThread;	private Animate draw;		public void init() {		System.out.println("Initializing Applet");				draw = new Animate();	// create a new Animate Canvas				// make sure the canvas is the proper size, otherwise it won't		// be visible (the Canvas class is funny like that)		draw.setSize(getSize());					add(draw);				// add the Canvas to the Applet				// This next line belongs in the run method, since the thread isn't really running yet		// running = true;  				System.out.println("Init complete.");	}			public void start() {		System.out.println("Starting Applet");				drawThread = new Thread(this);		drawThread.start();				System.out.println("Start complete");	}		public void stop() {		System.out.println("Stopping Applet");				running = false;		// signal the draw thread to exit				// since the draw thread might be blocked in a sleep call right now,		// go ahead and interrupt it so that it can wake up		drawThread.interrupt();				// wait for the draw thread to exit		try {			drawThread.join();		} catch(InterruptedException ie) {}				drawThread = null;				System.out.println("Applet stopped");	}		// Don't need this -- no resources to deallocate, but put it here anyway	// for demonstration purposes.	 public void destroy() {		 System.out.println("Destroying Applet");	 }		public void run() {		System.out.println("Draw thread has been entered");				running = true;		// now the draw thread is running				while(running) {						System.out.println("Top of draw thread loop");						draw.repaint();		// redraw the canvas						try {				Thread.sleep(500);			} catch(InterruptedException ie) {				// This isn't a 'whoops'. Threads are interrupted all of the				// time, and in this case we want it to be interrupted if it				// is sleeping when the app exits.			}						System.out.println("Bottom of draw thread loop");		}				System.out.println("Draw thread is exiting");	}		}// Animate.javaimport java.awt.Canvas;import java.awt.Color;import java.awt.Graphics;public class Animate extends Canvas {	private int x, y;		public Animate() {		setBackground(Color.darkGray);				x = y = 60;	}		public void paint(Graphics g) {		g.setColor(Color.red);				g.drawRect(5, 5, 20, 20);				x++;		y++;		g.drawRect(x, y, 20, 20);	}	}
Instigator
Instigator
OHHH .... man. You've helped me so much with this I don't know how to thank you. I finally see how this entire thing works. I really appreciate your help.

:D :D :D

EDIT**

Wow your example is so good, that I would recommend that you turn it into a tutorial.
capn_midnight
capn_midnight
You really want to be doing rendering on the main application thread. While Sun has done some work for Java 6 and is doing more to make cross-thread rendering more stable for Java 7, it isn't very good yet, and I don't have much confidence for the future.

Now, in most other GUI systems, doing ALL of your rendering in a single, arbitrary thread would be okay, even if it weren't the main application thread. However, because everything is fundamentally built on AWT or Swing, you aren't in control of all of the rendering. Since AWT and Swing do their own bit of this-and-that, you're stuck to using the main application thread to keep everything in one thread.

I *think* there is one way around this, though I'm not certain. You might be able to render to a BufferedImage in a seperate thread. Using an appropriate level of synchronization, you could then pause that seperate thread and render the BufferedImage to the application window on the main applicatin thread. I'm a little concerned that the Java2D rendering sub-system might be shared between any bit of rendering you might do.

You can get high-performance rendering out of Java, you just need to do a lot of reading into the overall architecture of the subsystems so that you know where to put stuff. You definitely need to do the footwork yourself, I don't trust tutorials for anything and I don't trust the Sun documentation on Best Practices anymore.
Aldacron
Aldacron
Quote:
Original post by capn_midnight
You really want to be doing rendering on the main application thread. While Sun has done some work for Java 6 and is doing more to make cross-thread rendering more stable for Java 7, it isn't very good yet, and I don't have much confidence for the future.


I have to disagree with you. Likely every animating Applet that has ever been made has used a separate thread for animation because that's what all of the literature says to do (for a good reason). You can find it in books going back to Laura LeMay's Java 1.1 Interactive Course (which actually covered 1.0) or Java Game Programming for Dummies, or more recent books like Ivor Horton's Beginning Java 2. Even Sun's examples use separate threads for animation.

Realistically, Applets are multi-threaded by nature. The base GUI is created in the event-dispatching thread, with the lifecycle methods called from one or more additional threads. So if you were to draw something directly to the applet in the start method, it is already being drawn in a thread that didn't create the Applet GUI. That means any components you add to the applet are created in a different thread than the base GUI.

Considering that most browsers call the lifecycle methods from a single thread, that also means that if you were to do an animation loop in the start method, the stop method would never be called. So there's a very good reason why all of the literature teaches us to use a separate thread for animation in applets.

Quote:
Now, in most other GUI systems, doing ALL of your rendering in a single, arbitrary thread would be okay, even if it weren't the main application thread. However, because everything is fundamentally built on AWT or Swing, you aren't in control of all of the rendering. Since AWT and Swing do their own bit of this-and-that, you're stuck to using the main application thread to keep everything in one thread.


I understand the logic, but it's wrong. Using Java2D, you can render from any thread even in a desktop app. People have been doing it for years now. The hardware accelerated OpenGL pipeline raised some issues with this, but internally both AWT and Swing can handle rendering from multiple threads (as long as you don't use too many, of course). This Chris Campbell blog entry from 2005 gives some details on how the Java2D team made the OpenGL renderer work with multiple threads.

Quote:
I don't trust tutorials for anything and I don't trust the Sun documentation on Best Practices anymore.


Heh, I guess you've had trouble with multi-threaded Java2D rendering at some point? Regardless, there are a multitude of applications and applets that do so with no problem at all.
harveypekar
harveypekar
I think I see the confusion here... indeed, swing/awt's repaint is no fun...

here's the good news though: you can easily turn off all repainting, and call repaint() yourself. It's not ideal, but at least you're in control...

Component has a method ignoreRepaint(boolean), which does exactly what you expect. You can always draw paint them manually with paintComponents(Graphics g).

Also, you can roll out your own RepaintManager (which does what you expect it do) to stop repainting. Just creating a implementation with no code, and then call myRepaintManager.install().

I find it easier in the long run to just disable repainting I didn't call myself. And as a plus, it would really help if you decide to move to a full-screen environment.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.