% -*- mode:Normal Text Tex YTex sabon convert; package:user -*- %
% Spelling checked: 6:52 pm, 29 April 1996

\input macros

\ifCable\def\figurePath{e:/eps/java/}\fi

\xtray Appendix D:\\Fighting Flicker

This appendix explains how to reduce the flicker often seen in dynamic applets.

\explanation

Suppose you want to define a logo for your application in
which a moon revolves around a planet.  
The logo, seen with the moon just disappearing behind the planet, looks as follows:

\with \stationary \makefigure \figurePath d.1

\explanation

You can define the \code{PlanetCanvas} class as follows:

\begincode
import java.awt.*;
public class PlanetCanvas extends Canvas {
 int position = 0;
 public void IncrementPosition() {
  if (position < 100) {++position;}
  else {position = 0;}
  repaint();
 }
 public void paint(Graphics g) {
  Dimension d = size();
  \dike details of drawing computation as in \chunkref\dSecondTry/!
  int x0 = d.width / 2;
  int y0 = d.height / 2;
  int pR = x0 / 5, pD = 2 * pR;
  int sR = x0 / 15, sD = 2 * sR;
  int orbitX = x0 / 2, orbitY = y0 / 6;
  double angle = Math.PI * position / 50;
  double sinAngle = Math.sin(angle);
  double cosAngle = Math.cos(angle);
  int dX = (int) (sinAngle * orbitX);
  int dY = (int) (cosAngle * orbitY);
\show
  g.setColor(Color.black);
  if (cosAngle <= 0) {g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);}
  g.setColor(Color.gray);
  g.fillOval(x0 - pR, y0 - pR, pD, pD);
  g.setColor(Color.black);
  if (cosAngle > 0) {g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);}
 }
}
\transfer
\endcode

When the moon is behind the planet, the moon is drawn first, and then the
planet is drawn over the moon, hiding all or part of it.  When the moon is
in front of the planet, the planet is drawn first, and then the moon is
drawn over the planet, hiding all or part of it.

\explanation

Because drawing takes time, the hidden part of the drawing may be seen
briefly before the hiding part obscures that hidden part.  If so, you
perceive flicker.

\explanation

One way to fight flicker is to draw on a virtual display first, and then
blast the bits in the virtual display onto your real display.  There is no
flicker because all the hidden parts are fully hidden before any part of
the drawing is moved to your display.

\explanation

\labelchunk \dExplanation To draw on a virtual display, you declare a
variable of the
\hide{\xcode{createImage}}
\hide{\xcode{getGraphics}}
\indxcode{Image} class.

\begincode
Image imageBuffer;
\endcode

Then, when you know the dimensions of the window in which the logo is to be
\hide{\xcode{fillOval}}
displayed, you create an \code{Image} instance: 

\begincode
                          *--------*-- Value is a Dimension instance
                          v        v
imageBuffer = createImage(d.width, d.height);
\endcode

From the new \code{Image} instance, you obtain a graphics context:

\begincode
g = imageBuffer.getGraphics();
\endcode

Then, you draw using the graphics context of the image as if it
were the graphics context of your display:  

\begincode
              *--------*------*---*-- Previously computed arguments.
              v        v      v   v
g.fillOval(x0 - pR, y0 - pR, pD, pD);
\endcode

Finally, when your drawing is complete, you blast the bits
onto your read display using the same \indxcode{drawImage} method that you see
used in \unitref\Uimages/ to draw images obtained from image files:

\begincode
g.drawImage(imageBuffer, 0, 0, this);
\endcode

\explanation

\labelchunk \dSecondTry Using the statements explained in
\chunkref\dExplanation/, you have the following revised definition of the
\code{PlanetCanvas} class:

\begincode
import java.awt.*;
public class PlanetCanvas extends Canvas {
 int position = 0;
 Image imageBuffer;                             ///
 public void IncrementPosition() {
  if (position < 100) {++position;}
  else {position = 0;}
  repaint();
 }
\seam
 public void setup(Graphics g) {
  Dimension d = size();
  imageBuffer = createImage(d.width, d.height); ///
  g = imageBuffer.getGraphics();                ///
  int x0 = d.width / 2;
  int y0 = d.height / 2;
  int pR = x0 / 5, pD = 2 * pR;
  int sR = x0 / 15, sD = 2 * sR;
  int orbitX = x0 / 2, orbitY = y0 / 6;
  double angle = Math.PI * position / 50;
  double sinAngle = Math.sin(angle);
  double cosAngle = Math.cos(angle);
  int dX = (int) (sinAngle * orbitX);
  int dY = (int) (cosAngle * orbitY);
  if (cosAngle <= 0) {
   g.setColor(Color.black);
   g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);
  }
  g.setColor(Color.gray);
  g.fillOval(x0 - pR, y0 - pR, pD, pD);
  g.setColor(Color.black);
  if (cosAngle > 0) {
   g.setColor(Color.black);
   g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);
  }
 }
 public void paint(Graphics g) {
  Dimension d = size();
  if (imageBuffer != null) { 
   g.drawImage(imageBuffer, 0, 0, this);        ///
  }
  setup(g);
}}
\transfer
\endcode

\explanation

To run the \code{PlanetCanvas} logo, you need to call the
\code{IncrementPosition} method periodically from a thread:

\begincode
import java.lang.*;
import java.awt.*;
public class PlanetThread extends Thread {
 PlanetCanvas canvas;
 PlanetThread (PlanetCanvas c) {canvas = c;}
 public void run () {
  while(true) {
   canvas.IncrementPosition();
   try {sleep(100);} catch (InterruptedException e) {stop();}
\transfer
}}}
\endcode

You also need a test applet:

\begincode
import java.applet.*;
import java.awt.*;
import java.util.*;
public class TestApplet extends Applet {
 public static void main (String args []) {                        
  AppletTestorFrame f = new AppletTestorFrame("BorderLayout Test");
  TestApplet p = new TestApplet(); p.init();                       
  f.add("Center", p); f.resize(400, 400);  f.show();               
 }                                                                 
 PlanetCanvas planetCanvas = new PlanetCanvas();                                                              
 PlanetThread planetThread = new PlanetThread(planetCanvas); 
 public void init () {
  setLayout(new BorderLayout());
  add("Center", planetCanvas);    
  planetThread.start();           
 }
}
\transfer
\endcode

\stop

\begincode
// Version with imageBuffer array
import java.awt.*;
public class PlanetCanvas extends Canvas {
 int position = 0;
 Image imageBuffer;
 Image imageArray [] = new Image[100];                  ///
 public void IncrementPosition() {
  if (position < 99) {++position;}
  else {position = 0;}
  repaint();
 }
 public void setup(Graphics g) {
  int i;
  for (i = 0; i <= 99; ++i) {
   Dimension d = size();
   // if (imageBuffer == null) {imageBuffer = createImage(d.width, d.height);}
   imageBuffer = createImage(d.width, d.height);
   g = imageBuffer.getGraphics();
   int x0 = d.width / 2;
   int y0 = d.height / 2;
   int pR = x0 / 5, pD = 2 * pR;
   int sR = x0 / 15, sD = 2 * sR;
   int orbitX = x0 / 2, orbitY = y0 / 6;
   double angle = Math.PI * i / 50;
   double sinAngle = Math.sin(angle);
   double cosAngle = Math.cos(angle);
   int dX = (int) (sinAngle * orbitX);
   int dY = (int) (cosAngle * orbitY);
   if (cosAngle <= 0) {
    g.setColor(Color.black);
    g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);
   }
   g.setColor(Color.gray);
   g.fillOval(x0 - pR, y0 - pR, pD, pD);
   g.setColor(Color.black);
   if (cosAngle > 0) {
    g.setColor(Color.black);
    g.fillOval(x0 + dX - sR, y0 + dY - sR, sD, sD);
   }
   System.out.println("Loading " + i + "; angle = " + angle);
   imageArray[i] = imageBuffer;
  }
 }
 public void paint(Graphics g) {
  Dimension d = size();
  if (imageBuffer != null) { 
   g.drawImage(imageArray[position], 0, 0, this);
   System.out.println("Position is " + position);
  }
  else {
   System.out.println("Setting up");
   setup(g);
  }
 }
}
\endcode



\stop

import java.awt.*;
import java.lang.Runnable;
import java.lang.Thread;

class ScrollTextPanel extends Panel implements Runnable {
    String displaying;
    Image  imageBuffer;
    Thread runner;
    Font   font;
    int    curoffset;
    int    maxlen;
    int    swidth;
    int    increment;
    
    ScrollTextPanel(String s) {
        displaying = null;
        setText(s);
        runner = new Thread(this);
        font = new Font("TimesRoman", Font.BOLD, 24);
        curoffset = 0;
        maxlen = 0;
        increment = 0;
    }

    public void setText(String s) {
        displaying = s;
    }

    public void run() {
        while (runner.isAlive()) {
            // increment the offset by an increment
            if ((curoffset + increment) >= maxlen)
              curoffset = 0;
            else
              curoffset += increment;
            
            repaint();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                break;
            }
        }
    }

    void setup(Graphics g, Dimension d) {
        g.setFont(font);
        FontMetrics f = g.getFontMetrics();
        int width = f.stringWidth(displaying);
        int fontHeight = f.getHeight();
        int fontDescent = f.getDescent();

        swidth = d.width;
        maxlen = d.width + width;
        increment = f.stringWidth("m");
        
        imageBuffer = createImage(maxlen + d.width, d.height);
        g = imageBuffer.getGraphics();
        g.setColor(Color.black);
        g.fillRect(0, 0, maxlen + d.width, d.height);
        g.setColor(Color.green);
        g.setFont(font);        
        g.drawString(displaying, d.width,
                     (d.height/2)+(fontHeight/2-fontDescent));
        curoffset = -increment;
        runner.start();
    }
    
    public void paint(Graphics g) {
        Dimension d = size();
        if (imageBuffer != null) 
          g.drawImage(imageBuffer, -curoffset, 0, this);
        else {
            setup(g, d);
        }
    }
};

public class ScrollText {
    public static void main(String args[]) {
        Frame f = new Frame();
        f.resize(200, 80);
        ScrollTextPanel p = new ScrollTextPanel("This is a simple test of the emergency broadcast system. Testing 1 2 3. Hello Patrick!" );
    
        f.add("Center", p);
        f.show();       
    }
};
