# JME - Rendering in BufferedImage



## Zapp_Branigan (7. Jun 2008)

Hallo ich hoffe hier ist jemand der sich mit der jmonkeyengine auskennt - im ofiziellen englischen Forum wurde mir leider nicht so richtig weiter geholfen...

Mit Mühe habe ich es geschaft eine Landschaft so auf den Bildschirm zu rendern wie ich es für mein kleines Projekt wollte. Problem: Eigentlich will ich die Landschaft nicht auf meinem Bildschirm sondern möglichst in einem BufferedImage das ich dann weiter verarbeiten kann...

Ich denke ich habe in den tiefen der JME-Wiki auch schon einen =offscreenrenderer] Ansatz gefunden aber den bekomme ich nicht so recht zum laufen und spätestens hier:


```
ImageData imgData = new ImageData(width, height, 32, new PaletteData(0xFF0000, 0x00FF00, 0x0000FF));
for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        imgData.setPixel(x, y, buffer.get((height - y - 1) * width + x));
    }
}
org.eclipse.swt.graphics.Image image = new Image (org.eclipse.swt.widgets.Display.getCurrent(), imgData);
```

Verstehe ich nicht mehr was das soll... ich Programmiere in Netbeans und nicht in Eclipse und es kommt sowiso kein BufferedImage raus...

Ich hoffe wirklich wirklich sehr das hier irgendjemand ist der sich die Mühe macht mir ein bisschen zu helfen!


----------



## EgonOlsen (7. Jun 2008)

JME nutzt doch auch LWJGL, oder? Dann kannst du mit einem direkten GL-Aufruf arbeiten. Ich habe jetzt keine Zeit, das komplett zu bauen, aber du erzeugst dir ein BufferedImage in der gewünschte n Größe, lässt dir davon die Pixel als int[] geben:


```
BufferedImage output = new BufferedImage(x, y, BufferedImage.TYPE_INT_ARGB);
int[] pixels = ((DataBufferInt) (output.getRaster().getDataBuffer()).getData();
```

Dann den aktuellen Bildschirminhalt über GL holen:


```
IntBuffer pixelBuf = ByteBuffer.allocateDirect((x*y)<<2).order(ByteOrder.nativeOrder()).asIntBuffer();
GL11.glReadPixels(0, 0, x, y, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, pixelBuf);
```

Dann vom Buffer in das BI kopieren:


```
for (int i = 0; i < pixels.length; i++) {
	pixels[i] = pixelBuf.get(i);
}
```

Evtl. musst du das BI noch irgendwie spiegeln oder so...musst du mal gucken. Aber irgendwie so sollte es gehen, sofern JME nicht selber eine entsprechende Funktionalität mitbringt. Weiß ich aber nicht, ich benutze das logischerweise nicht.


----------



## Illuvatar (7. Jun 2008)

Der Codeausschnitt, mit dem du Probleme hast, ist auch in SWT geschrieben. Für ein BufferedImage probier mal:


```
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 
for (int x = 0; x < width; x++) { 
    for (int y = 0; y < height; y++) { 
        img.setRGB (x, y, buffer.get((height - y - 1) * width + x)); 
    } 
}
```


----------



## Zapp_Branigan (9. Jun 2008)

Super!!! Dank eurer Hilfe bin ich jetzt ein ganzes Stück weiter gekommen.
Danke.


----------



## Zapp_Branigan (10. Jun 2008)

Es hat sich leider doch noch ein Problem ergeben und ich finde den Fehler nicht :-(
Die Landschaft wird auf dem Bildschirm richtig angezeigt, aber die exportierte Landschaft bleibt schwarz.
Ich vermute es liegt daran das das "Bild" nicht fertig erzeugt ist bevor das Bild gemacht wird. Es kann aber genauso gut ein Fehler in der Programmierung sein.
Ich habe echt keine Ahnung wie ich das Lösen kann :-( daher hoffe ich sehr ihr könnt mir wieder helfen.


Hier ist mein Code - ich weiß er ist bestimmt ganz fürchterlich geschrieben, aber ich kanns nocht nicht besser. 

```
import com.jme.app.SimpleGame;
import com.jme.renderer.ColorRGBA;
import com.jmex.terrain.TerrainBlock;
import com.jmex.terrain.util.ImageBasedHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jme.math.Vector3f;
import com.jme.bounding.BoundingBox;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
import com.jme.image.Texture;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;

public class Engine extends SimpleGame  {

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

        cam.setParallelProjection(true);
        float aspect = (float) display.getWidth() / display.getHeight();
        cam.setFrustum( -100, 1000, -50 * aspect, 50 * aspect, -50, 50 );
        cam.setLocation(new Vector3f(0, 0,0)); 
        cam.setDirection(new Vector3f(-1, -1, -1));
        cam.update();
  
        display.getRenderer().setBackgroundColor(ColorRGBA.white);
      
        complexTerrain();
        
        
        saveImageFile();
        //System.exit(0);
    
}
    
    public void saveImageFile()
            {
        
        int width = 1024; int height = 768;
       
        
        IntBuffer pixelBuf =    ByteBuffer.allocateDirect((width*height)<<2).order(ByteOrder.nativeOrder()).asIntBuffer(); 
        GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, pixelBuf);


        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 
        for (int x = 0; x < width; x++) { 
        for (int y = 0; y < height; y++) { 
        img.setRGB (x, y, pixelBuf.get((height - y - 1) * width + x)); 
            } 
        } 
   
        
        
         
        System.out.println(img.toString());
             String filename = "map//MapPattern_Test.jpg";

	 try
             {
                System.out.println("Schreibe Pattern Datei: "+filename);
                OutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
		ImageIO.write(img, "jpeg", out);
             }
	 catch(IOException e)
	    {
		System.out.println("Fehler beim schreiben der Pattern-Datei: "+filename);
	    }
    }

    /**
     * 
     * Läd ein Bild (jpeg) als Buffered Image und konvertiert es anschließend in Byte Code
     * @param Path
     * @return
     */
    byte[] loadImageFile(String Path)
    {
        
        String formatName = "jpeg"; //gif...
        
        File imageFile = new File (Path);
        BufferedImage image = null;
         
			try {
                            System.out.println("Wird eingelesen...");
		    	image = ImageIO.read(imageFile);
				}
                        
                        catch (IOException e) {
                            System.out.println("Fehler beim Einlesen der Datei \""+Path+"\"");
				}

        System.out.println("File \""+Path+"\" eingelesen");
                                
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        
        BufferedImage smallImage = image.getSubimage(0, 0, 50, 50);
    //    BufferedImage smallImage = image;
        
        try
        {
            ImageIO.write(smallImage, formatName, baos);
        } 
        catch (IOException ex)
        {
            Logger.getLogger(Engine.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Fehler beim Konvertieren der Bilddatei!");
        }
       
        byte[] imageData = baos.toByteArray();
        
        return imageData;
    }
    
    private void complexTerrain() {
        // This grayscale image will be our terrain
       
        // Create an image height map based on the gray scale of our image.
        ImageBasedHeightMap ib = new ImageBasedHeightMap(
              
                  
              
                 new ImageIcon(loadImageFile("TestLandscape.jpg")).getImage()
                  
                  );
        
        // Create a terrain block from the image's grey scale
        TerrainBlock tb = new TerrainBlock("image icon", ib.getSize(),
                new Vector3f(.5f, .05f, .5f), ib.getHeightMap(),
                
                new Vector3f(0, 0, 0), false);
        // Create an object to generate textured terrain from the
        // image based height map.
        ProceduralTextureGenerator pg = new ProceduralTextureGenerator(ib);
        // Look like water from height 0-60 with the strongest
        // "water look" at 30
        pg.addTexture(new ImageIcon(loadImageFile("textures"+File.separator+"textures"+File.separator+"wl_wasser_01.jpg")), 0, 30, 60);
        // Look like dirt from height 40-120 with the strongest
        // "dirt look" at 80
        pg.addTexture(new ImageIcon(loadImageFile("textures"+File.separator+"textures"+File.separator+"wl_wiese1_00.jpg")), 40, 80, 120); // Look like highest (pure white) from height 110-256
        // with the strongest "white look" at 130

        pg.addTexture(new ImageIcon(loadImageFile("textures"+File.separator+"textures"+File.separator+"wl_wiese4_00.jpg")), 110, 130, 256);
        // Tell pg to create a texture from the ImageIcon's it has recieved.
        pg.createTexture(256);
        TextureState ts = display.getRenderer().createTextureState();
        
        
        // Load the texture and assign it.
        ts.setTexture(
                TextureManager.loadTexture(
                pg.getImageIcon().getImage(),
                Texture.MM_LINEAR_LINEAR,
                Texture.FM_LINEAR,
                true));
        tb.setRenderState(ts);
        // Give the terrain a bounding box
        tb.setModelBound(new BoundingBox());
        tb.updateModelBound();
        // Move the terrain in front of the camera
       // tb.setLocalTranslation(new Vector3f(0, 0, 75));
         tb.setLocalTranslation(new Vector3f(0, 0, 0));
        
        // Attach the terrain to our rootNode.
        rootNode.attachChild(tb);
        
    }

}
```


----------



## Illuvatar (10. Jun 2008)

Meine Vermutung wäre, dass das in der init-Methode noch nicht geht. Probiers mal in update oder render.


----------



## Zapp_Branigan (10. Jun 2008)

Hm verstehe nicht genau was du meinst... cam.update();?


----------



## Zapp_Branigan (10. Jun 2008)

Ich glaube auch irgendwie nicht das was mit der init ist weil es ja richtig angezeigt wird...


----------



## Illuvatar (10. Jun 2008)

Ich kenn mich mit dem SimpleGame nicht so aus weil ich normal StandardGame verwende, aber es gibt doch in jedem Spiel eigentlich immer update und render-Methoden. Die werden für jedes Frame aus dem OpenGL-Thread aufgerufen und können den Scene Graph verändern bzw. zeichnen. In der init-Methode läuft der OGL-Thread doch glaub noch gar nicht, also wird auch noch nichts dargestellt. Wahrscheinlich wäre es das sinnvollste, das Bild am Ende der render-Methode zu machen. Du musst nur eben aufpassen, dass du das Bild nur einmal machst, nicht in jedem Frame.


----------



## Zapp_Branigan (10. Jun 2008)

Ich denke du hast recht. Jemand im JME Forum hat mir 2 Zeilen gesagt die dieses Problem lösen.
Jetzt ist nur wie so oft ein neues Problem da:
So sollte es aussehen:
www.TristanZindler.de/Bild4.png
So sieht es nach der Korrektur aus: 
www.TristanZindler.de/MapPattern_Test.jpg
Es ist irgendwie Invertiert wie kann ich das beheben?


----------



## EgonOlsen (10. Jun 2008)

Probier es mal mit BufferedImage.TYPE_INT_ARGB


----------



## Zapp_Branigan (10. Jun 2008)

leider macht das aus dem schwarzen hintergrund nur einen roten
 ;-) ich habe auch alle anderen Typs mal getestet - hilft nicht.


----------



## EgonOlsen (10. Jun 2008)

So schlecht ist rot doch gar nicht. Vielleicht sind die Farbwerte nur vertauscht. Wie sieht denn die Fläche in dem Fall aus? Wieder komplett weiß?


----------



## Zapp_Branigan (11. Jun 2008)

ne die fläche ist rot der rest ist genauso


----------



## Zapp_Branigan (12. Jun 2008)

habs hinbekommen... ich hatte da grundsätzliches bei JME noch nicht richtig verstanden.


----------

