ImagedBasedHeightMap on Android

Following the Hello Terrain tutorial using the ImagedBasedHeightMap. Works fine on the PC, but I get an exception on my Android, Droid Razr.

Image format: RGB565: java.lang

UnsupportedOperationException

Exception thrown in Thred[GL Thread 32,5,main]:

at com.jme3.terrain.heightmap.

ImageBasedHeightMap.getHeightAtPosition(170)

at com.jme3.terrain.heightmap.

ImageBasedHeightMap.load(135)

at com.jme3.terrain.heightmap.



[ Kill ]





/** 2. Create the height map */

AbstractHeightMap heightmap = null;

Texture heightMapImage = assetManager.loadTexture(

“Textures/Terrain/splat/mountains512.png”);

heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

heightmap.load();



If I use the randomized terrain height map method things work just fine.



Texture heightMapImage = assetManager.loadTexture(

“Textures/Terrain/splat/mountains512.png”);

heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

Has anyone had any success using the ImageBasedHeightMap on Android platform?

No answer as yet, but I’m trying to sort this one out too.

java.lang.UnsupportedOperationException: Image format: RGB565

when using ImageBasedHeightMap.

There is a fix here for now until I get it into core.

I think this is a different issue than the one referenced above. I integrated the changes and still got the error. I think it is due to how Android is importing the image initially.

It looks like the images are a BGR8 format (no alpha) when you run the Hello Terrain tutorial on Desktop, but the only non-alpha config for Android is the RGB_565 config. The Android image loader sets the format for these images to RGB565. ImageBasedHeightMap does not have a case for RGB565.



I think getHeightAtPostion needs to have a case defined to handle RGB565 and all will be well (in addition to the Android image loader creating the buffer).



Desktop Log

[java]

INFO: Loaded material definition: Unshaded

Jul 24, 2012 11:05:08 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Interface/Fonts/Default.png (Flipped)]

Jul 24, 2012 11:05:08 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: ABGR8

Jul 24, 2012 11:05:08 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/alphamap.png (Flipped) (Mipmapped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: BGR8

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/grass.jpg (Flipped) (Mipmapped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: BGR8

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/dirt.jpg (Flipped) (Mipmapped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: BGR8

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/road.jpg (Flipped) (Mipmapped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: BGR8

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/mountains512.png (Flipped) (Mipmapped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: Luminance8

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Interface/Fonts/Console.png (Flipped)]

Jul 24, 2012 11:05:09 PM com.jme3.texture.plugins.AWTLoader load

INFO: Image Config: ABGR8

[/java]





Android LogCat (Preferred Config = Target Bitmap format, Bitmap Config = Config of Bitmap after decodeStream)

[java]

07-24 22:41:23.093 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Interface/Fonts/Default.png (Flipped)]

07-24 22:41:23.101 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Preferred Config: ARGB_8888

07-24 22:41:23.101 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Bitmap Config: ARGB_8888

07-24 22:41:23.148 4298 4317 D dalvikvm: GC_EXTERNAL_ALLOC freed 706K, 43% free 4544K/7943K, external 2613K/2773K, paused 39ms

07-24 22:41:23.289 4298 4317 I com.jme3.scene.Node: INFO Node 10:41:23 PM Child (BitmapFont) attached to this node (null)

07-24 22:41:23.343 4298 4317 I MaterialDef: INFO MaterialDef 10:41:23 PM Loaded material definition: Terrain

07-24 22:41:23.429 4298 4300 D dalvikvm: GC_CONCURRENT freed 1106K, 38% free 5467K/8711K, external 2869K/3264K, paused 1ms+13ms

07-24 22:41:23.476 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/alphamap.png (Flipped) (Mipmapped)]

07-24 22:41:23.476 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Preferred Config: ARGB_8888

07-24 22:41:23.523 4298 4317 D dalvikvm: GC_EXTERNAL_ALLOC freed 2070K, 53% free 4100K/8711K, external 2869K/3264K, paused 44ms

07-24 22:41:23.687 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Bitmap Config: RGB_565

07-24 22:41:23.726 4298 4317 D dalvikvm: GC_EXTERNAL_ALLOC freed 40K, 54% free 4068K/8711K, external 4917K/6141K, paused 34ms

07-24 22:41:23.796 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/grass.jpg (Flipped) (Mipmapped)]

07-24 22:41:23.796 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Preferred Config: ARGB_8888

07-24 22:41:23.835 4298 4317 D dalvikvm: GC_EXTERNAL_ALLOC freed 587K, 47% free 4628K/8711K, external 6965K/6965K, paused 40ms

07-24 22:41:23.945 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:23 PM Bitmap Config: RGB_565

07-24 22:41:24.015 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/dirt.jpg (Flipped) (Mipmapped)]

07-24 22:41:24.015 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Preferred Config: ARGB_8888

07-24 22:41:24.117 4298 4317 D dalvikvm: GC_EXTERNAL_ALLOC freed 1150K, 47% free 4634K/8711K, external 11061K/11061K, paused 93ms

07-24 22:41:24.179 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Bitmap Config: RGB_565

07-24 22:41:24.210 486 617 D dalvikvm: GC_EXPLICIT freed 1070K, 33% free 10508K/15623K, external 4512K/5634K, paused 156ms

07-24 22:41:24.257 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/road.jpg (Flipped) (Mipmapped)]

07-24 22:41:24.257 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Preferred Config: ARGB_8888

07-24 22:41:24.265 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Bitmap Config: RGB_565

07-24 22:41:24.312 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM AssetInfo: com.jme3.asset.plugins.UrlAssetInfo[key=Textures/Terrain/splat/mountains512.png (Flipped) (Mipmapped)]

07-24 22:41:24.312 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Preferred Config: ARGB_8888

07-24 22:41:24.343 4298 4317 I AndroidImageInfo: INFO AndroidImageInfo 10:41:24 PM Bitmap Config: RGB_565

07-24 22:41:24.390 4298 4317 D dalvikvm: GC_FOR_MALLOC freed 1828K, 44% free 5126K/9095K, external 13173K/13621K, paused 38ms

07-24 22:41:24.390 4298 4317 I dalvikvm-heap: Grow heap (frag case) to 20.951MB for 1048592-byte allocation

07-24 22:41:24.429 4298 4299 D dalvikvm: GC_FOR_MALLOC freed 1047K, 50% free 5103K/10183K, external 13173K/13621K, paused 32ms

07-24 22:41:24.484 4298 4317 W dalvikvm: threadid=9: thread exiting with uncaught exception (group=0x4001e560)

07-24 22:41:24.484 4298 4317 E AndroidHarness: Exception thrown in Thread[GLThread 11,5,main]

07-24 22:41:24.484 4298 4317 E AndroidHarness: java.lang.UnsupportedOperationException: Image format: RGB565

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.terrain.heightmap.ImageBasedHeightMap.getHeightAtPostion(ImageBasedHeightMap.java:170)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.terrain.heightmap.ImageBasedHeightMap.load(ImageBasedHeightMap.java:135)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.terrain.heightmap.ImageBasedHeightMap.load(ImageBasedHeightMap.java:83)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at mygame.Main.simpleInitApp(Main.java:76)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:225)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.app.AndroidHarness.initialize(AndroidHarness.java:446)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.system.android.OGLESContext.initInThread(OGLESContext.java:209)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at com.jme3.system.android.OGLESContext.onSurfaceCreated(OGLESContext.java:180)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1348)

07-24 22:41:24.484 4298 4317 E AndroidHarness: at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1118)

[/java]

2 Likes

How does the heightmap hande the reading of the pixel value at the moment? I’ve been thinking for a while that Image should have a getPixel() method that knows how to read the various formats. That could then be used by custom image painters, by things like heightmaps, etc.

I almost have something working with HelloTerrain. I’ve got the scene to display on the phone, but the getHeightAtPostion calculation I’m doing for RGB565 is not working (scene looks flat instead of hilly).



The basic idea I’ve been looking at requires an Android specific Image class that extends the standard Image class and overrides the getData(index) method. There would be a new boolean in the standard Image class that is set when a buffer is required (like when using ImageBasedHeightMap. Then when getData(index) is called and the efficientData is of type AndroidImageInfo, then the getData(index) method creates the data buffer from the Bitmap image and sets it in the Image class. This way the standard way people are using the data buffer would still work. This also allows the other images in the scene that do not need the buffer woudl not get one created (OGELSShaderRenderer just uses the Bitmap image, not the buffer).



I had to do a image specific setting to create the buffer because running HelloTerrain with every image creating a buffer ran out of memory on my phone.



@zarch If a new Image.getPixel() method is created, then Android would still have to customize it to read the Bitmap image instead of getting it from the data buffer or BufferedImage. However, if that was done, Android would not have to have the full buffer created and would just have to read each pixel on demand. This might save a lot of memory on the phone and people would not have to manage setting the “create buffer” setting on the image. There would just have to be a platform specific class that knew how to interperet the stored image to return the pixel information.



I can try to change what I’m doing to do either one so we can see which way is better. I’m not very knowledgable in this area, but it’s a great way to learn how it works :slight_smile:

Not sure if this is related specifically to the RGB565 format, but I also ran into problems with the getHeightAtPosition() calculation. After looking at it for a long time, I figure that since we are only using grayscale images for the height map, I can simply take the red component as the height, as follows:



[java]

@Override

protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store) {

if (buf != null) {

return super.getHeightAtPostion(buf, image, position, store);

} else {

AndroidImageInfo imageInfo = (AndroidImageInfo) image.getEfficentData();

Bitmap bitmap = imageInfo.getBitmap();

int y = position / bitmap.getWidth();

int x = position - (y * bitmap.getWidth());

int color = bitmap.getPixel(x, y);

float h = Color.red(color) * heightScale;

//System.out.println(h);

return h;

}

}

[/java]



That’s another reason why I have decided to create my own version of ImageBasedHeightMap specifically for android. Check out this thread for details: http://hub.jmonkeyengine.org/groups/android/forum/topic/using-imagebasedheightmap/

I took a stab at @zarch idea to implement a getPixel method in the image class. Android then overrides this method to get the pixel color information directly from the Bitmap (very similar to @batkid example, but in the extended image class instead of the heightmap class).



Here are some pictures of the HelloTerrain tutorial. You’ll see there is some wierdness that exists is some parts of the picture. I haven’t looked at that yet.

https://dl.dropbox.com/u/88805030/0A3BB5F10D01C017_1.png

https://dl.dropbox.com/u/88805030/0A3BB5F10D01C017_2.png

https://dl.dropbox.com/u/88805030/0A3BB5F10D01C017_3.png



I should be able to get some patch files put together tomorrow so people can review the concept.

2 Likes
@iwgeric said:
I took a stab at @zarch idea to implement a getPixel method in the image class. Android then overrides this method to get the pixel color information directly from the Bitmap (very similar to @batkid example, but in the extended image class instead of the heightmap class).


Good one, thanks! I didn't think about overriding the image class instead of the heightmap class. Brilliant!

How are you implementing the getPixel method?



This is my (very rough) concept for how to do it. Essentially the image class delegates to the format enum with each format implementation knowing how to process pixels for that format. For now the more complex formats can just throw a NotYetImplemented exception.



[java]

public class Image extends NativeObject implements Savable /, Cloneable/ {



public enum Format {

/**

  • 8-bit alpha

    */

    Alpha8(8) {

    public float getRed(Image image, int x, int y) {

    return 0;

    }

    public float getGreen(Image image, int x, int y) {

    return 0;

    }

    public float getBlue(Image image, int x, int y) {

    return 0;

    }

    public float getAlpha(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position(image.getWidth()*y+x);

    return data.get() / 255f;

    }

    },



    /**
  • 8-bit red, green, and blue.

    */

    RGB8(24) {

    public float getRed(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position((image.getWidth()*y+x)*4);

    return data.get() / 255f;

    }

    public float getGreen(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position((image.getWidth()*y+x)*4+1);

    return data.get() / 255f;

    }

    public float getBlue(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position((image.getWidth()*y+x)*4+2);

    return data.get() / 255f;

    }

    public float getAlpha(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position((image.getWidth()*y+x)*4+3);

    return data.get() / 255f;

    }

    public ColorRGBA getPixel(Image image, int x, int y) {

    ByteBuffer data = image.getData().get(0);

    data.position((image.getWidth()*y+x)*4);

    return new ColorRGBA(data.get() / 255f, data.get()/255f, data.get()/255f, data.get()/255f);

    }

    },





    // SNIP OTHER ENUMS, each would need its own implementation of the bellow. For now could just

    // throw unsupportedoperation for the more complex cases.



    private int bpp;

    private boolean isDepth;

    private boolean isCompressed;

    private boolean isFloatingPoint;



    private Format(int bpp){

    this.bpp = bpp;

    }



    private Format(int bpp, boolean isFP){

    this(bpp);

    this.isFloatingPoint = isFP;

    }



    private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){

    this(bpp, isFP);

    this.isDepth = isDepth;

    this.isCompressed = isCompressed;

    }



    public abstract float getRed(Image image, int x, int y);

    public abstract float getGreen(Image image, int x, int y);

    public abstract float getBlue(Image image, int x, int y);

    public abstract float getAlpha(Image image, int x, int y);

    // Done in enum so that implementations above can override it if there is a more appropriate implementation

    public ColorRGBA getPixel(Image image, int x, int y) {

    return new ColorRGBA(getRed(x,y), getGreen(x,y), getBlue(x,y), getAlpha(x,y));

    }



    // SNIP



    }



    // SNIPPED VARIABLES



    public float getRed(int x, int y) {

    return format.getRed(this, x,y);

    }

    public float getGreen(int x, int y) {

    return format.getRed(this, x,y);

    }

    public float getBlue(int x, int y) {

    return format.getRed(this, x,y);

    }

    public float getAlpha(int x, int y) {

    return format.getRed(this, x,y);

    }

    public ColorRGBA getPixel(int x, int y) {

    return format.getPixel(this, x,y);

    }





    // SNIPPED OTHER CODE

    }



    [/java]



    Hope that makes sense, I just threw it together in here so there are probably terrible mistakes :slight_smile:

Created a separate thread for the getPixel discussion: http://hub.jmonkeyengine.org/groups/graphics/forum/topic/adding-getpixel-in-image/

@zarch This this what you were thinking when you mentioned overriding the getData method with a weak reference? I’ve never used WeakReference before. I have the Hello Terrain displaying on the phone with these changes. Still need to figure out why my scene is flat, but I’m getting the buffer data created when getData is called and from my debug, it is getting collected when GC runs after the scene is displayed.



From Android Specific Image Class:

[java]

/**

  • <code>getData</code> returns the data for this image. If the data is
  • undefined, null will be returned.

    *
  • @return the data for this image.

    */

    @Override

    public ByteBuffer getData(int index) {

    if (index == 0) {

    if (getEfficentData() instanceof AndroidImageInfo) {

    AndroidImageInfo info = (AndroidImageInfo) getEfficentData();

    return info.getBufferData();

    } else {

    return super.getData(index);

    }

    } else {

    return super.getData(index);

    }

    }

    [/java]



    Addition to AndroidImageInfo class:

    [java]

    private WeakReference<ByteBuffer> weakData = null;

    private int bufferSize = 0; // size is calculated based on image format when Android Bitmap is loaded





    public ByteBuffer getBufferData() {

    logger.log(Level.INFO, "getBufferData called");

    ByteBuffer buffer = null;



    if (weakData == null || weakData.get() == null) {

    logger.log(Level.INFO, "weakData is null");

    if (bitmap != null && bufferSize > 0) {

    logger.log(Level.INFO, "Creating buffer");

    buffer = BufferUtils.createByteBuffer(bufferSize);

    bitmap.copyPixelsToBuffer(buffer);

    buffer.position(0);

    weakData = new WeakReference<ByteBuffer>(buffer);

    } else {

    logger.log(Level.INFO, "bitmap is null");

    }

    } else {

    logger.log(Level.INFO, "getBufferData called, returning existing weakData");

    }



    return weakData.get();

    }



    [/java]

Yes, that looks correct. Essentially doing it that way means it is stored inside the image but is still available for garbage collection if the phone runs low on memory.



This also means that getPixel would work so long as it called getData so it solves both problems. Then the next step would be to look at the heightmap stuff and remove the direct reference to getData and have that call getPixel (or getLuminance would be better) . That way ImageBasedHeightMap automatically supports all formats that support getPixel rather than having it’s own implementation of the code to read the values.

Below is what I would recommend to do to make Hello Terrain work on Android. It also supports @zarch work on getPixel. The image looks fine on the phone when compared to the desktop version. The code still needs to be cleaned up, but it should run. Be patient, it takes a little while for the scene to load completely on the phone :slight_smile:



Please let me know what you think. 3 files modified and 1 new file.



New Android Specific Image Class:

[java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcandroidcomjme3texture

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: AndroidImage.java

— AndroidImage.java Locally New

+++ AndroidImage.java Locally New

@@ -0,0 +1,208 @@

+/*

    • Copyright © 2009-2010 jMonkeyEngine
    • All rights reserved.
  • *
    • Redistribution and use in source and binary forms, with or without
    • modification, are permitted provided that the following conditions are
    • met:
  • *
      • Redistributions of source code must retain the above copyright
    • notice, this list of conditions and the following disclaimer.
  • *
      • Redistributions in binary form must reproduce the above copyright
    • notice, this list of conditions and the following disclaimer in the
    • documentation and/or other materials provided with the distribution.
  • *
      • Neither the name of ‘jMonkeyEngine’ nor the names of its contributors
    • may be used to endorse or promote products derived from this software
    • without specific prior written permission.
  • *
    • THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    • "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    • TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    • PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    • CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    • EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    • PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    • PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    • LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    • NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    • SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  • */

    +

    +package com.jme3.texture;

    +

    +import com.jme3.asset.AndroidImageInfo;

    +import java.nio.ByteBuffer;

    +import java.util.ArrayList;

    +import java.util.List;

    +

    +/**
    • <code>Image</code> defines a data format for a graphical image. The image
    • is defined by a format, a height and width, and the image data. The width and
    • height must be greater than 0. The data is contained in a byte buffer, and
    • should be packed before creation of the image object.
  • *
    • @author Mark Powell
    • @author Joshua Slack
    • @version $Id: Image.java 4131 2009-03-19 20:15:28Z blaine.dev $
  • */

    +public class AndroidImage extends Image {
  • /**
  • * Constructor instantiates a new &lt;code&gt;Image&lt;/code&gt; object. All values<br />
    
  • * are undefined.<br />
    
  • */<br />
    
  • public AndroidImage() {
  •    super();<br />
    
  • }

    +
  • protected AndroidImage(int id){
  •    super(id);<br />
    
  • }

    +
  • /**
  • * Constructor instantiates a new &lt;code&gt;Image&lt;/code&gt; object. The<br />
    
  • * attributes of the image are defined during construction.<br />
    
  • *<br />
    
  • * @param format<br />
    
  • *            the data format of the image.<br />
    
  • * @param width<br />
    
  • *            the width of the image.<br />
    
  • * @param height<br />
    
  • *            the height of the image.<br />
    
  • * @param data<br />
    
  • *            the image data.<br />
    
  • * @param mipMapSizes<br />
    
  • *            the array of mipmap sizes, or null for no mipmaps.<br />
    
  • */<br />
    
  • public AndroidImage(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data,
  •        int[] mipMapSizes) {<br />
    
  •    super(format, width, height, depth, data, mipMapSizes);<br />
    
  • }

    +
  • /**
  • * Constructor instantiates a new &lt;code&gt;Image&lt;/code&gt; object. The<br />
    
  • * attributes of the image are defined during construction.<br />
    
  • *<br />
    
  • * @param format<br />
    
  • *            the data format of the image.<br />
    
  • * @param width<br />
    
  • *            the width of the image.<br />
    
  • * @param height<br />
    
  • *            the height of the image.<br />
    
  • * @param data<br />
    
  • *            the image data.<br />
    
  • * @param mipMapSizes<br />
    
  • *            the array of mipmap sizes, or null for no mipmaps.<br />
    
  • */<br />
    
  • public AndroidImage(Format format, int width, int height, ByteBuffer data,
  •        int[] mipMapSizes) {<br />
    
  •    super(format, width, height, data, mipMapSizes);<br />
    
  • }

    +
  • /**
  • * Constructor instantiates a new &lt;code&gt;Image&lt;/code&gt; object. The<br />
    
  • * attributes of the image are defined during construction.<br />
    
  • *<br />
    
  • * @param format<br />
    
  • *            the data format of the image.<br />
    
  • * @param width<br />
    
  • *            the width of the image.<br />
    
  • * @param height<br />
    
  • *            the height of the image.<br />
    
  • * @param data<br />
    
  • *            the image data.<br />
    
  • */<br />
    
  • public AndroidImage(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data) {
  •    super(format, width, height, depth, data, null);<br />
    
  • }

    +
  • /**
  • * Constructor instantiates a new &lt;code&gt;Image&lt;/code&gt; object. The<br />
    
  • * attributes of the image are defined during construction.<br />
    
  • *<br />
    
  • * @param format<br />
    
  • *            the data format of the image.<br />
    
  • * @param width<br />
    
  • *            the width of the image.<br />
    
  • * @param height<br />
    
  • *            the height of the image.<br />
    
  • * @param data<br />
    
  • *            the image data.<br />
    
  • */<br />
    
  • public AndroidImage(Format format, int width, int height, ByteBuffer data) {
  •    super(format, width, height, data, null);<br />
    
  • }

    +
  • /**
  • * &lt;code&gt;setData&lt;/code&gt; sets the data that makes up the image. This data<br />
    
  • * is packed into an array of &lt;code&gt;ByteBuffer&lt;/code&gt; objects.<br />
    
  • *<br />
    
  • * @param data<br />
    
  • *            the data that contains the image information.<br />
    
  • */<br />
    
  • @Override
  • public void setData(ArrayList<ByteBuffer> data) {
  •    throw new UnsupportedOperationException(&quot;Image setData not Supported on Android&quot;);<br />
    
  • }

    +
  • /**
  • * &lt;code&gt;setData&lt;/code&gt; sets the data that makes up the image. This data<br />
    
  • * is packed into a single &lt;code&gt;ByteBuffer&lt;/code&gt;.<br />
    
  • *<br />
    
  • * @param data<br />
    
  • *            the data that contains the image information.<br />
    
  • */<br />
    
  • @Override
  • public void setData(ByteBuffer data) {
  •    throw new UnsupportedOperationException(&quot;Image setData not Supported on Android&quot;);<br />
    
  • }

    +
  • @Override
  • public void setData(int index, ByteBuffer data) {
  •    throw new UnsupportedOperationException(&quot;Image setData not Supported on Android&quot;);<br />
    

+}

+

  • @Override
  • public void addData(ByteBuffer data) {
  •    throw new UnsupportedOperationException(&quot;Image addData not Supported on Android&quot;);<br />
    

+// if (this.data == null)

+// this.data = new ArrayList<ByteBuffer>(1);

+// this.data.add(data);

+// setUpdateNeeded();

  • }

    +
  • /**
  • * &lt;code&gt;getData&lt;/code&gt; returns the data for this image. If the data is<br />
    
  • * undefined, null will be returned.<br />
    
  • *<br />
    
  • * @return the data for this image.<br />
    
  • */<br />
    
  • @Override
  • public List<ByteBuffer> getData() {
  •    return null;<br />
    

+

+// return data;

  • }

    +
  • /**
  • * &lt;code&gt;getData&lt;/code&gt; returns the data for this image. If the data is<br />
    
  • * undefined, null will be returned.<br />
    
  • *<br />
    
  • * @return the data for this image.<br />
    
  • */<br />
    
  • @Override
  • public ByteBuffer getData(int index) {
  •    if (index == 0) {<br />
    
  •        if (getEfficentData() instanceof AndroidImageInfo) {<br />
    
  •            AndroidImageInfo info = (AndroidImageInfo) getEfficentData();<br />
    
  •            return info.getBufferData();<br />
    
  •        } else {<br />
    
  •            return super.getData(index);<br />
    
  •        }<br />
    
  •    } else {<br />
    
  •        return super.getData(index);<br />
    
  •    }<br />
    
  • }

    +

    +}

    [/java]



    AndroidImageInfo

    [java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcandroidcomjme3asset

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: AndroidImageInfo.java

— AndroidImageInfo.java Base (BASE)

+++ AndroidImageInfo.java Locally Modified (Based On LOCAL)

@@ -5,8 +5,11 @@

import android.graphics.Matrix;

import com.jme3.texture.Image;

import com.jme3.texture.Image.Format;

+import com.jme3.util.BufferUtils;

import java.io.IOException;

import java.io.InputStream;

+import java.lang.ref.WeakReference;

+import java.nio.ByteBuffer;

import java.util.logging.Level;

import java.util.logging.Logger;



@@ -25,6 +28,8 @@

protected AssetInfo assetInfo;

protected Bitmap bitmap;

protected Format format;

  • private WeakReference<ByteBuffer> weakData = null;
  • private int bufferSize = 0;



    public AndroidImageInfo(AssetInfo assetInfo) {

    this.assetInfo = assetInfo;

    @@ -78,20 +83,25 @@

    switch (bitmap.getConfig()) {

    case ALPHA_8:

    format = Image.Format.Alpha8;
  •            bufferSize = bitmap.getWidth() * bitmap.getHeight() * 1;<br />
    

break;

case ARGB_4444:

format = Image.Format.ARGB4444;

  •            bufferSize = bitmap.getWidth() * bitmap.getHeight() * 2;<br />
    

break;

case ARGB_8888:

format = Image.Format.RGBA8;

  •            bufferSize = bitmap.getWidth() * bitmap.getHeight() * 4;<br />
    

break;

case RGB_565:

format = Image.Format.RGB565;

  •            bufferSize = bitmap.getWidth() * bitmap.getHeight() * 2;<br />
    

break;

default:

// This should still work as long

// as renderer doesn’t check format

// but just loads bitmap directly.

  •            bufferSize = 0;<br />
    

format = null;

}



@@ -109,4 +119,27 @@

}

}

}

+

  • public ByteBuffer getBufferData() {
  •    logger.log(Level.INFO, &quot;getBufferData called&quot;);<br />
    
  •    ByteBuffer buffer = null;<br />
    

+

  •    if (weakData == null || weakData.get() == null) {<br />
    
  •        logger.log(Level.INFO, &quot;weakData is null&quot;);<br />
    
  •        if (bitmap != null &amp;&amp; bufferSize &gt; 0) {<br />
    
  •            logger.log(Level.INFO, &quot;Creating buffer&quot;);<br />
    
  •            buffer = BufferUtils.createByteBuffer(bufferSize);<br />
    
  •            bitmap.copyPixelsToBuffer(buffer);<br />
    
  •            buffer.position(0);<br />
    
  •            weakData = new WeakReference&lt;ByteBuffer&gt;(buffer);<br />
    
  •        } else {<br />
    
  •            logger.log(Level.INFO, &quot;bitmap is null&quot;);<br />
    

}

  •    } else {<br />
    
  •        logger.log(Level.INFO, &quot;getBufferData called, returning existing weakData&quot;);<br />
    
  •    }<br />
    

+

  •    return weakData.get();<br />
    
  • }

    +

    +}

    [/java]



    AndroidImageLoader

    [java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcandroidcomjme3textureplugins

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: AndroidImageLoader.java

— AndroidImageLoader.java Base (BASE)

+++ AndroidImageLoader.java Locally Modified (Based On LOCAL)

@@ -4,6 +4,7 @@

import com.jme3.asset.AndroidImageInfo;

import com.jme3.asset.AssetInfo;

import com.jme3.asset.AssetLoader;

+import com.jme3.texture.AndroidImage;

import com.jme3.texture.Image;

import java.io.IOException;



@@ -13,7 +14,7 @@

AndroidImageInfo imageInfo = new AndroidImageInfo(info);

Bitmap bitmap = imageInfo.getBitmap();


  •    Image image = new Image(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);<br />
    
  •    Image image = new AndroidImage(imageInfo.getFormat(), bitmap.getWidth(), bitmap.getHeight(), null);<br />
    

image.setEfficentData(imageInfo);

return image;

}

[/java]



ImageBasedHeightMap

[java]

This patch file was generated by NetBeans IDE

Following Index: paths are relative to: D:UserspotterecDocumentsjMonkeyProjectsjME3_Reposrcterraincomjme3terrainheightmap

This patch can be applied using context Tools: Patch action on respective folder.

It uses platform neutral UTF-8 encoding and n newlines.

Above lines and this line are ignored by the patching process.

Index: ImageBasedHeightMap.java

— ImageBasedHeightMap.java Base (BASE)

+++ ImageBasedHeightMap.java Locally Modified (Based On LOCAL)

@@ -36,6 +36,8 @@

import com.jme3.texture.Image;

import java.nio.ByteBuffer;

import java.nio.ShortBuffer;

+import java.util.logging.Level;

+import java.util.logging.Logger;



/**

  • <code>ImageBasedHeightMap</code> is a height map created from the grayscale

    @@ -108,17 +110,18 @@

    ColorRGBA colorStore = new ColorRGBA();



    int index = 0;
  •    boolean log = false;<br />
    

if (flipY) {

for (int h = 0; h < imageHeight; ++h) {

if (flipX) {

for (int w = imageWidth - 1; w >= 0; --w) {

int baseIndex = (h * imageWidth)+ w;

  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;<br />
    
  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore, false)*heightScale;<br />
    

}

} else {

for (int w = 0; w < imageWidth; ++w) {

int baseIndex = (h * imageWidth)+ w;

  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;<br />
    
  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore, false)*heightScale;<br />
    

}

}

}

@@ -127,21 +130,31 @@

if (flipX) {

for (int w = imageWidth - 1; w >= 0; --w) {

int baseIndex = (h * imageWidth)+ w;

  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;<br />
    
  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore, false)*heightScale;<br />
    

}

} else {

for (int w = 0; w < imageWidth; ++w) {

  •                    log = false;<br />
    

int baseIndex = (h * imageWidth)+ w;

  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale;<br />
    
  •                    if (index &lt; 100 || index &gt; heightData.length-100) {<br />
    
  •                        log = true;<br />
    

}

  •                    heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore, log)*heightScale;<br />
    
  •                    if (log) {<br />
    
  •                        Logger.getLogger(ImageBasedHeightMap.class.getName()).log(Level.INFO,<br />
    
  •                                &quot;heightData[{0}]: {1}, heightScale: {2}&quot;,<br />
    
  •                                new Object[]{index-1, heightData[index-1], heightScale});<br />
    

}

}

}

  •        }<br />
    
  •    }<br />
    

return true;
}

- protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store) {
+ protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store, boolean log) {
+
switch (image.getFormat()){
case RGBA8:
buf.position( position * 4 );
@@ -159,13 +172,39 @@
buf.position( position * 3 );
store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), 1);
return calculateHeight(store.r, store.g, store.b);
+ case RGB565:
+ buf.rewind();
+ ShortBuffer sbuf2 = buf.asShortBuffer();
+ sbuf2.position( position );
+ short data = sbuf2.get();
+ byte tmpR = (byte)(((data & 0xF800) >> 11) << 3); // 5 bits
+ byte tmpG = (byte)(((data & 0x07E0) >> 5) << 2); // 6 bits
+ byte tmpB = (byte)(((data & 0x001F) << 3)); // 5 bits
+ float tmpRfloat = byte2float(tmpR, log);
+ float tmpGfloat = byte2float(tmpG, log);
+ float tmpBfloat = byte2float(tmpB, log);
+ store.set(tmpRfloat, tmpGfloat, tmpBfloat, 1);
+ float calcHeight = calculateHeight(store.r, store.g, store.b);
+ float calcHeightFinal = calcHeight * 255;
+ if (log) {
+ Logger.getLogger(ImageBasedHeightMap.class.getName()).log(Level.INFO,
+ "ImageBasedHeightMap inside RGB565 at Position: {0} of {1}, tmpR: {2}, tmpG: {3}, tmpB: {4}, store: {5}, calcHeight: {6}, calcHeightFinal: {7}",
+ new Object[]{position, buf.capacity(), tmpR, tmpG, tmpB, store, calcHeight, calcHeightFinal});
+ }
+ return calcHeightFinal;
case Luminance8:
buf.position( position );
- return byte2float(buf.get())*255*heightScale;
+ float tmp = byte2float(buf.get(), log)*255;
+ if (log) {
+ Logger.getLogger(ImageBasedHeightMap.class.getName()).log(Level.INFO,
+ "ImageBasedHeightMap inside Luminance8 at Position: {0} of {1}, result: {2}",
+ new Object[]{position, buf.capacity(), tmp});
+ }
+ return tmp;
case Luminance16:
ShortBuffer sbuf = buf.asShortBuffer();
sbuf.position( position );
- return (sbuf.get() & 0xFFFF) / 65535f * 255f * heightScale;
+ return (sbuf.get() & 0xFFFF) / 65535f * 255f;
default:
throw new UnsupportedOperationException("Image format: "+image.getFormat());
}
@@ -174,4 +213,11 @@
private float byte2float(byte b){
return ((float)(b & 0xFF)) / 255f;
}
+ private float byte2float(byte b, boolean log){
+ if (log) {
+ Logger.getLogger(ImageBasedHeightMap.class.getName()).log(Level.INFO,
+ "byte2float: byte: {0}, float: {1}", new Object[]{b, ((float)(b & 0xFF)) / 255f});
}
+ return byte2float(b);
+ }
+}
No newline at end of file
[/java]

@batkid

Do you have any projects you could test this on? I don’t have anything other than HelloTerrain from the tutorials.

Sure I can test the patch. I’ll get on it after the weekend.

It would be easier to simply specify a flag on TextureKey that indicates it is used for CPU access rather than to upload as texture to the GPU. Of course this still won’t permit a method like getPixel() to work on Android. I think the best way would be to create an interface for getEfficientData() that would support pixel modification. jME3 Image then either defers to accessing the pixels directly from the data ByteBuffer or by using the shared interface for efficient data (depending on which one is set on the image).

Hello! I’m developing some sort of a game on Android using Jme and found out that TerrainQuad based on HeightMap works on Android with some engine modifications. I tried patches listed in this thread, but it’s still do not work.
So, is there something usable for Android’s TerrainQuad?
Or maybe there is a build of jme that works normally with Terrains, ByteBuffers and else on Android?

P.S. My device is Nexus 4.

Best regards,
Matvey.

Hi there people,
Is terrains working on android yet?
I am getting a black terrain with no materials.
Any help would be appreciated.