Tuesday 28 March 2017

ImageProducer, ImageConsumer & Cell Animation - Java Tutorials

ImageProducer

ImageProducer is an interface for objects that want to produce data for images. An object that implements the ImageProducer interface will supply integer or byte arrays that represent image data and produce Image objects. As you saw earlier, one form of the createImage( ) method takes an ImageProducer object as its argument. There are two image producers contained in java.awt.image: MemoryImageSource and FilteredImageSource. Here, we will examine MemoryImageSource and create a new Image object from data generated in an applet.


MemoryImageSource

MemoryImageSource is a class that creates a new Image from an array of data. It defines several constructors. Here is the one we will be using:

      MemoryImageSource(int width, int height, int pixel[ ], int offset, int scanLineWidth)

The MemoryImageSource object is constructed out of the array of integers specified by pixel, in the default RGB color model to produce data for an Image object. In the default color model, a pixel is an integer with Alpha, Red, Green, and Blue (0xAARRGGBB). The Alpha value represents a degree of transparency for the pixel. Fully transparent is 0 and fully opaque is 255. The width and height of the resulting image are passed in width and height. The starting point in the pixel array to begin reading data is passed in offset. The width of a scan line (which is often the same as the width of the image) is passed in scanLineWidth.

The following short example generates a MemoryImageSource object using a variation on a simple algorithm (a bitwise-exclusive-OR of the x and y address of each pixel) from the book Beyond Photography, The Digital Darkroom by Gerard J. Holzmann (Prentice Hall, 1988).

  /*
   * <applet code="MemoryImageGenerator" width=256 height=256>
   * </applet>
   */
  import java.applet.*;
  import java.awt.*;
  import java.awt.image.*;

  public class MemoryImageGenerator extends Applet {
    Image img;
    public void init() {
      Dimension d = getSize();
      int w = d.width;
      int h = d.height;
      int pixels[] = new int[w * h];
      int i = 0;

      for(int y=0; y<h; y++) {
        for(int x=0; x<w; x++) {
          int r = (x^y)&0xff;
          int g = (x*2^y*2)&0xff;
          int b = (x*4^y*4)&0xff;
          pixels[i++] = (255 << 24) | (r << 16) | (g << 8) | b;
        }
      }
      img = createImage(new MemoryImageSource(w, h, pixels, 0, w));
    }
    public void paint(Graphics g) {
      g.drawImage(img, 0, 0, this);
    }
  }

The data for the new MemoryImageSource is created in the init( ) method. An array of integers is created to hold the pixel values; the data is generated in the nested for loops where the r, g, and b values get shifted into a pixel in the pixels array. Finally, createImage( ) is called with a new instance of a MemoryImageSource created from the raw pixel data as its parameter.




ImageConsumer

ImageConsumer is an abstract interface for objects that want to take pixel data from images and supply it as another kind of data. This, obviously, is the opposite of ImageProducer, described earlier. An object that implements the ImageConsumer interface is going to create int or byte arrays that represent pixels from an Image object. We will examine the PixelGrabber class, which is a simple implementation of the ImageConsumer interface.


PixelGrabber

The PixelGrabber class is defined within java.lang.image. It is the inverse of the MemoryImageSource class. Rather than constructing an image from an array of pixel values, it takes an existing image and grabs the pixel array from it. To use PixelGrabber, you first create an array of ints big enough to hold the pixel data, and then you create a PixelGrabber instance passing in the rectangle that you want to grab. Finally, you call grabPixels( ) on that instance.

The PixelGrabber constructor that is used in this chapter is shown here:

      PixelGrabber(Image imgObj, int left, int top, int width, int height, int pixel[ ],
                             int offset, int scanLineWidth)

Here, imgObj is the object whose pixels are being grabbed. The values of left and top specify the upper-left corner of the rectangle, and width and height specify the dimensions of the rectangle from which the pixels will be obtained. The pixels will be stored in pixel beginning at offset. The width of a scan line (which is often the same as the width of the image) is passed in scanLineWidth.

grabPixels( ) is defined like this:

      boolean grabPixels( )
          throws InterruptedException

      boolean grabPixels(long milliseconds)
          throws InterruptedException

Both methods return true if successful and false otherwise. In the second form, milliseconds specifies how long the method will wait for the pixels.

Here is an example that grabs the pixels from an image and then creates a histogram of pixel brightness. The histogram is simply a count of pixels that are a certain brightness for all brightness settings between 0 and 255. After the applet paints the image, it draws the histogram over the top.

  /*
   * <applet code=HistoGrab.class width=341 height=400>
   * <param name=img value=vermeer.jpg>
   * </applet> */
  import java.applet.*;
  import java.awt.* ;
  import java.awt.image.* ;

  public class HistoGrab extends Applet {
    Dimension d;
    Image img;
    int iw, ih;
    int pixels[];
    int w, h;
    int hist[] = new int[256];
    int max_hist = 0;

    public void init() {
      d = getSize();
      w = d.width;
      h = d.height;

      try {
        img = getImage(getDocumentBase(), getParameter("img"));
        MediaTracker t = new MediaTracker(this);
        t.addImage(img, 0);
        t.waitForID(0);
        iw = img.getWidth(null);
        ih = img.getHeight(null);
        pixels = new int[iw * ih];
        PixelGrabber pg = new PixelGrabber(img, 0, 0, iw, ih,
                                           pixels, 0, iw);
        pg.grabPixels();
      } catch (InterruptedException e) { };

      for (int i=0; i<iw*ih; i++) {
        int p = pixels[i];
        int r = 0xff & (p >> 16);
        int g = 0xff & (p >> 8);
        int b = 0xff & (p);
        int y = (int) (.33 * r + .56 * g + .11 * b);
        hist[y]++;
      }
      for (int i=0; i<256; i++) {
        if (hist[i] > max_hist)
          max_hist = hist[i];
      }
    }

    public void update() {}

    public void paint(Graphics g) {
      g.drawImage(img, 0, 0, null);
      int x = (w - 256) / 2;
      int lasty = h - h * hist[0] / max_hist;
      for (int i=0; i<256; i++, x++) {
        int y = h - h * hist[i] / max_hist;
        g.setColor(new Color(i, i, i));
        g.fillRect(x, y, 1, h);
        g.setColor(Color.red);
        g.drawLine(x-1,lasty,x,y);
        lasty = y;
      }
    }
  }




Cell Animation

Now that we have presented an overview of the image APIs, we can put together an interesting applet that will display a sequence of animation cells. The animation cells are taken from a single image that can arrange the cells in a grid specified via the rows and cols <param> tags. Each cell in the image is snipped out in a way similar to that used in the TileImage example earlier. We obtain the sequence in which to display the cells from the sequence <param> tag. This is a comma-separated list of cell numbers that is zero-based and proceeds across the grid from left to right, top to bottom.

Once the applet has parsed the <param> tags and loaded the source image, it cuts the image into a number of small subimages. Then, a thread is started that causes the images to be displayed according to the order described in sequence. The thread sleeps for enough time to maintain the framerate. Here is the source code:

  // Animation example.
  import java.applet.*;
  import java.awt.*;
  import java.awt.image.*;
  import java.util.*;

  public class Animation extends Applet implements Runnable {
    Image cell[];
    final int MAXSEQ = 64;
    int sequence[];
    int nseq;
    int idx;
    int framerate;
    boolean stopFlag;

    private int intDef(String s, int def) {
      int n = def;
      if (s != null)
        try {
          n = Integer.parseInt(s);
        } catch (NumberFormatException e) { };
      return n;
    }

    public void init() {
      framerate = intDef(getParameter("framerate"), 5);
      int tilex = intDef(getParameter("cols"), 1);
      int tiley = intDef(getParameter("rows"), 1);
      cell = new Image[tilex*tiley];

      StringTokenizer st = new
                 StringTokenizer(getParameter("sequence"), ",");
      sequence = new int[MAXSEQ];
      nseq = 0;
      while(st.hasMoreTokens() && nseq < MAXSEQ) {
        sequence[nseq] = intDef(st.nextToken(), 0);
        nseq++;
      }

      try {
        Image img = getImage(getDocumentBase(), getParameter
                            ("img"));
        MediaTracker t = new MediaTracker(this);
        t.addImage(img, 0);
        t.waitForID(0);
        int iw = img.getWidth(null);
        int ih = img.getHeight(null);
        int tw = iw / tilex;
        int th = ih / tiley;
        CropImageFilter f;
        FilteredImageSource fis;
        for (int y=0; y<tiley; y++) {
          for (int x=0; x<tilex; x++) {
            f = new CropImageFilter(tw*x, th*y, tw, th);
            fis = new FilteredImageSource(img.getSource(), f);
            int i = y*tilex+x;
            cell[i] = createImage(fis);
            t.addImage(cell[i], i);
          }
        }
        t.waitForAll();
      } catch (InterruptedException e) { };
    }

    public void update(Graphics g) { }

    public void paint(Graphics g) {
        g.drawImage(cell[sequence[idx]], 0, 0, null);
    }

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

    public void stop() {
      stopFlag = true;
    }

    public void run() {
      idx = 0;
      while (true) {
        paint(getGraphics());
        idx = (idx + 1) % nseq;
        try { Thread.sleep(1000/framerate); } catch 
                          (Exception e) { };
        if(stopFlag)
        return;
      }
    }
  }

The following applet tag shows the famous locomotion study by Eadweard Muybridge, which proved that horses do, indeed, get all four hooves off the ground at once. (Of course, you can substitute another image file in your own applet.)

  <applet code=Animation width=67 height=48>
  <param name=img value=horse.gif>
  <param name=rows value=3>
  <param name=cols value=4>
  <param name=sequence value=0,1,2,3,4,5,6,7,8,9,10>
  <param name=framerate value=15>
  </applet>

Additional Imaging Classes

In addition to the imaging classes described in this chapter, java.awt.image supplies several others that offer enhanced control over the imaging process and that support advanced imaging techniques. Java 2, version 1.4 also adds a new imaging package called javax.imageio. This package supports plug-ins that handle various image formats. If sophisticated graphical output is of special interest to you, then you will want to explore the additional classes found in java.awt.image and javax.imageio.

No comments:

Post a Comment