How to save frames with many transparent images?

This is kind of a weird question, please forgive the longwinded explanation:

There must be an easier way to do this. I’m trying to save each frame (as it appears on the screen) and make a movie, but the frame captures that processing saves don’t look like the window, and I think because I added transparency to each pixel!

This code is just a super basic, modified Open Simplex Noise example. Since a new noise layer is added on top of the previous one, each frame I save needs to contain all the previous frames as layers as well (I think). This is all because save() and saveFrame() don’t capture what the screen actually looks like!

I created an array of PImage objects to store the images, but I’m not sure how to save them on top of each other?

If that approach is not possible then I was thinking I might have to iterate over every pixel from every previous frame and layer them all on top of each other? I feel like I must be missing something though…

Any advice or input would be greatly appreciated!

// https://thecodingtrain.com/CodingChallenges/137-4d-opensimplex-noise-loop

int totalFrames = 360;
int counter = 0;
boolean record = true;
boolean fourD = false;
PImage[] layeredImages = new PImage[360];

PImage img;

//zoom
float increment = 0.03;

// Just for non-looping demo
float zoff = 0;

OpenSimplexNoise noise;
void setup() {
  size(640, 360);
  img = createImage(640, 360, ARGB);
  noise = new OpenSimplexNoise();
}

void draw() {
  
  float percent = 0;
  if (record) {
    percent = float(counter) / totalFrames;
  } else {
    percent = float(counter % totalFrames) / totalFrames;
  }
 
  render(percent, counter);
    
  if (record) {
    layeredImages[counter] = img;
    layeredImages[counter].save("img/"+nf(counter, 3)+".png");
    if (counter == totalFrames-1) {
      exit();
    }
  }
  counter++;
}

void render(float percent, int counter) {
  float angle = map(percent, 0, 1, 0, TWO_PI);
  float uoff = map(sin(angle), -1, 1, 0, 2);
  float voff = map(sin(angle), -1, 1, 0, 2);
  float xoff = 0;
  
  img.loadPixels();
  for (int x = 0; x < width; x++) {
    float yoff = 0;
    for (int y = 0; y < height; y++) {
      float n;
      if (fourD) {
        // 4D Open Simplex Noise is very slow!
        n = (float) noise.eval(xoff, yoff, uoff, voff)*255+100;
      } else {
        // If you aren't worried about looping run this instead for speed!
        n = (float) noise.eval(xoff, yoff, zoff)*255+100;
      }
      //float bright = n > 0 ? 255 : 0;
      img.pixels[x + y * width] = color(n, n, n, 3);
      yoff += increment;
    }
    xoff += increment;
  }

  img.updatePixels();
  image(img, 0, 0);
  
  zoff += increment;
}

Hello,

Perhaps you can write each PImage layer to a PGraphics() object?
createGraphics() will allow you to write to a seperate buffer.
You can write multiple frames onto each other (keeping their transparency using beginDraw() and endDraw() to composite your layers. Then display them using image().

https://processing.org/reference/PGraphics.html
https://processing.org/reference/createGraphics_.html
https://processing.org/reference/PGraphics_beginDraw_.html

Thanks for the reply!

I looked thru the docs a ton and managed to almost make it work but I am missing something crucial. It seems that the first image is being assigned to every array index. So my graphics screenshots are layered with the right number of transparent layers (yay transparency!) but they are all the same image. :frowning:

My print statements show that each array index is being assigned properly by the for loop. There must be some hole in my understanding of the pixels > image > image array > graphics pipeline that i can’t seem to crack.

Here’s a more simplified version of the code above but with my progress from your suggestion.

int totalFrames = 360;
int counter = 0;
PImage[] layerArray = new PImage[totalFrames];

PImage layer;
PGraphics screenShot;

//Some Noise Settings
float increment = 0.03;
float zoff = 0;

OpenSimplexNoise noise;

void setup() {
  size(640, 360);
  layer = createImage(640, 360, ARGB);
  screenShot = createGraphics(640, 360);
  noise = new OpenSimplexNoise();
}

void draw() {
  //function that draws the noise pixels and creates an image
  render(counter);
  
  //assign the image to the array
  layerArray[counter] = layer;

  //start drawing the graphics object
  screenShot.beginDraw();

  //assign all previously drawn images to the graphics object
  for (int i = 0; i < counter+1; i++){
    screenShot.image(layerArray[i], 0, 0);
    print("layerArray["+i+"] drawn to buffer\n");
  }
  screenShot.endDraw();
  screenShot.save("img/"+nf(counter, 3)+".png");
  print("screenShot "+counter+" saved\n\n");
  counter++;
}

//Drawing the noise pixels
void render(int counter) {
  float angle = map(0, 0, 1, 0, TWO_PI);
  float uoff = map(sin(angle), -1, 1, 0, 2);
  float voff = map(sin(angle), -1, 1, 0, 2);
  float xoff = 0;
  
  layer.loadPixels();
  for (int x = 0; x < width; x++) {
    float yoff = 0;
    for (int y = 0; y < height; y++) {
      float n;
      n = (float) noise.eval(xoff, yoff, zoff)*255+100;
      layer.pixels[x + y * width] = color(n, n, n, 3);
      yoff += increment;
    }
    xoff += increment;
  }
  
  layer.updatePixels();
  image(layer, 0, 0);
  zoff += increment;
}

Hello,

You may only need to set the P2D renderer for the composition and the graphics buffer to P2D.

size(640, 360, P2D) ;
screenShot = createGraphics(640, 360, P2D);

I noticed this has a different behaviour when I tried it but the output frames were all identical.

I applied screenshot.clear() to remove previous pixeldata in the PGraphics layer and the output frames are now different. Is that the result you sought?

link to clear() in the reference: clear() \ Language (API) \ Processing 3+

I wonder if you can simply skip the PImage[] altogether and simply apply the noise directly to your PGraphics layer (which can be made into an array if you wish) and then write the PGraphics to image()?

Cheers,
JSG

int totalFrames = 360;

int counter = 0;

PImage[] layerArray = new PImage[totalFrames];



PImage layer;

PGraphics screenShot;

//Some Noise Settings

float increment = 0.03;

float zoff = 0;

OpenSimplexNoise noise;



void setup() {

  size(640, 360, P2D);

  layer = createImage(640, 360, ARGB);  

  screenShot = createGraphics(640, 360, P2D);

  noise = new OpenSimplexNoise();

}



void draw() {

  //function that draws the noise pixels and creates an image

  render(counter);

  

  //assign the image to the array

  layerArray[counter] = layer;



  //start drawing the graphics object

  screenShot.beginDraw();
  screenShot.clear();
  //assign all previously drawn images to the graphics object
  for (int i = 0; i < counter+1; i++){
  
    screenShot.image(layerArray[i], 0, 0);
    print("layerArray["+i+"] drawn to buffer\n");
  }

  screenShot.endDraw();

  screenShot.save("img/"+nf(counter, 3)+".png");
  print("screenShot "+counter+" saved\n\n");
  counter++;

}



//Drawing the noise pixels

void render(int counter) {

  float angle = map(0, 0, 1, 0, TWO_PI);

  float uoff = map(sin(angle), -1, 1, 0, 2);

  float voff = map(sin(angle), -1, 1, 0, 2);

  float xoff = 0;

  layer.loadPixels();

  for (int x = 0; x < width; x++) {

    float yoff = 0;

    for (int y = 0; y < height; y++) {

      float n;

      n = (float) noise.eval(xoff, yoff, zoff)*255+100;

      layer.pixels[x + y * width] = color(n, n, n, 3);

      yoff += increment;

    }

    xoff += increment;

  }

  

  layer.updatePixels();

  image(layer, 0, 0);

  zoff += increment;

}

That worked!

But dang the P2D renderer looks suuper different from the default renderer. This is rather unfortunate. I wonder if there is a way to make it look smoother and less stepped like the default. Looking thru some posts and the docs, it seems like messing with the P2D render settings might be challenging… :thinking:

I’m glad I coudl help! I had many of the same issues with some of my projects.

P2D and P3D do look a bit different but you can use smooth() to reduce aliasing.

https://processing.org/reference/smooth_.html

Way to perservere my friend!

Best,
JS

I’ll mess with that. Thanks for the help!
-Mark

All you really need to add is

smooth(8); //the higher the # the smoother the antialising.

to the line after size() in your setup() loop.

Take care.
js

Unfortunately smooth(8); had no effect on the banding. It looks the same with it on. :slightly_frowning_face:

Bummer. Perhaps hint() a related setting can help change the rendering settings.
https://processing.org/reference/hint_.html

Best of luck.
-JS