Exporting image as PNG

I have some problems with procedurally generated textures, so I decided to export them as PNG and see what is wrong. I googled, and soon enough found a way to do it

Sadly, this spits errors when I try to do it:

	at java.nio.DirectIntBufferU.get(DirectIntBufferU.java:271)
	at java.nio.IntBuffer.get(IntBuffer.java:715)
	at com.jme3.util.Screenshots.convertScreenShot2(Screenshots.java:50)
	at com.jme3.system.JmeDesktopSystem.writeImageFile(JmeDesktopSystem.java:94)
	at com.jme3.system.JmeSystem.writeImageFile(JmeSystem.java:137)
	at mytest.TextureManagerTest.savePng(TextureManagerTest.java:53)
	at mytest.TextureManagerTest.simpleInitApp(TextureManagerTest.java:42)
	at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:220)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.initInThread(LwjglAbstractDisplay.java:130)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:211)
	at java.lang.Thread.run(Thread.java:745)```

This error appears both with my real generated texture and 256x256px test image, loaded from PNG file. Both of them have alpha channels, could that break stuff? I'm running with 3.1.0 (stable) on desktop+LWJGL 2.

Or you could look and see how the ScreenShotAppState does it.

Binary exporter is definitely not the right way to make a PNG file.

Well, I just used what you suggested in the thread I linkedā€¦ As youā€™re core developer I thought it would be ā€˜correctā€™ way to do it.

Anyway, I checked ScreenshotAppState, which did sadly produce quite similar error:

	at java.nio.DirectIntBufferU.get(DirectIntBufferU.java:271)
	at java.nio.IntBuffer.get(IntBuffer.java:715)
	at com.jme3.util.Screenshots.convertScreenShot2(Screenshots.java:50)
	at com.jme3.system.JmeDesktopSystem.writeImageFile(JmeDesktopSystem.java:94)
	at com.jme3.system.JmeSystem.writeImageFile(JmeSystem.java:137)
	at mytest.TextureManagerTest.writeImageFile(TextureManagerTest.java:63)
	at mytest.TextureManagerTest.simpleInitApp(TextureManagerTest.java:43)
	at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:220)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.initInThread(LwjglAbstractDisplay.java:130)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:211)
	at java.lang.Thread.run(Thread.java:745)```

I basically copied writeImageFile (as it is protected), with only little changes, so this way doesn't seem to be any different in practise. As you can see, both are essentially doing the same thing, while the latter might be *cleaner* code.

I checked that my 256x256px RGBA image buffer indeed has correct amount (256Ā² * 4) bytes, so that shouldn't be the issue either.

I also had many problems with saving images, thatā€™s why I did my own utilities. If you want you can try them (If so, let me know if it works. It does for me :stuck_out_tongue: )

4 Likes

@NemesisMate Thanks a lot, works perfectly with both images!

Well, Iā€™m glad you got it working but I use the code from screen shot app state to save images all the time without issue.

In the future, if you need help debugging your code it will save a lot of time if you show us the code.

I encountered ā€œthe sameā€ java.nio.BufferUnderflowException. Actually @NemesisMate already provided some solution, but I thought its worth to provide a test case to get this (or some other reasonable) fix into core. This testcase:

package jme3test.texture;

import com.jme3.app.SimpleApplication;
import com.jme3.light.PointLight;
import com.jme3.scene.Spatial;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class TestSaveNormalTexture extends SimpleApplication {

    float angle;
    PointLight pl;
    Spatial lightMdl;

    public static void main(String[] args) {
        TestSaveNormalTexture app = new TestSaveNormalTexture();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        Texture t = assetManager.loadTexture("Textures/BumpMapTest/Simple_normal.png");
        try {
            savePng(File.createTempFile("test", ".png"), t.getImage());
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        
    }
    
    public static void savePng(File f, Image img) throws IOException {
        f.createNewFile();
        try (OutputStream out = new FileOutputStream(f)) {
            JmeSystem.writeImageFile(out, "png", img.getData(0), img.getWidth(), img.getHeight());
        }
    }
}

causes this exception stack trace:

SEVERE: Uncaught exception thrown in Thread[jME3 Main,6,main]
java.nio.BufferUnderflowException
        at java.nio.DirectIntBufferU.get(DirectIntBufferU.java:271)
        at java.nio.IntBuffer.get(IntBuffer.java:715)
        at com.jme3.util.Screenshots.convertScreenShot2(Screenshots.java:50)
        at com.jme3.system.JmeDesktopSystem.writeImageFile(JmeDesktopSystem.java:94)
        at com.jme3.system.JmeSystem.writeImageFile(JmeSystem.java:137)
        at jme3test.texture.TestSaveNormalTexture.savePng(TestSaveNormalTexture.java:39)
        at jme3test.texture.TestSaveNormalTexture.simpleInitApp(TestSaveNormalTexture.java:29)
        at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:220)
        at com.jme3.system.lwjgl.LwjglAbstractDisplay.initInThread(LwjglAbstractDisplay.java:130)
        at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:211)
        at java.lang.Thread.run(Thread.java:745)

This happens for all textures that have a format that uses != 32 bits per pixel - so in this case the normal map from the jme3 testdata ā€œTextures/BumpMapTest/Simple_normal.pngā€ has only 24 which causes the bufferconversion to fail with this exception. This might also be the reason why it never bothered anyone because its mainly used for saving screenshots i guess.

The above mentioned fix uses different conversions for these two cases:

public static BufferedImage getWriteImageFrom(Image image) {
//        return getARGBImageFrom(image);
        int bpp = image.getFormat().getBitsPerPixel();
        if(bpp == 32) {
            return getARGB8ImageFrom(image);
        } else if(bpp == 24) {
            return getRGB8ImageFrom(image);
        }

        throw new IllegalArgumentException();
    }

But i donā€™t know enough details to see if this is a reasonable fix for ā€œallā€ cases.

Would be appreciated if this could be fixed in the core;)

1 Like

Yeah thatā€™s basically the reason. That method is a backdoor to get platform-independent screenshot export functionality working, itā€™s not really intended for general use.

Thereā€™s a note that the image has to be in the RGBA8 format:

Perhaps a better approach would have been to fallback to ImageRaster, which allows reading color data from any jME image, in case a format other than RGBA8 is used. You can implement a PR for that if you like :slight_smile:

1 Like