Android - How to load Bitmaps in RGB_565

19 replies [Last post]
Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009

In response to the comment by Cass Surek
on Getting Started in Android Game Development

Original Post


Hi Robert,
Yes, the GameResources object appears to be a nice solution to simplify the handling of the various bits of data.

How do you load images on RGB_565 or any of the others?

My problem is that I am currently using the movements of the person with relatively large images at 320x480, with alpha. It works, but it needs improvement as each of these PNGs have around 20k.

What do you reckon? I was wanting to come up with at least 10 movements of 3 frames each. That'd give me around 600kb of PNGs. I need to show only head and torso, so I suppose it would be best to separate them?

Many thanks and have a great week over there!

Cass Surek

PS: I've fixed the XML preferences screen I had on the previous post. Yay! :)


Cass,

I'm glad you got that fixed!

First of all, 320x480 is huge for an animation. Are you displaying the animation full-screen? Perhaps you need a different approach for this? Would it be possible to have a quarter-resolution image (160x240) and display with a scale of 2.0 instead? It would be much faster and take up way less memory, though it won't look as smooth.

If possible, can you send me or post some screenshots of what you're trying to do? I'm having a hard time thinking up a scenario where images should be that large.

Here's how I load my images:

public class GameResources {
public static final Bitmap.Config DEFAULT_BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
public static final Bitmap.Config FAST_BITMAP_CONFIG = Bitmap.Config.RGB_565;

public Bitmap explosionFrames[] = new Bitmap[17];
public Paint basePaint = new Paint();
public void loadResources(Context context) {
Resources r = context.getResources();
explosionFrames[0] = loadBitmap(r.getDrawable(R.drawable.expl1));
explosionFrames[1] = loadBitmap(r.getDrawable(R.drawable.expl2));
explosionFrames[2] = loadBitmap(r.getDrawable(R.drawable.expl3));
explosionFrames[3] = loadBitmap(r.getDrawable(R.drawable.expl4));
explosionFrames[4] = loadBitmap(r.getDrawable(R.drawable.expl5));
explosionFrames[5] = loadBitmap(r.getDrawable(R.drawable.expl6));
explosionFrames[6] = loadBitmap(r.getDrawable(R.drawable.expl7));
explosionFrames[7] = loadBitmap(r.getDrawable(R.drawable.expl8));
explosionFrames[8] = loadBitmap(r.getDrawable(R.drawable.expl9));
explosionFrames[9] = loadBitmap(r.getDrawable(R.drawable.expl10));
explosionFrames[10] = loadBitmap(r.getDrawable(R.drawable.expl11));
explosionFrames[11] = loadBitmap(r.getDrawable(R.drawable.expl12));
explosionFrames[12] = loadBitmap(r.getDrawable(R.drawable.expl13));
explosionFrames[13] = loadBitmap(r.getDrawable(R.drawable.expl14));
explosionFrames[14] = loadBitmap(r.getDrawable(R.drawable.expl15));
explosionFrames[15] = loadBitmap(r.getDrawable(R.drawable.expl16));
explosionFrames[16] = loadBitmap(r.getDrawable(R.drawable.expl17));
}

public void release() {
explosionFrames = null;
basePaint = null;
}

public static Bitmap loadBitmap(Drawable sprite, Config bitmapConfig) {
int width = sprite.getIntrinsicWidth();
int height = sprite.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, bitmapConfig);
Canvas canvas = new Canvas(bitmap);
sprite.setBounds(0, 0, width, height);
sprite.draw(canvas);
return bitmap;
}

public static Bitmap loadBitmap(Drawable sprite) {
return loadBitmap(sprite, DEFAULT_BITMAP_CONFIG);
}

}

So if you want to load a bitmap as RGB_565, you can just change the loadBitmap call to use FAST_BITMAP_CONFIG.

Then, when creating a bitmap for a drawable layer in your game that doesn't need transparency, just use Bitmap.createBitmap(width, height, FAST_BITMAP_CONFIG)

Hope this helps!

cass
User offline. Last seen 5 years 8 weeks ago. Offline
Joined: 05/26/2009
Working now, but need to improve it still

Hi Robert, thanks again for the info.

I have devised the GameResouces class as per your model and I am drawing my bitmaps now with:

public void doDraw(Canvas pCanvas, Bitmap pBitmap) {

pCanvas.drawBitmap(pBitmap, 0, 0, null);
}

It works, but still lacks agility and responsiveness. Indeed, I believe the images are way too big.

The essence of the game is that the character with perform some movements depending on how the person shakes/moves the phone. An acceleration above a certain magnitude will trigger these.

The way I am drawing, as explained, is to draw the background at every pass and then draw every frame of the movement, with the large PNGs.

I suppose I will have to separate the head from the torso and position/draw them separately as they would be considerably smaller.

Since I am using only one canvas, how expensive is it to rotate it (when drawing the head) rather than creating two frames with different head angles?

Thanks again!

Cass

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Ahhh now it makes sense. I

Ahhh now it makes sense.

I would break up the head, arms and any other movable part into their own PNGs and crop off as much as you can. You want to draw as few pixels as possible.

here's kind of how I can see your code working:

public class Person {
private Matrix matrix = new Matrix();
private GameResources gameResources; // set in constructor
public void draw(Canvas canvas) {
Matrix m = this.matrix;
GameResources gameResources = this.gameResources;
m.reset();
m.postTranslate(headX, headY);
m.postRotate(newHeadAngle);
canvas.drawBitmap(gameResources.headBitmap, m, gameResources.normalPaint);
m.reset();
// do arms, etc
}

public void release() {
matrix = null;
}
}

The matrix calls aren't too expensive and you can cut your drawing way down this way.

jcrow
User offline. Last seen 4 years 51 weeks ago. Offline
Joined: 07/24/2009
Difference between rotating canvas/matrix

Hi,

Could someone please explain the difference between using the following:

canvas.save();
canvas.rotate(angle);
canvas.drawBitmap(..);
canvas.restore();

and the format described above where a matrix is used to apply the rotation?

Also, which is more efficient - using canvas.drawBitmap(..) on a bitmap, or using drawable.draw(canvas) on a drawable?

Many thanks,
Justin

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
canvas.rotate() creates and

canvas.rotate() creates and uses a matrix behind the scenes. save and restore set aside the current matrix and some other stuff and create new ones to use. What you may want to do is try those operations in my MaxFPS tester and see what kind of results you get. Try doing a hundred or so of this method vs the other and watch for 2 things: Speed and Garbage collection. If there is any GC, you will want to use a different method. GC is the enemy of every Java game!

jcrow
User offline. Last seen 4 years 51 weeks ago. Offline
Joined: 07/24/2009
Max FPS

Thanks Robert - I've been playing with your MaxFPS app and the results are interesting. Aside from anything else, it seems the mixing of bitmap configurations is what kills the frame rate. Using your test code, I get a faster frame rate when both bitmaps are created with the fast config, but slower if I mix configs - slower even than when both bitmaps are created using the slow config. So, should you never mix configs?

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
It's like this: Android

It's like this: Android renders to the screen in RGB_565. That will always draw fastest as the "bottom" layer. I use that for my background and then draw stuff with transparency in ARGB_8888 because that's the only way to get transparency. I say - if you can work it so that you're drawing a background in RGB_565, do that. Try to draw like-formats onto each other so there aren't conversions but also try to minimize how many full screen layers you have! The fewer, the better, but it always depends on exactly what you're doing.

jcrow
User offline. Last seen 4 years 51 weeks ago. Offline
Joined: 07/24/2009
Ahh - that makes sense. And

Ahh - that makes sense.

And it makes quite a difference - I altered your test code to render my full size background image every time, and draw animation on top. The RGB_565 code ran about 25fps faster!

Thanks for that.

ph303
User offline. Last seen 4 years 46 weeks ago. Offline
Joined: 09/04/2009
Inspired by your resource handling

Hey Rob,

I wanted to thank you for your excellent article about game development on Android. It actually really inspired me to start with my own game engine too!

I have found your resource handling very usefull and I have re-written it to be a bit more dynamic with some additional OO guddies. It will be a bit slower but very usefull if you need to deal with a lot of resources. I really appreciate if someone shares his brain like you do, keep up the good work! I haven't tested it so far but here is my code (somehow I cant get the code tags working):

GameResources class

package com.gkdev.androgame.res;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;

public class GameResources {
public static final Bitmap.Config DEFAULT_BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
public static final Bitmap.Config FAST_BITMAP_CONFIG = Bitmap.Config.RGB_565;

public static Bitmap loadBitmap(Drawable sprite, Bitmap.Config bitmapConfig) {
int width = sprite.getIntrinsicWidth();
int height = sprite.getIntrinsicHeight();

Bitmap bitmap = Bitmap.createBitmap(width, height, bitmapConfig);

Canvas canvas = new Canvas(bitmap);
sprite.setBounds(0, 0, width, height);
sprite.draw(canvas);

return bitmap;
}

public static Bitmap loadBitmap(Drawable sprite) {
return loadBitmap(sprite, DEFAULT_BITMAP_CONFIG);
}

private Context context;
private HashSet> loadedResourceSets;

/**
* Constructor for a GameResources object
* @param context The application context
*/
public GameResources(Context context) {
this.context = context;
loadedResourceSets = new HashSet>();
}

/**
* This method will generate a resource set containing Bitmap objects based on the passed resourceIds.
* The bitmaps will be loaded using the drawables that are referenced by the resource id's and by rendering
* them onto a canvas using the bitmapConfig that was passed.
* @param setName The name of the resource set that will be generated
* @param resourceIds The resource id's for the movie resources
* @param bitmapConfig The Bitmap.Config object that specifies the rendering method when the bitmap gets generated
* @return Returns the generated resource set
*/
public GameResourceSet loadBitmapResourceSet(String setName, int[] resourceIds, Bitmap.Config bitmapConfig) {
Resources r = context.getResources();

Bitmap[] bitmaps = new Bitmap[resourceIds.length];
for(int i = 0; i < resourceIds.length; i++) {
bitmaps[i] = loadBitmap(r.getDrawable(resourceIds[i]), bitmapConfig);
}

GameResourceSet resourceSet = new GameResourceSet(setName, bitmaps);
loadedResourceSets.add(resourceSet);

return resourceSet;
}

/**
* This method will load movie resources depending on the passed reourceIds.
* @param setName The name of the resource set that will be generated
* @param resourceIds The resource id's for the movie resources
* @return Returns the generated resource set
*/
public GameResourceSet loadMovieResourceSet(String setName, int[] resourceIds) {
Resources r = context.getResources();

Movie[] movies = new Movie[resourceIds.length];
for(int i = 0; i < resourceIds.length; i++) {
movies[i] = r.getMovie(resourceIds[i]);
}

GameResourceSet resourceSet = new GameResourceSet(setName, movies);
loadedResourceSets.add(resourceSet);

return resourceSet;
}

/**
* This method for loading raw resources into a resource set should only be called for loading small files
* that need to be buffered to optimize performance. It will store the raw resources in byte arrays.
* @param setName The name of the resource set that will be generated
* @param resourceIds The resource id's for the raw resources
* @return Returns the generated resource set
* @throws IOException
*/
public GameResourceSet loadRawBytesResourceSet(String setName, int[] resourceIds) throws IOException {
Resources r = context.getResources();

byte[][] byteBuffers = new byte[resourceIds.length][];
for(int i = 0; i < resourceIds.length; i++) {
InputStream is = r.openRawResource(resourceIds[i]);
is.read(byteBuffers[i]);
is.close();
}

GameResourceSet resourceSet = new GameResourceSet(setName, byteBuffers);
loadedResourceSets.add(resourceSet);

return resourceSet;
}

/**
* This method gets a loaded resource set by it's name.
* @param setName The name of the resource set that was loaded earlier
* @return Returns the resource set if found - else returns null
*/
public GameResourceSet<?> getResourceSetByName(String setName) {
for(GameResourceSet<?> resourceSet : loadedResourceSets) {
if(resourceSet.getResourceSetName().equals(setName)) {
return resourceSet;
}
}

return null;
}

/**
* Unloads a specific resource set. This will outlaw the resources in the resource set for the garbage collector.
* The resource set must be known or can be obtained using the getResourceByName method.
* @param resourceSet The resource set that should be unloaded
* @return true if the resource set was found and could be unloaded - false otherwise
*/
public boolean unloadResourceSet(GameResourceSet<?> resourceSet) {
if(resourceSet == null) {
return false;
}

resourceSet.freeResources();
return loadedResourceSets.remove(resourceSet);
}

/**
* This method unloads all resource sets in this GameResources object and outlaws all resources for garbage collecting.
*/
public void unloadAllResourceSets() {
for(GameResourceSet<?> resourceSet : loadedResourceSets) {
resourceSet.freeResources();
}

loadedResourceSets.clear();
loadedResourceSets = new HashSet>();
}
}

GameResourceSet class

package com.gkdev.androgame.res;

public class GameResourceSet {
private String resourceSetName;
private T[] resourceElements;

/**
* protected constructor that should only be visible to the class GameResources
* @param resourceSetName The resource set name which will be used to identify this resource set
* @param resourceElements The primitive array of resources
*/
protected GameResourceSet(String resourceSetName, T[] resourceElements) {
this.resourceSetName = resourceSetName;
this.resourceElements = resourceElements;
}

/**
* Protected method freeResources. This method will only be called by the hosting GameResources object.
* The cleanup call will be done by the GameResources object and should not be done by a class outside of
* the scope of this package.
*/
protected void freeResources() {
for(int i = 0; i < resourceElements.length; i++) {
resourceElements[i] = null;
}

resourceElements = null;
}

/**
*
* @return Returns the count of resources in this resource set
*/
public int getResourceCount() {
return resourceElements.length;
}

/**
*
* @return Returns the name of this resource set
*/
public String getResourceSetName() {
return resourceSetName;
}

/**
* This methode gets the resource at the specific position
* @param index The index of the resource
* @return Returns the resource
*/
public T getResource(int index) {
if(index < 0 || index >= resourceElements.length) {
return null;
} else {
return resourceElements[index];
}
}
}

Regards
Gion

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Very nice! I'll give it a

Very nice! I'll give it a whirl in the future if I ever need a dynamic resource manager.

Thanks for sharing!

Anonymous
GameResources ... example code

Hi Robert,

I have surely learned a lot with your tutorials. I am a bit stuck using this class. Do you have an example of some code that would load in 4 images and pause for 3 seconds in between each image? Using this code, I getting errors for each of the 4 images are follows:

[2010-08-23 08:59:55 - Unbranded] ActivityManager: DDM dispatch reg wait timeout
[2010-08-23 08:59:55 - Unbranded] ActivityManager: Can't dispatch DDM chunk 52454151: no handler defined

Thanks a lot, Rick

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Get a single image load

Get a single image load working and put it in the method loadNextImage()

Create a thread that loops and counts down to the next image load then when it hits 0, it calls loadNextImage() and restarts the count.

That's kind of what you want - or you can do it with timers but I never use those.

Anonymous
BitmapFactory

Hey Robert,
its me again (the guy from that blog). How about BitmapFactory? Seemed to be the only thing that worked for me in larger batches. Your approach worked, but only for a few bitmaps. Maybe I screwed the timming, but anyway, what can you say about it? Whats faster, what does have the smaller footprint?

I was in shock when my venerable Milesone refused to run a reasonable tile screen.

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Well when loading lots of

Well when loading lots of graphics you will run out of memory quickly. A 512x512 16 bit bitmap (RGB_565) uses 512KB of memory. That means you can only load about 20 and you will run completely out. If you are doing RGBA_8888 you can do half that, maybe 10.

Is that the problem you're having? Low memory?

Anonymous
Loading lots of bitmaps

Hello Robert,

I am using the above mentioned method to load images in my game, but the problem is that I have a lo tof images and I run out of memory. I have searched around for a while, but I am still unable to get an exact answer to this problem. Here is what I have found (I have to load all the images at the same time, to avoid loading them in the middle of a level):

1. Is using OpenGL or NDK the only solution?
2. Some people say that the bitmap memory doesn't count towards the vm heap limit. Is that correct? because I am getting an exception when I add too many bitmaps to my hashmap of bitmaps.

I know these questions have been answered in one way or the other many time on the internet, but too much information in confusing me.

Thanks
Eirij

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Hi, This is actually what I

Hi,

This is actually what I use in java to load bitmaps now:

public Bitmap loadBitmap(Context context, int resourceId, Config bitmapConfig) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = bitmapConfig;
// Not 1.5-compliant!
opts.inScaled = false;
return BitmapFactory.decodeResource(context.getResources(), resourceId, opts);
}

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Normally the problem is that

Normally the problem is that you're doing something inefficient, such as using several large bitmaps to do a full-screen animation. Think about ways you can conserve space at the cost of more programming to use smaller portions, smaller images, etc.

If you're out of space, you're out of space and that's all she wrote, so you have to find ways to achieve your goal without the brute force of just jamming lots of bitmaps into memory.

Anonymous
Thanks

Thanks Robert,

I have split the images into smaller groups that can be loaded and unloaded before the start of a level, and changed the background images to use RGB 565. For the rest, I am using ARGB_4444. That cut the required space in half.

Hopefully, everything will fit into the memory now.

Anonymous
Just for Learning

If I use Open GL, and load a lot of Textures. Would I still be bound by the heap limit? How do games like Need for Speed do it?

Thanks,
Eirij

Robert Green
Robert Green's picture
User offline. Last seen 14 weeks 4 days ago. Offline
Joined: 02/18/2009
Well it depends on the

Well it depends on the device, but for the most part the rules are a little different regarding video memory and main memory. I've never run out of video memory and have loaded lots and lots of textures in. Clever 3D games make a lot of reuse out of fewer textures to conserve memory.