Monday 27 March 2017

Double Buffering & MediaTracker - Java Tutorials

Double Buffering

Not only are images useful for storing pictures, as we’ve just shown, but you can also use them as offscreen drawing surfaces. This allows you to render any image, including text and graphics, to an offscreen buffer that you can display at a later time. The advantage to doing this is that the image is seen only when it is complete. Drawing a complicated image could take several milliseconds or more, which can be seen by the user as flashing or flickering. This flashing is distracting and causes the user to perceive your rendering as slower than it actually is. Use of an offscreen image to reduce flicker is called double buffering, because the screen is considered a buffer for pixels, and the offscreen image is the second buffer, where you can prepare pixels for display.

Earlier in this chapter, you saw how to create a blank Image object. Now you will see how to draw on that image rather than the screen. As you recall from earlier chapters, you need a Graphics object in order to use any of Java’s rendering methods. Conveniently, the Graphics object that you can use to draw on an Image is available via the getGraphics( ) method. Here is a code fragment that creates a new image, obtains its graphics context, and fills the entire image with red pixels:

  Canvas c = new Canvas();
  Image test = c.createImage(200, 100);
  Graphics gc = test.getGraphics();
  gc.setColor(Color.red);
  gc.fillRect(0, 0, 200, 100);

Once you have constructed and filled an offscreen image, it will still not be visible. To actually display the image, call drawImage( ). Here is an example that draws a time-consuming image, to demonstrate the difference that double buffering can make in perceived drawing time:

  /*
   * <applet code=DoubleBuffer width=250 height=250>
   * </applet>
   */
  import java.awt.*;
  import java.awt.event.*;
  import java.applet.*;

  public class DoubleBuffer extends Applet {
    int gap = 3;
    int mx, my;
    boolean flicker = true;
    Image buffer = null;
    int w, h;

    public void init() {
      Dimension d = getSize();
      w = d.width;
      h = d.height;
      buffer = createImage(w, h);
      addMouseMotionListener(new MouseMotionAdapter() {
        public void mouseDragged(MouseEvent me) {
          mx = me.getX();
          my = me.getY();
          flicker = false;
          repaint();
        }
        public void mouseMoved(MouseEvent me) {
          mx = me.getX();
          my = me.getY();
          flicker = true;
          repaint();
        }
      });
    }

    public void paint(Graphics g) {
      Graphics screengc = null;

      if (!flicker) {
        screengc = g;
        g = buffer.getGraphics();
      }

      g.setColor(Color.blue);
      g.fillRect(0, 0, w, h);

      g.setColor(Color.red);
      for (int i=0; i<w; i+=gap)
        g.drawLine(i, 0, w-i, h);
      for (int i=0; i<h; i+=gap)
        g.drawLine(0, i, w, h-i);

      g.setColor(Color.black);
      g.drawString("Press mouse button to double buffer", 10, h/2);

      g.setColor(Color.yellow);
      g.fillOval(mx - gap, my - gap, gap*2+1, gap*2+1);

      if (!flicker) {
        screengc.drawImage(buffer, 0, 0, null);
      }
    }
    public void update(Graphics g) {
      paint(g);
    }
  }

This simple applet has a complicated paint( ) method. It fills the background with blue and then draws a red moiré pattern on top of that. It paints some black text on top of that and then paints a yellow circle centered at the coordinates mx,my. The mouseMoved( ) and mouseDragged( ) methods are overridden to track the mouse position. These methods are identical, except for the setting of the flicker Boolean variable. mouseMoved( ) sets flicker to true, and mouseDragged( ) sets it to false. This has the effect of calling repaint( ) with flicker set to true when the mouse is moved (but no button is pressed) and set to false when the mouse is dragged with any button pressed.

When paint( ) gets called with flicker set to true, we see each drawing operation as it is executed on the screen. In the case where a mouse button is pressed and paint( ) is called with flicker set to false, we see quite a different picture. The paint( ) method swaps the Graphics reference g with the graphics context that refers to the offscreen canvas, buffer, which we created in init( ). Then all of the drawing operations are invisible. At the end of paint( ), we simply call drawImage( ) to show the results of these drawing methods all at once.

Notice that it is okay to pass in a null as the fourth parameter to drawImage( ). This is the parameter used to pass an ImageObserver object that receives notification of image events. Since this is an image that is not being produced from a network stream, we have no need for notification. As you can see, the image was in the middle of repainting when this snapshot was taken. The right snapshot shows how, when a mouse button is pressed, the image is always complete and clean due to double buffering.




MediaTracker

Many early Java developers found the ImageObserver interface far too difficult to understand and manage when there were multiple images to be loaded. The developer community asked for a simpler solution that would allow programmers to load all of their images synchronously, without having to worry about imageUpdate( ). In response to this, Sun Microsystems added a class to java.awt called MediaTracker in a subsequent release of the JDK. A MediaTracker is an object that will check the status of an arbitrary number of images in parallel. To use MediaTracker, you create a new instance and use its addImage( ) method to track the loading status of an image. addImage( ) has the following general forms:

      void addImage(Image imgObj, int imgID)
      void addImage(Image imgObj, int imgID, int width, int height)

Here, imgObj is the image being tracked. Its identification number is passed in imgID. ID numbers do not need to be unique. You can use the same number with several images as a means of identifying them as part of a group. In the second form, width and height specify the dimensions of the object when it is displayed.

Once you’ve registered an image, you can check whether it’s loaded, or you can wait for it to completely load. To check the status of an image, call checkID( ). The version used in this chapter is shown here:

      boolean checkID(int imgID)

Here, imgID specifies the ID of the image you want to check. The method returns true if all images that have the specified ID have been loaded (or if an error or user- abort has terminated loading). Otherwise, it returns false. You can use the checkAll( ) method to see if all images being tracked have been loaded.

You should use MediaTracker when loading a group of images. If all of the images that you’re interested in aren’t downloaded, you can display something else to entertain the user until they all arrive.

If you use MediaTracker once you’ve called addImage( ) on an image, a reference in MediaTracker will prevent the system from garbage collecting it. If you want the system to be able to garbage collect images that were being tracked, make sure it can collect the MediaTracker instance as well.

Here’s an example that loads a seven-image slide show and displays a nice bar chart of the loading progress:

  /*
   * <applet code="TrackedImageLoad" width=300 height=400>
   * <param name="img"
   * value="vincent+leonardo+matisse+picasso+renoir+seurat+vermeer">
   * </applet>
   */
  import java.util.*;
  import java.applet.*;
  import java.awt.*;

  public class TrackedImageLoad extends Applet implements Runnable {
    MediaTracker tracker;
    int tracked;
    int frame_rate = 5;
    int current_img = 0;
    Thread motor;
    static final int MAXIMAGES = 10;
    Image img[] = new Image[MAXIMAGES];
    String name[] = new String[MAXIMAGES];
    boolean stopFlag;

    public void init() {
      tracker = new MediaTracker(this);
      StringTokenizer st = new StringTokenizer(getParameter("img"),
                                               "+");
      while(st.hasMoreTokens() && tracked <= MAXIMAGES) {
        name[tracked] = st.nextToken();
        img[tracked] = getImage(getDocumentBase(),
                                name[tracked] + ".jpg");
        tracker.addImage(img[tracked], tracked);
        tracked++;
      }
    }

    public void paint(Graphics g) {
      String loaded = "";
      int donecount = 0;

      for(int i=0; i<tracked; i++) {
        if (tracker.checkID(i, true)) {
          donecount++;
          loaded += name[i] + " ";
        }
      }

      Dimension d = getSize();
      int w = d.width;
      int h = d.height;

      if (donecount == tracked) {
        frame_rate = 1;
        Image i = img[current_img++];
        int iw = i.getWidth(null);
        int ih = i.getHeight(null);
        g.drawImage(i, (w - iw)/2, (h - ih)/2, null);
        if (current_img >= tracked)
          current_img = 0;
      } else {
        int x = w * donecount / tracked;
        g.setColor(Color.black);
        g.fillRect(0, h/3, x, 16);
        g.setColor(Color.white);
        g.fillRect(x, h/3, w-x, 16);
        g.setColor(Color.black);
        g.drawString(loaded, 10, h/2);
      }
    }

    public void start() {
      motor = new Thread(this);
      stopFlag = false;
      motor.start();
    }

    public void stop() {
      stopFlag = true;
    }

    public void run() {
      motor.setPriority(Thread.MIN_PRIORITY);
      while (true) {
        repaint();
        try {
          Thread.sleep(1000/frame_rate);
        } catch (InterruptedException e) { };
        if(stopFlag)
          return;
      }
    }
  }

This example creates a new MediaTracker in the init( ) method, and then adds each of the named images as a tracked image with addImage( ). In the paint( ) method, it calls checkID( ) on each of the images that we’re tracking. If all of the images are loaded, they are displayed. If not, a simple bar chart of the number of images loaded is shown, with the names of the fully loaded images displayed underneath the bar. One is the bar chart, displaying that three of the images have been loaded. The other is the Van Gogh self-portrait during the slide show.

No comments:

Post a Comment