Java 2D Graphics Dimension d = getSize(); if (checkImage(d)) { Graphics imageGraphics = mImage.getGraphics(); // Clear the image background. imageGraphics.setColor(getBackground()); imageGraphics.fillRect(0, 0, d.width, d.height); imageGraphics.setColor(getForeground()); // Draw this component offscreen. paint(imageGraphics); // Now put the offscreen image on the screen. g.drawImage(mImage, 0, 0, null); // Clean up. imageGraphics.dispose(); } g.dispose(); } } // Offscreen image. protected boolean checkImage(Dimension d) { if (d.width == 0 || d.height == 0) return false; if (mImage == null || mImage.getWidth(null) != d.width || mImage.getHeight(null) != d.height) { mImage = createImage(d.width, d.height); } return true; } protected void calculateFrameRate() { // Measure the frame ratelong now = System.currentTimeMillis(); int numberOfFrames = mPreviousTimes.length; double newRate; // Use the more stable method if a history is available. if (mPreviousFilled) newRate = (double)numberOfFrames / (double)(now - mPreviousTimes[mPreviousIndex]) * 1000.0; else newRate = 1000.0 / (double)(now - mPreviousTimes[numberOfFrames - 1]); firePropertyChange(”frameRate”, mFrameRate, newRate); mFrameRate = newRate; // Update the history. mPreviousTimes[mPreviousIndex] = now; mPreviousIndex++; if (mPreviousIndex >= numberOfFrames) { mPreviousIndex = 0; mPreviousFilled = true; } } public double getFrameRate() { return mFrameRate; } // Property change support. private transient AnimationFrame mRateListener; public void setRateListener(AnimationFrame af) { mRateListener = af; } protected void firePropertyChange(String name, double oldValue, double newValue) { mRateListener.rateChanged(newValue); page 257
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics // Measure the frame rate long now = System.currentTimeMillis(); int numberOfFrames = mPreviousTimes.length; double newRate; // Use the more stable method if a history is available. if (mPreviousFilled) newRate = (double)numberOfFrames / (double)(now - mPreviousTimes[mPreviousIndex]) * 1000.0; else newRate = 1000.0 / (double)(now - mPreviousTimes[numberOfFrames - 1]); firePropertyChange(”frameRate”, mFrameRate, newRate); mFrameRate = newRate; // Update the history. mPreviousTimes[mPreviousIndex] = now; mPreviousIndex++; if (mPreviousIndex >= numberOfFrames) { mPreviousIndex = 0; mPreviousFilled = true; } } Sharp-eyed readers will have noticed that calculateFrameRate() fires off a property event when the rate changes. What’s that all about? Animation-Component allows other objects that are interested in the frame rate to receive notification when it changes. This is done through the use of the java.beans.PropertyChangeSupport class. Without further ado, here is the entire AnimationComponent class: import java.awt.*; public abstract class AnimationComponentextends Container implements Runnable { private boolean mTrucking = true; private long[] mPreviousTimes; // milliseconds private int mPreviousIndex; private boolean mPreviousFilled; private double mFrameRate; // frames per second private Image mImage; public AnimationComponent() { mPreviousTimes = new long[128]; mPreviousTimes[0] = System.currentTimeMillis(); mPreviousIndex = 1; mPreviousFilled = false; } public abstract void timeStep(); public void run() { while (mTrucking) { render(); timeStep(); calculateFrameRate(); } } protected void render() { Graphics g = getGraphics(); if (g != null) { page 256
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics calculateFrameRate(); } } You may be wondering what render() does. Why not just call repaint()? The reason has to do with how repaint() works. The problem is that repaint() doesn’t do the drawing right away. That is, it doesn’t call the component’s paint() method directly. Instead, it tells AWT to redraw the component the next time it gets a chance. In a tight animation loop like this one, AWT doesn’t ever get a chance to redraw the window.[1] Because of this, the render() method actually does the drawing directly. The render() method implements one other nice feature: double buffering. It draws the component into an offscreen image and then renders the image on the screen. [1] Be careful if you decide to port this class to Swing. Threading in Swing is a bit more subtle; for more details, see Java Swing , by Robert Eckstein, Marc Loy, and Dave Wood (O’Reilly). protected void render() { Graphics g = getGraphics(); if (g != null) { Dimension d = getSize(); if (checkImage(d)) { Graphics imageGraphics = mImage.getGraphics(); // Clear the image background. imageGraphics.setColor(getBackground()); imageGraphics.fillRect(0, 0, d.width, d.height); imageGraphics.setColor(getForeground()); // Draw this component offscreen. paint(imageGraphics); // Now put the offscreen image on the screen. g.drawImage(mImage, 0, 0, null); // Clean up. imageGraphics.dispose(); } g.dispose(); } } There are two Graphics objects involved. The first, g, is the drawing surface of the onscreen component. The second, imageGraphics, is the drawing surface of the offscreen image. All the painting is performed on imageGraphics. Then the offscreen image is rendered to g to put it on the screen. AnimationComponent also calculates the frame rate of the animation. The basic algorithm for calculating the frame rate is to find the amount of time between two adjacent frames and take the reciprocal. Because the times are measured in milliseconds, the result is multiplied by 1000 to get frames per second: This result, however, fluctuates wildly depending on the current state of the system. It is also subject to the resolution of the clock.[2] For more stable results, AnimationComponent calculates an average frame rate. It keeps an array of frame times in the mPreviousTimes array and calculates the frame rate based on the oldest entry in that array. Until this array is filled, however, AnimationComponent uses the previous method. Here’s the code for calculateFrameRate(): [2] On my Windows NT system, for example, I only get readings in multiples of ten milliseconds. protected void calculateFrameRate() { page 255
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics Finally, the initial release of the 2D API may not be particularly fast. Sun was concentrating hard on getting the API design correct, which means they weren’t worrying as much about fixing bugs or making it run fast. Subsequent releases of the JDK should get better and better. And the elusive vaporware called HotSpot is supposed to improve performance dramatically some time in the Java 2 era. See http://java.sun.com/products/hotspot/ for more information. The bottom line is that it’s extremely difficult to pin down exactly what will make an application run quickly or slowly. As you read this chapter, keep your wits about you. Performance analysis and tuning is as much an art as a science. A healthy dose of common sense will take you a long way. There are tools that will help you analyze your application to find out what parts take the most time. These are called profilers. But as of this writing, profilers aren’t available for the Java 2 platform yet. 14.2 See for Yourself To see the effect of 2D operations on animation speeds, let’s develop an animation application framework. Through the rest of the chapter, I’ll develop classes that plug in to this framework to perform animation. The framework is composed of two classes, AnimationComponent and AnimationFrame. AnimationFrame serves as a container for Animation-Component and displays the current animation rate. 14.2.1 AnimationComponent The AnimationComponent class is an abstract subclass of java.awt.Component. It contains one additional method, timeStep(), that is called every time the animation should be moved ahead by a single frame. Creating a subclass of AnimationComponent is as simple as defining two methods: public void timeStep() This method is called before every frame of the animation. Your subclass should update its internal state appropriately. For a bouncing ball animation, for example, this method might update the position of the ball. public void paint(Graphics g) This is the paint() method defined in Component. This method should render the component’s current state. For the bouncing ball animation, this method should draw the ball in its current location. AnimationComponent is a Runnable, which means it can be placed in its own thread. The run() method is a tight loop that renders the current frame (by calling render()) and prepares for the next frame (by calling timeStep()). It also calculates a frame rate. I’ll explain how that works a little later. public void run() { while (mTrucking) { render(); timeStep(); page 254
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics public void append(Printable painter, PageFormat page) This method adds a single page to the end of this Book. The page will have the given PageFormat and be rendered using the supplied Printable. public void append(Printable painter, PageFormat page, int numPages) This method adds numPages new pages to the end of this Book. Each of the new pages will use the supplied Printable and PageFormat. public void setPage(int pageIndex, Printable painter, PageFormat page) Use this method to change an existing page in this Book. The page will use the supplied Printable and PageFormat. The following example shows how to use a Book. It uses the Patchwork-Component from earlier in this chapter. Two of these components are created and wrapped with BookComponentPrintable instances. The BookComponent-Printable class is just like the ComponentPrintable presented earlier, with one important difference. While the ComponentPrintable class was only designed to print the first page of a print job, BookComponentPrintable is capable of printing any page. Thus, its print() method never returns NO_SUCH_PAGE. The other important thing to notice is that instead of calling setPrintable() on the PrinterJob, this example creates a Book and passes it to the setPageable() method. When you run this example, you should get two pages of output. The first page is in portrait orientation and contains the string “printable1,” while the second page is in landscape orientation with the string “printable2.” import java.awt.*; import java.awt.print.*; public class Booker { public static void main(String[] args) { PrinterJob pj = PrinterJob.getPrinterJob(); // Create two Printables. Component c1 = new PatchworkComponent(”printable1″); Component c2 = new PatchworkComponent(”printable2″); c1.setSize(500, 400); c2.setSize(500, 400); BookComponentPrintable printable1 = new BookComponentPrintable(c1); BookComponentPrintable printable2 = new BookComponentPrintable(c2); // Create two PageFormats. PageFormat pageFormat1 = pj.defaultPage(); PageFormat pageFormat2 = (PageFormat)pageFormat1.clone(); pageFormat2.setOrientation(PageFormat.LANDSCAPE); // Create a Book. Book book = new Book(); book.append(printable1, pageFormat1); book.append(printable2, pageFormat2); // Print the Book. pj.setPageable(book); if (pj.printDialog()) { try { pj.print(); } catch (PrinterException e) { System.out.println(e); } page 252
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics } } } class BookComponentPrintableimplements Printable { private Component mComponent; public BookComponentPrintable(Component c) { mComponent = c; } public int print(Graphics g, PageFormat pageFormat, int pageIndex) { Graphics2D g2 = (Graphics2D)g; g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); mComponent.paint(g2); return PAGE_EXISTS; } } Chapter 14. Animation and Performance Performance is a term that refers to how fast an application runs. If someone says that an application has “performance issues,” it means the application is too slow. Computer chips double in speed every eighteen months or so, but software increases proportionately in size and complexity. There’s a lot of stuff between your application and the silicon. If you’re a Java developer, there’s an extra layer the Java Virtual Machine (JVM). All developers, and especially Java developers, have to be careful to make sure that their applications run fast enough to be useful. What does this have to do with the 2D API? Many developers have expressed interest in using the 2D API in animations, where each frame of the animation is generated on the fly. While the 2D API wasn’t designed specifically for animation, it can be used in this way. This chapter includes a flexible framework for building animation applications. It also includes three applications, built from the framework, that allow you to examine the effects of various 2D operations on the animation rate. 14.1 It’s Tougher Than You Might Think Whether 2D can render frames at a smooth animation rate depends on many factors, including the following: the application itself the JVM implementation the 2D API implementation, which may vary in different JDK releases the operating system the operating system configuration the display hardware (i.e. a video card) the system processor speed the system bus speed available memory To make things more complicated, the 2D API may perform different operations depending on the display hardware and software that’s available. page 253
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex >= mPages.size()) return NO_SUCH_PAGE; int savedPage = mCurrentPage; mCurrentPage = pageIndex; Graphics2D g2 = (Graphics2D)g; g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); paint(g2); mCurrentPage = savedPage; return PAGE_EXISTS; } public Dimension getPreferredSize() { return mPreferredSize; } public int getCurrentPage() { return mCurrentPage; } public int getNumPages() { return mPages.size(); } public void nextPage() { if (mCurrentPage < mPages.size() - 1) mCurrentPage++; repaint(); } public void previousPage() { if (mCurrentPage > 0) mCurrentPage–; repaint(); } } The initial release of Java 2 (JDK 1.2) contains printing bugs in Graphics2D’s drawString() methods. Specifically, the draw-String(String, float, float) and drawString(Attributed-CharacterIterator, float, float) methods do not work when printing. These bugs are fixed in JDK 1.2.1. If FilePrinter doesn’t work the first time you try, make sure you’re running JDK 1.2.1. 13.3.3 Advanced Page Control You can use Printable to print as many pages as you want, but they all will have the same page setup. In complex documents, you may want to mix different paper orientations and sizes. To accomplish this, you need the java.awt.print.Pageable interface. This interface specifies a way to associate a PageFormat and a Printable with each page of a print job. Most likely, you will use the java.awt.print.Book class, which implements the Pageable interface. Pageable defines three simple methods: public int getNumberOfPages() This method returns the total number of pages in this Pageable. public PageFormat getPageFormat(int pageIndex) This method returns the PageFormat that corresponds to the given page. public Printable getPrintable(int pageIndex) This method returns the Printable that should be used to render the given page. The Book class supports the three methods described above and adds three more: page 251
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics private int mFontSize; private Dimension mPreferredSize; public FilePageRenderer(File file, PageFormat pageFormat) throws IOException { mFontSize = 12; mFont = new Font(”Serif”, Font.PLAIN, mFontSize); // Open the file. BufferedReader in = new BufferedReader( new FileReader(file)); // Read all the lines. String line; mLines = new Vector(); while ((line = in.readLine()) != null) mLines.addElement(line); // Clean up. in.close(); // Now paginate, based on the PageFormat. paginate(pageFormat); } public void paginate(PageFormat pageFormat) { mCurrentPage = 0; mPages = new Vector(); float y = mFontSize; Vector page = new Vector(); for (int i = 0; i < mLines.size(); i++) { String line = (String)mLines.elementAt(i); page.addElement(line); y += mFontSize; if (y + mFontSize * 2 > pageFormat.getImageableHeight()) { y = 0; mPages.addElement(page); page = new Vector(); } } // Add the last page. if (page.size() > 0) mPages.addElement(page); // Set our preferred size based on the PageFormat. mPreferredSize = new Dimension((int)pageFormat.getImageableWidth(), (int)pageFormat.getImageableHeight()); repaint(); } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; // Make the background white. java.awt.geom.Rectangle2D r = new java.awt.geom.Rectangle2D.Float(0, 0, mPreferredSize.width, mPreferredSize.height); g2.setPaint(Color.white); g2.fill(r); // Get the current page. Vector page = (Vector)mPages.elementAt(mCurrentPage); // Draw all the lines for this page. g2.setFont(mFont); g2.setPaint(Color.black); float x = 0; float y = mFontSize; for (int i = 0; i < page.size(); i++) { String line = (String)page.elementAt(i); if (line.length() > 0) g2.drawString(line, (int)x, (int)y); y += mFontSize; } } page 250
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost JSP Web Hosting services
Java 2D Graphics extends AbstractAction { public PageNextPageAction() { super(”Next page”); } public void actionPerformed(ActionEvent ae) { if (mPageRenderer != null) mPageRenderer.nextPage(); showTitle(); } } public class PagePreviousPageAction extends AbstractAction { public PagePreviousPageAction() { super(”Previous page”); } public void actionPerformed(ActionEvent ae) { if (mPageRenderer != null) mPageRenderer.previousPage(); showTitle(); } } } The FilePageRenderer class does the work of rendering each page. It’s a subclass of JComponentthat also implements the Printable interface. The paint() method does all of the page rendering. The print() method adjusts the origin of the Graphics2D to coincide with the paper’s imageable area and then calls paint() to render the page. A FilePageRenderer is created with a File and a PageFormat. It initializes itself in two steps: 1. The entire file is read, line by line. The lines are stored in a Vector member variable, mLines. 2. The FilePageRenderer paginates itself using the supplied PageFormat. It goes through the mLines vector and figures out how many lines will fit on each page. Each page is stored as a Vector of Strings. The pages themselves are stored in a Vector called mPages. This algorithm is contained in the paginate() method. If the current PageFormat changes (i.e., if the user chooses the Page setup menu item), the FilePageRenderer can be repaginated with another call to paginate(). To render a single page, FilePageRenderer simply looks in mPages to find the current page. Then it renders all of the strings for the current page, not really caring whether it’s rendering to the screen or the printer. Here’s an example: import java.awt.*; import java.awt.print.*; import java.io.*; import java.util.Vector; import javax.swing.*; public class FilePageRendererextends JComponentimplements Printable { private int mCurrentPage; // mLines contains all the lines of the file. private Vector mLines; // mPages is a Vector of Vectors. Each of its elements // represents a single page. Each of its elements is // a Vector containing Strings that are the lines for // a particular page. private Vector mPages; private Font mFont; page 249
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost Java Web Hosting services
Java 2D Graphics public class FileOpenAction extends AbstractAction { public FileOpenAction() { super(”Open…”); } public void actionPerformed(ActionEvent ae) { // Pop up a file dialog. JFileChooser fc = new JFileChooser(”.”); int result = fc.showOpenDialog(FilePrinter.this); if (result != 0) { return; } java.io.File f = fc.getSelectedFile(); if (f == null) { return; } // Load the specified file. try { mPageRenderer = new FilePageRenderer(f, mPageFormat); mTitle = “[” + f.getName() + “]”; showTitle(); JScrollPane jsp = new JScrollPane(mPageRenderer); getContentPane().removeAll(); getContentPane().add(jsp, BorderLayout.CENTER); validate(); } catch (java.io.IOException ioe) { System.out.println(ioe); } } } public class FilePrintAction extends AbstractAction { public FilePrintAction() { super(”Print”); } public void actionPerformed(ActionEvent ae) { PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPrintable(mPageRenderer, mPageFormat); if (pj.printDialog()) { try { pj.print(); } catch (PrinterException e) { System.out.println(e); } } } } public class FilePageSetupAction extends AbstractAction { public FilePageSetupAction() { super(”Page setup…”); } public void actionPerformed(ActionEvent ae) { PrinterJob pj = PrinterJob.getPrinterJob(); mPageFormat = pj.pageDialog(mPageFormat); if (mPageRenderer != null) { mPageRenderer.paginate(mPageFormat); showTitle(); } } } public class FileQuitAction extends AbstractAction { public FileQuitAction() { super(”Quit”); } public void actionPerformed(ActionEvent ae) { System.exit(0); } } public class PageNextPageAction page 248
Note: If you are looking for good and high quality web space to host and run your application check Lunarwebhost Java Web Hosting services