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:
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 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.
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.
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.
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.
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");
@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!
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
}
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:
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()?
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.
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?