Friday 31 March 2017

Source Code Overview - Java Tutorials ( Page 3 of 4 )

FadeTransition.java

The FadeTransition class changes one image into another by randomly including a number of new pixels from the next billboard in each successive cell frame. This makes the next billboard appear to fade in over the old billboard.

The heart of this transition is a two-dimensional array of short integers called random. This array holds an index for every element in the next billboard’s image pixel array. These indexes are randomly distributed in the two-dimensional array. The eight elements in the first dimension of this array will be used when cells are created, one for each new cell. The last element is never actually used, because there are only seven cells. It is included when the random array is created to ensure that the indexes are randomly distributed correctly.

The FadeTransition uses this array to pick pixels from the next billboard to overwrite pixels of the old billboard. For the first cell, the work_pixels array contains nothing but pixels from the old billboard. One-eighth of these pixels get changed to the next billboard’s pixels. For the following cell, the same work_pixels array is used, and one-eighth more pixels are filled in from the next billboard. For this cell the result has one-fourth of the pixels from the next billboard, while the remainder are from the old billboard. This continues until the last cell, cell number seven, which has seven-eighths of its pixels from the new billboard. Remember, the DynamicBillboard applet simply uses the whole image from the next billboard after the last cell to complete the transition.

Because the size of this two-dimensional array is dependent on the size of the applet, it must be unique to each applet. Using a static variable to store this array is unacceptable, because applets of different sizes would share this array. Since it is fairly time-consuming to create this array, it does not make sense to re-create it every time this transition is to be used.

This is where the superclass’ static variable, object_table, first comes into play. Once this array is created, it can be stored in this hash table with a key that includes the size of the applet. When the array needs to be used, the applet can get the appropriate one out of the hash table. If it does not exist in the hash table, the applet can then create the array and store it in the hash table for future use. New applets of the same size as the current applet will benefit from a usable array already being there. This seems like a lot of effort, but in practice, web sites tend to use this applet on a large number of pages with a standard layout size for each banner advertisement. So, it saves an enormous amount of memory and CPU time to cache these tables.

createRandomArray( )
The createRandomArray( ) static method creates the two-dimensional random array. It takes two parameters that describe the size of the applet. It is highly optimized, because originally it was too slow. It includes its own random-number generator that is very fast, but with a short cycle. Because of this, it is fairly complicated and beyond the scope of this book. The basic idea is that Java’s built-in random-number generator is better at generating truly random distribution, but it is too slow for this application. Plus, the user will not notice exactly how random this transition is, so Robert’s home-grown random-number generator is sufficient.

init( )
The init( ) method for this transition starts like all other transitions, with a call to the base class’ init( ) method. Then, like some other transitions, it copies all of the old billboard’s pixels into the work_pixels array.

The two-dimensional random array is pulled out of the object_table for an applet of this size. If it does not exist yet, it is created and stored in the object_table. With the random array in hand, the method just loops through each cell and each index in the random array, copying pixels from the next billboard into the work pixels.

The Code
Here is the source code for the FadeTransition class:

  import java.awt.*;
  import java.awt.image.*;

  public class FadeTransition extends BillTransition {
    private static final int CELLS = 7;
    private static final int MULTIPLIER = 0x5D1E2F;
    
    private static short[][] createRandomArray(int number_pixels,
                                               int cell_h) {
      int total_cells = CELLS + 1;
      int new_pixels_per_cell = number_pixels / total_cells;
      short[][]random = new short[total_cells][new_pixels_per_cell];
      int random_count[] = new int[total_cells];
      for(int s = 0; s < total_cells; ++s) {
        random_count[s] = 0;
      }

      int cell;
      int rounded_new_pixels_per_cell =
            new_pixels_per_cell * total_cells;
      int seed = (int)System.currentTimeMillis();

      int denominator = 10;
      while((new_pixels_per_cell % denominator > 0 ||
        cell_h % denominator == 0) && denominator > 1) {
        --denominator;
      }
      int new_randoms_per_cell = new_pixels_per_cell / denominator;
      int new_randoms = rounded_new_pixels_per_cell / denominator;

      for(int p = 0; p < new_randoms_per_cell; ++p) {
        seed *= MULTIPLIER;
        cell = (seed >>> 29);
        random[cell][random_count[cell]++] = (short)p;
      }
      seed += 0x5050;
      try { Thread.sleep(150); } catch (InterruptedException e) {}

      for(int p = new_randoms_per_cell; p < new_randoms; ++p) {
        seed *= MULTIPLIER;
        cell = (seed >>> 29);

        while(random_count[cell] >= new_randoms_per_cell) {
          if(++cell >= total_cells) {
            cell = 0;
          }
        }
        random[cell][random_count[cell]++] = (short)p;
      }

      for(int s = 0; s < CELLS; ++s) {

        for(int ps = new_randoms_per_cell; ps < new_pixels_per_cell;
              ps += new_randoms_per_cell) {

          int offset = ps * total_cells;

          for(int p = 0; p < new_randoms_per_cell; ++p) {
            random[s][ps + p] = (short)(random[s][p] + offset);
          }
        }
        try { Thread.sleep(50); } catch (InterruptedException e) {}
      }
      random[CELLS] = null;
      return random;
    }

    public void init(Component owner, int[] current, int[] next) {
      init(owner, current, next, CELLS);
      System.arraycopy(current_pixels, 0, work_pixels,
                       0, pixels_per_cell);

      short random[][] = (short[][])object_table.get(
              getClass().getName() + pixels_per_cell);

      if(random == null) {
        random = createRandomArray(pixels_per_cell, cell_h);
        object_table.put(getClass().getName() + pixels_per_cell,
                         random);
      }

      for(int c = 0; c < CELLS; ++c) {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        int limit = random[c].length;
        for(int p = 0; p < limit; ++p) {
          int pixel_index = random[c][p];
          work_pixels[pixel_index] = next_pixels[pixel_index];
        }
        try { Thread.sleep(50); } catch (InterruptedException e) {}
        createCellFromWorkPixels(c);
      }
      work_pixels = null;
    }
  }


SmashTransition.java

The SmashTransition class changes one image into another by dropping the new image onto the old one. The old image appears to crumble under the weight of the new image.

Two instance variables, drop_amount and location, are used to create the frames. The location variable keeps track of the pixel that the smashed image starts on. The drop_amount variable stores the number of pixels of the new image to drop onto the smashed image every frame. In other words, it is the number to add to the location variable each frame. A static array called fill_pixels is used to color a whole line of the work_pixels array white.

The smash effect is done by drawing the old image in an accordion-like fashion. It starts out by drawing the first lines of the old image offset to the right. Each progressive line is offset a little bit more to the right. This continues until some maximum left offset is reached. At this point, the offset is reduced every line until an offset of zero is reached. This continues until all of the lines of the smashed image are drawn.

It does not draw the lines from the old image in their entirety. It uses a length that is a bit shorter than the actual length.

The number of lines to draw for the smashed image decreases each frame as the old billboard becomes more and more compacted. This transition uses lines that are evenly distributed across the old image. This ensures that the smashed image does not appear to be falling off the bottom of the applet or sliding under the new image.

setupFillPixels( )
The setupFillPixels( ) static method is used to ensure that the fill_pixels array is initialized and is at least as long as one whole line for this applet. If this array has not been initialized yet or is not long enough for this applet, then this method respectively re-creates or creates and fills in the array. If there is more than one instance of this applet running, both can share this fill_pixels array, but it must be at least as long as the widest applet.

init( )
The init( ) method for this transition starts like all other transitions, with a call to the base class’ init( ) method. It follows this with a call to the method described earlier, setupFillPixels( ). The initial values of the drop_amount and location variables are then calculated. After this, the init( ) method goes into a loop to create each frame. It actually does this in reverse, creating the last frame first. It does not have to be done in reverse. However, running loops in reverse saves one byte of code in the resulting class file. After each cell is created, the location variable is incremented to the next proper location.

Smash( )
The Smash( ) method modifies the work_pixels array for the next cell. It creates the smashed image of the old billboard in the work_pixels array and draws in the pixels for the new image. This method takes one parameter, max_fold, which is used as the maximum right offset that the lines in the fold will have. It is also used by subtracting this from the line width to determine the length of the lines to draw for the folds.

The method begins by copying the pixels from the new image onto work_ pixels. It then initializes a number of variables that it uses to draw the smashed image. The drawing of this smashed image is done line by line, in a loop. Within the loop, it first makes the current line totally white. It then copies a portion of the correct line from the old billboard over this line. To get the accordion effect, it does not start drawing onto the same pixel location as it did for drawing the white line. It instead offsets the destination pixels to the right by a few pixels. After drawing in the line, it adds a number to the offset counter. It follows this with a bounds check to see if the offset has gone beyond the minimum or maximum offset. If it has, it flips the sign of the number it adds to the offset counter each line. The effect of this is that the direction of the offset is reversed.

The Code
Here is the source code for the SmashTransition class:

  import java.awt.*;
  import java.awt.image.*;

  public class SmashTransition extends BillTransition {
    final static int CELLS = 8;
    final static float FOLDS = 8.0f;
    static int[] fill_pixels;

    static void setupFillPixels(int width) {
      if(fill_pixels != null && fill_pixels.length <= width) {
        return;
      }
      fill_pixels = new int[width];
      for(int f = 0; f < width; ++f) {
        fill_pixels[f] = 0xFFFFFFFF;
      }
    }

    int drop_amount;
    int location;

    public void init(Component owner, int[] current, int[] next) {
      init(owner, current, next, CELLS, 160);
      setupFillPixels(cell_w);
      drop_amount = (cell_h / CELLS) * cell_w;
      location = pixels_per_cell - ((cell_h / CELLS) / 2) * cell_w;
      for(int c = CELLS - 1; c >= 0; --c) {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        Smash(c + 1);
        try { Thread.sleep(150); } catch (InterruptedException e) {}
        createCellFromWorkPixels(c);
        location -= drop_amount;
      }
      work_pixels = null;
    }

    void Smash(int max_fold) {
      System.arraycopy(next_pixels, pixels_per_cell - location,
                       work_pixels, 0, location);
      int height = cell_h - location / cell_w;
      float fold_offset_adder = (float)max_fold * FOLDS / 
                                (float)height;
      float fold_offset = 0.0f;
      int fold_width = cell_w - max_fold;
      float src_y_adder = (float)cell_h / (float)height;
      float src_y_offset = cell_h - src_y_adder / 2;
      for(int p = pixels_per_cell - cell_w; p >= location; p -=
          cell_w) {
        System.arraycopy(fill_pixels, 0, work_pixels, p, cell_w);
        System.arraycopy(current_pixels, (int)src_y_offset * cell_w,
                     work_pixels, p + (int)fold_offset, fold_width);
        src_y_offset -= src_y_adder;
        fold_offset += fold_offset_adder;
        if(fold_offset < 0.0 || fold_offset >= max_fold) {
          fold_offset_adder *= -1.0f;
        }
      }
    }
  }

No comments:

Post a Comment