I create 14 Bitmap objects like this:
bmp[0]=BitmapFactory.decodeResource(getResources(), R.drawable.a0000);
bmp[1]=BitmapFactory.decodeResource(getResources(), R.drawable.a0001);
bmp[2]=BitmapFactory.decodeResource(getResources(), R.drawable.a0002);
bmp[3]=BitmapFactory.decodeResource(getResources(), R.drawable.a0003);
bmp[4]=BitmapFactory.decodeResource(getResources(), R.drawable.a0004);
bmp[5]=BitmapFactory.decodeResource(getResources(), R.drawable.a0005);
bmp[6]=BitmapFactory.decodeResource(getResources(), R.drawable.a0006);
bmp[7]=BitmapFactory.decodeResource(getResources(), R.drawable.a0007);
bmp[8]=BitmapFactory.decodeResource(getResources(), R.drawable.a0008);
bmp[9]=BitmapFactory.decodeResource(getResources(), R.drawable.a0009);
bmp[10]=BitmapFactory.decodeResource(getResources(), R.drawable.a0010);
bmp[11]=BitmapFactory.decodeResource(getResources(), R.drawable.a0011);
bmp[12]=BitmapFactory.decodeResource(getResources(), R.drawable.a0012);
bmp[13]=BitmapFactory.decodeResource(getResources(), R.drawable.a0013);
those Bitmaps are used to make an animation of the background, the resources are 14 jpj images of 320x480 pixels. so I created a custom view and then call the bitmaps at the onDraw method. Then I test the app on my phone that have the same resolution of my images, it run smoothly, then I try to use the app on a tablet with a larger resolution(like 1280x720), but my background don't fit the entire screen, so I investigate how fill the entire screen by make a new bitmap with different resolution based on the device resolution and the result was this:
newWidth= context.getResources().getDisplayMetrics().widthPixels;
newHeight= context.getResources().getDisplayMetrics().heightPixels;
int contador=0;
do{
bmp[contador]= Bitmap.createScaledBitmap(bmp[contador], newWidth, newHeight,true);
contador++;
}while(contador<13);
Then I simply put these line on my onDraw method.
canvas.drawBitmap(bmp[i], 0, 0, null);
i++;
If I test the app on my phone it runs fine but when I test it on my tablet throws me the "bitmap size exceeds VM budget" error... So I investigate a little bit more and I had the following solution: Create the original 14 bmp Bitmap objects and make a second array of Bitmaps called bmp2 and then only assign the bitmap when I need it and then clear it like this on my onDraw method:
bmp2[i]= Bitmap.createScaledBitmap(bmp[i], newWidth, newHeight,true);
canvas.drawBitmap(bmp2[i], 0, 0, null);
bmp2[i].recycle();
bmp2[i]=null;
It work on my tablet and my phone but with a really bad performance I assume is because I assign the image in real time (on the onDraw method) but if I use the recycle method and don't assign again the bmp2 there will be not a new bmp2...
So finally my real question is: How can I clear some memory after I show my image and then when I need it again I can use it. Thanks!
Other have mentioned how scaling up small bitmaps to fill a large screen will not give high-quality results. But anyway, to answer your specific question, this is how you can draw your low-res bitmaps scaled up to fill the screen without memory errors.
You need to use one of the alternative Canvas.drawBitmap() methods which can scale your bitmap to the correct size while drawing.
For example:
public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)
From the Javadoc:
Draw the specified bitmap, scaling/translating automatically to fill
the destination rectangle.
Set the dst rectangle to be the screen size you calculated earlier.
Then, you do not need to create separate scaled bitmaps in memory, so your problem goes away.
I am afraid there is no safe way of doing a fullscreen animation using scaled bitmaps.
Why?
Let's do some counting. Each your initial bitmap is 320x480, yes? That's 320*480*4*14=8601600, which is roughly 8Mb of memory. When you scale to 1280x720, you add up 1280*720*4*14=51609600 bytes, which is 49 megabytes. 49 + 8 = 57 megabytes. Given that the newest tablets have maximum of 64 megabytes allocated for your app VM, no surprise it won't fit. And even if it fits with your tablet, what would happen if a Nexus 10 owner would buy your app? N10 has 2560Ă—1600 resolution (not to mention that scaled bitmap would look pathetic).
Allocating single bitmap for single frame will hardly let you have a smooth animation, I'm afraid. You could try keeping a window of 5 bitmaps at once and recycling others, but still I don't thik it would be enough. Besides that would consume ton of CPU work for constant bitmap scaling and will destroy your battery.
If you really want an animated background, you should probably look at either OpenGL, or TextureView.
Most of the times the GC would do the job.
You should have different bitmap resolutions handled by Android by folder (xhdpi, hdpi, etc.).
You should also check BitmapFactory.Options in order to have lower quality version Bitmaps generated by your BitmapFactory signatures.
Edit
You might also want to consider using WeakReferences to your Bitmaps.
You should design new bitmaps for your Tablets and Other Higher Screens and put them in the xhdpi folder under the res directory. This is the most efficient option. Let The Android OS itself pull the best fitting Resource for you.
See this for more detailed help
http://developer.android.com/training/multiscreen/index.html
This is what the documentation says
Although the system performs scaling and resizing to make your
application work on different screens, you should make the effort to
optimize your application for different screen sizes and densities. In
doing so, you maximize the user experience for all devices and your
users believe that your application was actually designed for their
devices—rather than simply stretched to fit the screen on their
devices.
Related
I'm developing for Android, and my program involves a lot of bitmap images - these are set using BitmapFactory. One of my images is 80x30, and I assign it to a Bitmap variable wit the following instruction:
bmapImg = BitmapFactory.decodeResource(currContext.getResources(), R.drawable.brick);
When I then use the instruction
myImgWidth = bmapImg.getWidth();
myImgWidth is evaluated to 160 instead of 80. Likewise, when I run
myImgHeight = bmapImg.getHeight();
myImgHeight is evaluated to 60 instead of 30. I've looked through other questions on StackOverflow relating to these methods, but all of those issues were about getWidth() and getHeight() returning 0. Am I doing something wrong, or is there a reason as to why these functions return double the actual image dimensions?
You using decodeResource, so image is scaled based on your DPI. To scale it to size you need, place it in different folder. I suggest drawable_xhdpi is what you need, if scale factor is 2.
And keep in mind, it will be scaled on devices with different screen density.
To completely avoid scaling you may put your image to assets folder for example.
It is all about which screen you are running your app on. The values returned by those methods are the pixels that the image takes on the current screen. They will return values that are 1:1 with your image's resolution if you are running on a mdpi screen and you used 80dp and 30dp respectively for their dimensions. I would bet you are probably running your app on a xhdpi screen and do not have a resource for that density bucket
As mentioned in other answer, image is resized based on the device you check on.
If you don't want android to resize your image based on DPI, I recommend you to add your image in drawable-nodpi folder of your project
The Android app I'm developing needs to render a few large bitmaps that are being updated continuously every frame. For the past year I rendered them through SurfaceView and Canvas, but now with the huge screen size of the Galaxy Nexus, drawing a large bitmap is extremely slow.
I'm making attempts to swap the rendering to openGL, but I can't update the textures fast enough each frame. At the moment I'm using glTexSubImage2d() to copy the bitmap data into a texture every frame, but this is much too slow. I've searched around a bit and apparently glCopyTexSubImage is somewhat faster, but the Android implementation doesn't accept a data parameter.
Any suggestions how I might be able to render dynamic textures without lag?
I'm not sure this answers your question, but the way I'm doing it is I have a framebuffer allocated in C/C++ and pass this to java via ByteBuffer (jni->NewDirectByteBuffer()). I'm using this as a texture and it works fast and fine. Then, I can modify this framebuffer directly in C/C++ and the changes are visible immedialty after render is performed on the OpenGL sufrace. I'm currently having problems with ICS Galaxy Nexus, as it does not apply the texture correctly if the texture exceeds 2048x2048 pixels. I'm assuming the error is related to hardware constrains of the device, but I am still doing some testing.
My application draws to the canvas in a continuous loop and on each loop re-evaluates the positions of the drawables and cycles them to animate. My question is which of the following 2 methods is superior and why? I'm a beginner so I have no idea how to benchmark methods and that kinda stuff, so if you can, or you already have, i'd appreciate the input.
The first method, (the one i'm using) is to assign the png resource a Handle as a Drawable. then every time I want to draw the object I call:
Drawable.setBounds(x,y,x,y);
Drawable.draw(canvas);
My question is would it be faster to (in the constructor), decode the resource as a BitMap, and then scale it it to the appropriate size. Then on each loop Draw the resource via:
canvas.drawBitmap(DrawableName, 0, 0, null);
The reason I ask is that my app draws hundreds of resources, so changing a few doesn't do enough to tell a difference, and i'd like to know whether it would be significantly faster doing it this way before I overhaul the code. Regardless, I need to increase the performance somehow so any other good ideas are also welcome.
In general, drawing bitmaps is faster than drawing as with the right preparation, drawing a bitmap is just dumping memory to the screen. If you need to draw a scaled bitmap, then draw it as one using createScaledBitmap rather than creating it then scaling it. You can achieve this by:
Bitmap myBitmap = BitmapFactory.decodeFile(myFile.getPath());
myBitmap = myBitmap.createScaledBitmap(myBitmap, width, height, true);
The Android developers documentation on the above function
Calculating and drawing primitives while running takes calculations and when drawing many of them will decrease performance, so use more bitmaps where you can - but be careful of doing premature optimisation - there's no point creating lots of bitmaps if there's no need as there will not be a significance (i.e. noticable) performance increase.
I'm currently developing my first Android app and am having some issues rendering images. The image itself is great quality to begin with, but upon rendering it the quality drastically lowers. Edges become jagged and it just looks poorly done. Everyone I've showed it to thus far has almost immediately noticed it, without any prompting about it. [start on left, end on right:]
I'm trying everything I am aware of and every tip I've been able to find by looking around online, but nothing seems to fix it.
Currently, I get the image as a Bitmap and scale it:
Bitmap holeImage = BitmapFactory.decodeResource(res, R.drawable.hole_image);
Bitmap holeImageBMP = Bitmap.createScaledBitmap(holeImage, width, height, true);
Once I have the image, I create a Paint, set a few smoothing attributes to true, and then draw it on the canvas:
Paint smoothingPaint = new Paint();
smoothingPaint.setAntiAlias(true);
smoothingPaint.setFilterBitmap(true);
smoothingPaint.setDither(true);
canvas.drawBitmap(holeImageBMP, 0, 0, smoothingPaint);
Yet, as you can obviously see above, the image quality drastically decreases. I've seen plenty of images being rendered beautifully and I'm honestly just not sure what's going on so any advice would be great!
Other notes: I'm using a SurfaceView method to handle the drawing, similar in nature to the LunarLander example given in the SDK.
Thanks again!
If you aren't restricted to much less colors than the original picture has (Does Android have 256 color modes?), I'd suggest to disable dithering, if you zoom into your picture, it does have a visible effect that perhaps destroys a smooth look.
I think in your case, dithering infers with anti-aliasing by destroying the additional colors that anti-aliasing needs for a smooth look. A quick color count on your pictures (left one about 850, right one about 140) confirms this.
That is probably related to converting images from one format to another. Also, android screens vary from device to device. Try to use another device and it might look better... Almost for sure it will have a different tone.
Try to read this great article on this problem (and banding and dithering) and consider adapting the image you created for it to work better in android devices: http://www.curious-creature.org/2010/12/08/bitmap-quality-banding-and-dithering/
I am trying to set a different wallpaper for every home screen, but I get OutOfMemory issues. I have 5 Bitmaps which I am trying to overlay on a wallpaper Bitmap which is 5 times the display width. Using the code below I get OOM. The problem seems to be the first line of code which creates the large wallpaper Bitmap. My question is whether there is a way to do this (i.e. some way that takes up less memory, or someway to allocate more memory??). Thanks!
Bitmap wallpaper = Bitmap.createBitmap(displayWidth*5,displayHeight, Config.ARGB_8888);
Canvas canvas = new Canvas(wallpaper);
Uri data = getIntent().getData();
Bitmap bmp = getBitmap(data, imagePosition, displayWidth, displayHeight);
canvas.drawBitmap(bmp, 0, 0,null);
WallpaperManager wallpaperManager = (WallpaperManager) SetterActivity.this.getSystemService(Context.WALLPAPER_SERVICE);
wallpaperManager.setBitmap(wallpaper);
wallpaperManager.suggestDesiredDimensions(bmp.getWidth()*2, bmp.getHeight());
If you can't get around having 5 screens worth of images loaded at a time, you could try only having one loaded at a time, and switch based on which home screen is currently being viewed.
That is, you have 2 bitmaps in memory, one is current and one is next. Load the current bitmap on first view. Use onOffsetsChanged to determine when a scroll is happening, and at that point load bitmap next based on which home screen will be showing next. You'll need to do that interpolation from screen A to B on your own. when the scroll is finished, recycle current and save next as current.
I'm sure there are some gotchas in this implementation that I'm not thinking of right now, but you'll only ever have 2 bitmaps allocated with this method. Good luck!
Bitmap data is allocated in the Native heap (see BitmapFactory OOM driving me nuts for details). How much is available depends on the platform (eg API level 2.2 has 24M Native heap total), but there is no way to grow it beyond that. And how much of the native heap space is allocated depends on what your and other applications are doing with bitmaps/graphics.
Your first line of code / wallpaper bitmap is only 480 * 800 * 32 / 8 = 1.536 Mbytes, which is well within the maximum. So it appears likely that much of the heap is already allocated by the time you get to that line?
Note that the native heap is garbage collected but infrequently - and data is not recovered if the application does not explicitly free it. So if you were running application without recycling your bitmaps (in onDestroy) you might well eat up the Native heap after a few runs.