Animating images in Java (1)
Blinking lights, rotating cogs, other changing things
Now that we can draw images and use timers in Java, the next step is to combine these two concepts and create animations. Make those images move!
The code is available as demo006, and you may want to download it and run it as you read the following text, to actually see the pictures in motion.
First, a little math
The idea of repetition is mathematically represented by the modulo operation, which provides us the remainder of an integer division. In Java, the operator is “%”. It works like this:
Imagine that we have a sequence of integers, starting from zero:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14…
Integer division by 5 (dividing by 5 and rounding down) gives us the following values:
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
The remainder of that division, in Java: “x % 5”, gives us the following values:
0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4…
Intuitively, we can use division to slow down a growing sequence of numbers, and the remainder to create a cycle of given length. In this case, the length was 5.
Just like in the previous article, we will use one “javax.swing.Timer” object to generate time events. The application contains multiple views, each of them implementing
void onTime(int timeInMilliseconds)a method to tell them that a certain amount of time has passed. The view updates its state in response to this information. Then the application repaints the view’s panel.
Some views start with adding the provided time intervals to calculate the total time:
int totalTime = 0;@Override public void onTime(int timeInMilliseconds) {
totalTime += timeInMilliseconds;
}and then paint the image depending on the value of the “totalTime” variable.
Flashing light bulb
The easiest animation is a flashing light bulb. We need two images: the light bulb turned off, and the light bulb turned on. We will switch between the images in regular time intervals.
At each moment of time we need to determine whether the light bulb is turned on or off, and then draw the corresponding image:
boolean lightBulbOn = ...;
g.drawImage(lightBulbOn ? imageOn : imageOff, x, y, null);If we had a separate timer for the light bulb, the entire algorithm could be just like:
boolean lightBulbOn = false;
var timer = new Timer(INTERVAL, e -> {
lightBulbOn = !lightBulbOn;
view.repaint();
});But this would require creating as many timers as there are animated things, which could lead to some problems. So instead, let’s use one global timer for everything, and adapt the code to depend on “totalTime”.
Regular blinking of a light bulb is defined by a few numbers:
how long does it stay turned off
how long does it stay turned on
phase shift
As an example, imagine a light bulb that stays off for 0.5 seconds, then turns on and stays on for 0.3 seconds, then turns off, etc. Now imagine also another light bulb that does exactly the same thing, only 0.1 seconds earlier than the first one.
Suppose that the times spent turned off and turned on are defined as constants:
static int OFF = 500; // milliseconds
static int ON = 300; // millisecondsThe length of the entire cycle is then “OFF + ON”. We can find our position in the cycle using the modulo operation:
int phase = totalTime % (OFF + ON)This gives us a number between 0 and 799 (=500+300-1). We can imagine a value that grows each millisecond by 1, but when it reaches 800, it changes to 0 again.
We want the light bulb to be off for the first 500 values, that is between 0 and 499, that is when “phase < OFF”.
int phase = totalTime % (OFF + ON);
g.drawImage((phase < OFF) ? imageOff : imageOn, x, y, null);This means the light bulb will be off for values between 0 and 499 (i.e. for 500 ms), and on for values between 500 and 799 (i.e. for 300 ms).
If we want another light bulb to do the same thing, but slightly earlier, we need to add a small value to the “totalTime” before we calculate the remainder. Like this:
int phase = (totalTime + phaseShift) % (OFF + ON);
g.drawImage((phase < OFF) ? imageOff : imageOn, x, y, null);Periodic change
Instead of two images, suppose that we have N images, and we want to display them in a cycle, each one of them for the same length of time.
The time interval each of those images is displayed will be called “INTERVAL”. The length of the entire cycle is then “N * INTERVAL”. The moment in the current cycle is:
int phase = (totalTime + phaseShift) % (N * INTERVAL);For example, if we have 4 images, and want to display each of them for 100 milliseconds, the length of the entire cycle is 400 milliseconds. The phase is a value between 0 and 399. Now we need to find out which image should be displayed:
int imageIndex = phase / INTERVAL;If each image is displayed for 100 milliseconds, we can find the index of the current image by dividing the phase by 100. The result is a value between 0 and 3.
Then we draw the image; assuming that all images are in an array:
g.drawImage(images[imageIndex], x, y, null);We can put all of this together, like this:
int imageIndex = ((totalTime + phaseShift) % (N * INTERVAL)) / INTERVAL;
g.drawImage(images[imageIndex], x, y, null);These are the basic ways of animating an image in place.
A few tricks
Sometimes an animation requires fewer images than it has phases, because some images repeat.
Imagine an animation of a blinking eye that gradually opens and closes. For the extremes, we need images of an eye fully closed and an eye fully open. But the images in between, the partially open eyes, will play in a sequence as the eye is opening, and then in the opposite sequence when the eye is closing.
Suppose that we have two images of a partially open eye. The cycle goes like this:
imgClosed, imgPartial1, imgPartial2, imgOpen, imgPartial2, imgPartial1…
There are 6 steps in the animation cycle, but only 4 distinct images. To animate this, we can use the formula for a 6-step animation, with added extra step to convert the step to the actual image index. With only a few steps, we can enumerate them like this:
var imageIndexForStep = Arrays.asList(0, 1, 2, 3, 2, 1);
int step = ((totalTime + phaseShift) % (6 * INTERVAL)) / INTERVAL;
int imageIndex = imageIndexForStep.get(step);
g.drawImage(images[imageIndex], x, y, null);If we animate an image that has some inner symmetry, we may save ourselves a lot of work. For example, imagine that we want to make an animation of a rotating square, with a 15° rotation at each step. At first sight, it may seem like we will need 360°/15° = 24 images for that. But actually, a square rotated by 90° looks exactly the same as the original one, so we only need 6 images. The seventh step will be the same as the first one, even if the player’s brain may perceive it as “the original square is now rotated sideways”.
All these animations are implemented in the StandingAnimationView class.





