everybody!
I draw a plot based on some data in my app. The plot is with scrolling and zooming. I create 2 bitmaps for this purpose in MyMainFragment.onActivityCreated().
if (bitmaps[0] != null)
{
return;
}
final int deviceWidth = getResources().getDisplayMetrics().widthPixels;
final int deviceHeight = getResources().getDisplayMetrics().heightPixels;
final int deviceMaxDim = Math.max(deviceHeight, deviceWidth);
bitmaps[0] = Bitmap.createBitmap(
deviceMaxDim * 2,
deviceMaxDim * 1,
Bitmap.Config.ARGB_8888);
bitmaps[1] = Bitmap.createBitmap(
bitmaps[0].getWidth(),
bitmaps[0].getHeight(),
bitmaps[0].getConfig());
I need 2 bitmaps and not only 1 because of the drawing algorithm I use. Running the program on Asus Transformer, the bmp size in pixels is 1280 * 2 * 1280, thus the byte size being 1280 * 2 * 1280 * 4. When I run the program from Eclipse, everything's fine. But when I launch it like an ordinary user from the tablet, it crashes every second time.
What's happening behind the scene when I launch my app from Eclipse that allows it to run and can I follow the same steps programmatically so that it doesn't crash when launched normally? Or should I use some other drawing algorithms (maybe something like OpenGL)?
Thanks a lot.
The solution was simple. I created a Canvas somewhere in my code, called Canvas#setBitmap(bitmaps[?]) on it, but didn't realise, that I must call Canvas.setBitmap(null) to free the reference.
Related
My app displays pictures takes from the camera. On some devices, it happens that the pictures ain't displayed on an ImageView because of the error message: Bitmap too large to be uploaded into a texture (1624x2251, max=2048x2048)
To solve this issue, the app simply scales the image down to the maximum supported texture size. The code looks like this:
int[] maxTextureSize = new int[1];
GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
if (maxTextureSize[0] < 2048) {
maxTextureSize[0] = 2048;
}
if (bitmap.getWidth() > maxTextureSize[0] || bitmap.getHeight() > maxTextureSize[0]) {
float factor = Math.min((float)maxTextureSize[0] / (float)bitmap.getWidth(), (float)maxTextureSize[0] / (float)bitmap.getHeight());
int newWidth = (int) (bitmap.getWidth() * factor);
int newHeight = (int) (bitmap.getHeight() * factor);
bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
}
Now this works in most of the cases, except from time to time (randomly), when GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE) causes the following error message without returning the texture size:
call to OpenGL ES API with no current context (logged once per thread)
Now the question is, how can the app reliably determine the maximum displayable bitmap size of an ImageView? Preferable without setting up an OpenGL context. Is there any alternative?
Take a look at Displaying Bitmaps Efficiently. It's a great in-depth official guide tackling the exact problem you're facing.
From this answer on stack overflow, you can use:
MemoryInfo mi = new MemoryInfo();
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
long availableMegs = mi.availMem / 1048576L;
//Percentage can be calculated for API 16+
long percentAvail = mi.availMem / mi.totalMem;
to get the memory available to your app. Using this and the size of your bitmap, you can figure out exactly how big your image can be.
the app simply scales the image down to the maximum supported texture size
I must say, you're probably trying to render images bigger than can be displayed. Are you considering the device's resolution before rendering the bitmap? Not a whole lot of devices are 2048 pixels wide.
I'm doing a gallery image. I get path of image in device and parse to URI.
Then I use Picasso Android Lib to load image into Imageview in Gridview. It's work fine until have a large image. Picasso can not load large image. I got error Out Of Memory. Is there any suggestion to load large image into ImageView? And have any lib to load image into ImageView can instead Picasso?
I found ImageLoader lib for my problem. It works fine. I tested on my project, and then I saw that ImageLoader looks better than Picasso.
Learn how to use common techniques to process and load Bitmap objects in a way that keeps your user interface (UI) components responsive and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly consume your available memory budget leading to an application crash due to the dreaded exception:
java.lang.OutofMemoryError: bitmap size exceeds VM budget.
There are a number of reasons why loading bitmaps in your Android application is tricky:
Mobile devices typically have constrained system resources. Android devices can have as little as 16MB of memory available to a single application. The Android Compatibility Definition Document (CDD), Section 3.7. Virtual Machine Compatibility gives the required minimum application memory for various screen sizes and densities. Applications should be optimized to perform under this minimum memory limit. However, keep in mind many devices are configured with higher limits.
Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.
Android app UI’s frequently require several bitmaps to be loaded at once. Components such as ListView, GridView and ViewPager commonly include multiple bitmaps on-screen at once with many more potentially off-screen ready to show at the flick of a finger.
Read More Regarding this issue
Here is an example I once used, but it is not perfect! (Sorry)
You can reduce the bitmap size:
public Bitmap resizeBitmap(Bitmap bitmap) {
if (bitmap.getHeight() > 4096 || bitmap.getWidth() > 4096) {
int width = (int) (bitmap.getWidth() * 0.9);
int height = (int) (bitmap.getHeight() * 0.9);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
resizeBitmap(resizedBitmap);
returnresizedBitmap;
} else {
return bitmap;
}
}
If I should do it again: (not tested)
public Bitmap resizeBitmap(Bitmap bitmap) {
int originalWidth = bitmap.getWidth();
int originalHeight = bitmap.getHeight();
if (originalWidth > 4096 || originalHeight > 4096) {
int height;
int width;
if(originalHeight > originalWidth) {
height = 4096;
width = originalWidth / (originalHeight / 4096);
} else {
width = 4096;
height = originalHeight / (originalWidth / 4096);
}
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
return resizedBitmap;
} else {
return bitmap;
}
}
You can custom recent-images library for your purpose. It's very simple and easy to use library. It creates a thumbnail of image for showing in gridview and then in click opens the original.
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.
The game I'm developing is progressing, more and more elements are being added, but I'm of course facing new problems, one of which is the performance.
Currently, I'm having 3 threads, two of which perform calculations, the other one updates the canvas. These three threads are synchronized with a CyclicBarrier, to have all calculations finished when beginning to draw the canvas.
I'm using several bitmaps in different sizes. In the drawing method, bitmaps are being rotated (by using drawBitmap-matrix combination with scaling/translating/rotating added into the matrix for "native" (i guess) management of it) and of course drawn. The problem I am facing is that whenever I have too many "moving and rotating" elements on the screen, it gets choppy.
Matrix matrix = new Matrix();
matrix.setTranslate(view.getX(), view.getY());
matrix.preScale((1.0f * view.getWidth() / view.getCurrentBitmap().getWidth()), (1.0f * view.getHeight() / view.getCurrentBitmap().getHeight()));
matrix.postRotate(view.getRotation(), view.getX() + view.getWidth()/2f, view.getY() + view.getHeight()/2f);
mCanvas.drawBitmap(view.getCurrentBitmap(), matrix, mBasicPaint);
For example, this is how the player-object is being drawn according to the rotation and position:
private void drawPlayer(final Canvas mCanvas) {
final Bitmap playerBitmap = mPlayer.getCurrentBitmap();
mPlayer.nextFrame();
if(playerBitmap != null) {
if(mPlayer.getRotation() != 0) {
Matrix matrix = new Matrix();
matrix.setTranslate(-mPlayer.getCurrentBitmap().getWidth()/2f, -mPlayer.getCurrentBitmap().getHeight()/2f);
matrix.postRotate(mPlayer.getRotation());
matrix.postTranslate(mPlayer.getX() + + mPlayer.getCurrentBitmap().getWidth()/2f, mPlayer.getY() + mPlayer.getCurrentBitmap().getHeight()/2f);
matrix.postScale((1.0f * mPlayer.getWidth() / mPlayer.getCurrentBitmap().getWidth()), (1.0f * mPlayer.getHeight() / mPlayer.getCurrentBitmap().getHeight()), mPlayer.getX(), mPlayer.getY());
mCanvas.drawBitmap(mPlayer.getCurrentBitmap(), matrix, mBasicPaint);
} else {
Matrix matrix = new Matrix();
matrix.setScale((1.0f * mPlayer.getWidth() / mPlayer.getCurrentBitmap().getWidth()), (1.0f * mPlayer.getHeight() / mPlayer.getCurrentBitmap().getHeight()));
matrix.postTranslate(mPlayer.getX(), mPlayer.getY());
mCanvas.drawBitmap(mPlayer.getCurrentBitmap(), matrix, mBasicPaint);
}
} else log("bitmap = null!");
}
(This is a kind of deprecated version, the .getCurrentBitmap() calls are reduced to one in the current version.)
How could I improve the performance? Should I create some sort of a "Loading..." screen, in which I pre-load EVERY bitmap (in its biggest size) and a pre-rotated version of each bitmap? (This would result in, if I go with 2-4 degree steps, 90-180 versions of each bitmap, which seems kind of.. a lot?) Or would this, with the rotated bitmaps stored as well, be too much on the memory? I don't know anything about OpenGL etc, this is why I'm using a SurfaceView and no other game engine, and I'm sure it has to work like this as well - somehow.
You are creating a Matrix (matrix) and Bitmap (playerBitmap) object each time you call drawPlayer method. As far as I understand, you will call this method each frame. So, each frame, you are creating 2 large objects that need to be Garbage collected when you exit the method and this will slow down your frame rate. You can create only 2 objects matrix and playerBitmap as class level variables and refresh them on each call of drawPlayer, reducing the number of GC calls.
Honestly, I don't like to ask things this way, but I have no clue about this one!
Have you seen this before??
It can be seen that the image is scrambled following some defined pattern. This happens only in some (low end) devices, with Non Power of two images (FBO). It works well on other devices.
What I do, is to load an Android Bitmap to a FBO (works OK, as it shows ok on the screen). I do some editing (I paste a sticker, which in the image seems to be in the right place), and finally save the FBO into a Bitmap again. It works ok for a 512x512 FBO (the FBO has the image size), but no for that one (507x800).
Any Ideas??? I don't post code because I have no clue, please tell me and I'll add it.
This is the GL call to retrieve info from FBO
public Buffer toPixelBuffer(){
final int w = this.getWidth(); //colorTexture width
final int h = this.getHeight();
final ByteBuffer pixels = BufferUtils.newByteBuffer(w*h * 4);
Gdx.gl.glPixelStorei(GL10.GL_PACK_ALIGNMENT, 1);
Gdx.gl.glReadPixels(0,0, w, h, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);
pixels.clear();
return pixels;
}
I also don't have a buggy device with me to test right now :(
Thank you!
I had the exact same problem. I experienced this on Galaxy Ace, Galaxy Y, and some other devices.
After lot of testing I did found out that it wasn't even required POT textures, so keeping the texture size with a 64 pixel increment made the trick. So lets say if I have a 122x53 texture, I need to convert it to 128x64. An so on.
Next is the function I use to get a valid texture dimension. Call it for both Width and Height.
/**
* Some GPUs such as the "VideoCore IV HW" on the Samsung Galaxy Ace
* require texture (FBO) sizes to be in '64' increments (WTF!!!!)
*
* #param dimension
* Base dimension to calculate
* #return Resolved 64 dimension
*/
public static int calculate64Dimension(final int dimension)
{
return (((dimension - 1) >> 6) << 6) + 64;
}