Loading ImagesThe ImagesLoader class can load four different formats of images, which I call o, n, s, and g images. The images are assumed to be in a local JAR file in a subdirectory Images/ below ImagesLoader. They are loaded as BufferedImages using ImageIO's read( ), so they can become managed images. The typical way of using an ImagesLoader object is to supply it with a configuration file containing the filenames of the images that should be loaded before game play Figure 6-2. Class diagrams for ImagesTestsbegins. However, it is possible to call ImagesLoader's load methods at any time during execution. Here is the imsInfo.txt configuration file used in ImagesTests: // imsInfo.txt images o atomic.gif o balls.jpg o bee.gif o cheese.gif o eyeChart.gif o house.gif o pumpkin.png o scooter.gif o ufo.gif o owl.png n numbers*.gif 6 n figure*.gif 9 g fighter left.gif right.gif still.gif up.gif s cars.gif 8 s cats.gif 6 s kaboom.gif 6 o basn6a08.png o basn6a16.png Blank lines, and lines beginning with //, are ignored by the loader. The syntax for the four image formats is: o <fnm> n <fnm*.ext> <number> s <fnm> <number> g <name> <fnm> [ <fnm> ]* An o line causes a single filename, called <fnm>, to be loaded from Images/. A n line loads a series of numbered image files, whose filenames use the numbers 0-<number>-1 in place of the * character in the filename. For example: n numbers*.gif 6 This indicates that the files numbers0.gif, numbers1.gif, and so forth, up to numbers5.gif, should be loaded. An s line loads a strip file (called fnm) containing a single row of <number> images. After the file's graphic has been loaded, it's automatically divided up into the component images. For instance: s kaboom.gif 6 This refers to the strip file kaboom.gif containing a row of six images, as shown in Figure 6-3. Figure 6-3. The kaboom.gif strip fileA g line specifies a group of files with different names. After being loaded, the images can be accessible using a positional notation or by means of their filenames (minus the extension). For example, the fighter g images are defined this way: g fighter left.gif right.gif still.gif up.gif Subsequently, the image in right.gif can be accessed using the number 1 or the string "right". Internal Data StructuresThe ImagesLoader object creates two main data structures as it loads the images, both of them HashMaps: private HashMap imagesMap, gNamesMap; The imagesMap key is the image's name, and its value is an ArrayList of BufferedImage objects associated with that name. The exact meaning of the name depends on the type of image loaded:
The loading of g images also causes updates to the gNamesMap HashMap. Its key is the g name (e.g., fighter), but its value is an ArrayList of filename strings (minus their extensions). For instance, the fighter name has an ArrayList associated with it holding the strings "left", "right", "still", and "up". Getting an ImageThe image accessing interface is uniform and independent of whether o, n, s, or g images are being accessed. Three public getImage( ) methods are in ImagesLoader and getImages( ). Their prototypes are shown here: BufferedImage getImage(String name); BufferedImage getImage(String name, int posn); BufferedImage getImage(String name, String fnmPrefix); ArrayList getImages(String name); The single argument version of getImage( ) returns the image associated with name and is intended primarily for accessing o images, which only have a single image. If an n, s, or g image is accessed, then the first image in the ArrayList will be returned. The two-argument version of getImage( ), which takes an integer position argument, is more useful for accessing n, s, and g names with multiple images in their ArrayLists. If the supplied number is negative, then the first image will be returned. If the number is too large, then it will be reduced modulo the ArrayList size. The third getImage( ) method takes a String argument and is aimed at g images. The String should be a filename, which is used to index into the g name's ArrayList. The getImages( ) method returns the entire ArrayList for the given name. Using ImagesLoaderImagesTests employs ImagesLoader by supplying it with an images configuration file: ImagesLoader imsLoader = new ImagesLoader("imsInfo.txt"); The ImagesLoader constructor assumes the file (and all the images) is in the Images/ subdirectory below the current directory, and everything is packed inside a JAR.
Loading o images is straightforward: BufferedImage atomic = imsLoader.getImage("atomic"); Loading n, s, and g images usually requires a numerical value: BufferedImage cats1 = imsLoader.getImage("cats", 1); A related method is numImages( ), which returns the number of images associated with a given name: int numCats = imsLoader.numImage("cats"); g images can be accessed using a filename prefix: BufferedImage leftFighter = imsLoader.getImage("fighter", "left"); If a requested image cannot be found, then null will be returned by the loader. An alternative way of using ImagesLoader is to create an empty loader (in other words, no configuration file is supplied to the constructor). Then public methods for loading o, n, s, and g images can then be called by the application, rather than being handled when a configuration file is loaded: ImagesLoader imsLoader = new ImagesLoader( ); // empty loader imsLoader.loadSingleImage("atomic.gif"); // load images at run-rime imsLoader.loadNumImages("numbers*.gif", 6); imsLoader.loadStripImages("kaboom.gif", 6); String[] fnms = {"left.gif", "right.gif", "still.gif", "up.gif"}; imsLoader.loadGroupImages("fighter", fnms ); Implementation DetailsA large part of ImagesLoader is given over to parsing and error checking. The top-level method for parsing the configuration file is loadImagesFile( ): private void loadImagesFile(String fnm) { String imsFNm = IMAGE_DIR + fnm; System.out.println("Reading file: " + imsFNm); try { InputStream in = this.getClass( ).getResourceAsStream(imsFNm); BufferedReader br = new BufferedReader( new InputStreamReader(in)); String line; char ch; while((line = br.readLine( )) != null) { if (line.length( ) == 0) // blank line continue; if (line.startsWith("//")) // comment continue; ch = Character.toLowerCase( line.charAt(0) ); if (ch == 'o') // a single image getFileNameImage(line); else if (ch == 'n') // a numbered sequence of images getNumberedImages(line); else if (ch == 's') // an images strip getStripImages(line); else if (ch == 'g') // a group of images getGroupImages(line); else System.out.println("Do not recognize line: " + line); } br.close( ); } catch (IOException e) { System.out.println("Error reading file: " + imsFNm); System.exit(1); } } // end of loadImagesFile( ) One line of the file is read at a time, and a multiway branch decides which syntactic form should be processed, depending on the first character on the input line. The input stream coming from the configuration file is created using Class.getResourceAsStream( ), which is needed when the application and all the resources all wrapped up inside a JAR. getFileNameImage( ) is typical in that it extracts the tokens from the line and processes them by calling loadSingleImage( ): private void getFileNameImage(String line) // format is o <fnm> { StringTokenizer tokens = new StringTokenizer(line); if (tokens.countTokens( ) != 2) System.out.println("Wrong no. of arguments for " + line); else { tokens.nextToken( ); // skip command label System.out.print("o Line: "); loadSingleImage( tokens.nextToken( ) ); } } loadSingleImage( ) is the public method for loading an o image. If an entry for the image's name doesn't exist, then imagesMap will be extended with a new key (holding name) and an ArrayList containing a single BufferedImage: public boolean loadSingleImage(String fnm) { String name = getPrefix(fnm); if (imagesMap.containsKey(name)) { System.out.println( "Error: " + name + "already used"); return false; } BufferedImage bi = loadImage(fnm); if (bi != null) { ArrayList imsList = new ArrayList( ); imsList.add(bi); imagesMap.put(name, imsList); System.out.println(" Stored " + name + "/" + fnm); return true; } else return false; } Image LoadingWe arrive at the image-loading method, loadImage( ), which is at the heart of the processing of n and g lines. Its implementation is almost identical to the loadImage( ) method described in the section "The Internals of BufferedImage" in Chapter 5: public BufferedImage loadImage(String fnm) { try { BufferedImage im = ImageIO.read( getClass( ).getResource(IMAGE_DIR + fnm) ); int transparency = im.getColorModel( ).getTransparency( ); BufferedImage copy = gc.createCompatibleImage( im.getWidth( ), im.getHeight( ), transparency ); // create a graphics context Graphics2D g2d = copy.createGraphics( ); // reportTransparency(IMAGE_DIR + fnm, transparency); // copy image g2d.drawImage(im,0,0,null); g2d.dispose( ); return copy; } catch(IOException e) { System.out.println("Load Image error for " + IMAGE_DIR + "/" + fnm + ":\n" + e); return null; } } // end of loadImage( ) using ImageIO reportTransparency( ) is a debugging utility for printing out the transparency value of the loaded image. It's useful for checking if the transparency/translucency of the image has been detected.
ImagesLoader contains two other versions of loadImages( ), called loadImages2( ) and loadImages3( ). They play no part in the functioning of the class and are only included to show how BufferedImages can be loaded using ImageIcon or Image's getImage( ). The ImageIcon code in loadImages2( ) uses this code: ImageIcon imIcon = new ImageIcon( getClass( ).getResource(IMAGE_DIR + fnm) ); Then, it calls makeBIM( ) to convert its Image into a BufferedImage. makeBIM( ) is described in the section "From Image to BufferedImage" in Chapter 5. The Image code in loadImage3( ) uses a MediaTracker to delay execution until the image is fully loaded and then calls makeBIM( ) to obtain a BufferedImage. Loading Strip File ImagesThe images from a strip file are obtained in steps: First, the entire graphic is loaded from the file, cut into pieces, and each resulting image is placed in an array. This array is subsequently stored as an ArrayList in imagesMap under the s name: public BufferedImage[] loadStripImageArray(String fnm, int number) { if (number <= 0) { System.out.println("number <= 0; returning null"); return null; } BufferedImage stripIm; if ((stripIm = loadImage(fnm)) == null) { System.out.println("Returning null"); return null; } int imWidth = (int) stripIm.getWidth( ) / number; int height = stripIm.getHeight( ); int transparency = stripIm.getColorModel( ).getTransparency( ); BufferedImage[] strip = new BufferedImage[number]; Graphics2D stripGC; // each BufferedImage from the strip file is stored in strip[] for (int i=0; i < number; i++) { strip[i]=gc.createCompatibleImage(imWidth,height,transparency); // create a graphics context stripGC = strip[i].createGraphics( ); // copy image stripGC.drawImage(stripIm, 0,0, imWidth,height, i*imWidth,0, (i*imWidth)+imWidth,height, null); stripGC.dispose( ); } return strip; } // end of loadStripImageArray( ) drawImage( ) is used to clip the images out of the strip.
|