teaching machines

CS 145 Lecture 24 – Animation

November 5, 2016 by . Filed under cs145, fall 2016, lectures.

Dear students,

Let’s start with a Program This!

Write a method times with the following behavior:
  • times('!', 3) yields "!!!"
  • times('#', 6) yields "######"
  • times('-', 13) yields "-------------"

We’ll use this method to generate a random spelunking workout.

After that, we’ll generate a few more images. This time we won’t use loops to march through every pixel. Instead we’ll loop through points on a line or timesteps in an animation! Let’s solve these problems:

See you next class!

Sincerely,

P.S. It’s Haiku Friday!

They’re little windows
Shining past light, like Grandma’s
I wish pixels smelled

P.P.S. Here’s the code we wrote together…

RandomSpelunking.java

package lecture1104;

import java.util.Random;

public class RandomSpelunking {
  public static void main(String[] args) {
    Random generator = new Random();

    int nLevels = getNumberOfLevels();
    for (int i = 0; i < nLevels; ++i) {
      System.out.println(times('-', generator.nextInt(7) + 4));
    }

  }
  
  public static int getNumberOfLevels() {
    System.out.println("Hey, we're callin' this method!");
    return new Random().nextInt(20);
  }

  public static String times(char c, int n) {
    String s = "";
    for (int i = 0; i < n; ++i) {
      s += c;
    }
    return s;
  }
}

GifSequenceWriter.java

package lecture1104;

//  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;
  }
}

Animation.java

package lecture1104;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;

public class Animation {
  public static void main(String[] args) {
    File outputFile = new File("/Users/johnch/Desktop/ball.gif");
    GifSequenceWriter gif = new GifSequenceWriter(outputFile, 100, false);

    BufferedImage image = new BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB);

    Graphics g = image.getGraphics();
    double x = 0;
    double y = 0;
    double delta = 0.1;
    double velocityY = 31;
    double velocityX = 12;
    double acceleration = 2 * 9.81;

    for (double t = 0.0; t < 30; t += delta) {
      g.setColor(Color.BLUE);
      g.fillRect(0, 0, image.getWidth(), image.getHeight());
      
      g.setColor(Color.YELLOW);
      g.fillOval((int) x, (int) y, 100, 100);
      
      if (y > 410) {
        velocityY *= -0.9;
      }
      
      x += velocityX * delta;
      y += velocityY * delta;
      
      velocityY += acceleration;
      
      gif.appendFrame(image);
    }

    gif.close();
  }
}