Full-Screen Graphics

Let's take a quick look at how the hardware works before you start coding it. There are two parts to display hardware: the video card and the monitor. The video card stores what's on the screen in its memory and has several functions for modifying what's displayed. Also, the video card works behind the scenes to push what's in its memory to the monitor. The monitor simply displays the information that the video card tells it to.

Screen Layout

The monitor's screen is divided into tiny little color pixels that are all the same size. A pixel, derived from the term picture element, is a single point of light displayed by the monitor. The number of horizontal and vertical pixels that make up a screen is called the screen's resolution. The screen's origin is in the upper-left corner of the screen, as shown in Screenshot. The pixels are stored in video memory like you read a tutorial, starting from the upper-left corner and reading left to right, top to bottom. A location on the screen can be expressed as (x,y), where x is the number of horizontal pixels from the origin, and y is the number of vertical pixels from the origin.

Screenshot The screen is divided into tiny pixels, and the origin is in the upper-left corner. This figure depicts a 800x600 screen.

Java graphics 02fig01.gif


What resolutions you can pick depends on the capabilities of the video card and monitor. Typical resolutions are 640x480, 800x600, 1024x768, and 1280x1024. Typical monitors and televisions have a display size ratio of 4:3. That means the height of the display is three quarters of the size of the width. Some newer monitors have a wider screen with a display size ratio of 3:2 or 16:10. To compare, wide-screen movies typically have a display size ratio of 16:9. Big old CRT (cathode ray tube) monitors are capable of showing all sorts of resolutions cleanly because CRTs draw pixels with an electron beam. Leaner LCD (liquid crystal display) monitors found on laptops and newer computer systems are a different story, however. Because each pixel on a LCD is lit with a physical transistor, LCDs have one native resolution, which is the resolution that the LCD was designed to display. Non-native resolutions might appear blocky or fuzzy on LCDs. Because of this, it's a good idea to make sure your game can run in two or three different resolutions so players can pick the one that looks best on their system.

Pixel Color and Bit Depth

As a child, you were probably taught the three primary colors of red, yellow, and blue. You also probably heard the phrase, "yellow plus blue is green." The idea is that when you are painting or using another physical medium, you can combine these three colors to create any color you want. This is known as a subtractive color model, in which the absence of all color is white. (Actually, it's not an exact color model—modern printers use a more sophisticated color model using cyan, magenta, yellow, and black instead.) This is similar to how computer monitors and televisions work. Monitors combine red, blue, and green to create any color. Instead of a physical medium such as paint or inks, monitors emit light, so the RGB color model is an additive color model, in which adding all the colors together creates white. The number of colors a monitor can display depends on the bit depth of the display mode. Common bit depths are 8, 15, 16, 24, and 32 bits.

  • 8-bit color has 28 = 256 colors. Only 256 colors can be displayed at a time, based on a palette of colors. Unfortunately, there is currently no way in Java to change the palette, and the specification doesn't say what those colors are. The Java runtime can use a web-safe palette, which has 216 possible colors: 6 possible values each for red, green, and blue (6x6x6=216).
  • 15-bit color has 5 bits for red, green, and blue, for a total of 215 = 32,768 colors.
  • 16-bit color has 5 bits for red and blue, and 6 bits for green, for a total of 216 = 65,536 colors.
  • 24-bit color has 8 bits for red, green, and blue, or 224 = 16,777,216 colors.
  • 32-bit color is the same as 24-bit color, but with an extra 8 bits of padding to keep pixel data aligned on 32-bit boundaries.

Most modern video cards support 8-, 16-, and 32-bit modes. Because the human eye can see about 10 million colors, 24-bit color is ideal. 16-bit color is a little faster than 24-bit color because there is less data to transfer, but the color quality isn't as accurate.

Refresh Rate

Even though your monitor looks like it's displaying a solid image, each pixel actually fades away after a few milliseconds. To make up for this, the monitor continuously refreshes the display to keep it from fading. How fast it refreshes the display is known as the refresh rate. The refresh rate is measured in Hertz (Hz), which means cycles per second. A refresh rate between 75Hz and 85Hz is usually suitable for the human eye.

Switching the Display to Full-Screen Mode

Now that you know all about resolutions, bit depths, and refresh rates, let's write some code. You'll need a few objects to switch the display to full-screen mode:

  • A Window object. The Window object is an abstraction of what is displayed on the screen—think of it as a canvas to draw on. The examples here actually use a JFrame, which is a subclass of the Window class and can also be used for making windowed apps.
  • A DisplayMode object. The DisplayMode object specifies the resolution, bit depth, and refresh rate to switch the display to.
  • A GraphicsDevice object. The GraphicsDevice object enables you to change the display mode and inspect display properties. Think of it as an interface to your video card. The GraphicsDevice object is acquired from the GraphicsEnvironment object.

Here's an example of how to switch the display to full-screen mode:

JFrame window = new JFrame();
DisplayMode displayMode = new DisplayMode(800, 600, 16, 75);
// get the GraphicsDevice GraphicsEnvironment environment =
 GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice device = environment.getDefaultScreenDevice();
// use the JFrame as the full screen window device.setFullScreenWindow(window);
// change the display mode device.setDisplayMode(displayMode);


Afterward, to switch back to the previous display mode, set the full-screen Window to null:

device.setFullScreenWindow(null);


Note that this code isn't complete. Some systems won't allow you to change the display mode, and setDisplayMode() throws an IllegalArgumentException on those systems. Also, by default, JFrames still show their borders and title bars, even in full-screen mode. We'll deal with these issues and simplify the functions by creating a wrapper class called SimpleScreenManager. The SimpleScreenManager class shown in Listing 2.1 is a simple interface to change the display to full-screen mode. Behind the scenes, SimpleScreenManager catches Exceptions and removes the JFrame's border and title bar with a call to setUndecorated(true). Also, the JFrame is disposed when the screen is restored.

Listing 2.1 SimpleScreenManager.java
import java.awt.*;
import javax.swing.JFrame;
/**
 The SimpleScreenManager class manages initializing and
 displaying full screen graphics modes.
*/
public class SimpleScreenManager {
 private GraphicsDevice device;
 /**
 Creates a new SimpleScreenManager object.
 */
 public SimpleScreenManager() {
 GraphicsEnvironment environment =
 GraphicsEnvironment.getLocalGraphicsEnvironment();
 device = environment.getDefaultScreenDevice();
 }
 /**
 Enters full screen mode and changes the display mode.
 */
 public void setFullScreen(DisplayMode displayMode,
 JFrame window)
 {
 window.setUndecorated(true);
 window.setResizable(false);
 device.setFullScreenWindow(window);
 if (displayMode != null &&
 device.isDisplayChangeSupported())
 {
 try {
 device.setDisplayMode(displayMode);
 }
 catch (IllegalArgumentException ex) {
 // ignore - illegal mode for this device
 }
 }
 }
 /**
 Returns the window currently used in full screen mode.
 */
 public Window getFullScreenWindow() {
 return device.getFullScreenWindow();
 }
 /**
 Restores the screen's display mode.
 */
 public void restoreScreen() {
 Window window = device.getFullScreenWindow();
 if (window != null) {
 window.dispose();
 }
 device.setFullScreenWindow(null);
 }
}


Now let's put the SimpleScreenManager to use. The FullScreenTest class in Listing 2.2 tests the methods of the SimpleScreenManager class. It changes the display to full-screen mode, displays a "Hello World!" message, waits five seconds, and exits. FullScreenTest runs in a resolution of 800x600 and a bit depth of 16. If you want to run at a different resolution, just specify the different display mode at the command line. The program treats the first, second, and third arguments passed to it as the width, height, and bit depth of the display mode. For example, entering this at the console runs the program at resolution of 1024x768 and a bit depth of 32:

java FullScreenTest 1024 768 32


Note that the code doesn't allow you to set an "illegal" mode that will do something crazy like set the monitor on fire. If the display mode you request isn't available or the system doesn't have full-screen support, an IllegalArgumentException is thrown and the demo mimics full-screen mode using the current display mode.

Listing 2.2 FullScreenTest.java
import java.awt.*;
import javax.swing.JFrame;
public class FullScreenTest extends JFrame {
 public static void main(String[] args) {
 DisplayMode displayMode;
 if (args.length == 3) {
 displayMode = new DisplayMode(
 Integer.parseInt(args[0]),
 Integer.parseInt(args[1]),
 Integer.parseInt(args[2]),
 DisplayMode.REFRESH_RATE_UNKNOWN);
 }
 else {
 displayMode = new DisplayMode(800, 600, 16,
 DisplayMode.REFRESH_RATE_UNKNOWN);
 }
 FullScreenTest test = new FullScreenTest();
 test.run(displayMode);
 }
 private static final long DEMO_TIME = 5000;
 public void run(DisplayMode displayMode) {
 setBackground(Color.blue);
 setForeground(Color.white);
 setFont(new Font("Dialog", Font.PLAIN, 24));
 SimpleScreenManager screen = new SimpleScreenManager();
 try {
 screen.setFullScreen(displayMode, this);
 try {
 Thread.sleep(DEMO_TIME);
 }
 catch (InterruptedException ex) { }
 }
 finally {
 screen.restoreScreen();
 }
 }
 public void paint(Graphics g) {
 g.drawString("Hello World!", 20, 50);
 }
}


One thing to note in FullScreenTest is the use of the try/finally block in the run() method. Restoring the screen in the finally block guarantees that the screen will always be restored, even if an Exception is thrown in the try block. Besides that, the first thing you might notice in FullScreenTest is the Graphics object used in the paint() method. The Graphics object provides all sorts of functions for drawing text, lines, rectangles, ovals, polygons, images, and so on. Most of the methods are self-explanatory, so check out the Java API specification for all the juicy details. If you're asking yourself, "Hey, how does that paint() method get called, anyway?" the answer is, well, magic. Actually, you'll notice that FullScreenTest is a JFrame. When a JFrame (or any other component, for that matter) is displayed, Java's Abstract Window Toolkit, or AWT, calls the component's paint() method. If you want to force the AWT to call the paint() method, call repaint(). This signals to the AWT to call the paint() method when it gets around to it. The AWT send paint events in a separate thread, so if you want to send a repaint event and then wait for the painting to complete, use something like this:

public class MyComponent extends SomeComponent {
 ...
 public synchronized void repaintAndWait() {
 repaint();
 try {
 wait();
 }
 catch (InterruptedException ex) { }
 }
 public synchronized void paint(Graphics g) {
 // do painting here
 ...
 // notify that we're done painting
 notifyAll();
 }
}


Anti-Aliasing

You might notice that the "Hello World" text from FullScreenTest looks a little jagged around the edges. This is because the text isn't anti-aliased. It might sound strange, but anti-aliasing makes the text look sharper by blurring the edges so that the text's color blends with the background color, making the staircase of pixels appear to be smoother. See Figures 2.2 and 2.3 for a comparison.

Screenshot Normal text appears jagged around the edges.

Java graphics 02fig02.gif


Screenshot Anti-aliased text blurs the edges a bit for a cleaner look.

Java graphics 02fig03.gif


To make text anti-aliased, set the appropriate rendering hint before drawing any text. The rendering hint functionality is present only in the Graphics2D class, which is a subclass of Graphics. For backward compatibility, the paint() method takes a Graphics object as a parameter. However, starting with Java SDK 1.2, it's actually a Graphics2D object that is passed to the method. Here's the code for a paint() method with text anti-aliasing:

public void paint(Graphics g) {
 if (g instanceof Graphics2D) {
 Graphics2D g2 = (Graphics2D)g;
 g2.setRenderingHint(
 RenderingHints.KEY_TEXT_ANTIALIASING,
 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 }
 g.drawString("Hello World!", 20, 50);
}


Other rendering hints exist, including making all geometric lines and shapes anti-aliased, setting rendering quality, and so on. See the documentation for the RenderingHints class for details.

Which Display Mode to Use

A lot of display modes are available, but which one should your game run at? Well, make sure your game runs in at least two resolutions. The general rule is to allow players to change display modes so they can pick the one they like best on their display. If possible, initially run your game at the same resolution as the current resolution. On LCDs, the current resolution is most likely the LCD's native resolution, so the game will look nicer if you use the current one. As for bit depth, using 16-, 24-, or 32-bit color is a good idea for modern games. 16-bit color is usually a little faster, but use a higher bit depth if you need more accurate color representation. Also, a refresh rate between 75Hz and 85Hz is suitable for the human eye.



   
Comments