ScreenshotAppState for Android

OK, the patch was committed to SVN, although with a few changes. I don’t like the code duplication of ScreenshotAppState so instead I made it platform independent by implementing platform specific image writing code in the JmeSystem class.



Is anything else needed?

I think you need to move the ScreenshotAppState file from Desktop to Core or Android won’t see it, right? Also, I would add a renderer.setViewPort(0,0,width, height) before calling readFrameBuffer to ensure that the entire screen is captured. If the last postView ViewPort is smaller than full screen, the image won’t capture correctly.

@Momoko_Fan and others:



I corrected a few things with the latest ScreenshotAppState to make it usable for both Desktop and Android.



1.) I moved the file from Desktop to Core so that Android can see the file

2.) I added a “takeScreenshot” method so that you don’t have to have a dedicated input trigger, but rather an “on demand” method to take the image (obviously, SysRq is not available as a trigger for Android)

[java]

public void takeScreenshot() {

capture = true;

}

[/java]

3.) I added a call to setViewPort before the readFrameBuffer call to force the viewport to match the desired image size (full OpenGL screensize)

[java]renderer.setViewPort(0, 0, width, height);[/java]

4.) I added a call to JmeSystem to get the storage folder. Without it, Android fails due to permissions since the directory structure is different.

[java]File file = new File(JmeSystem.getStorageFolder() + File.separator + appName + shotIndex + “.png”).getAbsoluteFile();[/java]



Side Effects: The call to JmeSystem.getStorageFolder is necessary to make the storage location platform dependent, but it changes the location of where the image is stored for the Desktop version. Previously, the image on Desktop was stored in the app directory. Now, the image is stored in the user .jme3 directory.



I left the package as com.jme3.app.state even though other app states provided by the engine are located in com.jme3.app. I think it makes more sense to put it into the com.jme3.app directory with the other provided app states, but didn’t want to break the imports people already have defined if it wasn’t necessary.



With these changes, there is one app state for all platforms. I’ll wait until tomorrow sometime to commit the changes in case anyone has any issues with these changes. Right now, it is only usable for Desktop.



Below is the full file (patch file wasn’t available since I moved the file :frowning: )

[java]

package com.jme3.app.state;



import com.jme3.app.Application;

import com.jme3.input.InputManager;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.ActionListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.post.SceneProcessor;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.Renderer;

import com.jme3.renderer.ViewPort;

import com.jme3.renderer.queue.RenderQueue;

import com.jme3.system.JmeSystem;

import com.jme3.texture.FrameBuffer;

import com.jme3.util.BufferUtils;

import java.io.File;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.OutputStream;

import java.nio.ByteBuffer;

import java.util.List;

import java.util.logging.Level;

import java.util.logging.Logger;



public class ScreenshotAppState extends AbstractAppState implements ActionListener, SceneProcessor {



private static final Logger logger = Logger.getLogger(ScreenshotAppState.class.getName());

private boolean capture = false;

private Renderer renderer;

private ByteBuffer outBuf;

private String appName;

private int shotIndex = 0;

private int width, height;



@Override

public void initialize(AppStateManager stateManager, Application app) {

if (!super.isInitialized()){

InputManager inputManager = app.getInputManager();

inputManager.addMapping(“ScreenShot”, new KeyTrigger(KeyInput.KEY_SYSRQ));

inputManager.addListener(this, “ScreenShot”);



List<ViewPort> vps = app.getRenderManager().getPostViews();

ViewPort last = vps.get(vps.size()-1);

last.addProcessor(this);



appName = app.getClass().getSimpleName();

}



super.initialize(stateManager, app);

}



public void onAction(String name, boolean value, float tpf) {

if (value){

capture = true;

}

}



public void takeScreenshot() {

capture = true;

}



public void initialize(RenderManager rm, ViewPort vp) {

renderer = rm.getRenderer();

reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());

}



@Override

public boolean isInitialized() {

return super.isInitialized() && renderer != null;

}



public void reshape(ViewPort vp, int w, int h) {

outBuf = BufferUtils.createByteBuffer(w * h * 4);

width = w;

height = h;

}



public void preFrame(float tpf) {

}



public void postQueue(RenderQueue rq) {

}



public void postFrame(FrameBuffer out) {

if (capture){

capture = false;

shotIndex++;



renderer.setViewPort(0, 0, width, height);

renderer.readFrameBuffer(out, outBuf);



File file = new File(JmeSystem.getStorageFolder() + File.separator + appName + shotIndex + “.png”).getAbsoluteFile();

logger.log(Level.INFO, “Saving ScreenShot to: {0}”, file.getAbsolutePath());



OutputStream outStream = null;

try {

outStream = new FileOutputStream(file);

JmeSystem.writeImageFile(outStream, “png”, outBuf, width, height);

} catch (IOException ex) {

logger.log(Level.SEVERE, “Error while saving screenshot”, ex);

} finally {

if (outStream != null){

try {

outStream.close();

} catch (IOException ex) {

logger.log(Level.SEVERE, “Error while saving screenshot”, ex);

}

}

}

}

}

}

[/java]

@Momoko_Fan @normen

If you don’t have any issues with my changes, I’ll commit the changes. The only issue I can see is the change of the storage location for the screenshots for Desktop (was the app directory, now uses JmeSystem.getStorageFolder).



Let me know if you have any concerns with the changes.

I committed the changes. I also added constructors and a method for desktop users to set the file path if they don’t want to use the system default storage folder.