1

I would like to display, in Processing, one photo fading up and fading down over 15 seconds, with a second photo doing the same one second later, and another, etc, ad infinitum.

This example displays 15 objects, but they all start together:

PImage[] imgs = new PImage[42];
int Timer;
Pic[] pics = new Pic[15];

void setup() {
  size(1000, 880);
  for (int i = 0; i < pics.length; i++) {
  pics[i] = new Pic(int(random(0, 29)), random(0, 800), random(0, height));
  }
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
}

void draw() {
  background(255);
  for (int i = 0; i < pics.length; i++) {
    pics[i].display();
  }
}

class Pic {
  float x;
  float y;
  int num;
  int f = 0;
  boolean change = true;

  Pic(int tempNum, float tempX, float tempY) {
    num = tempNum;
    x = tempX;
    y = tempY;
  }

  void display() {
    imageMode(CENTER);
    if (change)f++;
    else f--;
    if (f==0||f==555)change=!change;
    tint(0, 153, 204, f);
    image(imgs[num], x, y);
  }
}
    

Thanks!!!

user2449986
  • 77
  • 1
  • 10

1 Answers1

2

The main issue is you're updating/rendering all images all Pic instances at once. Perhaps you meant to display one at a time (one after the other).

There other sections that raise questions as well:

  • Timer is never used: it's a good idea to simplify/cleanup code as much as possible
  • f is used for both the tint alpha (0-255 range) and to fake a delay across multple frames (==555 check)
  • num is asigned to a random value which means potentially the same pic may be repeated (potentially even consecutively ?) making it hard to notice the effect

I recommend slowing down and breaking the problem down:

  • fade a single image in and out
  • determine when a fade in/out cycle is complete
  • increment a single index (so the next image can fade in and out)

Ideally you want to take the timing (15 seconds into account).

You can work out across how many frames you'd need to fade over 15 seconds. Processing's frameRate property can help with that:

numAlphaSteps = 15 * frameRate;

(e.g. at 60 fps that would be 15 * 60 = 900 frames) That being said, it takes a few frames for frameRate to "warm up" and become stable. The safer option would be to call frameRate() passing the resired frames per second and reusing that number for the animation

The next step is to map each increment to an alpha value, as it needs to ramp up and back down. You could use a bit of arithmetic. If you subtract half the number of total fade frames (e.g. 900 / 2 = 450) from each frame number you'd get a value that goes from -half the number of frames to half the number of frames. Here's a minimal sketch you can try out:

int numFrames = 10;
int numFramesHalf = numFrames / 2;

for(int i = 0 ; i < numFrames; i++){
  println("frame index", i, "ramp value", i - numFramesHalf);
}

Here's the same, visualised:

background(0);

int numFrames = 10;
int numFramesHalf = numFrames / 2;

for(int i = 0 ; i < numFrames; i++){
  int ramp = i - numFramesHalf;
  println("frame index", i, "ramp value", ramp);
  // visualise numbers
  fill(0, 128, 0);
  rect(i * 10, 50, 10, map(ramp, 0, 5, 0, 39));
  fill(255);
  text(i + "\n" + ramp, i * 10, height / 2);
}

plot of the number -5 to 4 as a bar graph with green boxes and white text overlaid displaying the values, on top of a black background

(Feel free to change numFrames to 900, 10 is easier to see in console).

If you pass the subtraction result to abs() you'll get positive values:

int numFrames = 10;
int numFramesHalf = numFrames / 2;

for(int i = 0 ; i < numFrames; i++){
  println("frame index", i, "ramp value", abs(i - numFramesHalf));
}

and the visualisation:

background(0);

int numFrames = 10;
int numFramesHalf = numFrames / 2;

for(int i = 0 ; i < numFrames; i++){
  int ramp = abs(i - numFramesHalf);
  println("frame index", i, "ramp value", ramp);
  // visualise numbers
  fill(0, 128, 0);
  rect(i * 10, 0, 10, map(ramp, 0, 5, 0, 39));
  fill(255);
  text(i + "\n" + ramp, i * 10, height / 2);
}

plot of the number 5,4,3,2,1,0,1,2,3,4 as a bar graph with green boxes and white text overlaid displaying the values, on top of a black background

If you look in Processing Console you should see values that resemble a linear ramp (e.g. large to 0 then back to large).

This is a range that can be easily remapped to a the 0 to 255 range, required for the alpha value, using map(yourValue, inputMinValue, inputMaxValue, outputMinValue, outputMaxValue).

Here's a sketch visualising the tint value going from 0 to 255 and back to 255:

size(255, 255);

int numFrames = 10;
int numFramesHalf = numFrames / 2;

for(int i = 0 ; i <= numFrames; i++){
  int frameIndexToTint = abs(i - numFramesHalf);
  float tint = map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  println("frame index", i, "ramp value", frameIndexToTint, "tint", tint);
  rect((width / numFrames) * i, height, 1, -tint);
}

plot of the number 0 to 255 and back to 0 (across 10 values) as a thin black lines on a gray background

Now with these "ingredients" it should be possible to switch from a for loop draw():

int numFrames = 180;
int numFramesHalf = numFrames / 2;

int frameIndex = 0;

void setup(){
  size(255, 255);
}

void draw(){
  // increment frame
  frameIndex++;
  // reset frame
  if(frameIndex > numFrames){
    frameIndex = 0;
    println("fade transition complete");
  }
  // compute tint
  int frameIndexToTint = abs(frameIndex - numFramesHalf);
  float tint = map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  // visualise tint as background gray
  background(tint);
  fill(255 - tint);
  text(String.format("frameIndex: %d\ntint: %.2f", frameIndex, tint), 10, 15);
}

Notice that using custom index for the transition frame index makes it easy to know when a transition is complete (so it can be reset): this is useful to also increment to the next image:

int numFrames = 180;
int numFramesHalf = numFrames / 2;

int frameIndex = 0;
int imageIndex = 0;
int maxImages  = 15;

void setup(){
  size(255, 255);
}

void draw(){
  // increment frame
  frameIndex++;
  // reset frame (if larger than transition frames total)
  if(frameIndex >= numFrames){
    frameIndex = 0;
    // increment image index
    imageIndex++;
    // reset image index (if larger than total images to display)
    if(imageIndex >= maxImages){
      imageIndex = 0;
    }
    println("fade transition complete, next image: ", imageIndex);
  }
  // compute tint
  int frameIndexToTint = abs(frameIndex - numFramesHalf);
  float tint = map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  // visualise tint as background gray
  background(tint);
  fill(255 - tint);
  text(String.format("frameIndex: %d\ntint: %.2f\nimageIndex: %d", frameIndex, tint, imageIndex), 10, 15);
}

These are the main ingredients for your program. You can easily swap the placeholder background drawing with your images:

int numFrames = 180;
int numFramesHalf = numFrames / 2;

int frameIndex = 0;
int imageIndex = 0;
int maxImages  = 15;

PImage[] imgs = new PImage[42];
int[] randomImageIndices = new int[maxImages];

void setup(){
  size(255, 255);
  // load images
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
  // pick random images
  for (int i = 0; i < maxImages; i++) {
    randomImageIndices[i] = int(random(0, 29));
  }
}

void draw(){
  // increment frame
  frameIndex++;
  // reset frame (if larger than transition frames total)
  if(frameIndex >= numFrames){
    frameIndex = 0;
    // increment image index
    imageIndex++;
    // reset image index (if larger than total images to display)
    if(imageIndex >= maxImages){
      imageIndex = 0;
    }
    println("fade transition complete, next image: ", imageIndex);
  }
  // compute tint
  int frameIndexToTint = abs(frameIndex - numFramesHalf);
  float tint = map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  // visualise tint as background gray
  background(0);
  PImage randomImage =imgs[randomImageIndices[imageIndex]];
  tint(255, tint);
  image(randomImage, 0, 0);
  //debug info
  fill(255);
  text(String.format("frameIndex: %d\ntint: %.2f\nimageIndex: %d", frameIndex, tint, imageIndex), 10, 15);
}

The x, y position of the image isn't random, but that should be easy to replicated based on how the random image index is used.

Alternatively you can use a single random index and position that get's reset at the end of each transition:

int numFrames = 180;
int numFramesHalf = numFrames / 2;

int frameIndex = 0;
int imageIndex = 0;
int maxImages  = 15;

PImage[] imgs = new PImage[42];
int randomImageIndex;
float randomX, randomY;

void setup(){
  size(255, 255);
  // load images
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
  // pick random index
  randomImageIndex = int(random(0, 29));
  randomX = random(width);
  randomY = random(height);
}

void draw(){
  // increment frame
  frameIndex++;
  // reset frame (if larger than transition frames total)
  if(frameIndex >= numFrames){
    frameIndex = 0;
    // increment image index
    imageIndex++;
    // reset image index (if larger than total images to display)
    if(imageIndex >= maxImages){
      imageIndex = 0;
    }
    // reset random values
    randomImageIndex = int(random(0, 29));
    randomX = random(width);
    randomY = random(height);
    println("fade transition complete, next image: ", imageIndex);
  }
  // compute tint
  int frameIndexToTint = abs(frameIndex - numFramesHalf);
  float tint = map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  // visualise tint as background gray
  background(0);
  PImage randomImage =imgs[randomImageIndex];
  tint(255, tint);
  image(randomImage, randomX, randomY);
  //debug info
  fill(255);
  text(String.format("frameIndex: %d\ntint: %.2f\nimageIndex: %d", frameIndex, tint, imageIndex), 10, 15);
}

And you can also encapsulate instructions grouped by functionality into functions (removing the redundant imageIndex since we're using a random index):

int numFrames = 180;
int numFramesHalf = numFrames / 2;

int frameIndex = 0;
int imageIndex = 0;
int maxImages  = 15;

PImage[] imgs = new PImage[42];
int randomImageIndex;
float randomX, randomY;

void setup(){
  size(255, 255);
  // load images
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
  // pick random index
  randomizeImage();
}

void draw(){
  updateFrameAndImageIndices();
  background(0);
  PImage randomImage =imgs[randomImageIndex];
  tint(255, tintFromFrameIndex());
  image(randomImage, randomX, randomY);
}

float tintFromFrameIndex(){
  int frameIndexToTint = abs(frameIndex - numFramesHalf);
  return map(frameIndexToTint, 0, numFramesHalf, 255, 0);
}

void updateFrameAndImageIndices(){
  // increment frame
  frameIndex++;
  // reset frame (if larger than transition frames total)
  if(frameIndex >= numFrames){
    frameIndex = 0;
    // increment image index
    imageIndex++;
    // reset image index (if larger than total images to display)
    if(imageIndex >= maxImages){
      imageIndex = 0;
    }
    // reset random values
    randomizeImage();
    println("fade transition complete, next image: ", imageIndex);
  }
}

void randomizeImage(){
  randomImageIndex = int(random(0, 29));
  randomX = random(width);
  randomY = random(height);
}

If the goal of this coding excecise is to practice using classes, you can easily further encapsulate the image fade related functions and variables into a class:

PImage[] imgs = new PImage[42];
ImagesFader fader;

void setup(){
  size(255, 255);
  frameRate(60);
  // load images
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
  // setup fader instance
  // constructor args: PImage[] images, float transitionDurationSeconds, int frameRate
  // use imgs as the images array, transition in and out within 1s per image at 60 frames per second 
  fader = new ImagesFader(imgs, 1.0, 60);
}

void draw(){
  background(0);
  fader.draw();
}

class ImagesFader{
  
  int numFrames;
  int numFramesHalf;
  
  int frameIndex = 0;
  
  PImage[] images;
  int maxImages  = 15;
  
  int randomImageIndex;
  float randomX, randomY;

  ImagesFader(PImage[] images, float transitionDurationSeconds, int frameRate){
    numFrames = (int)(frameRate * transitionDurationSeconds);  
    numFramesHalf = numFrames / 2;
    println(numFrames);
    this.images = images;
    // safety check: ensure maxImage index isn't larger than the total number of images
    maxImages = min(maxImages, images.length - 1);
    // pick random index
    randomizeImage();
  }
  
  void draw(){
    updateFrameAndImageIndices();
    PImage randomImage = imgs[randomImageIndex];
    
    // isolate drawing style (so only the image fades, not everything in the sketch)
    pushStyle();
    tint(255, tintFromFrameIndex());
    image(randomImage, randomX, randomY);
    popStyle();
  }
  
  float tintFromFrameIndex(){
    int frameIndexToTint = abs(frameIndex - numFramesHalf);
    return map(frameIndexToTint, 0, numFramesHalf, 255, 0);
  }
  
  void updateFrameAndImageIndices(){
    // increment frame
    frameIndex++;
    // reset frame (if larger than transition frames total)
    if(frameIndex >= numFrames){
      frameIndex = 0;
      // randomize index and position
      randomizeImage();
      println("fade transition complete, next image: ", randomImageIndex);
    }
  }
  
  void randomizeImage(){
    randomImageIndex = int(random(0, 29));
    randomX = random(width);
    randomY = random(height);
  }
  
}

If you're comfortable using Processing libraries, you can achieve the same with a tweening library like Ani:

import de.looksgood.ani.*;
import de.looksgood.ani.easing.*;

PImage[] imgs = new PImage[42];

float tintValue;
int randomImageIndex;
float randomX, randomY;

AniSequence fadeInOut;

void setup(){
  size(255, 255);
  frameRate(60);
  // load images
  for (int i = 0; i < imgs.length; i++) {
    imgs[i] = loadImage(i+".png");
  }
  randomizeImage();
  // Ani.init() must be called always first!
  Ani.init(this);

  // create a sequence
  // dont forget to call beginSequence() and endSequence()
  fadeInOut = new AniSequence(this);
  fadeInOut.beginSequence();
  
  // fade in
  fadeInOut.add(Ani.to(this, 0.5, "tintValue", 255));

  // fade out (and call sequenceEnd() when on completion)
  fadeInOut.add(Ani.to(this, 0.5, "tintValue", 0, Ani.QUAD_OUT, "onEnd:sequenceEnd"));
  
  fadeInOut.endSequence();

  // start the whole sequence
  fadeInOut.start();
}

void draw(){
  background(0);
  tint(255, tintValue);
  image(imgs[randomImageIndex], randomX, randomY);
}

void sequenceEnd() {
  randomizeImage();
  fadeInOut.start();
}

void randomizeImage(){
  randomImageIndex = int(random(0, 29));
  randomX = random(width);
  randomY = random(height);
}

Update Based on your comment regarding loading images, can you try this sketch ?

void setup(){
  size(1000, 500);
  textAlign(CENTER);
  PImage[] images = loadImages("Data","png");
  int w = 100;
  int h = 100;
  for(int i = 0; i < images.length; i++){
    float x = i % 10 * w;
    float y = i / 10 * h;
    image(images[i], x, y, w, h);
    text("["+i+"]",x + w / 2, y + h / 2);
  }
}

PImage[] loadImages(String dir, String extension){
  String[] files = listPaths(dir, "files", "extension=" + extension);
  int numFiles = files.length;
  PImage[] images = new PImage[numFiles];
  for(int i = 0 ; i  < numFiles; i++){
    images[i] = loadImage(files[i]);
  }
  return images;
}

It should load images in the "Data" folder (as the comment mentions, not "data" which is commonly used in Processing). If "Data" is a typo, fix the path first (as Processing is key sensitive ("Data" != "data")). If the 50 images load correctly, it should display in a 10x5 grid at 100x100 px each (e.g. disregarding each image's aspect ratio). This should help test if the images load correctly. (Again, breaking the problem down to individual steps).

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • George, your answer is incredibly detailed and helpful! Heartfelt thanks for your effort. I followed along with your examples, until you got to the parts with references to actual images (png's). I saved your code and added a Data folder with 0.png -> 49.png files, but the images didn't show up. Also, my main problem was not getting images to fade up and down one after another, but to get one image to start its fade up/down cycle while another image was part way through its fade up/down cycle. That's why I was exploring objects. Again, thanks!! – user2449986 Jun 24 '22 at 02:05
  • Happy to hear it helps (and thank you for the vote up)! The way I understood "a second photo doing the same one second later" is that you want an image to fade in, then out completely and only after the next image transitions with a fade in, fade out. What you're describing in the comment souns like crossfading: fading one image out while simultaneously a second image fades in. Can you please post another question mentioning the crossfade and ideally timing details ? (e.g. how long does the transition last for and for long should the image be on screen ?) – George Profenza Jun 24 '22 at 08:37
  • I've posted another small sketch at the bottom of the sketch to test loading images. – George Profenza Jun 24 '22 at 08:41
  • Hi George. I have posted another question. I get that the first one was ambiguous! "Crossfading multiple images in Processing" – user2449986 Jun 25 '22 at 01:26