So I'm getting really confused here. The designer I work with wants high-quality images (png files) for Android tablets, but the game also has smaller images for less-powerful devices. I figured that the amount of memory on the heap would be the metric to determine which set of images to use, by using Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory(). That doesn't seem to be the case though. On BlueStacks it can load the high-quality images just fine, and it has around 40,000,000 bytes. The designer's Galaxy Nexus has black boxes for some of the larger images (which I understand is due to a lack of memory for loading the image), but his Galaxy Nexus has about 50,000,000 available bytes, which is even more than BlueStacks.
So what is the limiting factor? And on a related matter, how is it that there are mobile games that have impressive quality visuals, yet I can't manage to load a few images? What am I doing wrong?
To note, I am using AndEngine, and below is an example of how I'm loading the images.
BuildableBitmapTextureAtlas resetTA = new BuildableBitmapTextureAtlas(this.getTextureManager(), 310 / d, 190 / d,
TextureOptions.BILINEAR);
resetTR = BitmapTextureAtlasTextureRegionFactory.createTiledFromAsset(resetTA, this, "gfx/" + lowres + "reset.png", 1, 1);
try
{
resetTA.build(new BlackPawnTextureAtlasBuilder<IBitmapTextureAtlasSource, BitmapTextureAtlas>(0, 0, 0));
resetTA.load();
}
catch (TextureAtlasBuilderException e)
{
Debug.e(e);
}
One of the images that isn't loading in the Galaxy Nexus is a sprite sheet png file that's 2320x464.
There are two limiting factors here. First it's the heap memory. To find the available heap for your app you can use the method with the Runtime class, but that will tell you the maximum memory your app can use before it completely crashes. A limit that your app should respect in Android can be found this way:
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
Log.d("MyTag", "Heap: + Integer.toString(memoryClass));
The second is GL_MAX_TEXTURE_SIZE value that limits the maximum dimension of a square texture for given device. This can vary, but the minimum these days seems to be 2048, therefore your textures can be as much as 2048x2048 pixels large. However the only recommendation is that the dimension must be larger than the screen dimensions and the real size is up to the manufacturer of the phone.
I think you can use the following code to find out the size:
int[] maxTextureSize = new int[1];
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
Log.d("MyTag", "GL_MAX_TEXTURE_SIZE: " + Integer.toString(int[0]));
The games can have impressive graphics before they split the big textures to smaller, load only what is needed and reuse as much as possible. I've made a game where some levels have 50000px wide ground by assembling it from 256x256 pieces making the game sharp even on full HD tablets. The pieces were distributed over several 2048x2048 textures.
Related
I am developing a simple 2D game. I have multiple sprites. Each sprite has around 80 png/frames of 265* 256. I used LibGdx's Texture packer to package the atlas. Am enabling mimap using following code to pac
TexturePacker.Settings settings = new TexturePacker.Settings();
settings.combineSubdirectories = true;
settings.filterMin = Texture.TextureFilter.MipMapNearestLinear;
settings.filterMag = Texture.TextureFilter.Linear;
TexturePacker.process(settings, f.getPath(), outputFolderName, atlasFileName);
Questions:
Are 80 images/frames for single sprite too much?
Is 0.5 GB memory usage too much for a simple game like Fruit ninja?
How can i reduce my memory usage?
Any other things i should try?
Update1:
Here is the the screen shot taken from android profiler.
80 frames is kind of a lot but that number has never been a problem in my projects. That said, most of our 60 frame pre-rendered animations are small, like an icon flashing graphic in 48x48 segments, with one sheet (so there is minimal context switching). This has never been a performance issue for us ... BUT you saying 256x256 scares me a little, especially if it is uncropped and a lot of pngs are created!
While my projects have sprites of similar frame numbers, they have been optimized via the Texture Packer. Make sure you have Trim mode set to "Trim" and that it isn't "None" (the setting is near the bottom under Sprites). This setting will de-homogenize the 256x256 into smaller pieces if at all possible, reducing the number of total sheets and texture bindings (I think, and also fewer context switches) .. I'm not totally sure how you packed your sprites or if they can even be trimmed, but this could potentially be a performance life saver.
Let me show you an example:
Before
After
Also if you provided the code in how your animations are created, we could double check to make sure you aren't binding 80 Textures when only 1 would be needed. I create my animations via the following process:
TextureAtlas atlas = assetManager.get(atlas_name);
Sprite[] spriteFrames = new Sprite[numFrames];
for (int index=0; index<numFrames; index++) {
if (atlas.findRegion(lookup_name, index) == null)
Engine.console("[ERROR] problem loading and finding region for " + atlas_name + " " + lookup_name + " index: " + index + " not found in spritesheet .. fix immediately");
else {
spriteFrames[index] = atlas.createSprite(lookup_name, index);
}
}
Animation<Sprite> animation = new Animation<Sprite>(timeBetweenFrames, spriteFrames);
I hoped this helped providing some insight.
I'm developing an Android app that's going to work with bitmaps extensively and I'm looking for a reliable way to get the maximum texture size for OpenGL on different devices.
I know the minimum size = 2048x2048, but that's not good enough since there are already tablets out there with much higher resolutions (2560x1600 for example)
So is there a reliable way to get this information?
So far I've tried:
Canvas.getMaximumBitmapWidth() (Returns 32766, instead of 2048)
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE ...) (Returns 0)
I'm working with minimum-sdk = 15 (ICS) and I'm testing it on a Asus Transformer TF700t Infinity
Does anyone know another way to get it?
Or will I have to compile a list of known GPUs with their max canvas size?
try using this code
int[] maxTextureSize = new int[1];
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
maxTextureSize stores the size limit for decoded image such as 4096x4096, 8192x8192 . Remember to run this piece of code in the MainThread or you will get Zero.
This will give you the maximum height allowed.
Canvas canvas = new Canvas();
canvas.getMaximumBitmapHeight() / 8
after googling a lot I have not yet found a way to resize an image preserving quality.
I have my image - stored by camera in full resolution - in
String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + "/my_directory/my_file_name.jpg";
Now, I need to resize it preserving aspect ratio and then save to another path.
What's the best way to do this without occurring the error "Out of memory on a xxxxxxx-byte allocation."?
I continue to retrieve this error on Samsung devices, I tried in every way, even with the library Picasso.
Thanks!
1st things 1st: depending on device and bitmap size, no matter what magic code you do, it will crash! Specially cheap Samsung phones that usually have no more than 16mb of RAM to the VM.
You can use this code How to get current memory usage in android? to check on amount of memory available and deal with it properly.
When doing those calculations, remember that bitmaps are uncompressed images, that means, even thou the JPG might be 100kb, the Bitmap might take several MB.
You'll use the code shown here https://developer.android.com/training/displaying-bitmaps/load-bitmap.html to read the bitmap boundaries, and do an approximate scale down as close as possible to the size you actually need, or enough to make the device not crash. That's why it's important to properly measure the memory.
That 1st code takes virtually no RAM as it creates from the disk, making it smaller by simply skipping pixels from the image. That's why it's approximate, it only does in power of 2 the scaling.
Then you'll use the standard API to scale down to the size you actually need https://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap(android.graphics.Bitmap, int, int, boolean)
so the pseudo code for it, will be:
try{
Info info = getImageInfo(File);
int power2scale = calculateScale(info, w, h);
Bitmap smaller = preScaleFromDisk(File, power2scale);
Bitmap bitmap = Bitmap.createScaledBitmap(smaller, w, h, f);
} catch(OutOfMemoryError ooe){
// call GC
// sleep to let GC run
// try again with higher power2scale
}
I'm capturing an image from the camera which I want to pass through some processing in OpenCV. On older devices, this is failing at the first hurdle though:
public void onPictureTaken(byte[] jpeg, Camera c) {
mImageBitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length);
org.opencv.android.Utils.bitmapToMat(mImageBitmap, Utils.cameraMat);
...
}
Produces:
04-22 14:23:41.708: I/System.out(7289): Available memory (bytes): 5646168
04-22 14:23:41.718: I/dalvikvm-heap(7289): Forcing collection of SoftReferences for 51121168-byte allocation
04-22 14:23:41.758: E/dalvikvm-heap(7289): Out of memory on a 51121168-byte allocation.
Reading up about this, the advice seems to be to work with a smaller version of the image, but how do I work out roughly what dimensions to resize to given the amount of available memory left on the device?
You need to resize your image before passing it to openCV. The decode instruction consumes a lot of memory because it loads the full image in memory. Even if you don't display it.
This official tutorial will show you how to resize your image without having to load it in memory first
You can find out how much memory your application has available to use, total, with getMemoryClass. You'll have to work out how much of that you're eating up with your application's other data, and the decoded bitmap above.
Some rough estimates:
The S II has an 8 MP camera. Assuming an RGB24 Bitmap, a single decoded bitmap will take up 8 * 3 = 24 MB of memory. The OpenCV matrix will probably take up about the same, possible more depending on what its internal format is (the out-of-memory error suggests it's trying to allocate ~48 MB, which is about twice the expectation here).
That's easily above what a standard app can use for memory - the minimum baseline is only 16 MB. If you need more memory, you probably want to look into using a large heap instead of the normal-sized one. That will let you get roughly double the memory, although it's still device-dependent.
In order to minimize the memory usage of bitmaps, yet still try to maximize the quality of them, I would like to ask a simple question:
Is there a way for me to check if a given image file (.png file) has transparency using the API, without checking every pixel in it?
If the image doesn't have any transparency, it would be the best to use a different bitmap format that uses only the RGB values.
The problem is that Android also doesn't have a format for just the 3 colors. Only RGB_565, which they say that degrade the quality of the image and that should have dithering feature enabled.
Is there also a way to read only the RGB values and be able to show them?
For me bitmap.hasAlpha() works fine to check first if the bitmap has alpha values. Afterwards you have to run through the pixels and create a second bitmap with no alpha I would suggest.
Let's start a bit off-topic
the problem is that android also doesn't have a format for just the 3 colors . only RGB_565 , which they say that degrade the quality of the image and that should have dithering feature enabled.
The reason for that problem is not really Android specific. It's about performance while drawing images. You get the best performance if the pixeldata fits exactly in 1 32bit memory cell.
So the most obvious good pixel format is the ARGB_8888 format which uses exactly 32bit (24 for the color 8 for alpha). While drawing you don't need to do anything but to loop over the image data and each cell you read can be drawn directly. The only downside is the required memory to work with such images, both when they just sit in memory and while displaying them since the graphic hardware has to transfer more data.
The second best option is to use a format where several pixels fit into 1 cell. Using 2 pixels in 32bit you have 16bit per pixel left and one of the formats using 16bit is the 565 format. 5bit red, 6bit green, 5bit blue. While drawing this you can still work on memory cells separately and all you have to do is to split 1 cell in parts. Due to the smaller memory size required for images, drawing can sometimes be even faster than using 32bit colors. Since in the beginning of android memory was a much bigger problem they chose this format to be the default.
And the worst category of formats are those where pixels don't fit into those cells. If you take just the 3 colors you get 24 bit and those need to be distributed over 2 cells in 3 out of 4 cases. For example the second pixel would use the remaining 8 bit from the first cell & the first 16bit of the next cell. The extra work required to work with 24bit colors is so big that it is not used. And when drawing images you usually have alpha at some point anyways and if not you simply use 32bit but ignore the alpha bits.
So the 16bit approach looks ugly & the 24 bit approach does not make sense. And since the memory limitations of Android are not as tight as they were and the hardware got faster, Android has switched it's default to 32bit (explained in even more details in http://www.curious-creature.org/2010/12/08/bitmap-quality-banding-and-dithering/)
Back to your real question
is there a way for me to check if a given image file (png file) has transparency using the API , without checking every pixel in it?
I don't know. But JPEG images don't support alpha and PNG images usually have alpha. You could simply abuse the file extension to get it right in most cases.
But I would suggest you don't bother with all that and simply use ARGB_8888 and apply the nice image loading techniques detailed in the Android Training documentation about Displaying Bitmaps Efficiently.
The reason people run into memory problems is usually either that they have way more images loaded in memory than they currently display or they use giant images that can't be displayed on the small screen of a phone. And in my opinion it makes more sense to add good memory management than complicating your code to downgrade the image quality.
There is a way to check if a PNG file has transparency, or at least if it supports it:
public final static int COLOR_GREY = 0;
public final static int COLOR_TRUE = 2;
public final static int COLOR_INDEX = 3;
public final static int COLOR_GREY_ALPHA = 4;
public final static int COLOR_TRUE_ALPHA = 6;
private final static int DECODE_BUFFER_SIZE = 16 * 1024;
private final static int HEADER_DECODE_BUFFER_SIZE = 1024;
/** given an inputStream of a png file , returns true iff found that it has transparency (in its header) */
private static boolean isPngInputStreamContainTransparency(final InputStream pngInputStream) {
try {
// skip: png signature,header chunk declaration,width,height,bitDepth :
pngInputStream.skip(12 + 4 + 4 + 4 + 1);
final byte colorType = (byte) pngInputStream.read();
switch (colorType) {
case COLOR_GREY_ALPHA:
case COLOR_TRUE_ALPHA:
return true;
case COLOR_INDEX:
case COLOR_GREY:
case COLOR_TRUE:
return false;
}
return true;
} catch (final Exception e) {
}
return false;
}
Other than that, I don't know if such a thing is possible.
i've found the next links which could be helpful for checking if the png file has transparency . sadly, it's a solution only for png files . rest of the files (like webP , bmp, ...) need to have a different parser .
links:
http://www.java2s.com/Code/Java/2D-Graphics-GUI/PNGDecoder.htm
http://hg.l33tlabs.org/twl/file/tip/src/de/matthiasmann/twl/utils/PNGDecoder.java
http://www.java-gaming.org/index.php/topic,24202