NullPointerException when calling save() after surface.setSize() in P2D

Hi all,

I’m getting a NullPointerException when trying to use save() in conjunction with surface.setSize() and the P2D renderer. Here’s a simplified version of my code:

void settings() {
  size(768, 512, P2D);
}

void setup() {
  background(0);
  fill(0);
  stroke(255);
  ellipse(256, 256, 128, 128);
}

void draw() {
  // save("before.png");
  surface.setSize(512, 512);
  save("after.png");
  exit();
}

Using the default renderer seems to work just fine. If I comment out the “, P2D” in the call to size() inside settings(), I get a file called “after.png” with a nicely centered circle; and if I also uncomment the call to save(“before.png”), I get another file with a correctly off-center circle.

Using the P2D renderer, the call to save(“after.png”) gives the following errors:

java.lang.NullPointerException
at java.awt.image.BufferedImage.setRGB(BufferedImage.java:1058)
at processing.core.PImage.saveImageIO(PImage.java:3229)
at processing.core.PImage.save(PImage.java:3406)
at processing.core.PGraphics.save(PGraphics.java:8399)
at processing.opengl.PGraphicsOpenGL.saveImpl(PGraphicsOpenGL.java:762)
at processing.opengl.PGraphicsOpenGL.save(PGraphicsOpenGL.java:5573)
at processing.core.PApplet.save(PApplet.java:3933)
at e45.draw(e45.java:31)
at processing.core.PApplet.handleDraw(PApplet.java:2475)
at processing.opengl.PSurfaceJOGL$DrawListener.display(PSurfaceJOGL.java:866)
at jogamp.opengl.GLDrawableHelper.displayImpl(GLDrawableHelper.java:692)
at jogamp.opengl.GLDrawableHelper.display(GLDrawableHelper.java:674)
at jogamp.opengl.GLAutoDrawableBase$2.run(GLAutoDrawableBase.java:443)
at jogamp.opengl.GLDrawableHelper.invokeGLImpl(GLDrawableHelper.java:1293)
at jogamp.opengl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:1147)
at com.jogamp.newt.opengl.GLWindow.display(GLWindow.java:759)
at com.jogamp.opengl.util.AWTAnimatorImpl.display(AWTAnimatorImpl.java:81)
at com.jogamp.opengl.util.AnimatorBase.display(AnimatorBase.java:452)
at com.jogamp.opengl.util.FPSAnimator$MainTask.run(FPSAnimator.java:178)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
Error while saving image.
java.io.IOException: image save failed.
at processing.core.PImage.saveImageIO(PImage.java:3275)
at processing.core.PImage.save(PImage.java:3406)
at processing.core.PGraphics.save(PGraphics.java:8399)
at processing.opengl.PGraphicsOpenGL.saveImpl(PGraphicsOpenGL.java:762)
at processing.opengl.PGraphicsOpenGL.save(PGraphicsOpenGL.java:5573)
at processing.core.PApplet.save(PApplet.java:3933)
at e45.draw(e45.java:31)
at processing.core.PApplet.handleDraw(PApplet.java:2475)
at processing.opengl.PSurfaceJOGL$DrawListener.display(PSurfaceJOGL.java:866)
at jogamp.opengl.GLDrawableHelper.displayImpl(GLDrawableHelper.java:692)
at jogamp.opengl.GLDrawableHelper.display(GLDrawableHelper.java:674)
at jogamp.opengl.GLAutoDrawableBase$2.run(GLAutoDrawableBase.java:443)
at jogamp.opengl.GLDrawableHelper.invokeGLImpl(GLDrawableHelper.java:1293)
at jogamp.opengl.GLDrawableHelper.invokeGL(GLDrawableHelper.java:1147)
at com.jogamp.newt.opengl.GLWindow.display(GLWindow.java:759)
at com.jogamp.opengl.util.AWTAnimatorImpl.display(AWTAnimatorImpl.java:81)
at com.jogamp.opengl.util.AnimatorBase.display(AnimatorBase.java:452)
at com.jogamp.opengl.util.FPSAnimator$MainTask.run(FPSAnimator.java:178)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)

Interestingly enough, if I uncomment the call to save(“before.png”) while still using the P2D renderer, it creates a correct-looking “before.png”, and it doesn’t crash when saving “after.png”, but the resulting file looks like it has been drawn on an incorrectly-sized window:

I’m using Processing 3.5.3 with MacOS 10.13.6.
Please let me know if there’s other data I can gather to help diagnose the problem.

1 Like

I confirm I can reproduce this issue when using Windows 10.

Quick notes:

  • If you remove exit() when using the P2D, it works but you get the last picture that you show above which seems to be a bug.
  • If you remove both resize and exit, it works as intended

My advise: Don’t call exit or resize the way you do.

Second advise: Create a github issue in the Processing repo to report this problem. I would say it is a bug and it should be documented and hopefully somebody will address it.

Kf

1 Like

Glad to hear it gives you similar results… On my system, when I remove the call to exit(), the draw() loop just gives me the same NPE over and over, so that doesn’t seem to help. And I raised the issue because I need to call resize.

So I’ve filed a bug (#5840) in the Processing repo, and now I’ll hope someone can take a look at it. Thanks for the advice.

1 Like

This is a bug for the P2D renderer. However, fixing this does not mean you should use it that way. I wouldn’t. I mean, you are resizing your main canvas twice per drawing cycle. This is like saying your display hardware is able to chance its display settings 60 times per second.

If you explain your use case, somebody in the community could share alternative options.

Thank you for opening the bug.

Kf

The real code that I’m working on is a series of drawing exercises, where I have one simple API and I’m coming up with many implementations of it. The program that hit this bug is a helper for those exercises that lets me select four nice-looking renderings of each implementation and then save the results to a file. I do this by creating a rectangular window, with space for four selections in the left/center and the current candidate on the right, and use key presses to make the four selections. Once I’m happy with them, I press another key; this resizes the window so it just has enough space for the selected drawings, calls save(), and exits.

In short, my actual program only resizes once, from a keyPressed() callback, and it does so immediately before exiting. If there are better ways of doing it, I’d be happy to hear them, but this didn’t seem too bad to me.

You could use states. Based on the state, draw() performs an action.

  • State 1: draw on a sketch with size A x B
  • State 2: resize sketch and draw on sketch with size C x D
  • State 3: exit

Kf

1 Like

Processing.org/reference/saveFrame_.html

Are you resizing the window to make it big enough for the selections, or small enough to crop them? … In either case, you don’t need to do this. You can create a PGraphics of whatever size you want, draw onto that, and save it – no screen resizing needed.

Or create a PImage and copy part of the canvas onto that – assuming your canvas was big enough and you have already drawn onto the canvas – then save the PImage. You can do that with:

PImage myCrop = get(x, y, w, h);
myCrop.save("myCrop.jpg");
1 Like

@jeremydouglass – I’ve done some drawing onto PGraphics. That’s not my favorite approach because (as far as I can tell) I can’t set the pixel density of a PGraphics, so it doesn’t render as crisply as the default graphics context on my Retina MacBook. If there is a way to do this, I’d love to hear it.

But the idea of using get() + save() is certainly simpler than what I’ve been doing, and seems to work great. Thanks for the suggestion!

Glad to hear!

I’m not sure I understand – can you explain what you mean by that? Are you trying to set it separately from the main canvas? This works fine on my Retina MacBook:

PGraphics pg;

void setup() {
  pixelDensity(2);                // set density
  println(g.pixelDensity);        // check canvas density -- 2
  pg = createGraphics(100, 100);  // create graphics
  println(pg.pixelDensity);       // check graphics density -- 2
  pg.beginDraw();
  pg.ellipse(75, 50, 20, 20);     // draw graphics circle
  pg.endDraw();
}

void draw() {
  background(192);
  ellipse(25, 50, 20, 20);        // draw circle
  image(pg, 0, 0);                // render graphics circle -- same
}

PixelDensityOnPGraphics

Thanks for asking. This got me to look more closely at the program I’d left tagged as “not getting proper pixel density”, and showed me that it was something totally different.

Here are three circles that seemed like they should be identical, but aren’t:

29%20PM

and here’s the code that generated them:

void setup() {
  background(0);
}

void draw() {
}

void mousePressed() {
  // Create a mask.
  PGraphics mask = createGraphics(20, 20);
  mask.beginDraw();
  mask.background(255);
  mask.endDraw();

  // Draw onto the default PGraphics.
  noStroke();
  fill(255);
  ellipse(20, 50, 20, 20);

  // Draw onto a new PGraphics.
  PGraphics c1 = createGraphics(20, 20);
  c1.beginDraw();
  c1.background(0);
  c1.noStroke();
  c1.fill(255);
  c1.ellipse(10, 10, 20, 20);
  c1.mask(mask);
  c1.endDraw();
  image(c1, 40, 40);

  // Draw onto a new PGraphics.
  PGraphics c2 = createGraphics(20, 20);
  c2.beginDraw();
  c2.background(0, 0);  // transparent
  c2.noStroke();
  c2.fill(255);
  c2.ellipse(10, 10, 20, 20);
  c2.mask(mask);
  c2.endDraw();
  image(c2, 70, 40);
}

As you can see, pixel density doesn’t even come up. The difference is that the blurry circle was drawn using a transparent background, and when I applied a mask to it (which seems like it should have been a no-op, since the mask was all white), the edges lost any semblance of anti-aliasing.

This is pretty far afield from my initial question, obviously. I’ve found better ways of doing this since I wrote it, but I’m still curious about a couple things:

  • Why does the transparent background lead to a badly masked image?
  • Why does the left circle (drawn plainly using ellipse()) also turn into the bad octagon-thing if I call it in draw(), as opposed to just drawing it once in mousePressed()?

Grateful for any ideas.

I’m guessing here (because you didn’t share the code that caused this), but my guess is that you put background() in setup, then you put ellipse in draw and let it run. Don’t do that if you want antialiased edges – if you draw transparent pixels over and over and over again, after a few dozen frames they white-out. Instead, put background in draw if you intend to composite antialiased shapes.

You can set your frameRate low to watch it happen over ~30 seconds.

void setup(){
  background(0);
  noStroke();
  frameRate(4);
}
void draw(){
  ellipse(50, 50, 20, 20);
}

Or better you can just draw the same ellipse many times manually to see what happens over time:

void setup(){
  size(200, 100);
  background(0);
  noStroke();
  noLoop();
}
void draw(){
  ellipse(20, 50, 20, 20);
  for(int i=0; i<4; i++) ellipse(50, 50, 20, 20);
  for(int i=0; i<20; i++) ellipse(80, 50, 20, 20);
  for(int i=0; i<120; i++) ellipse(110, 50, 20, 20);
}

I’m guessing here (because you didn’t share the code that caused this), but my guess is that you put background() in setup, then you put ellipse in draw and let it run.

Yep, that’s exactly what happened.

Don’t do that if you want antialiased edges – if you draw transparent pixels over and over and over again, after a few dozen frames they white-out. Instead, put background in draw if you intend to composite antialiased shapes.

Oh, sure! Seeing it explained that way, it makes perfect sense. Thanks.

Any idea why applying a mask to the image with a transparent background led to a similar effect?

I haven’t tested or looked at that code carefully, but guesses at some starting points to check for when alpha is disappearing or weirdly flattened –

  1. repeat drawing without clearing with background (as above)
  2. compositing multiple frame draws, e.g. keyPressed vs keyReleased
  3. drawing onto and copying off of the main canvas (which doesn’t support alpha)
  4. using an RGB PImage rather than ARGB
  5. blendMode isn’t set correctly