PImage displays well but exports as empty file

Hello. In this code snippet I create a canvas and draw on it using a shader. The created image displays nicely, but when exported the PNG file is empty. Exporting the canvas directly wont work either. When I do loadPixels() on the PImage it shows as empty too.

Any idea why the PImage is empty?

PShader shader;

void setup() {
  size(1080, 500, P2D);
  shader = new PShader (this, vertSrc, fragSrc);
}

void draw() {
  background(0);

  if (mouseX > 0 && mouseY > 0) {
    PImage img = getPhenotype(mouseX, mouseY, shader);
    image(img, 0, 0);
    if(mousePressed)img.save("img.png");
  }
}

PImage getPhenotype(float _w, float _h, PShader _shader) {
  int w = floor(_w);
  int h = floor(_h);

  PGraphics canvas = createGraphics(w, h, P2D);

  canvas.beginDraw();

  canvas.shader(_shader);

  canvas.rect(0, 0, canvas.width, canvas.height);

  canvas.endDraw();

  return canvas;
}

String[] vertSrc = {"""
#version 330 core
uniform mat4 modelview;
uniform mat4 projection;
in vec4 position;
in vec4 texCoord;
out vec2 uv;
void main() {
  uv = texCoord.xy;
  gl_Position = projection * modelview * position;
}
"""};

String[] fragSrc = {"""
#version 330 core
in vec2 uv;
out vec4 fragColor;
void main() {
  fragColor = vec4( length(uv-0.5) < 0.25 ? vec2(0.) : uv, 0.0, 1.0 );
}
"""};

Snippet based on code by scudly on another of my posts

Thanks for the help!

Hello,

Try this:

    if(mousePressed) 
      {
      save("sketch.png");
      
      g.save("g.png");
      
      PImage pg = g.copy();
      pg.updatePixels();
      pg.save("test.png");
      println(pg.pixels.length);
      }

Worked here.

You may just have to add this to your code:

img.updatePixels();

:)

1 Like

I do it by keeping the type as PGraphics and passing that reference into image() and calling .save() on it. Alternatively, in getPhenotype(), also createImage() and fill it with canvas.get() and return that instead of the PGraphics.

OpenGL stores its images on the GPU rather than in system memory and Processing doesn’t always seem to know when it needs to copy the pixels over. Copying the pixels from video memory to system memory can be slow so they try not to do it unless it’s needed.

Also, you probably don’t want to trigger the save by reading a state variable in draw() since it will likely trigger many frames in a row saving the image over and over again. Safer to trigger it from a mousePressed() or keyPressed() callback.

1 Like

Thanks for the quick response :slight_smile:
That solution isn’t working for me though…

void draw() {
  background(0);

  if (mouseX < 1 || mouseY < 1) return;
  
  PImage img = getPhenotype(mouseX, mouseY, shader);
  image(img, 0, 0);

  if (mousePressed) {
    img.save("canvas.png");

    PImage toExport = img.copy();
    toExport.updatePixels();
    toExport.save("img.png");
  }
}

This is what you meant right? Still exporting an empty file on my end

Thanks for the help :slight_smile:
The solutions don’t seem to be working on my end though

Using PGraphics object still exports an empty PNG, had even tried it already before creating the post

void draw() {
  background(0);

  if (mouseX < 1 || mouseY < 1) return;
  
  PGraphics canvas = getPhenotype(mouseX, mouseY, shader);
  image(canvas, 0, 0);
  
  if (mousePressed) {
    canvas.save("canvas.png");
  }
}

Using canvas.get() stops displaying the image altogether

PImage getPhenotype(float _w, float _h, PShader _shader) {
  int w = floor(_w);
  int h = floor(_h);

  PGraphics canvas = createGraphics(w, h, P2D);

  canvas.beginDraw();

  canvas.shader(_shader);

  canvas.rect(0, 0, canvas.width, canvas.height);

  canvas.endDraw();
  
  PImage toReturn = canvas.get(); // meant this right?

  return toReturn;
}

Thanks for the tip! But this was just a quick sketch to replicate the problem I’m getting on a larger project. Doing decent buttons on there

The code below works for me if we just leave PImage out of it.

On the other hand, you most certainly don’t want to be creating a new PGraphics every frame. Every createGraphics() is allocating a new OpenGL frame buffer object and I don’t know how efficiently Processing / JOGL / your video driver is at cleaning them up.

PShader shader;
boolean bSaveImage = false;

void setup() {
  size(1080, 500, P2D);
  shader = new PShader (this, vertSrc, fragSrc);
}

void draw() {
  background(0);

  if (mouseX > 0 && mouseY > 0) {
    PGraphics img = getPhenotype(mouseX, mouseY, shader);
    image(img, 0, 0);
    if( bSaveImage ) {
      img.save("img.png");
      bSaveImage = false;
    }
  }
}

void mousePressed() {
  bSaveImage = true;
}

PGraphics getPhenotype(float _w, float _h, PShader _shader) {
  int w = floor(_w);
  int h = floor(_h);

  PGraphics canvas = createGraphics(w, h, P2D);

  canvas.beginDraw();
  canvas.shader(_shader);
  canvas.rect(0, 0, canvas.width, canvas.height);
  canvas.endDraw();

  return canvas;
}

String[] vertSrc = {"""
#version 330 core
uniform mat4 modelview;
uniform mat4 projection;
in vec4 position;
in vec4 texCoord;
out vec2 uv;
void main() {
  uv = texCoord.xy;
  gl_Position = projection * modelview * position;
}
"""};

String[] fragSrc = {"""
#version 330 core
in vec2 uv;
out vec4 fragColor;
void main() {
  fragColor = vec4( length(uv-0.5) < 0.25 ? vec2(0.) : uv, 0.0, 1.0 );
}
"""};

Hello @TellAJoke,

I am getting different results today!
I did get an empty file yesterday; maybe that is a message to not code on weekends!

I tested your code with mousePressed in draw() and as an event.
Working this morning.

Use one or the other:

PImage img;

void draw() {
  background(0);

  if (mouseX < 1 || mouseY < 1) return;
  
  img = getPhenotype(mouseX, mouseY, shader);
  image(img, 0, 0);

  // This makes multiple saves
  //if (mousePressed) {
  //  img.save("canvas.png");

  //  PImage toExport = img.copy();
  //  //toExport.updatePixels();  Did not need this!
  //  toExport.save("img.png");
  //  println(frameCount);  //Monitor saves in console
  //}
}

// Saves once at end of draw()
void mousePressed()
  {
  img.save("canvas.png");

  PImage toExport = img.copy();
  //toExport.updatePixels();  Did not need this!
  toExport.save("img.png");
  println(frameCount);  //Monitor saves in console
  }

The println() statements were useful for debugging.

I was able to monitor captures in real-time viewing the sketch window:

:)