teaching machines

CS 148: Lab 9 – Spinner

November 3, 2017 by . Filed under cs1, cs148, fall 2017, labs.

Welcome to lab 9!

If you have checkpoints from the last lab to show your instructor or TA, do so immediately. No credit will be given if you have not already completed the work, nor will credit be given after the first 10 minutes of this lab.

Checkpoint 1

Person A types.

Your task is to programmatically generate a spinner animation. A spinner is an image that shows the progress or activity of some computational task, like the spinning beachball of the Mac OS. Check out some of the following websites:

Create a lab09 package in Eclipse. With the package selected, paste in the following class, which contains a lot of stuff I don’t understand. What’s important is that it lets us create animated GIFs.

package lab09;

//  GifSequenceWriter.java
//  
//  Created by Elliot Kroo on 2009-04-25.
//
// This work is licensed under the Creative Commons Attribution 3.0 Unported
// License. To view a copy of this license, visit
// http://creativecommons.org/licenses/by/3.0/ or send a letter to Creative
// Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.

import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;
import java.awt.image.*;
import java.io.*;
import java.util.Iterator;

public class GifSequenceWriter {
  protected ImageWriter gifWriter;
  protected ImageWriteParam imageWriteParam;
  protected IIOMetadata imageMetaData;
  protected ImageOutputStream outputStream;

  /**
   * Creates a new GifSequenceWriter
   * 
   * @param outputStream
   * the ImageOutputStream to be written to
   * @param imageType
   * one of the imageTypes specified in BufferedImage
   * @param timeBetweenFramesMS
   * the time between frames in miliseconds
   * @param loopContinuously
   * wether the gif should loop repeatedly
   * @throws IIOException
   * if no gif ImageWriters are found
   *
   * @author Elliot Kroo (elliot[at]kroo[dot]net)
   */
  public GifSequenceWriter(File outFile,
                           int timeBetweenFramesMS,
                           boolean loopContinuously) {
    try {
      // my method to create a writer
      gifWriter = getWriter();
      imageWriteParam = gifWriter.getDefaultWriteParam();
      ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);

      imageMetaData = gifWriter.getDefaultImageMetadata(imageTypeSpecifier, imageWriteParam);

      String metaFormatName = imageMetaData.getNativeMetadataFormatName();

      IIOMetadataNode root = (IIOMetadataNode) imageMetaData.getAsTree(metaFormatName);

      IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");

      graphicsControlExtensionNode.setAttribute("disposalMethod", "none");
      graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
      graphicsControlExtensionNode.setAttribute("transparentColorFlag", "FALSE");
      graphicsControlExtensionNode.setAttribute("delayTime", Integer.toString(timeBetweenFramesMS / 10));
      graphicsControlExtensionNode.setAttribute("transparentColorIndex", "0");

      IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
      commentsNode.setAttribute("CommentExtension", "Created by MAH");

      IIOMetadataNode appEntensionsNode = getNode(root, "ApplicationExtensions");

      IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");

      child.setAttribute("applicationID", "NETSCAPE");
      child.setAttribute("authenticationCode", "2.0");

      int loop = loopContinuously ? 0 : 1;

      child.setUserObject(new byte[]{
        0x1,
        (byte) (loop & 0xFF),
        (byte) ((loop >> 8) & 0xFF)
      });
      appEntensionsNode.appendChild(child);

      imageMetaData.setFromTree(metaFormatName, root);

      outputStream = new FileImageOutputStream(outFile);
      gifWriter.setOutput(outputStream);
      gifWriter.prepareWriteSequence(null);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public void appendFrame(BufferedImage img) {
    try {
      gifWriter.writeToSequence(new IIOImage(img, null, imageMetaData), imageWriteParam);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Close this GifSequenceWriter object. This does not close the underlying
   * stream, just finishes off the GIF.
   */
  public void close() {
    try {
      gifWriter.endWriteSequence();
      outputStream.close();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Returns the first available GIF ImageWriter using
   * ImageIO.getImageWritersBySuffix("gif").
   * 
   * @return a GIF ImageWriter object
   * @throws IIOException
   * if no GIF image writers are returned
   */
  private static ImageWriter getWriter() throws IIOException {
    try {
      Iterator<ImageWriter> iter = ImageIO.getImageWritersBySuffix("gif");
      if (!iter.hasNext()) {
        throw new IIOException("No GIF Image Writers Exist");
      } else {
        return iter.next();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Returns an existing child node, or creates and returns a new child node (if
   * the requested node does not exist).
   * 
   * @param rootNode
   * the <tt>IIOMetadataNode</tt> to search for the child node.
   * @param nodeName
   * the name of the child node.
   * 
   * @return the child node, if found or a new node created with the given name.
   */
  private static IIOMetadataNode getNode(IIOMetadataNode rootNode,
                                         String nodeName) {
    int nNodes = rootNode.getLength();
    for (int i = 0; i < nNodes; i++) {
      if (rootNode.item(i).getNodeName().compareToIgnoreCase(nodeName) == 0) {
        return ((IIOMetadataNode) rootNode.item(i));
      }
    }
    IIOMetadataNode node = new IIOMetadataNode(nodeName);
    rootNode.appendChild(node);
    return node;
  }
}

Create a separate class named Spinner.

Give it a method generateFrame that accepts an int timestep and returns a BufferedImage. Just have it return null for the moment—your partner will flesh it out.

Write a main method that creates a GifSequenceWriter. Have the writer loop and write to a file on your desktop, like this one:

File dst = new File(new File(System.getProperty("user.home")), "Desktop/spinner.gif");

Then, iterate through a sequence of timesteps, like 0 through 10, or 0 through 100. The exact value of the upper bound is not so important right now—just pick something and tweak it later. On each timestep, call generateFrame to produce a single frame of your animation. Append the frame to your GifSequenceWriter. After all frames have been generated, close the GifSequenceWriter.

Checkpoint 2

Person B types.

Your task is to implement the generateFrame method so that it draws a single frame of your spinner animation based on the time parameter that it is given. Complete the following tasks in generateFrame:

  1. Create a BufferedImage of some fixed size. Every time this method is called, an image of the same size should be generated.
  2. Draw on your image some indication of progress as a function of the timestep parameter. For example, if your progress indicator is an hourglass, the sandy-colored pixels will be at the top of the image when the timestep is early in the animation, and at the bottom later on. If your progress indicator is a rotating wheel, then the rotation angle should be computed using the timestep. Please be creative. Recall that you can turn a BufferedImage into a drawing canvas like this:
    Graphics canvas = image.getGraphics();
    canvas.setColor(...);
    canvas.fillOval(...);
    
  3. Let your partner’s loop advance through the animation and write it out to an animated GIF. Upload it.