Animations for the Web
7.4 R EPEATEDLY APPLYING A FILTER
Interesting effects in animations can be achieved by repeatedly applying the same fil-ter, such as a blur, shear, resize or translation. Repeating these effects can make objects appear out of thin air, rotate into view, or zoom into existence. All of these involve using the same base image and transforming or moving it according to some rules.
Each application of these rules results in a frame or part of a frame being incorporated into a larger animation. When the image size changes, for example with a shear, resize, or crop, the resulting frames will have boundaries that are different from the preced-ing images. In this case, you might need to adjust some of the frame-positionpreced-ing parameters to make certain the resulting images overlap in the way you intended. We will see more on that in section 7.4.2.
First, we will look at how to repeatedly apply a filter to slowly increase the visual effect that a single pass of that filter would have. Stringing together the resulting images into an animation can provide some interesting effects.
7.4.1 Example: making objects appear
Sometimes you will want to make it look like an object in your animation appears out of nothing. This can be achieved by starting with an image that you want to be the end result, and repeatedly applying a blurring type of filter to it. If, at the same time you also adjust the brightness, you’ll find that it looks like your image slowly dis-appears, or dissolves into nothing. All you need to do now to create the illusion that the object is appearing is reverse all the frames of the sequence you just created. The following program uses Image::Magick to do exactly that:
c
Copy the input image, and create the framesd
Modify the next frame$rc = $im->Quantize(colors => 256, dither => 1, global_colormap => 1);
warn $rc if $rc;
$im->Set(delay => 10, loop => 1);
$rc = $im->Write('logo-spread.gif');
die $rc if $rc;
b
The program starts by setting $frames to the total number of animation frames. We will work backward through the animation, and put the frames that are generated at the start of the image sequence. The last frame is the image itself, which is read from disk.c
A copy of the image object is made, and this copy is subjected to three filtering oper-ations for each frame that is needed. At the end of each loop, a copy of the frame is pushed to the front of the current animation with unshift, thus creating a reverse sequence of frames that are progressively changed by the three filters.d
The filters chosen to do this job are Spread(), Blur() and Modulate() to adjust the brightness. The first two will spread and smear out the pixels of the original image, making it look more and more blurred after each pass. At the same time, the brightness of the image is increased by a certain factor to make it more vague and lighter. I’ve found that for most images a total amount, over all frames, of about 75 percent is a good value, but for darker images you might need to adjust that.e
Since this animation is destined for the Web we’ll optimize it slightly and make sure that all frames in the animation use the same color map by calling Quantize().f
We set the delay time of each frame to 1/10th of a second, make sure that we only go through the animation once (since it makes no sense to repeat it), and save it to a file.The result of all of this can be seen in figure 7.1.
Instead of the three filters used in this example, you can use any of the special effects mentioned in appendix A, as long as they don’t change the size of the image they operate on. If you do need to change the size of the image, some extra steps will be required to align everything properly. We will see an example of this in the next section. repeat-edly applies a blur, spread and brightness change to an image to achieve a re-verse dissolution effect.
This effect makes it look as if the subject of the im-age slowly appears out of nothing.
To make this a more usable program, you could consider parsing the command line arguments, and allowing the user to specify the number of frames, the total amount of brightness change and the amount of spread and blur applied. It could also show you a preview with the Animate() function, if requested.
7.4.2 Example: zooming in on an object
The GIF format specifies that the first image in a multi-frame animation determines the bounding box of the animation, and that none of the following frames are allowed to go beyond that border. If your software doesn’t automatically clip these frames, you will have to remember to do it yourself. In the case of Image::Magick, the clipping happens automatically, but you are still responsible for making certain that the first image indicates the desired size of the total animation. Of course, when images are smaller than the background, you also need to position them correctly, unless you want them all to appear in the top left corner.
Suppose you want to create an animation in which you zoom in on the same image used in the previous example. If you merely apply the same program as above, and replace the three filters with a call to Resize() or Scale(), you’ll find that the result will be an animation that does not work. This would be because the first frame in the animation is also the smallest, resulting in a bounding box that can’t accommodate the later images. Depending on the software used to create and display this animation, the result can be surprising, and it might even cause a crash.
The following program is very similar to the one presented before, but adds all the necessary code to control the size and layout of different sized frames in an animation.
use Image::Magick;
b
First, the image that will become the last frame is read in, and its width and height are determined.c
This image is cloned and the clone is filled with a white color. This copy will become the background, i.e., the first frame, of the animation.$frame->Resize(geometry => '75%');
my ($frame_width, $frame_height) = $frame->Get('width', 'height');
my $offset_x = ($im_width - $frame_width )/2;
my $offset_y = ($im_height - $frame_height)/2;
$rc = $frame->Set(page => "+$offset_x+$offset_y");
warn $rc if $rc;
unshift @$im, $frame->Clone();
}
unshift @$im, $background;
d
The image is cloned again, as in the last example, to create the individual frames of the animation.e
For each iteration, the image is resized to 75 percent of its current size, and the width and height of the result are determined. These dimensions are used to calculate the off-sets needed to center this frame with respect to the background, and the page image attribute is set accordingly. Then the frame is added to the start of the $im object.f
The last frame to be added (to the start) is the image background that was created earlier.$rc = $im->Quantize(dispose => 1, colors => 256, dither => 1,
global_colormap => 1);
warn $rc if $rc;
$im->Set(delay => 10, loop => 1);
$im->[0]->Set(delay => 0);
$im->[$#$im]->Set(page => '+0+0');
g
As in the previous example, we quantize the color palette of the animation, so that all frames use a global palette, and we set the delay of all frames to 1/10th of a second.The first frame does not need to be displayed for any amount of time, so its delay is reset to 0.
h
The last frame, which is the original image, does not yet have a page offset, so we set that now, before saving the image. If we didn’t explicitly set it, it would end up with the same offset as the frame before it, which is of course not what we want.3The result of this program can be seen in figure 7.2.
e
Modify the copied imagef
Add the background as the first imageg
Reduce the color palette sizeh
Set the offset for the last frame3 This might be a bug in ImageMagick, and is possibly going to be fixed in a future release.
If you want to save this zooming animation in a format that doesn’t support frame offsets, such as MPEG, you will find that you end up with a corrupt video. This is because Image::Magick will not automatically attempt to correct for features that are not present in the output format because it assumes that you know what you are doing, and you intend to do exactly that. Since we do know what we are doing, we can solve the problem by using the Coalesce() method on the image sequence before we save it.4
my $im2 = $im->Coalesce();
warn $im2 unless ref $im2;
$rc = $im2->Write('logo-zoom.mpg');
warn $rc if $rc;