Hopefully this should be an easy question. I'm trying to copy a series of small bitmaps into a larger one, arranging them side by side without any gaps or overlap in their pixels. For example, if I have 3 square bitmaps, I'd like to copy them into one long and thin rectangle. I know how to do the opposite, namely creating a small bitmap out of a larger one, but not this way around. What's the right command?
(If anyone's curious, I want to do this to be able to reuse some code I wrote for handling animation with a single bitmap.)
Thanks!
Create a canvas for the large bitmap, then use that to draw your small bitmaps. I'm pretty new to android, but I'm guessing that it's something like this:
Bitmap makeBigBitmap(Bitmap srcBmps[]) {
Bitmap wideBmp;
Canvas wideBmpCanvas;
Rect src, dest;
// assume all of the src bitmaps are the same height & width
wideBmp = Bitmap.createBitmap(srcBmps[0].getWidth() * srcBmps.length,
srcBmps[0].getHeight(), srcBitmaps[0].getConfig());
wideBmpCanvas = new Canvas(wideBmp);
for (int i = 0; i < srcBmps.length; i++) {
src = new Rect(0, 0, srcBmps[i].getWidth(), srcBmps[i].getHeight());
dest = new Rect(src);
dest.offset(i * srcBmps[i].getWidth(), 0);
wideBmpCanvas.drawBitmap(srcBmps[i], src, dest, null);
}
return wideBmp;
}
Related
first the important lines of code:
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), getDrawableForLvl(drawable));
int []pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
//..
pixels = null; // please gc remove this huge data
System.gc();
So I'm working on an Android app (game) with multiple levels. Every level is an image that I load as bitmap on level start. Then I have to analyse every pixel (line 3). For that I create an array (line 2). The images are 1280x800, so the array has a size over one million ints. No problems here and since it's a local variable it should be destroyed on method return. But it's java, so it's not -.- Depending on the device the garbage collector is running fast enough in time or not. So when a user starts and closes levels very fast it produces a java.lang.OutOfMemoryError in line 2. I guess because the old array(s) wasn't/weren't removed yet and now I have multiple ones filling the memory.
I could make the array a static member. So it's created once and is always available. Then it's not possible to have multiple instances of it. But I think that's a bad coding style, because it's also available (4 MB) when not needed.
I don't need all pixels at the same time. I'm splitting the images in more than a hundred rectangles, so I could use a way smaller array and fill it one after another with pixels. But I have problems to understand the methods parameters can someone help me here?
There is also a method to get just one pixel at position x,y, but I guess a million function calls is pretty slow.
Has someone a better idea? There is no way to force an object out of memory in java, is there?
Update1:
As vzoha suggested to get only the first quarter:
int []pixels = new int[bitmap.getWidth()/2*bitmap.getHeight()/2];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth()/2, bitmap.getHeight()/2);
gives an ArrayIndexOutOfBound. I think the function call is just getting the pixels of the first quarter, but still expects the full size array and the other fields will be left untouched.
Update2: I guess I can do it row by row (half row by half row for the first quarter):
int []pixels = new int[bitmap.getWidth()/2*bitmap.getHeight()/2];
for(int row = 0; row < bitmap.getHeight()/2; ++row)
bitmap.getPixels(pixels, bitmap.getWidth()/2, bitmap.getWidth(), 0, row, bitmap.getWidth()/2, 1);
But when I do that for 20x10 parts it's not much better than getting each pixel by itself. Well it is much better but still the method should be capable to do that with one call, shouldn't it? I just don't get this "stride" parameter: "The number of entries in pixels[] to skip between rows (must be >= bitmap's width). Can be negative." How can it be negativ when >= width?
The size in pixels doesn't directly translate to how much memory the image will take up in memory. Bitmaps in Android (before using ART) are notoriously difficult to use heavily while avoiding OOM exceptions, enough so, that there's a page dedicated to how to use them efficiently. The problem is normally that there is actually enough memory available, but it has become fragmented and there isn't a single contiguous block the size you need available.
My first suggestion (for a quick win) would be to decode the bitmap with a specific config, you should be able to occupy only 1/4 of the amount of memory you were previously using by switching to use ALPHA_8 for your bitmap config. This will use 1 byte per pixel instead of 4 (the default is ARGB_8888)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8
bitmap = BitmapFactory.decodeResource(getResources(), getDrawableForLvl(drawable), options);
My next suggestion would be to scale you bitmap to start with and place the appropriate one in your hdpi,xhdpi,xxhdpi folders.
In fact it is pretty simple to get just the pixels of a specific area. But the documentation is wrong about the stride parameter. It says:
public void getPixels (int[] pixels, int offset, int stride, int x, int y, int width, int height)
stride: The number of entries in pixels[] to skip between rows (must be >= bitmap's width). Can be negative.
But the truth is it can (and must in most cases) be less than the bitmap's width, it just has to be bigger or equal to the passed width (the second to last parameter of getPixels), meaning the width of the area from which I want the pixels.
So to get the pixels of the first quarter of a bitmap:
int []pixels = new int[bitmap.getWidth()>>1 * bitmap.getHeight()>>1]; // quarter of the bitmap size
bitmap.getPixels(pixels, 0, bitmap.getWidth()>>1, 0, 0, bitmap.getWidth()>>1, bitmap.getHeight()>>1);
Or to get a specific rect with x,y (upper left corner) and width, height:
int []pixels = new int[width*height];
bitmap.getPixels(pixels, 0, width, x, y, width, height);
So pretty simple. It was just the wrong documentation that put a twist in my brain.
Simply put, I have a Bitmap resource that is 1800 x 1800 pix due to the detail I need. I create a canvas from the Bitmap and then draw on it. After drawing is complete, It's attached to an ImageView. It works fine on devices with large Heaps but on small devices, it crashes. The Bitmap needs to be the same size for all devices when added to the canvas because the coordinates that I draw to are precise locations on the Bitmap.
Here is my code
initialBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.VeryLargeBitmap);
mutableBitmap = initialBitmap.copy(Bitmap.Config.RGB_565, true);
canvas = new Canvas(mutableBitmap);
....draw stuff here
canvas.drawLine(x, y, x2, y2, paint);
ImageView.setImageBitmap(mutableBitmap);
ImageView..setAdjustViewBounds(true);
I'm sure there is a better way. I have looked into OpenGL but have not tried it yet. It looks to complex for what I'm trying to accomplish.
BitmapFactory.Options o = new BitmapFactory.Options();
o.inMutable = true;
initialBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.VeryLargeBitmap, o);
Doing this should remove the need to copy the bitmap to an immutable one. When you're done with it (saved to file or ready for a new one) do this:
initialBitmap.recycle();
initialBitmap = null;
To remove any reference to it (NOTE: recycle may not be necessary but I like it "to make sure").
EDIT:
Special note is that creating a Bitmap is CPU intensive so it'd be best to decode it in a thread and start drawing when it's ready. You should never create a Bitmap in an onDraw or draw method.
ex.:
I have a bitmap size 500x500. And on this bitmap I have coordinates to 2 regions.
one region is at X=10, Y=10, size 10x10
second region is at X=400, Y=400, size 10x10
What would be the best way to swap those two regions in the bitmap.
You can do it trough Canvas.
Something like:
Bitmap swapped = Bitmap.createBitmap(origin.getWidth(), origin.getHeight(), origin.getConfig());
Canvas drawer = new Canvas(swapped);
drawer.drawBitmap(origin, new Rect(0,0,100,100), new Rect(100,100,100,100), paint);
drawer.drawBitmap(origin, new Rect(100,100,100,100), new Rect(0,0,100,100), paint);
At that point your 'swapped' Bitmap will have the origin pieces drawed in different regions.
For more see the Canvas documentation:
http://developer.android.com/reference/android/graphics/Canvas.html#drawBitmap(android.graphics.Bitmap, android.graphics.Matrix, android.graphics.Paint)
Hm, a simple "cruel" approach can do the work:
Load the bitmap in an 2-dimensional array and swap your cells.
It will take roughly around: 500x500x4 bytes which is a bit less than a 1 mega-byte of memory, which is nothing for android phones nowdays since app have at least 8/16 mgb of ram at their use.(on weaker phones)
Also the operations will be quite fast, even if you do a bunch of processing of the bit maps, such as resizing and so on.....
If you wont best performance you can use native code, there are some libraries for processing bitmaps that are quite memory and cpu efficient.
The best way would be the same as switching any type of data:
make a temporary bitmap to hold area1 data and put there the data.
put area2 data into area1.
put the temporary bitmap data into area2 , and recycle the temporary bitmap.
Here's a sample code that I've written. It's not tested, but should work:
Bitmap origin=...;
Rect r1=...,r2=... ; //assumption: both rectangles are of the same size
//copy from region1 to temp bitmap
Bitmap temp= Bitmap.createBitmap(origin,r1.left,r1.top,r1.width(),r1.height());
//copy from region2 into region1
Canvas canvas=new Canvas(origin);
canvas.drawBitmap(origin, r2, r1, new Paint());
//copy from temp bitmap to region2
canvas.drawBitmap(temp, new Rect(0,0,r2.width(),r2.height()), r2, paint);
temp.recycle();
An alternative way (which might be better in terms of speed and/or memory) would be to use int array instead of a new bitmap object, but I think this method is easy to understand.
Here's the alternative:
Bitmap origin=...;
Rect r1=...,r2=... ; //assumption: both rectangles are of the same size
//copy from region1 to temp pixels
int[] pixels=new int[r1.width()*r1.height()];
origin.getPixels ( pixels, 0, origin.getWidth(), r1.left, r1.top, r1.width(), r1.height());
//copy from region2 into region1
Canvas canvas=new Canvas(origin);
canvas.drawBitmap(origin, r2, r1, new Paint());
//copy from temp pixels to region2
origin.setPixels (pixels, 0, origin.getWidth(), r2.left, r2.top, r2.width(), r2.height());
I hope I didn't make any mistakes here, since I haven't tested it.
I come from the Qt world and i am porting an application to Android. I am bit confused, i am banging my head for a few days now on something that must be so trivial that i cannot find why it's not working.
Some background: i have a C++ engine which i use trough NDK and JNI. This engine creates some bitmaps and passes them to the Java side, the Java side must display them on a View and let the user interact with them (drag and such).
The engine works properly, because i use it under Qt with full success. This is the workflow:
1- Java loads a big Bitmap from a custom data file (the C++ engine expects it to be in ARGB format, but it's compressed JPG data)
Bitmap.Config fmt = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeByteArray(buffer, 0, size).copy( fmt , false);
2- initialize the C++ engine passing the bitmap. The C++ engine "breaks" the bitmap in smaller tiles. For tile it builds a rather complex alpha mask and stores it into the first byte of the bitmap (the "a" byte). This alpha mask only uses two values: 0xFF for opaque and 0x00 for transparent.
init_C_engine( this.fullImage );
3- The Java side then allocates all the tiles bitmaps, i do in two steps because before init i dont know which size will the tiles be. The engine will populate the tile_width and tile_height arrays:
Bitmap.Config fmt = Bitmap.Config.ARGB_8888;
for (int t = 0; t < this.puzzle_size; t++ ){
tile_data[ t ] = Bitmap.createBitmap( tile_width[t], tile_height[t], fmt);
4- Last step,inside the C++ engine, all the tiles bitmaps are filled:
for ( int n = 0; n < nBitmaps; n++ )
{
jobject bitmap = env->GetObjectArrayElement( bitmaps, n );
AndroidBitmap_getInfo(env, bitmap, &info);
AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&pixels));
game->getTileBitmap( n, (unsigned char*)pixels );
AndroidBitmap_unlockPixels(env, bitmap);
env->SetObjectArrayElement( bitmaps, n, bitmap );
}
}
Now, in my custom View:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
for ( int tile = 0; tile < board.nTiles; tile++ ){
canvas.drawBitmap( tile_data[tile],
tile_x[tile],
tile_y[tile], paint);
}
}
What i expect is that on my View i see my tiles with transparent areas. what i get instead is a weird behaviour so that on the black background i see the ENTIRE tile like the alpha bytes are all set to opaque, but when i move the tiles one of top of the other, the "transparent" areas get combined in some strange way, like colors are "xor"ed or multiplied in some way! When i move one tile on the other i can see the areas where the alpha bytes are set to transparent but colors gets mangled instead of being transparend!
Basically i expect that pixels having alpha set to 0 are drawn as transparent... i looked on internet but i could not find anything usefull to help me out....
Does somebody have ideas? Anything will be appreciated!
thanks.
Shouldn't you use the index t instead of tile inside the for loop inside onDraw? Like this:
canvas.drawBitmap(tile_data[t], tile_x[t], tile_y[t], paint);
I have a big spritesheet (3808x1632) composed by 42 frames.
I would present an animation with these frames and I use a thread to load a bitmap array with all the frames, with a splash screen waiting for its end.
I'm not using a SurfaceView (and a draw function of a canvas), I just load frame by frame in an ImageView in my main layout.
My approach is similar to Loading a large number of images from a spritesheet
The completion actually takes almost 15 seconds, not acceptable.
I use this kind of function:
for (int i=0; i<TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
mVectorTeapotBG.add(Bitmap.createBitmap(framesBitmapTeapotBG, xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG));
}
framesBitmapTeapotBG is the big spritesheet.
Looking more deeply, I've read in the logcat that the createBitmap function takes a lot of time, maybe because the spritesheet is too big.
I found somewhere that I could make a window on the big spritesheet, using the rect function and canvas, creating small bitmaps to be loaded in the array, but it was not really clear. I'm talking about that post: cut the portion of bitmap
My question is: how can I speed the spritesheet cut?
Edit:
I'm trying to use this approach but I cannot see the final animation:
for (int i=0; i<TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmFrame);
Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG);
Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);
c.drawBitmap(framesBitmapTeapotBG, src, dst, null);
mVectorTeapotBG.add(bmFrame);
}
Probably, the Bitmap bmFrame is not correctly managed.
The short answer is better memory management.
The sprite sheet you're loading is huge, and then you're making a copy of it into a bunch of little bitmaps. Supposing the sprite sheet can't be any smaller, I'd suggest taking one of two approaches:
Use individual bitmaps. This will reduce the memory copies as well as the number of times Dalvik will have to grow the heap. However, these benefits may be limited by the need to load many images off the filesystem instead of just one. This would be the case in a normal computer, but Android systems may get different results since they're run off flash memory.
Blit directly from your sprite sheet. When drawing, just draw straight from sprite sheet using something like Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint). This will reduce your file loads to one large allocation that probably only needs to happen once in the lifetime of your activity.
I think the second option is probably the better of the two since it will be easier on the memory system and be less work for the GC.
Thanks to stevehb for the suggestion, I finally got it:
for (int i = 0; i < TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmFrame);
Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, xStartTeapotBG+frameWidthTeapotBG, yStartTeapotBG+frameHeightTeapotBG);
Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);
c.drawBitmap(framesBitmapTeapotBG, src, dst, null);
mVectorTeapotBG.add(bmFrame);
}
The computation time falls incredibly! :)
Use a LevelListDrawable. Cut the sprites into individual frames and drop them in your drawable resource directory. Either programmatically or through an xml based level-list drawable create your drawable. Then use ImageView.setImageLevel() to pick your frame.
I use a method of slicing based on rows and columns. However your sprite sheet is rather huge. You have to think you are putting that whole sheet into memory. 3808x1632x4 is the size of the image in memory.
Anyway, what I do is I take an image (lets say a 128x128) and then tell it there are 4 columns and 2 rows in the Sprite(bitmap, 4, 2) constructor. Then you can slice and dice based on that. bitmap.getWidth() / 4 etc... pretty simple stuff. However if you want to do some real stuff use OpenGL and use textures.
Oh I also forgot to mention there are some onDraw stuff that needs to happen. Basically you keep an index counter and slice a rectangle from the bitmap and draw that from a source rectangle to a destination rectangle on the canvas.