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…
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.
This matches case insensitive. To make it case sensitive remove the (?!)
Cheers
— mnse
Thanks for the guidance - noted
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?
Hi @JonnieNightTime,
Sorry! completely missed out that we are want working with ArrayList above …
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
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
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:
This is a much simpler solution to load all images from a sketch’s “data/” folder via listPaths():
Hi @GoToLoop,
Quite funny… Never stumbled about listPaths.
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.
Cheers
— mnse
Well, it’s not listed at Processing’s online reference either:
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.
B/c we’re still transitioning from P3 to P4 (P4 is still buggy btW! ), 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.
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%.
But for parity’s sake to your P4-only solution, I’ve upgraded my sketch w/ more checks & features:
/**
* 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]);
}
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?
}
}