Java Lecture 7 Animation Issues

11 downloads 353 Views 48KB Size Report
Lecture 7. Animation Issues. Flicker (also called Flashing): The marquee applet may have flickered. If not, increase the frames per second till it does.
Java Lecture 7 Animation Issues Flicker (also called Flashing): The marquee applet may have flickered. If not, increase the frames per second till it does. More complicated output may flicker much more. Why??? When a repaint is issued, update gets called. Update clears the window and then calls paint to recreate the display. For high frame rates the update/clear/paint gets called more frequently sometimes so fast that the image has virtually no time being displayed. Thgis results in a flicker effect.

An Example: Generate an applet called MyFriendFlicka with multithreading, no animation support (yet) and mouseDown capture. Then add code as shown below. This baby will flicker like crazy.

import java.applet.*; import java.awt.*; public class MyFriendFlicka extends Applet implements Runnable { private Thread m_MyFriendFlicka = null; int frameNumber = -1; int delay = 100; // fps = 10 boolean frozen = false; int squareSize = 20; boolean fillColumnTop = true; public MyFriendFlicka() { } public String getAppletInfo() { return ""; }

public void init() { } public void destroy() { } public void start() { if ( frozen ) ; // do nothing - user request else { // start animation if (m_MyFriendFlicka == null) { m_MyFriendFlicka = new Thread(this); m_MyFriendFlicka.start(); } } } public void stop() { if (m_MyFriendFlicka != null) { m_MyFriendFlicka = null; } } public void run() { // lower this threads priority m_MyFriendFlicka.currentThread() .setPriority(Thread.MIN_PRIORITY); // starting time long startTime = System.currentTimeMillis(); // animation loop while ( m_MyFriendFlicka == Thread.currentThread() ) { // advance the frame number count frameNumber++;

// display it repaint(); // delay depending how far behind try { startTime+= delay; Thread.sleep( Math.max( 0, startTime System.currentTimeMillis() )); } catch (InterruptedException w ) { break; } } } public boolean mouseDown(Event evt, int x, int y) { if (frozen) { // unfreeze frozen = false; start(); } else { // freeze frozen = true; stop(); } return true; } public boolean mouseUp(Event evt, int x, int y) { return true; } public void paint(Graphics g) { // get the window size Dimension d = size(); boolean fillSquare, fillNextFrame; int rowWidth = 0, w, h, tmp; int x=0, y=0; fillSquare = fillColumnTop; fillColumnTop = !fillColumnTop;

tmp = frameNumber % squareSize; if ( tmp != 0 ) { w = tmp; fillNextFrame = fillSquare; } else { w = squareSize; fillNextFrame = !fillSquare; } // draw while ( x < d.width ) { int colHeight = 0; while ( y < d.height ) { colHeight+= squareSize; // not enough room for full square if ( colHeight > d.height ) h = d.height - y; else h = squareSize; // draw the rectangle if (fillSquare) { g.fillRect(x, y, w, h); fillSquare = false; } else fillSquare = true; y+= h; } x+= w; y = 0; w = squareSize; rowWidth+= w; if ( rowWidth > d.width ) w = d.width - x; fillSquare = fillColumnTop; fillColumnTop = !fillColumnTop; } fillColumnTop = fillNextFrame; } }

Simple Tricks to Reduce Flicker: 1. Paint first the things the user will focus on. 2. Paint the parts that can be drawn quickly.

Avoid Screen Clears: A slightly more sophisticated method is to avoid a screen clear leaving ok parts alone. We can write our own update routine (overriding the default) and have it NOT do a clear (except where or when needed) and paint everything needed. We still need a regular paint routine because the browser will call it to redo the window when needed (like after a minimize). public void update (Graphics g)

Double Buffering: None of these simple tricks will help our MyFriendFlicka applet. The idea behind double buffering is to leave the screen alone and draw the next image to an off-screen graphics object. So, while this is happening, the origian image will be unchanging. When we are done drawing the new image we'll do a block copy to the applet window. The off-screen image is a second image buffer hence the "double-buffering" terminology. This will erase the flicker!

Dimension offDimension; Image offImage; Graphics offGraphics; public void stop() { m_DoubleBuf = null; // get rid of double buffer stuff offGraphics = null; offImage = null; } public boolean mouseDown(Event evt, int x, int y) { if (frozen) { // unfreeze frozen = false; start(); } else { // freeze frozen = true; // just kill the thread m_DoubleBuf = null; } return true; }

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

public void update(Graphics g) { // get the window size Dimension d = size(); boolean fillSquare, fillNextFrame; int rowWidth = 0, w, h, tmp; int x=0, y=0; // create the off-screen graphics context // if needed if ( offGraphics == null || (d.width != offDimension.width) || (d.height != offDimension.height) ) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } // erase previous image in offScreen graphic offGraphics.setColor(getBackground()); offGraphics.fillRect(0,0, d.width, d.height); offGraphics.setColor(Color.black); fillSquare = fillColumnTop; fillColumnTop = !fillColumnTop; tmp = frameNumber % squareSize; if ( tmp != 0 ) { w = tmp; fillNextFrame = fillSquare; } else { w = squareSize; fillNextFrame = !fillSquare; } // draw while ( x < d.width ) { int colHeight = 0; while ( y < d.height ) { colHeight+= squareSize; // not enough room for full square if ( colHeight > d.height ) h = d.height - y;

else h = squareSize; // draw the rectangle if (fillSquare) { offGraphics.fillRect(x, y, w, h); fillSquare = false; } else fillSquare = true; y+= h; } x+= w; y = 0; w = squareSize; rowWidth+= w; if ( rowWidth > d.width ) w = d.width - x; fillSquare = fillColumnTop; fillColumnTop = !fillColumnTop; } fillColumnTop = fillNextFrame; // paint the image on the screen g.drawImage(offImage, 0, 0, this); }

Raw Animation with Image Files (like gifs): Real animation means replaying images so quickly that the user sees continuous motion. In the MyFriendFlicka example, the computer was generating the image and we were able to throw these at the display fast enough to create the animation effect. However, most animations are achieved by using images (such as gif files) and displaying one after another fast. But there is no way to transmit these to an applet in real-time. The way to handle this is to play off the double-buffering idea. Say we have 20 images (i.e., 20 gif files). We could load each of these into an off-screen buffer at start up and then flip through them. The AppWizard will set up the mechanism to handle all this when you select the animation support. Generate an applet with multi-treading and animation support.

The only changes I made deal with using the html parameters instead of constants built-in by the appWizard. :

// will set number from html // private final int NUM_IMAGES = 18; public void run() { m_nCurrImage = 0; if (!m_fAllLoaded) { repaint(); m_Graphics = getGraphics(); m_Images = new Image[m_numImages]; // Load in all the images //-----------------------------------------------------------------MediaTracker tracker = new MediaTracker(this); String strImage; // For each image in the animation, this method first constructs a // string containing the path to the image file; then it begins // loading the image into the m_Images array. Note that the call to // getImage will return before the image is completely loaded. //-----------------------------------------------------------------for (int i = 1; i