Infinite scrolling?!

Hi all,

I am trying to create infinite scrolling of images (only vertically), basically like any infinite scroll (social) media platform. What would be the way to go about this? I have tried preloading a large amount of images and only loading a small batch at a time versus arraylist of image objects, but I haven’t coded in Processing in quite some time and am not succeeding in load new images and deleting old ones as they are viewed and past the screen. Is it possible at all? Help would be greatly greatly appreciated!

I am trying to create a demo for an alternative home screen in which the user can scroll through offline and online content combined, and in which they can adjust what type of content (e.g. how invasive and new).

3 Likes

Hey,

There are several ways you can approach this. I wrote something you can start playing with that could work as a proof of concept if you don’t need a lot of control and manipulation of the images.

I created an Img object that contains some information about how to position an image within a grid, how to update the x and y values, and update the index position with an array. The objects essentially get recycled as you scroll up or down. I don’t use the scroll wheel in my sketches so there is a bit of an issue when scrolling at high speeds and you try to recycle the Img objects. If you are doing this on a website it might be easier as you wouldn’t be placing the object based on it’s x and y precisely but below or above a

or some other tag. This would keep the spacing consistent, however, this is a start.

You will just need to update a path to a folder with images (used three images buy duplicated them several times). I sized all my images to 200 x 200 pixels. However, if there is no valid path to images or once you scroll beyond the available images you just get a rect with a random fill and a display for the index number showing it would be updating with the corresponding image should there be one available.

With this approach you are only loading a few images at a time which would work as a proof of concept depending on the size and number of images. Perhaps not the most efficient, but again, a start.

infinite_scroll_sm

/**************************************************************
 Project:  Infinite vertical scroll template with and w/o images
 
 Notes:
 - Processing 4.0b2
 - update path to folder containing images 
 
 To Do:
 - improve scroll element
 - improve scroll end (i am sure you'll need an end at some point
     or you can loop?)
 
 **************************************************************/
 
ArrayList<Img>   images;        // array list of Img objects
String[]         filenames;     // array of image file names (String)
String           path;          // path to folder containing images
boolean          pathValid;     // toggle if path to images contains images

void setup() {
  size(900, 900);
  
  // update path to folder containing your desired images
  // I downloaded some random images to and images folder
  // within my downloads folder
  path = "C:/Users/asher/Downloads/images";
  pathValid = false;
  
  // can use relative path to sketch folder
  //java.io.File folder = new java.io.File(sketchPath(""));
  
  // create list of image file names in desired folder
  java.io.File folder = new java.io.File(path);
  filenames = folder.list();
  // check to see if images were obtained via path
  if((filenames != null)) {
    pathValid = true;
    println(filenames.length, "images available:");
    for(int i = 0; i < filenames.length;i++) {
      println(filenames[i]);
    }  
  } 
  
  // initialize list of Img objects
  images = new ArrayList<Img>();
  
  // place Img objects in display grid of
  // 3 x 3 with a 4th row loaded but not visible
  for(int y = 0; y < 4; y++) {
    for(int x = 0; x < 3; x++) {
      images.add(new Img(x, y, 200));
    }
  }   
}

void draw() {
  background(240);
  
  // display img objects with desired images
  for(Img img: images) {
    img.display();
  }
  
  // create a basic banner
  noStroke();
  fill(220);
  rect(0, 0, width, 150);
  
  // display app name and/or other UI elements
  fill(0);
  textAlign(CENTER, CENTER);
  textSize(40);
  text("Infinite Scroll", width/2, 75);     
}

/** 
* scroll wheel function
*/
void mouseWheel(MouseEvent event) {
  // update Img objects position based on scroll
  float e = event.getCount();
  for(Img img: images) {
    img.update(e);
  }
}

/**
* Class that holds an image and controls its movement and postion
*/
class Img {
  float       x, y;              // the x and y position in pixels    
  int         xIndex, yIndex;    // the x and y position as index locations
  int         imgSize;           // the size of the image
  float       scrollSpeed;       // speed of scroll
  color       imgColor;          // default of image box (when no images being used)
  int         index;             // index position within filenames list
  int         startIndex;        // hold original index for position look up
  int         runningScroll;     // track scrolling
  PImage      img;               // the image itself
   
  /**
  * Constructor
  * @param {int} _x    x position of the image (index)
  * @param {int} _y    y position of the image (index)
  * @param {int} _s    image size
  */
  Img(int _x, int _y, int _s) {
    xIndex = _x;
    yIndex = _y;
    
    // get starting index
    index = xIndex + yIndex * 3;
    startIndex = index;
    
    // spacing for x and y position
    x = 125 + (xIndex * 225);    
    y = 200 + (yIndex * 225);
    imgSize = _s;
    
    scrollSpeed = 20;
    runningScroll = 0;
    imgColor = color(255, random(150, 225), random(255));
    
    // load image per index
    updateImage();
    
  }
  
  /**
  *  update the position per scroll wheel
  *
  * @param {float}  _y     the scroll value from wheel
  */
  void update(float _y) {
    
    // not the best logic for controlling scroll but a start
    // scroll up and down unless at initial itial photo in filenames
    // #TODO optimize this
    if(runningScroll < 0) {
      y += map(_y, -5, 5, -scrollSpeed, scrollSpeed);
      runningScroll += map(_y, -5, 5, -scrollSpeed, scrollSpeed);      
    } else {
      if(_y > 0) _y = 0;
      y += map(_y, -5, 0, -scrollSpeed, 0);
      runningScroll += map(_y, -5, 0, -scrollSpeed, 0);   
    }   
    
    // update y position with boutn checking
    // not the greatest but works as a start
    // recycle img by moving y position to bottom of canvas
    if(y < -25) {
      
      // for infinite scroll use this
      index += 12;
      updateImage();
      if(startIndex < 3) { 
        y = images.get(startIndex + 9).y + 200 + 25;
      } else {
        y = images.get(startIndex - 3).y + 200 + 25;
      }
      
      // #TODO: to limit scroll to images in folder uncomment this
      // and comment above code
      //index += 12;
      //if(index < filenames.length) {
      //  if(startIndex < 3) { 
      //  y = images.get(startIndex + 9).y + 200 + 25;
      //  } else {
      //    y = images.get(startIndex - 3).y + 200 + 25;
      //  }
      //  updateImage();
      //} else {
      //  index -= 12;
      //}
    }
    
    // recycle img by moving y to top of canvas
    if(y > 875) {
      index -= 12; 
       if(startIndex < 9) {       
        y = images.get(startIndex + 3).y -  200 - 25;
      } else {
        y = images.get(startIndex - 9 ).y - 200 - 25;
      }
      updateImage();     
    }  
  }
  
  /**
  *  method for updating image to be displayed
  */
  void updateImage() {
    
    if(pathValid) {
      if(index >= 0 && index < filenames.length) {
        img = loadImage(path + "/" + filenames[index]);
      }
    }
  }
    
  /**
  * display the image and appropriate position
  */
  void display() {
    // when no images used
    fill(imgColor);
    rect(x, y, imgSize, imgSize);
    
    // display index values of img
    fill(0);
    text(index, x + 100, y + 100);
       
    // display image if there is a valid path to images
    // and within number of images (otherwise it loops images)
    if(pathValid) {
      if(index < filenames.length) {
        image(img, x, y);
      }
    }   
  }  
}
2 Likes

Hi @doordewar,

Thank you for posting this thread because it was a great challenge! :wink:

Here is my take on this:

The first thing was to get 100 images from an API somewhere and here is a useful one: https://picsum.photos/

Using this bash script to do download them automatically:

# download.sh

set -B

for i in {1..100}; do
  let id=i*2
  wget -O $i.png https://picsum.photos/id/$id/200
done
$ bash download.sh

Then this is a schema of the different variables and the layout:

The idea is to display the images linearly and have an infinite scroll. The first step was to have a working scroll effect with the mouse which I did with simple velocity/acceleration stuff.

Next was to display the current scroll position with a scroll bar, for this the map() function is quite useful.

Last step was to load/unload images as soon as you scroll too far. This was the most tricky part but I figured out the way to do this with a LinkedList to easily add or remove images.

So the images are loaded and removed as chunks (of 5 for example) as soon as you go too far so you only have a fixed amount of loaded images at every time.

infinite_scroll

This is the final code:

// Joseph HENRY
// https://discourse.processing.org/t/infinite-scrolling/34028

import java.util.LinkedList;

// Images are stored in a LinkedList for FIFO insert and removal
LinkedList<PImage> images;

int totalImages = 100;

// The amount of images loaded in memory
int imagesBufferSize = 10;

// Individual image height
int imageHeight = 200;

// The total height of images
int totalHeight = totalImages * imageHeight;

// Image offset of scroll
int imagesOffset = 0;

// Chunk size of images loaded/unloaded when scrolling
int imagesChunkLoadingSize = 5;

// The scroll position in pixels
int scrollPosition = 0;

// Scroll velocity added to the position
float scrollVelocity = 0;

// Scroll acceleration added to the velocity
float scrollAcceleration = 0;

int scrollBarWidth = 10;

/**
 * Taken from: https://easings.net/#easeOutCubic
 */
float easeOutCubic(float x) {
  return 1 - pow(1 - x, 3);
}

/**
 * Loads an image at the specified index
 */
PImage loadImageAtIndex(int i) {
  return loadImage(str(i) + ".png");
}

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

  images = new LinkedList<PImage>();
  
  // First load a chunk of images
  for (int i = 1; i <= imagesBufferSize; i++) {
    images.add(loadImageAtIndex(i));
  }
}

void displayScrollBar() {
  // Scrollbar backdrop
  noStroke();
  fill(0, 200);
  int scrollBarX = width - scrollBarWidth;
  rect(scrollBarX, 0, scrollBarWidth, height);

  // Loaded area bar
  float loadedAreaY = map(imagesOffset * imageHeight, 0, totalHeight, 0, height);
  float loadedAreaHeight = map(images.size(), 0, totalImages, 0, height);
  fill(#9664A0);
  rect(scrollBarX, loadedAreaY, scrollBarWidth, loadedAreaHeight, 10);

  // Scroll handle bar
  int visibleSize = height;
  float scrollHandleHeight = (float) visibleSize / totalHeight * height;
  float scrollHandlePosition = map(scrollPosition, 0, totalHeight - height, 0, height - scrollHandleHeight);
  fill(#7982DE);
  rect(scrollBarX, scrollHandlePosition, scrollBarWidth, scrollHandleHeight, 10);
}

void handleImageLoading() {
  // Compute the limits of the loaded images
  int firstImageYPosition = imagesOffset * imageHeight;
  int lastImageYPosition = firstImageYPosition + images.size() * imageHeight;
  
  // Determine if we are close to the limits
  boolean closeToFirstImage = scrollPosition < firstImageYPosition + imageHeight / 2.0;
  boolean closeToLastImage = scrollPosition > lastImageYPosition - height - imageHeight / 2.0;

  if (closeToFirstImage || closeToLastImage) {
    int offsetAdd = 0;

    for (int i = 0; i < imagesChunkLoadingSize; i++) {
      int nextImageIndex = closeToFirstImage ? imagesOffset - i : images.size() + imagesOffset + 1 + i;
      
      // If the image can be loaded (we are not at the boundaries)
      if (nextImageIndex >= 1 && nextImageIndex <= totalImages) {
        PImage img = loadImageAtIndex(nextImageIndex);
        
        // Append at the end or the beginning depending on the direction
        if (closeToFirstImage) {
          images.addFirst(img);
          images.removeLast();
        } else {
          images.addLast(img);
          images.removeFirst();
        }

        offsetAdd += (closeToFirstImage ? -1 : 1);
      }
    }
    
    // Update the image offset
    imagesOffset += offsetAdd;
  }
}

void updateScrollPhysics() {
  // Introduce drag in acceleration and velocity
  scrollAcceleration *= 0.9;
  scrollVelocity += scrollAcceleration;
  
  scrollVelocity *= 0.95;
  scrollPosition += scrollVelocity;
  
  // Limit the scroll min and max value
  scrollPosition = constrain(scrollPosition, 0, totalHeight - height);
}

void displayImages() {
  for (int i = 0; i < images.size(); i++) {
    PImage img = images.get(i);
    image(img, 0, (i + imagesOffset) * imageHeight - scrollPosition);
  }
}

void draw() {
  background(255);

  displayImages();
  displayScrollBar();

  handleImageLoading();

  fill(255, 0, 0);
  textSize(15);
  text("Loaded " + images.size() + " images", 10, 30);

  updateScrollPhysics();
}

void mouseDragged() {
  // When dragged use the cursor movements
  scrollPosition -= mouseY - pmouseY;
}

void mouseReleased() {
  // Compute the cursor y diff
  int diffY = -(mouseY - pmouseY);
  
  // Cancel velocity when going in the other direction
  if (!(scrollAcceleration < 0 && diffY < 0) || !(scrollAcceleration > 0 && diffY > 0)) {
    scrollVelocity = 0;
  }
  
  // Add movement to the scroll
  scrollVelocity = diffY / 10.0;
  scrollAcceleration = diffY / 5.0;
}

Have fun! :yum:

1 Like

@josephh @ASHER Thanks to both of you, actually amazing! Incredibly useful and already learning many tricks from it (like the linked list). I think this thread will be very resourceful for more people :sunny:

Currently knees deep on creating an interaction metaphor for exchanging privacy/novel information, all part of a challenge to make people feel at home in digital spaces (@home?). If I find time, I am going to try to add an HTML reader, so I can blend in things like websites and articles. Might post the final product if I am confident enough about it heh :^)

2 Likes

Also thanks for that great tiny workshop, very insightful

That sounds like a really interesting project. I would love to hear more about it. Good luck!

Glad you liked it! :heart_eyes:
Feel free to ask any questions

Hi all thanks for this code its amazing.

I am attempting to change the height between images, so that they have a fixed width but will be sized depending upon the height of that individual image.

Anyone have any ideas?

1 Like