14.1. Generating Frame-Based AnimationsTo make movies, we're going to create a series of JPEG frames and display them. We have included a class FrameSequencer which will help you generate the frames. The class FrameSequencer also creates an object of the class MoviePlayer to display the frames. The MediaTools application on the CD can reassemble JPEG frames into a JMV movie. You can also use tools such as Apple's QuickTime Pro (http://www.apple.com/quicktime) or ImageMagick (http://www.imagemagick.org/), which can also create QuickTime, MPEG, or AVI movies from individual frames (and go in reverseburst a movie into a bunch of frames). We'll place all of our frames in a single directory, and number them so that the tools know how to reassemble them into a movie in the right order. We'll literally name our files frame0001.jpg, frame0002.jpg, and so on with leading zeros, so that the files are in order when placed in alphabetical order. Here's our first movie-generating program, which simply moves a red rectangle down diagonally (Figure 14.1). We have created a new class MovieMaker to hold the methods that make movies. Figure 14.1. A few frames from the first movie: Moving a rectangle. |
import java.awt.*; /** * Class to create frames for a movie * @author Barb Ericson */ public class MovieMaker { /** * Method to make a movie that has a rectangle moving * around * @param directory the directory to put the movie * frames */ public void makeRectangleMovie(String directory) { int framesPerSec = 30; Picture p = null; Graphics g = null; FrameSequencer frameSequencer = new FrameSequencer(directory); // loop through the first second for (int i = 0; i < framesPerSec; i++) { // draw a filled rectangle p = new Picture(640,480); g = p.getGraphics(); g.setColor(Color.RED); g.fillRect(i * 10, i * 5, 50,50); // add frame to sequencer frameSequencer.addFrame(p); } // replay the movie frameSequencer.show(); frameSequencer.replay(framesPerSec); } // main for testing public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/rectangle/"; movieMaker.makeRectangleMovie(dir); } } |
You can change the directory that will hold the created movie frames from what is specified as the dir variable in the main method. Compile and run the main method for the class MovieMaker. You should see the rectangle move as shown in (Figure 14.1).
|
The key part of this recipe is the g.fillRect(i * 10, i * 5, 50,50);. Each time through the loop we create a new Picture object and then draw the rectangle at a new position in the Picture based on the value of the loop index. When we add the Picture object to the FrameSequencer object, it will write out the frame to the directory. It will also display the current frame using a MoviePlayer object.
Here are the first five values in the call to the fillRect method:
g.fillRect(0,0,50,50); // i is 0 g.fillRect(10,5,50,50); // i is 1 g.fillRect(20,10,50,50); // i is 2 g.fillRect(30,15,50,50); // i is 3 g.fillRect(40,20,50,50); // i is 4
While setPixel() gets upset if you try to set a pixel outside of the bounds of the picture, the graphics methods of the Graphics class drawString and fillRect don't generate errors. They'll simply clip the image for the picture, so you can create simple code to make animations and not worry about going out of bounds. This makes creating a tickertape movie fairly simple.
You can add the following method to the MovieMaker class to generate a movie with the text appearing on the right side of the picture and moving across to the left.
/** * Method to create a tickertape movie * @param directory the directory to write to * @param message the string to display */ public void makeTickerTapeMovie(String directory, String message) { int framesPerSec = 30; Picture p = null; Graphics g = null; FrameSequencer frameSequencer = new FrameSequencer(directory); Font font = new Font("Arial",Font.BOLD,24); // loop for 2 seconds of animation for (int i = 0; i < framesPerSec * 2; i++) { // draw the string p = new Picture(300,100); g = p.getGraphics(); g.setColor(Color.BLACK); g.setFont(font); g.drawString(message,300 - (i * 10), 50); // add frame to sequencer frameSequencer.addFrame(p); } // replay the movie frameSequencer.show(); frameSequencer.replay(framesPerSec); } |
You can test this with the following main. You can change the directory that will hold the created movie frames from what is specified as the dir variable in the main method. The result of running this main method is shown in Figure 14.2.
public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/tickertape/"; movieMaker.makeTickerTapeMovie(dir,"Buy more widgets"); }
Can we move more than one thing at once? Sure! Our drawing code just gets a little more complicated. Here's a recipe that uses sine and cosine to create circular motion to match our linear motion of Program 130 (page 486) (Figure 14.3). You can add this method to the class MovieMaker.
/** * Method to make a movie that has a two rectangles moving * around * @param directory the directory to put the movie * frames */ public void makeTwoRectangleMovie(String directory) { int framesPerSec = 30; Picture p = null; Graphics g = null; FrameSequencer frameSequencer = new FrameSequencer(directory); // loop through the first second for (int i = 0; i < framesPerSec; i++) { // draw a filled rectangle p = new Picture(640,480); g = p.getGraphics(); g.setColor(Color.RED); g.fillRect(i * 10, i * 5, 50,50); g.setColor(Color.BLUE); g.fillRect(100 + (int) (10 * Math.sin(i)), 4 * i + (int) (10 * Math.cos(i)), 50,50); // add frame to sequencer frameSequencer.addFrame(p); } // replay the movie frameSequencer.show(); frameSequencer.replay(framesPerSec); } |
You can test this with the following main. You can change the directory that will hold the created movie frames from what is specified as the dir variable in the main method. The result of executing the following main method is shown in Figure 14.3.
public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/rectangle2/"; movieMaker.makeTwoRectangleMovie(dir); }
We don't have to create our animations out of things that we can draw, like rectangles. We can copy Picture objects to different locations. This kind of code runs pretty slowly.
The recipe below moves Mark's head around on the screen. This method took over a minute to complete on a fast computer. You can add this method to the MovieMaker class.
/** * Method to move Mark's head around */ public void moveMarksHead(String directory) { // load the picture of Mark String fName = FileChooser.getMediaPath("blue-Mark.jpg"); Picture markP = new Picture(fName); // declare other variables Picture target = null; FrameSequencer frameSequencer = new FrameSequencer(directory); int framesPerSec = 30; // loop creating the frames for (int i = 0; i < framesPerSec; i++) { target = new Picture(640,480); target.copy(markP,281,164,382,301,i * 10, i * 5); frameSequencer.addFrame(target); } |
You can test this with the following main. You can change the directory that will hold the created movie frames from what is specified as the dir variable in the main method. The result of executing the following main method is shown in Figure 14.4.
public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/mark/"; movieMaker.moveMarksHead(dir); }
We can use image manipulations that we created in Chapters 47, over multiple frames, to create quite interesting movies. Remember the sunset generating program Program 8 (page 109) in class Picture? First let's add another method that takes the amount to reduce the blue and green by in the picture.
/** * Method to simulate a sunset by decreasing the green * and blue * @param the amount to multiply the original values by */ public void makeSunset(double reduction) { Pixel[] pixelArray = this.getPixels(); Pixel pixel = null; int value = 0; int i = 0; |
Now let's create a new method in the class MovieMaker to make the sunset happen across many frames (Figure 14.5). To do this we will create a picture of a beach one time and then repeatedly call the method makeSunset to keep reducing the blue and green color.
/** * Method to slowly create a sunset * @param directory the directory to write to */ public void makeSunsetMovie(String directory) { // load the picture of the beach String fName = FileChooser.getMediaPath("beach-smaller.jpg"); Picture beachP = new Picture(fName); // declare other variables Picture target = null; FrameSequencer frameSequencer = new FrameSequencer(directory); |
You can test this method with the following main method:
public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/sunset/"; movieMaker.makeSunsetMovie(dir); }
The swapBackground recipe (Program 43 (page 198)) that we made a while ago can also be used to good effect for generating movies. We can pass in the frame number as the threshold. The effect is a slow fade into the background image (Figure 14.6). Add the following method to the MovieMaker class.
/** * Method to create a movie that fades out the person from * one background to another. * @param directory the directory to write to */ public void makeFadeOutMovie(String directory) { // load the pictures String kidF = FileChooser.getMediaPath("kid-in-frame.jpg"); |
You can test this method with the following main method:
public static void main(String[] args) { MovieMaker movieMaker = new MovieMaker(); String dir = "c:/intro-prog-java/movies/fade/"; movieMaker.makeFadeOutMovie(dir); }