Friday 31 March 2017

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

TearTransition.java

The TearTransition creates the illusion of the current billboard getting torn off the applet like a piece of paper. It gets ripped upwards and toward the left to reveal the next billboard image underneath.

There is only one member variable used in this transition, x_cross. It is used as a multiplier to create the tear effect. The larger the value of this variable, the smaller the tear effect will appear to be.

The code for this transition has many optimizations. One optimization of significance is to create the cell frames in reverse order, building the last cell frame first and the first, last. In their normal order, each subsequent cell frame reveals a little bit more of the new image underneath. If the frames were to be created in the normal order, the tear effect would have to be drawn, along with the new image pixels revealed in the current frame, which had been covered by the tearing effect in the previous frame. Doing it in reverse only requires redrawing the tearing effect each cell frame. For example, the last frame, created first, starts out with the tearing effect using only a small portion of the upper-left corner of the image, while the rest of the pixels are taken from the new billboard image. In the second to the last frame, which is created second, the new tear effect draws over a little bit more of the upper-left corner of the image, while the rest of the image remains the same. The cell frames that follow draw the new tear effect over more and more of the image, but always covering up the old tear effect from the last frame.

init( )
The init( ) method for this transition starts like all other transitions, with a call to the base class’ init( ) method. It then copies all of the new billboard’s pixels into the work_pixels array and copies the first line of the old billboard’s pixels onto the first line of the work_pixels array. After the x_cross variable is initialized, the init( ) method loops through each cell frame in reverse order. Inside the loop, it creates each cell frame and decreases the value of the x_cross variable.

Tear( )
The Tear( ) method modifies the work_pixels array for the next cell. It draws the tear effect onto the work pixels. It draws the tear effect line by line. To draw one line, the method copies pixels from the old image pixels into the work_pixels array. It uses two counters, one that is an index into the work_pixels array, the destination, and one that references an index into the array of pixels for the old billboard, the source. Both counters are started at zero. The destination counter is always incremented by one. The source counter, however, is incremented by a floating-point number that is always greater than one. When the loop is run until the destination index is larger than the width of the line, the result is the source index growing faster than the destination index. The overall effect is that in the destination, only a number of pixels on the left side of the image will be copied from the source. The pixels taken from the source will skip some pixels, resulting in pixels taken from the source being evenly distributed across the line.

Each line of the cell frame will use a larger value for the floating-point number on the line above. This makes lines toward the bottom draw on fewer pixels for the tear effect than lines toward the top.

This method has one big optimization that it uses to get around the slow array indexing in Java. Whenever a element in an array is used, bounds checking is done to ensure that the index is within the bounds of the array. There is a performance hit involved in this bounds checking. The standard Java class, System, provides a method that allows you to copy sections of arrays from one array to another almost as fast or as fast as copying one array element into another. This method is used to speed up the creation lines within the cell frames. It only uses this method when the applet knows some of the source pixels will be adjacent to one another. If the applet skips at least every other pixel from the source image, then it will use the standard loop method. An x_increment value less than 0.5 indicates that less than 1.5 will be added to the source index counter each time, and there will be a speed benefit from using the array copy method for a particular line.

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

  import java.awt.*;
  import java.awt.image.*;
  public class TearTransition extends BillTransition {
    static final int CELLS = 7;
    static final float INITIAL_X_CROSS = 1.6f;
    static final float X_CROSS_DIVISOR = 3.5f;
    float x_cross;

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

      x_cross = INITIAL_X_CROSS;

      for(int c = CELLS - 1; c >= 0; --c) {
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        Tear();
        try { Thread.sleep(150); } catch (InterruptedException e) {}
        createCellFromWorkPixels(c);
        x_cross /= X_CROSS_DIVISOR;
      }
      work_pixels = null;
    }

    final void Tear() {
      float x_increment;
      int p, height_adder;

      p = height_adder = cell_w;
      for (int y = 1; y < cell_h; ++y) {
        x_increment = x_cross * y;
        if(x_increment >= 0.50f) {
          float fx = 0.0f;
          x_increment += 1.0f;
          int x = 0;
          do {
            work_pixels[p++] = current_pixels[height_adder + x];
            x = (int)(fx += x_increment);
          } while(x < cell_w);
        }
        else {
          float overflow = 1.0f / x_increment;
          float dst_end = overflow / 2.0f + 1.49999999f;
          int dst_start = 0, src_offset = 0, length = (int)dst_end;
          while(dst_start + src_offset + length < cell_w) {
            System.arraycopy(current_pixels, p + src_offset,
                             work_pixels, p, length);
            ++src_offset;
            dst_end += overflow;
            p += length;
            dst_start += length;
            length = (int)dst_end - dst_start;
          }
          length = cell_w - src_offset - dst_start;
          System.arraycopy(current_pixels, p + src_offset,
                           work_pixels, p, length);
        }
        p = height_adder += cell_w;
      }
    }
  }


UnrollTransition.java

UnrollTransition makes it appear as if a rolled-up poster is placed on the bottom of the applet and then unrolled upward, gradually revealing the next image and covering the old image. To enhance the unroll illusion, the roll gradually decreases in size as it makes its way upward on the billboard.

Two instance variables are used during the creation of unroll transitions. The location variable references pixels within the pixel arrays. It stores the current pixel that the roll first appears on. The unroll_amount array variable tells the class how many vertical pixels the roll should move upward each frame.

The most difficult part of creating each cell frame is drawing the roll. The only other task that needs to be completed each frame is to draw the pixels from the new image onto the space vacated by the roll from the previous frame.

The roll is drawn with scan lines from the new image. The first line of the roll is drawn with the scan line located at the Y coordinate above the roll’s Y coordinate on the applet. For example, if the roll is located on line ten for a particular cell frame, then line nine of the new image will be used to draw the first line of the roll. Each subsequent line of the roll is drawn using a line from the image located above the previous line of the new image. So, continuing the example, the second line of the roll will be drawn using line eight of the new image.

The roll is painted with its 3-D appearance by drawing each line of the roll with a slight offset to the left. Lines closer to the center of the roll are drawn with a larger offset than lines close to the top and bottom. The top and bottom lines of the rolls are then shaded to make it look as if a light were above the applet. This results in the top line being a bit brighter than the rest of the roll and the bottom line being a bit darker.

createUnrollAmountArray( )
Each consecutive cell frame in this transition unrolls the roll onto the applet a little bit less than the previous cell frame. The createUnrollAmountArray( ) static method is used to calculate an array that indicates how much each cell frame should unroll the roll.

init( )
The init( ) method for this transition starts like all other transitions, with a call to the base class’ init( ) method. Then the location variable is initialized to an index past the last pixel in a pixel array. This is followed by copying all of the old billboard’s pixels into the work_pixels array.

An array that stores the number of pixels to unroll each frame is pulled out of the object_table for an applet of this height. If it does not exist in the hash table, it is created and stored in the object_table.

Then the init( ) method loops through each cell, moving the roll upward by subtracting from the location variable and drawing each cell frame. It makes the current thread take a break before and after the processor-intensive cell frame creation, to allow other threads in Java’s multitasking environment to execute. After the cell frame is created from the work_pixels array with the call to the createCellFromWorkPixels( ) method, the area the roll was on is drawn over with pixels from the new image. This prepares the work_pixels array for the next cell frame.

Unroll( )
The Unroll( ) method modifies the work_pixels array for the next cell. It draws the roll onto the work pixels. This method first calculates the offset that it needs to use for drawing each line of the roll. It then loops through each line of the roll, copying scan lines from the new image onto the work_pixels array. The pixels that are exposed due to the left offset of each line are filled in with pixels from the static fill_pixels array. Another loop then increments though each pixel on the top and the bottom lines of the roll, brightening pixels on the top line and darkening pixels on the bottom line.

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

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

  public class UnrollTransition extends BillTransition {
    final static int CELLS = 9;
    static int fill_pixels[] = { 0xFFFFFFFF, 0xFF000000,
                                 0xFF000000, 0xFFFFFFFF };

    private static int[] createUnrollAmountArray(int cell_h) {
      float unroll_increment =
        ((float)cell_h / (float)(CELLS + 1)) /
        ((float)(CELLS + 2) / 2.0f);

      int total = 0;
      int unroll_amount[] = new int[CELLS + 1];
      for(int u = 0; u <= CELLS; ++u) {
       unroll_amount[u] = (int)(unroll_increment * (CELLS - u + 1));
       total += unroll_amount[u];
      }
      if(total < 0) {
        unroll_amount[0] -= 1;
      }
      return unroll_amount;
    }

    int location;
    int[] unroll_amount;
    public void init(Component owner, int[] current, int[] next) {
      init(owner, current, next, CELLS, 220);
      location = pixels_per_cell;
      System.arraycopy(current_pixels, 0,
                       work_pixels, 0, pixels_per_cell);
      unroll_amount = (int[])object_table.get(getClass().getName() +
                       cell_h);
      if(unroll_amount == null) {
        unroll_amount = createUnrollAmountArray(cell_h);
        object_table.put(getClass().getName() + 
                         cell_h, unroll_amount);
      }

      for(int c = 0; c < CELLS; ++c) {
        location -= unroll_amount[c] * cell_w;
        try { Thread.sleep(150); } catch (InterruptedException e) {}
        Unroll(c);
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        createCellFromWorkPixels(c);
        System.arraycopy(next_pixels, location,
                         work_pixels, location,
                         unroll_amount[c] * cell_w);
      }
      work_pixels = null;
    }

    void Unroll(int c) {
      int y_flip = cell_w;
      int offset[] = new int[unroll_amount[c]];
      for(int o = 0; o < unroll_amount[c]; ++o) {
        offset[o] = 4;
      }
      offset[0] = 2;
      if(unroll_amount[c] > 1) {
        offset[1] = 3;
      }
      if(unroll_amount[c] > 2) {
        offset[unroll_amount[c] - 1] = 2;
      }
      if(unroll_amount[c] > 3) {
        offset[unroll_amount[c] - 2] = 3;
      }

      int offset_index = 0;
      int end_location = location + unroll_amount[c] * cell_w;
      for(int p = location; p < end_location; p += cell_w) {
        System.arraycopy(next_pixels,
                         p - y_flip + offset[offset_index],
                         work_pixels,
                         p, cell_w - offset[offset_index]);

        System.arraycopy(fill_pixels, 0,
                         work_pixels,
                         p + cell_w - offset[offset_index],
                         offset[offset_index]);

        ++offset_index;
        y_flip += cell_w + cell_w;
      }
      for(int x = location + cell_w - 1; x > location; --x) {
        work_pixels[x] |= 0xFFAAAAAA;
        work_pixels[x + unroll_amount[c]] &= 0xFF555555;
      }
    }
  }

No comments:

Post a Comment