Chapter 5. Bitmap-Based Graphics

Java has a robust and feature-rich set of classes for working with 2D bitmap-based graphics (also known as raster graphics), allowing you to load and draw bitmaps very easily. Bitmaps are the keys to building a good 2D game with images rather than vector shapes.

Here are the key topics in this chapter:

  • Loading and drawing bitmap images

  • Applying transformations to bitmap images

  • Drawing opaque and transparent images

Programming Bitmapped Graphics

I mentioned before that there are many methods for drawing bitmap images in Java. Actually, most of those methods are found in the base Graphics class, while several more are found in Graphics2D. I think you will find the Graphics2D methods more useful, so we won’t spend any time working with the legacy versions. The great thing about the Graphics2D class is how its methods for manipulating 2D graphics work equally well with vectors and bitmaps. This means you will be able to translate, rotate, and scale bitmap images just as easily as you have manipulated vector graphics thus far. This awesome functionality will translate well into the subsequent chapters on sprite and animation programming. The real difference when working with images is that you will need to create a separate AffineTransform class to manipulate the Image object, rather than going directly through Graphics2D. This strangely named class allows us to rotate, scale, and move bitmaps.

Note

The online documentation for the Java API can be found at this website: http://download.oracle.com/javase/6/docs/api.

Loading and Drawing Images

To load an image from a file, we have to use a helper class called Toolkit:

Toolkit tk = Toolkit.getDefaultToolkit();

Toolkit includes a method called getImage() that can load a bitmap file (the most common format for Java is PNG, the Portable Network Graphics format). This method is found in Toolkit, which is why we have to create a Toolkit object to load the artwork for a game. The method for drawing a bitmap is found in Graphics2D and is similarly easy to use: just call drawImage() with the appropriate parameters.

Let’s write a program that demonstrates how to load and draw a bitmap image. We can use the getImage() method to load an image file, and then use draw-Image() to draw it onto the applet window. Figure 5.1 shows the output from the DrawImage program. I have highlighted the important lines of code.

The DrawImage program loads and draws a bitmap file.

Figure 5.1. The DrawImage program loads and draws a bitmap file.

Note

This high-quality castle image was rendered by Reiner Prokein using Caligari trueSpace. He offers a large amount of royalty-free game artwork, such as this castle, at his website, www.reinerstileset.de (a German site with an English version).

// DrawImage program
import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.net.*;

public class DrawImage extends JFrame {
    private Image image;
public static void main(String[] args) {
    new DrawImage();
}

public DrawImage() {
    super("DrawImage");
    setSize(600,600);
    setVisible(true);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Toolkit tk = Toolkit.getDefaultToolkit();
    image = tk.getImage(getURL("castle.png"));
}

private URL getURL(String filename) {
    URL url = null;
    try {
        url = this.getClass().getResource(filename);
    }
    catch (Exception e) { }
    return url;
}
    public void paint(Graphics g) {
        //create an instance of Graphics2D
        Graphics2D g2d = (Graphics2D) g;

        //fill the background with black
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, getSize().width, getSize().height);

        //draw the image
        g2d.drawImage(image, 0, 40, this);
    }
}

Applying Transforms to Images

Now I’ll demonstrate how to apply a transform to a simple bitmap image. Remember, a transform affects the position, rotation, or scale. Transforms will make our sprite code in the upcoming chapters really fun because the sprite images will be manipulated with these transforms as well. Since this code is similar to the code for transforming vectors, it should look at least somewhat familiar even if you don’t fully understand it. One difference when working with an image is that you must define a separate AffineTransform object for manipulating the Image object because the Graphics2D transforms are designed to work only with vectors. Figure 5.2 shows the output of the RandomImages program, showing a spaceship image being moved, rotated, and scaled.

The RandomImages program draws images at random locations, with random rotation and scaling.

Figure 5.2. The RandomImages program draws images at random locations, with random rotation and scaling.

// RandomImages program
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.geom.*;
import java.net.*;

public class RandomImages extends JFrame {
    private Image image;

    public static void main(String[] args) {
        new RandomImages();
    }
//applet init event
public RandomImages() {
    super("RandomImages");
    setSize(600,500);
    setVisible(true);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Toolkit tk = Toolkit.getDefaultToolkit();
    image = tk.getImage(getURL("spaceship.png"));
}

//identity transformation
AffineTransform identity = new AffineTransform();

private URL getURL(String filename) {
    URL url = null;
    try {
        url = this.getClass().getResource(filename);
    }
        catch (Exception e) { }
        return url;
    }

    //applet paint event
    public void paint(Graphics g) {
        //create an instance of Graphics2D
        Graphics2D g2d = (Graphics2D) g;

        //working transform object
        AffineTransform trans = new AffineTransform();

        //random number generator
        Random rand = new Random();

        //applet window width/height
        int width = getSize().width;
        int height = getSize().height;

        //fill the background with black
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, getSize().width, getSize().height);

        //draw the image multiple times
        for (int n = 0; n < 50; n++) {
            trans.setTransform(identity);
            //move, rotate, scale the image randomly
            trans.translate(rand.nextInt()%width, rand.nextInt()%height);
            trans.rotate(Math.toRadians(360 * rand.nextDouble()));
            double scale = rand.nextDouble()+1;
            trans.scale(scale, scale);

            //draw the image
            g2d.drawImage(image, trans, this);
        }
    }
}

Transparency

Although you can load and draw a bitmap at this point, the code you’ve seen so far is very limited. For one thing, the getImage() method can’t load a bitmap file out of a Java Archive (JAR) file. JAR files will become very important later in Part III, when we build the Galactic War game. Since the game is so large, with so many bitmap and sound files, it takes a long time for the game to load over the web (unless you have a broadband connection). You’ll learn how to create and use a JAR file soon enough. All I’m concerned about right now is that we are using code that will be compatible with a JAR, so that Java can read files out of the JAR as easily as it reads the raw files from the web server (or the directory in which your program is located if you are running it locally).

The Abstract Window Toolkit, known as AWT, provides a class called Toolkit that knows how to load a bitmap file. It’s smart enough to look in the current URL path where the applet is located (something that you must pass to the getImage() method). You can use Toolkit in your own programs or you can instantiate a global Toolkit object and then use it throughout the game; there are many options. Let’s take a look at how this class works:

Toolkit tk = Toolkit.getDefaultToolkit();
Image ship = tk.getImage("star_destroyer.png");

First, I created a Toolkit object by returning the object passed back from Toolkit.getDefaultToolkit(). This method returns a Toolkit object that represents the state of the Java program or applet. You can then use this Toolkit object’s getImage() method to load a bitmap file. Since we want our applets to be JAR-friendly so games will run on the web as efficiently as possible, I will use the getURL() method again:

Image ship = tk.getImage(getURL("star_destroyer.png"));

Opaque Images

Let’s start with what you have already learned up to this point—how to load and draw a bitmap without any transparency. At this point, it doesn’t matter whether you use the Applet or the Toolkit to load a bitmap file because the end result will be the same. I leave it to you to decide which method you prefer, and I will use them both interchangeably. Let’s write a short program to serve as a basis for discussing this topic. The output from the BitmapTest program is shown in Figure 5.3. I have highlighted the key portions of code in bold in the listing that follows.

The BitmapTest program demonstrates the loading and drawing of opaque images.

Figure 5.3. The BitmapTest program demonstrates the loading and drawing of opaque images.

// BitmapTest program
import java.awt.*;
import java.util.*;
import java.net.*;
import javax.swing.*;

public class BitmapTest extends JFrame implements Runnable {
    Image image;
    Thread gameloop;
    Random rand = new Random();

    public static void main(String[] args) {
        new BitmapTest();
    }

    public BitmapTest() {
        super("Opaque Bitmap Test");
        setSize(640,480);
    setVisible(true);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    Toolkit tk = Toolkit.getDefaultToolkit();
    image = tk.getImage(getURL("asteroid2.png"));
    gameloop = new Thread(this);
    gameloop.start();
}

private URL getURL(String filename) {
    URL url = null;
    try {
        url = this.getClass().getResource(filename);
    }
    catch (Exception e) { }
    return url;
}

public void run() {
    Thread t = Thread.currentThread();
    while (t == gameloop) {
        try {
            Thread.sleep(20);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        repaint();
    }
}

public void update(Graphics g) {
    paint(g);
}

public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;
int width = 640 - image.getWidth(this);
int height = 480 - image.getHeight(this);
g2d.drawImage(image, rand.nextInt(width), rand.nextInt(height), this);
}

This short program loads the bitmap image shown in Figure 5.4. In many programming languages and graphics libraries, you must specify a transparent pixel color to be used for transparency. In the example shown here, the black region around the edges of the asteroid would be considered the “tranparent zone” of the image. This transparent color is black in the example shown here (with an RGB value of 0,0,0), but other colors can be used for the transparent color too—the color pink (255,0,255) is often used for the transparent color because it stands out so well.

This opaque bitmap image contains no transparency information.

Figure 5.4. This opaque bitmap image contains no transparency information.

Java uses a more advanced method to handle transparency, as the next section explains.

Transparent Images

Java is a smart language that handles a lot of things for the programmer automatically, including the drawing of transparent images. This really makes life easier for a Java game programmer because many game libraries use a transparent pixel for transparency instead of a mask layer. So instead of dealing with transparency in code, it’s handled in the source artwork. If you supply Java with a transparent bitmap file, it will draw that image transparently.

Most Java programs use the PNG format because it offers decent compression and transparency information without sacrificing image quality. You will need to use a graphics editor such as GIMP to convert images from whatever source format they are in (most likely the BMP format) to the PNG format, along with the mask layer that makes transparency possible.

Tip

I have used many graphic editors, including Paint Shop Pro, GIMP, and Photoshop. Although they are functionally different, they all share a similar toolset, including the ability to create an alpha channel. The instructions given here for GIMP will be similar to the steps in most other graphic editors.

Let’s take the same program you just typed in for BitmapTest and run it again. Only this time, it will load up a new version of the asteroid1.png file that has been edited to support transparency. Figure 5.5 shows the output from the TransparentTest program. The source code has not changed (refer earlier to the BitmapTest program listing), but the PNG file has changed, which accounts for the difference!

The asteroid image is drawing with transparency due to its alpha channel.

Figure 5.5. The asteroid image is drawing with transparency due to its alpha channel.

Working Some Masking Magic

Let’s take a look at how you actually create a masked PNG image. I’m using GIMP because it’s very easy to use and free. If you want to use this program, you can download it from www.gimp.org. To add a transparency layer to an image, you need to locate the Magic Wand tool available in most graphics editors. After selecting the Magic Wand icon with your mouse, click somewhere in the black region (or on any pixel that isn’t part of the game object). This should locate the edges of the game object and highlight everything around it (see Figure 5.6). Another more precise way to select a background is with the Color Picker or Select By Color tool.

The outer edge of the asteroid image has been selected with the Magic Wand tool.

Figure 5.6. The outer edge of the asteroid image has been selected with the Magic Wand tool.

Now that you have a selection available, you can create a Layer Mask to invert it because this selection will exclude the image. Click on the Selections menu and choose Invert (see Figure 5.7). This brings up the dialog shown in Figure 5.8. Choose the Selection option and check the Invert mask option.

Preparing to add a layer mask.

Figure 5.7. Preparing to add a layer mask.

The Add Layer Mask dialog is used to choose options for the new layer mask.

Figure 5.8. The Add Layer Mask dialog is used to choose options for the new layer mask.

Tip

If you have a complex image and would like to exclude many portions of it in order to select the boundary of the real image, you can hold down the Shift key while clicking with the Fuzzy Select (or Magic Wand) tool inside portions of the image to add new selections.

The next step is to create a new mask layer in the image to represent the transparent portion. You can tell GIMP to generate a mask based on the selection you’ve made in the image. To do this, open the Layers menu, select Apply Layer Mask, and then Show Selection, as shown in Figure 5.9.

Applying the new layer mask makes it permanent.

Figure 5.9. Applying the new layer mask makes it permanent.

Tip

GIMP is a freeware graphic editor for multiple platforms with many good features found in costly commercial graphic editors. Download GIMP (GNU Image Manipulation Program) from www.gimp.org.

In Figure 5.10, the alpha channel has been created based on the masked selection. The checkerboard background behind the asteroid image shows the transparent region. The result looks very nice; this asteroid is ready for rendering! You can load this image into your Java applet and draw it, and it will automatically be drawn with transparency so the outer edges of the image (where the black pixels used to be) will not overwrite the background of the screen.

The asteroid image now has a masked transparency layer.

Figure 5.10. The asteroid image now has a masked transparency layer.

What You Have Learned

We will continue to work with transparent images from this point forward, so you have learned a very important tool in this chapter that will make it possible to create extremely attractive games. Specifically, you learned:

  • How to draw bitmap images

  • How to translate, rotate, and scale bitmap images

  • How to draw bitmaps with transparency

Review Questions

The following questions will help you to determine how well you have learned the subjects discussed in this chapter. The answers are provided in Appendix A, “Chapter Quiz Answers.”

1.

What is the primary class we’ve been using to manipulate bitmapped graphics in this chapter?

2.

What method initializes the keyboard listener interface?

3.

What Graphics2D method is used to draw an image?

4.

Which Java class contains the getImage() method?

5.

What class makes it possible to perform translation, rotation, and scaling of images?

6.

Which Graphics2D method draws an image?

7.

Which transform method moves an image to a new location?

8.

What is the name of the “transparency” channel in a 32-bit PNG image?

9.

What is the Applet class method used to load a resource from a JAR?

10.

Which KeyListener event detects key presses?

On Your Own

Use the following exercises to test your grasp of the material covered in this chapter.

Exercise 1

There are many example programs in this chapter that could be modified and experimented upon. Tweak the RandomImages program. Modify the program so that it loads and draws two different images randomly instead of just a single image.

Exercise 2

Modify the DrawImage program so that it will scale the image larger or smaller with the use of the keyboard plus (+) and minus (—) keys.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.134.232