Android: Working with bitmaps without OutOfMemoryError - android

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.

Related

Get specific two pixel from the bitmap for processing

I have an image on which I want to process pixel number 159 and 160 specifically. I am running this code but it gives me ArrayIndexOutOfBoundsException. I think the way I have declared that array that is the problem.
int[] twoPositions = {159, 160};
bitmap.GetgetPixels(twoPositions, 0, width, 0, 0, width, height); //exception line (ArrayIndex....)
how to declare array in a way which will give me 0 row, 159th and 160th column pixel?
thank you in advance...

compare 2 images to avoid duplication

I am comparing 2 similar images and would like to see if both are similar .Currently I used the code:
public void foo(Bitmap bitmapFoo) {
int[] pixels;
int height = bitmapFoo.getHeight();
int width = bitmapFoo.getWidth();
pixels = new int[height * width];
bitmapFoo.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
and I call the function : foo(img1) where :
img1=(Bitmap)data.getExtras().get("data");
I would like to know how to get the above getpixel,I tried assigning it to variable but did not work .Should it have a return type ?? and in format it is ?
And also how do I compare 2 images??
Also both the images may be of different dimensions based on the mobile camera the snapshot is taken from .
Also can it recognize if the same image is shot in the morning and night ???
Thanks in Advance.
This code will compare pixel from base image with other image.
If both pixel at location (x,y) are same then add same pixel in result image without change. Otherwise it modifies that pixel and add to our result image.
In case of baseline image height or width is larger than other image then it will add red color for extra pixel which is not available in other images.
Both image file format should be same for comparison.
Code will use base image file format to create resultant image file and resultant image contains highlighted portion where difference observed.
Here is a Link To Code with sample example attached.
If you want to copy the pixels of the bitmap to a byte array, the easiest way is:
int height = bitmapFoo.getHeight();
int width = bitmapFoo.getWidth();
pixels = new int[height * width];
bitmapFoo.copyPixelsToBuffer(pixels);
See the documentation
I should warn you that you will have to handle this with care, any other way you will get OutOfMemoryError.
To get All Pixels
bitmapFoo.copyPixelsToBuffer(pixels);
or
bitmapFoo.getPixels(pixels, 0, width, 0, 0, width, height);
To get One Pixel
The two arguments must be two integers within the range [0, getWidth()-1] and [0, getHeight()-1]
int pix = bitmapFoo.getPixel(x, y);

OutOfMemoryError while create a bitmap

I want to send a fax from my app.
A fax document has a resolution of 1728 x 2444 pixels.
So I create a bitmap, add text and/or pictures and encode it to CCITT (Huffman):
Bitmap image = Bitmap.createBitmap(1728, 2444, Config.ALPHA_8);
Canvas canvas = new Canvas(image);
canvas.drawText("This is a fax", 100, 100, new Paint());
ByteBuffer buffer = ByteBuffer.allocateDirect(image.getWidth() * image.getHeight());
image.copyPixelsToBuffer(buffer);
image.recycle();
encodeCCITT(buffer, width, height);
This works perfect on my Galaxy SII (64 MB heap size), but not at emulator (24 MB). After creating the second fax page I get "4223232-byte external allocation too large for this process...java.lang.OutOfMemoryError" while allocating the buffer.
I already reduced color depth from ARGB_8888 (4 byte per pixel) to ALPHA_8 (1 byte), because fax pages are monochrome anyway.
I need this resolution and I need to have access to the pixels for encoding.
What is the best way?
Android doesn't support 1-Bpp bitmaps, and the Java heap size limit of 24/32/48MB is part of Android. Real devices can't allocate more than the Java heap limit no matter how much RAM they have. There appear to be only two possible solutions:
1) Work within the limitations of the Java heap.
2) Use native code (NDK).
In native code you can allocate the entire available RAM of the device. The only down side is that you will need to write your own code to edit and encode your bitmap.
In addition to BitBank's already good answer, you have to null the reference if you want the Garbage collector to actually clean up your Bitmap's references. The documentation for that method states:
This is an advanced call, and normally need not be called, since the
normal GC process will free up this memory when there are no more
references to this bitmap.
instead of copy all pixels to a ByteBuffer, you can copy step by step. Here with a int[] array. So, you need less memory:
int countLines = 100;
int[] pixels = new int[width * countLines];
for (int y = 0; y < heigth; y += countLines) {
image.getPixels(line, 0, width, 0, y, width, countLines);
// do something with pixels...
image.setPixels(line, 0, width, 0, y, width, countLines);
}

Spritesheet programmatically cutting: best practices

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.

How to copy a smaller bitmap into a larger one?

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;
}

Categories

Resources