Fading and Zoom (image slideshow)

I’m so confused, I’m not getting this at all…hmmm

Well, a bit too fency but by processing 4+ you can do s.th. like that for the image list… :wink:

Cheers
— mnse

PImage[] images;
int      currentImage=0;
void setup() {
  size(500,500,P2D);
  File directory = new File(dataPath(""));    
  images = directory.isDirectory()    // directory exists ?
         ? java.util.stream.Stream.of(directory.listFiles()) // get the list of File Objects
           .filter(f -> f.isFile()) // is it a regular file ?
           .map(File::getAbsolutePath) // convert to a path string
           .filter(f -> f.matches(".*(?i)(\\.(jpg|png))$")) // does extension fits in ? 
           .map(f -> loadImage(f)) // make it a PImage
           .filter(java.util.Objects::nonNull) // filter if loading failed
           .toArray(PImage[]::new) // convert to PImage array
         : new PImage[0]; // not a directory or not exists give empty default 
                 
  if(images.length > 0)
    printArray(images);
  else
    println("No valid images");  
}

void draw() {
  background(0);
  if (images.length > 0) {
    surface.setTitle("Image: " + currentImage);
    image(images[currentImage],0,0,width,height);
    if (frameCount % 120 == 0)
      currentImage = (currentImage + 1) % images.length;
  }  
}

I just want to add that the names of the images must match exactly and it’s case-sensitive.

So 8.PNG won’t work. Not all file explorers / finder show the file extension correctly.

1 Like

This matches case insensitive. To make it case sensitive remove the (?!)

Cheers
— mnse

1 Like

Thanks for the guidance - noted

1 Like

Dear mnse - very fancy indeed - working well - I can see the potential here. What is the terminology to correctly refer to how you’ve done what you’ve done? i.e the lines from. How can I get a better grasp of the syntax you’ve used - all the . 's, → 's, : 's and ::'s!!

  images = directory.isDirectory()    // directory exists ?
..
...
...
 : new PImage[0]; // not a directory or not exists give empty default

I’m also a bit lost about how to incorporate this with the earlier examples with fade and zoom - is there some advantage in using an PImage array here rather than a (hugely exotic sounding) ArrayList as earlier?

1 Like

Hi @JonnieNightTime,

Sorry! completely missed out that we are want working with ArrayList above … :slight_smile:
Here is an adjusted version which generates an ArrayList instead of an array…
I’ve also adjusted the error handling because loadImage could result in two kinds of errors
and also added some status messages for loading…

import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Objects;


ArrayList<PImage> images;
int               currentImage=0;

void setup() {
  size(500, 500, P2D);
  textAlign(CENTER, CENTER);
  textSize(32);
  File directory = new File(dataPath(""));
  images = directory.isDirectory()                                  // directory exists ?
         ? Stream.of(directory.listFiles())                         // get a list of file objects as stream 
           .filter(File::isFile)                                    // filter for regular files
           .map(File::getAbsolutePath)                              // convert to a path string
           .filter(str -> str.matches(".*(?i)(\\.(jpg|png))$"))     // filter for wanted extensions
           .peek(str -> print("try loading image [" + str + "]: ")) // print info which image should be loaded
           .map(str -> loadImage(str))                              // make it a PImage
           .peek(pimg -> println((pimg==null || pimg.width==-1) ? "failed!" : "succeeded!")) // print status of loading
           .filter(pimg -> pimg != null && pimg.width != -1)        // filter for successfully loaded images
           .collect(Collectors.toCollection(ArrayList::new))        // convert to ArrayList of PImage
         : new ArrayList<>();                                       // not a valid directory set empty default

  if (images.size() < 1)
    print("No valid images found!");
  else
    print("images found: " + images.size());
}

void draw() {
  background(0);
  if (images.size() > 0) {
    surface.setTitle("Image: " + currentImage);
    image(images.get(currentImage), 0, 0, width, height);
    if (frameCount % 120 == 0)
      currentImage = (currentImage + 1) % images.size();
  } else {
    text("No valid images found!", width/2, height/2);
    return;
  }
}


The terminology is called java streams and lambda expressions introduced by java 8 (which is now possible to use since Processing 4 rolled out with a modern java version).
There are many resources which can be found for further information. I've attached two which should give you some information as a starting point below...

Hope that helps…

Cheers
— mnse

2 Likes

Yeah, the main bit is the ternary operator with

images = test ? expression1 : expression2

  • images = is the target ArrayList
  • test is directory.isDirectory()
  • expression1 is the long java stream and lambda expressions
  • expression2 is just new ArrayList<>(); when it fails

see ?: (conditional) / Reference / Processing.org

getting rid of the ternary operator gives us:

if(directory.isDirectory()) {
    // Yes 
    images = 
      Stream.of(directory.listFiles())                         // get a list of file objects as stream 
           .filter(File::isFile)                                    // filter for regular files
           .map(File::getAbsolutePath)                              // convert to a path string
           .filter(str -> str.matches(".*(?i)(\\.(jpg|png))$"))     // filter for wanted extensions
           .peek(str -> print("try loading image [" + str + "]: ")) // print info which image should be loaded
           .map(str -> loadImage(str))                              // make it a PImage
           .peek(pimg -> println((pimg==null || pimg.width==-1) ? "failed!" : "succeeded!")) // print status of loading
           .filter(pimg -> pimg != null && pimg.width != -1)        // filter for successfully loaded images
           .collect(Collectors.toCollection(ArrayList::new));       // convert to ArrayList of PImage
}
else {
    // fail
    images = new ArrayList<>(); 
}

this is a regular expression which is a smart way to analyze a String. Checks if the ending is jpg or png etc.

similar see match() / Reference / Processing.org

2 Likes

Just for the sake of completeness another variant which uses exception handling and the advantages of the newly introduced Files.list Method by java 8 (which not loading the whole Files as a list at once, but rather as a lazy stream)

Cheers
— mnse

import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Objects;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;

ArrayList<PImage> images;
int               currentImage=0;

void setup() {
  size(500, 500, P2D);
  textAlign(CENTER, CENTER);
  textSize(32);

  try (Stream<Path> stream = Files.list(Paths.get(dataPath("")))) {
    images = stream                                            // get a list of file objects as stream
      .filter(Files::isRegularFile)                            // filter for regular files
      .map(Path::toAbsolutePath)                               // Get the Filename
      .map(Path::toString)                                     // convert to a path string
      .filter(str -> str.matches(".*(?i)(\\.(jpg|png))$"))     // filter for wanted extensions
      .peek(str -> print("try loading image [" + str + "]: ")) // print info which image should be loaded
      .map(str -> loadImage(str))                              // make it a PImage
      .peek(pimg -> println((pimg==null || pimg.width==-1) ? "failed!" : "pass!")) // print loding info message  
      .filter(Objects::nonNull)                                // filter for successfully loaded images
      .filter(pimg -> pimg.width != -1)                        // filter for successfully loaded images
      .collect(Collectors.toCollection(ArrayList::new));       // convert to ArrayList of PImage
  }
  catch(Exception e) {
    println("Warning: " + e.getMessage());
    images = new ArrayList<>();
  }

  if (images.size() < 1)
    print("No valid images found!");
  else
    print("images found: " + images.size());
}

void draw() {
  background(0);
  if (images.size() > 0) {
    surface.setTitle("Image: " + currentImage);
    image(images.get(currentImage), 0, 0, width, height);
    if (frameCount % 120 == 0)
      currentImage = (currentImage + 1) % images.size();
  } else {
    text("No valid images found!", width/2, height/2);
    return;
  }
}

Further info:

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#list(java.nio.file.Path)

1 Like

This is a much simpler solution to load all images from a sketch’s “data/” folder via listPaths(): :file_folder:

1 Like

Hi @GoToLoop,

Quite funny… Never stumbled about listPaths. :slight_smile:

Nevertheless, the loadAllImages of your post missing some error handling, though.

Imo also don’t think it is really easier, but there are various methods to reach the goal. I only wanted to demonstrate a way that is almost safe and makes use of the “new” Java features. :wink:

Cheers
— mnse

2 Likes

Well, it’s not listed at Processing’s online reference either: :man_shrugging:

Well, AFAIK, there’s not a single example about Java streams on Processing’s examples yet; b/c obviously it’s only now debuting on P4:

And Java streams subject isn’t exactly beginner’s friendly, especially when considering Processing’s target audience.

And I guess every1 would agree cryptic RegExp is the hardest feature to master in programming. :smiling_imp:

B/c we’re still transitioning from P3 to P4 (P4 is still buggy btW! :beetle:), it’s safer to code examples that’d run on both P3 & P4 for now.

So when we create a P4-only sketch, it’d be nice to also post a P3-compatible version if possible. :pleading_face:

For simplification’s sake, indeed that function assumes that any image file found inside “data/” subfolders should load w/o any errors; which is likely true 99%. :stuck_out_tongue:

But for parity’s sake to your P4-only solution, I’ve upgraded my sketch w/ more checks & features: :innocent:

/**
 * Load All Images (v1.0.2)
 * GoToLoop (2023/Mar/04)
 *
 * https://Discourse.Processing.org/t/fading-and-zoom-image-slideshow/40812/32
 *
 * https://Discourse.Processing.org/t/
 * how-do-i-slowly-layer-photos-to-appear-on-top-of-eachother/36698/5
 */

static final String PICS_EXTS = "extensions=,png,jpg,jpeg,gif,bmp";
static final float FPS = .25;

PImage[] images;

void setup() {
  size(1200, 700);

  frameRate(FPS);
  imageMode(CENTER);

  images = loadAllImages();
  if (images.length == 0)  exit();
}

void draw() {
  final int idx = frameCount % images.length;

  clear();
  image(images[idx], width >> 1, height >> 1);
  surface.setTitle("Index: " + idx);
}

PImage[] loadAllImages() {
  final File dir = dataFile("");
  println(dir);

  if (!dir.isDirectory()) {
    println("Sketch's subfolder \"data/\" hasn't been created yet!");
    return new PImage[0];
  }

  final String[] imagePaths =
    listPaths(dir.getPath(), "files", "recursive", PICS_EXTS);

  final int found = imagePaths.length;
  final ArrayList<PImage> imgs = new ArrayList<PImage>(found);

  java.util.Arrays.sort(imagePaths);
  printArray(imagePaths);

  for (final String path : imagePaths) {
    final PImage img = loadImage(path);
    if (img != null && img.width > 0)  imgs.add(img);
  }

  final int valid = imgs.size(), rejected = found - valid;

  print("Found", found, "image file(s) and ");
  println(valid, "is/are valid (", rejected, "rejected ).");

  return imgs.toArray(new PImage[valid]);
}
2 Likes

Just to say how appreciative I am of the input here - totally blown away by your knowledge, clarity and consideration.

I’ve landed on the below - and using some images based on microorganisms and adding Blob detection the result is looking pretty splendid. I’ll soon be working towards sending OSC messages from selected Blob values - mentioning this because there might be implications for my next question.

///////////////////////////////////////////////////////// IMPORT LIBS

import blobDetection.*;

import java.util.Date;
import java.util.stream.Stream;
import java.util.stream.Collectors;
import java.util.Objects;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;

import milchreis.imageprocessing.*; //unused right now!@!@

import netP5.*;
import oscP5.*;
import processing.video.*;

///////////////////////////////////////////////////////// Blob Variables

PImage Blobimg;
BlobDetection theBlobDetection;
PImage img;

///////////////////////////////////////////////////////// Camera Variables (for BlobDetection)

Capture cam;
boolean newFrame=false;

///////////////////////////////////////////////////////// Graphics Variables

PGraphics canvas;

///////////////////////////////////////////////////////// Img Variables

ArrayList<PImage> images;
int   currentImage=0;
int   lastImageIndex =  0;
int   nextImageIndex =  1;
int   cImageIndex =  1;
float alphaValue     =  0;
//float alphaShift     =  0.0004; // very slow
//float alphaShift     =  0.004; //  slow
float alphaShift     =  0.008; //  MEDIUM
//float alphaShift     =  0.04; //  fast

int randomIndexFade;
float zoomFactor = 0.1;

///////////////////////////////////////////////////////// OSC / Network Variables
OscP5 oscP5;
NetAddress myRemoteLocation;

void setup() { /////////////////////////////////////////////////////////////// setup starts
  //fullScreen(P3D,0);

  //size(1920,1080, P3D); // HDTV 1080, FullHD, 1080p
  //size(1280, 720, P3D); //HD
  size(1024,576, P3D);
  
  canvas = createGraphics(width, height);
  
  cam = new Capture(this,160,120); // for Blob
  cam.start();
  
  // BlobDetection
  // img which will be sent to detection (a smaller copy of the cam frame);
  Blobimg = new PImage(80,60); 
  theBlobDetection = new BlobDetection(Blobimg.width, Blobimg.height);
  theBlobDetection.setPosDiscrimination(true);
  theBlobDetection.setThreshold(0.08); // will detect bright areas whose luminosity > 0.1;

  try (Stream<Path> stream = Files.list(Paths.get(dataPath("")))) {
    images = stream                                            // get a list of file objects as stream
      .filter(Files::isRegularFile)                            // filter for regular files
      .map(Path::toAbsolutePath)                               // Get the Filename
      .map(Path::toString)                                     // convert to a path string
      .filter(str -> str.matches(".*(?i)(\\.(jpg|png))$"))     // filter for wanted extensions
      .peek(str -> print("try loading image [" + str + "]: ")) // print info which image should be loaded
      .map(str -> loadImage(str))                              // make it a PImage
      .peek(pimg -> println((pimg==null || pimg.width==-1) ? "failed!" : "pass!")) // print loding info message  
      .filter(Objects::nonNull)                                // filter for successfully loaded images
      .filter(pimg -> pimg.width != -1)                        // filter for successfully loaded images
      .collect(Collectors.toCollection(ArrayList::new));       // convert to ArrayList of PImage
  }
  catch(Exception e) {
    println("Warning: " + e.getMessage());
    images = new ArrayList<>();
  }
    if (images.size() < 1)
    print("No valid images found!");
    else
    print("images found: " + images.size());
    
    myRemoteLocation = new NetAddress("192.168.2.10",12000); // to BlackIpad TouchOSC
    
    noStroke();
    frameRate(60);   

} //////////////////////////////////////////////////////////////////////////// setup ends

void captureEvent(Capture cam) //////////////////////////////////////////// captureEvent()
{
  cam.read();
  newFrame = true;
}

void draw() { ////////////////////////////////////////////////////////////////// draw loop
  background(0);
  
    if (images.size() > 0) {
    surface.setTitle("Image: " + currentImage);
    image(images.get(currentImage), 0, 0, width, height);
    if (frameCount % 120 == 0)
      currentImage = (currentImage + 1) % images.size();
  } else {
    text("No valid images found!", width/2, height/2);
    return;
  }

  int ListSize = images.size();
  float RandomIndex = random(ListSize);
  int RandomIndexInt = round (RandomIndex);

  fade(lastImageIndex, nextImageIndex, alphaValue, nextImageIndex % 2 != 0);
  if (alphaValue >= 1.0) {
    lastImageIndex = nextImageIndex;
    nextImageIndex = (lastImageIndex + RandomIndexInt) % images.size();
    cImageIndex = (lastImageIndex + RandomIndexInt) % images.size();
    alphaValue = 0;
  }
  alphaValue += alphaShift;
  
  if (newFrame)
  {
    newFrame=false;
    image(cam,0,0,0,0);
    Blobimg.copy(canvas, 0, 0, canvas.width, canvas.height, 0, 0, Blobimg.width, Blobimg.height);
    theBlobDetection.computeBlobs(Blobimg.pixels);
    drawBlobsAndEdges(true,true);
  }  
  
} ///////////////////////////////////////////////////////////////////////// draw loop end

void fade(int l, int n, float a, boolean zi) { //////////////////////////// fade Start

    canvas.beginDraw();
    canvas.background(0);
    canvas.pushMatrix();
    
    //canvas.image(outAB, 0,0); 

  if (l >= 0) {
    float invAlpha = 1.0-a;
    canvas.tint(#0000ff,invAlpha * 255);
    PImage limg=images.get(l);
    canvas.imageMode(CENTER);
    canvas.image(limg, width/2, height/2, width, height);
  }
    canvas.tint(#0000ff, a * 255);
    PImage nimg=images.get(n);
    canvas.imageMode(CENTER);
    canvas.image(nimg, width/2, height /2, width, height);

    canvas.popMatrix();
    canvas.endDraw();
    image(canvas, 0, 0);
  
} //////////////////////////////////////////////////////////////////////////////// fade End

void drawBlobsAndEdges(boolean drawBlobs, boolean drawEdges) { ////////////// BlobsAndEdges 

 strokeWeight(width /999); 

  Blob b;
  EdgeVertex eA,eB;
  for (int n=0 ; n<theBlobDetection.getBlobNb() ; n++)
  {
    b=theBlobDetection.getBlob(n);
    if (b!=null)
    {
      if (drawEdges) // Edges
      { 
        smooth();
        strokeWeight(3);
        stroke(255, 255, 255, 127);
        for (int m=0;m<b.getEdgeNb();m++)
        { eA = b.getEdgeVertexA(m); eB = b.getEdgeVertexB(m);
          if (eA !=null && eB !=null)
          line(eA.x * width, eA.y * height, eB.x * width, eB.y * height );
        
          //println(theBlobDetection.getBlobNb()+ " blobs"); use the first 8 blobs at any given time 
          //- send their x and y as SC
  }
      }
      
      if (drawBlobs) // Blobs
        noFill();
      {
        strokeWeight(1);
        stroke(0, 127, 255, 127);
        
        rect(b.xMin * width,b.yMin * height, b.w * width,b.h * height);}
        
        fill(#00ccff, 127);
        textSize(9);
        text(b.xMin, b.xMin * width,b.yMin * height);
        println(b.xMin + " x   " + b.yMin + " y ");

    }
      }
} /////////////////////////////////////////////////////////////////////// BlobsAndEdges end

The next nut to crack, (and my brain melts at even trying to formulate the question) relates to this code’s role in the larger project I’m working on. GoToLoop and Chrisir might recall GoTo’s awesome solution to an earlier question How should I organise this? 256 array with conditional content - Class? - #4 by GoToLoop Is it possible to put this Slideshow into an abstract class? Perhaps not because now “those cool stuffs don’t fit into a method”?

i.e.
Main sketch

void draw(){
..
    stuffs[PatternPlusBankOffSet].display(); // call createCoolStuff patterns
...
}

And Patterns

void createCoolStuffs() {
  
   stuffs[0] = new CoolStuff() { 
    @Override public void display() { 
      
// image slideshow here somehow
// also blob detection
// other methods?
   
    }
  }