I display images in sequence but I see only the last one. Why?

Sometime, I see in the forum code looking like:

PImage img1;
PImage img2;
// [...]
PImage imgn;

void setup()
{
  size(500, 500);
  img1 = loadImage("img1.png");
  img1 = loadImage("img2.png");
  // [...]
  imgn = loadImage("imgn.png"); // n is a number
}

void draw()
{
  image(img1, 20, 20);
  delay(1000);
  image(img2, 20, 20);
  // [...]
  delay(1000);
  image(imgn, 20, 20);
}

This code is wrong in several ways...

First, it breaks the DRY programming principle: Don't Repeat Yourself. Don't do repetitive tasks with editor (copy/paste/edit), let the computer do such repetitive work, that's what's it is good at. Obviously, you have a pattern here, so you can use a loop to load the images in sequence.

So, some people go to a further step, perhaps an idea coming from other (rare) programming languages: they ask how to generate the img1, img2, etc. variables.
It is just not possible (at least simply) in Java. Instead, you have to use arrays. An example of code is:

PImage[] images = new PImage[20]; // Supposing we have 20 images

void setup()
{
  size(500, 500);
  for (int i = 0; i < images.length; i++)
  {
    images[i] = loadImage("img" + nf(i+1, 2) + ".png"); // nf() allows to generate 01, 02, etc.
  }
}

As you see, it is faster to type and to maintain.

"So, you can use a loop to display the images", say some people already knowing this construct:

void draw()
{
  for (int i = 0; i < images.length; i++)
  {
    image(images[i], 20, 20);
    delay(1000);
  }
}
// OR, with the equivalent while loop:
void draw()
{
  int i = 0; // Declare a counter
  while (i < images.length) // Manage the exit condition
  {
    image(images[i], 20, 20);
    delay(1000);
    i++; // Increment the counter
  }
}

Then they complain they see nothing for several seconds (or just say Processing is crashed / stuck, because they stopped the sketch before completion!), then seeing only the last image, again.

Now, the main problem: why only the last image is displayed?
The answer is simple: all drawing operations done within draw() are cumulated, and only the end result is displayed when draw() exits. So, actually, you paint each image in sequence, one over the others, then you display the end result, the last image.

How to display the images in sequence?

Use draw() as the body of a loop (an infinite one, until you stop the sketch). You can use global variables as loop counter. Example:

int counter; // Automatically initialized at 0

void draw()
{
  background(255);
  image(images[counter], 0, 0);
  // Increment counter, then compute its modulo, ie. reset it at zero when reaching images.length
  counter = ++counter % images.length;
}

But unless you tweak the frame rate, the images will be displayed too briefly (unless if you are doing an animation...). You can be tempted to use delay() again. But using delay() is generally a bad idea, as everything stops for the duration of the delay. Actually, you rarely need to use it, except when dealing with the system (getting serial data, etc.). Often, you want to do something else than displaying the images, so you need to keep running the frames at the normal rate.
delay() have been so badly misused that the Processing designers decided to remove it from the version 2.0... (it was intended to be moved to the Serial library, but it was restored for compatibility purpose).

How to display the images for some time?

It is simpler than you might thing: you display the image, and count the time: you check on each frame how many seconds went since the last display. When a given amount of time is spent, you can display the next image. For this, you use millis() to check the time. An example follows, illustrating this common programming scheme:

PImage[] images = new PImage[20]; // Supposing we have 20 images
int counter; // Automatically initialized at 0
final int DISPLAY_TIME = 2000; // 2000 ms = 2 seconds
int lastTime; // When the current image was first displayed

void setup()
{
  size(500, 500);

  for (int i = 0; i < images.length; i++)
  {
    images[i] = loadImage("img" + nf(i+1, 2) + ".png"); // nf() allows to generate 01, 02, etc.
  }
  lastTime = millis();
}

void draw()
{
  background(255);
  fill(#005599);
  text(frameCount, 10, 20); // Shows the sketch isn't stopped between each image

  if (millis() - lastTime >= DISPLAY_TIME) // Time to display next image
  {
    // Increment counter, then compute its modulo, ie. reset it at zero when reaching images.length
    counter = ++counter % images.length;
    lastTime = millis();
  }
  image(images[counter], 50, 50);
}

This code is easy to adapt to other needs. For example, if you want to display an image corresponding to a value, be it input from user, data from a file or Internet or get from the serial port, etc., you can map the value to the index of an image:

PImage[] images = new PImage[5]; // Supposing we have 5 images
float MIN_VALUE; // The lowest value we can get (here: 0)
float MAX_VALUE; // The highest value we can get

void setup()
{
  size(500, 500);

  MAX_VALUE = width * PI; // Adjust to your real data
  for (int i = 0; i < images.length; i++)
  {
    images[i] = loadImage("img" + nf(i+1, 2) + ".png"); // nf() allows to generate 01, 02, etc.
  }
}

void draw()
{
  background(255);

  float someValue = mouseX * PI; // Can be coming from a file, serial port, Internet, etc.
  int imageIndex = int(map(someValue, 0, MAX_VALUE, 0, images.length));

  fill(#005599);
  String msg = nf(someValue, 1, 3) + " -> " + imageIndex; // Here nf() limits the number of decimal digits
  text(msg, 10, 20); // Shows the current value and how it is mapped

  image(images[imageIndex], 50, 50);
}
Sign In or Register to comment.