I have created a small applicaiton (a game), that reuses the same set of images several times. SO i thought i should create a class that is responsible for loading all of the different images once, from which i can then access them statically from other classes. However, i believe this may have caused an issue with the Garbage Collector, which causes my App to lag whenever the GC is run. Here is What the ImgLoader class looks like:
public class ImgLoader extends View {
public static Bitmap tree1;
public ImgLoader(Context context) {
super(context);
loadImgs();
}
public void loadImgs() {
System.gc(); // Manually Call GC
// TREES
tree1 = BitmapFactory.decodeResource(getResources(), R.drawable.tree);
tree1 = getResizedBitmap(tree1, MainActivity.height / 2,
MainActivity.width / 10);
}
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// "RECREATE" THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
return resizedBitmap;
}
public static Bitmap RotateBitmap(Bitmap source, float angle) {
Matrix matrix = new Matrix();
matrix.postRotate(angle);
return Bitmap.createBitmap(source, 0, 0, source.getWidth(),
source.getHeight(), matrix, true);
}
}
Note, i only use one Bitmap for the question. Essentially, when i want to use an image in my application, i say:
object.image = ImgLoader.tree1;
What is an alternative to doing this, that is not so memory intensive?
Thank you!
This seems like very bad practice to me, especially because you are loading multiple Bitmaps and keep static references of them.
Since even small Bitmaps consume considerably large amounts of memory, you will run into OutOfMemoryErrors very soon.
A Bitmap 512x512 already consumes 1 Megabyte of RAM (in 32 Bit) color. You can load your Bitmaps in RGB_565 (16 Bit) to reduce memory consuption.
Why not just load the Bitmap from the Resources when you need it?
try getting them in InputStream format ... they will be in byte .. but personnaly,( and its really personnally ) i prefer to load every image when i need it instead of leaving them in a memory .. for future use , the number of images can increase , plus , as u mentionned and i noticed in your code , the images are store locally ( not doenloaded ) so it shldnt b a big problem to reload them .. but again its my own opinion
Don't think it has anything to do with the gc; you are loading images when you manually call the gc.
I believe it's the loading of images that is causing your app to lag. You could load the bitmap asynchronously.
Related
I am developing an Instagram like application for learning image processing and android. But I am stuck, I have a problem implementing Grayscale Filter in my application. I am trying a simple approach for now to convert individual pixels in a Bitmap to Grayscale.
Here's the whole class I am writing to apply various filters to an Image:
package com.dosa2.photoeditor.ImageEffects;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
public class ImageEffects {
Bitmap bitmap;
int width, height;
public ImageEffects(Bitmap bitmap) {
this.bitmap = bitmap;
width = bitmap.getWidth();
height = bitmap.getHeight();
}
public Bitmap toGrayscale() {
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(resultBitmap);
Paint p = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
p.setColorFilter(f);
c.drawBitmap(bitmap, 0, 0, p);
return resultBitmap;
}
public Bitmap toGrayscale2() {
Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
for(int i=0;i<height;i++) {
for (int j=0;i<width;j++) {
int c = bitmap.getPixel(i,j);
resultBitmap.setPixel(i, j, (Color.red(c)+Color.blue(c)+Color.green(c)/3));
}
}
return resultBitmap;
}
}
I have tried 2 methods to convert the Bitmap into Grayscale. The former seems to be working(but I am not able to understand it) and the latter is not.
Can anyone help me out? And do mention if there's an easier way to manipulate Images in Android.
The error (or at least one of them...) is in one of your for loops:
for (int j=0;i<width;j++)
should be
for (int j=0;j<width;j++)
to prevent an indefinite loop.
Your method "toGrayscale" is using the ColorMatrix class, which I think internally uses the RenderScript API to do the rendering (or at least GPU shaders). RenderScript is the Android's computing API (which is similar to OpenCL, in fact, is a layer that works on OpenCL), so you are not using only the CPU to do the color filtering, you are using even the GPU or other DSPs your device may have. The second method "toGrayscale2" is slower because you are using only the CPU to convert your Bitmap to grayscale (pixel by pixel) and you shouldn't use it. Check this presentation (it's very intersting) in order to understand a bit more how your first method works, the link points to the page 12 which is about color filtering, but you should view it entirely in order to understand better.
What is the correct way to rotate bitmap every frame in main game loop?
What I have tried:
I created rotated bitmap every frame:
Bitmap image, tmp;
Matrix m;
...
public void mainGameLoop(Canvas c){
m.reset();
m.postRotate(angle);
tmp = Bitmap.createBitmap(image, 0, 0, width, height, m, true);
c.drawBitmap(tmp, 50, 50, null);
}
However, because Bitmap.createBitmap creates bitmap every frame Garbage Collector works so madly, it causes low FPS.
I have tried saving bitmap in array at all angles, and just taking bitmap which I want to. However, this needs a lot of RAM and app just couldn't launch at high screen resolutions.
If you have any ideas, please comment below.
Some think like this:
Bitmap image;
Matrix m;
.
.
.
public YourClass(){
m = new Matrix();
image = Bitmap.createBitmap(image, 0, 0, width, height,);
}
.
.
.
public void mainGameLoop(Canvas c){
m.setRotate(angle, imageCenterX, imageCenterY);
yourCanvas.drawBitmap(image, m, null);
}
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I know this topic has been discussed so many times and I have read so many articles on handling large bitmap but if we put that problem aside, I would like to know why does a single bitmap take so much memory. Now, I am developing an Android application with a dynamic background. The background is gradient-like image and it's supposed to go from white, over gray to black color. I have 4 background images which together make the wanted gradient. Now, if I use only one image and make the background static, everything works perfectly fine on a device with VM heap of 64 Mb. If, however, i decided to use all backgrounds, i get outofmemory error even on a device with VM heap of 128 Mb. Now, of course, I can understand why that happens if i load all 4 images but I edited my code so that only 2 images are loaded in the memory at one moment ( if one background image is no longer on the screen, it is recycled) and that works, but barely. If i add just a single other image, which has negligible dimensions compared to the background image, it get the same error.
Another problem in all of this is that I have to scale my bitmaps after loading them. I scale them to the size of the screen. I know scaling takes up a lot of memory too, but if i do the calculations, a bitmap of dimensions 1280x720 stored as ARGB888 should use no more than 4 MB of memory.
Setting largeHeap to true won't do any help either, as some devices have heap as low as 16 MB ..
Even caching bitmaps to storage won't help because at a single moment i need more bitmaps than some devices could handle..
Also, loading sampled image won't do any help since images aren't very large (1000x1000 for xxhdpi
density)
Code for BitmapDrawing:
private void DrawBackground(Canvas canvas) {
//temp and bgs[1] are loaded at start up, as they are required right away
if (bg_num > 3) {
canvas.drawBitmap(bgs[3], 0, 0, null);
if (!bgs[0].isRecycled())
bgs[0].recycle();
return;
}
if (bgs[0] == null) {
bgs[0] = Bitmap.createScaledBitmap(temp, getWidth(), getHeight(),
false);
temp.recycle();
bgs_resized[0] = true;
}
if (bg_num == 2 && !bgs[0].isRecycled()) {
bgs[0].recycle();
bgs[2] = BitmapFactory.decodeResource(getResources(),
R.drawable.bg3, bitmapOptions);
}
if (bg_num == 3 && !bgs[1].isRecycled()) {
bgs[1].recycle();
bgs[3] = BitmapFactory.decodeResource(getResources(),
R.drawable.bg4, bitmapOptions);
}
if (!bgs_resized[bg_num]) {
temp = bgs[bg_num];
bgs[bg_num] = Bitmap.createScaledBitmap(temp, getWidth(),
getHeight(), false);
bgs_resized[bg_num] = true;
temp.recycle();
}
if (bg_pos < getHeight()) {
canvas.drawBitmap(bgs[bg_num], 0, (bg_pos += BG_TRANSITION_SPEED)
- getHeight() + 2, null);
canvas.drawBitmap(bgs[bg_num - 1], 0, bg_pos, null);
} else {
canvas.drawBitmap(bgs[bg_num], 0, 0, null);
++bg_num;
bg_pos = 0;
}
}
Am i doing something wrong or do i need to turn to NDK for my solution.
Thanks.
you kind of answered your own question you reserve memory when you use a bitmap equal to your resolutions x 4 bytes. So this gets out of hand really quickly.
a couple of tips are.
1) use a bitmap factory that decodes your bitmap to the necessary size. for example if an imageview needs only 512x384 you don't want to load the bigger resolution bitmap in memory.
you can do it like this:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
2) make sure when you load a bitmap from disk that you do it on a different thread then the UI.
3) you should try to call recycle as soon as possible on your bitmap to free up memory (when you don't need it anymore)
4) following the previous tip. try to cache your bitmaps to increase responsiveness
Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;
// If you're running on Honeycomb or newer, create a
// synchronized HashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()) {
mReusableBitmaps =
Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
// Notify the removed entry that is no longer being cached.
#Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
// The removed entry is a recycling drawable, so notify it
// that it has been removed from the memory cache.
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
// The removed entry is a standard BitmapDrawable.
if (Utils.hasHoneycomb()) {
// We're running on Honeycomb or later, so add the bitmap
// to a SoftReference set for possible use with inBitmap later.
mReusableBitmaps.add
(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
....
}
I am creating this Android project with Java. However, I am wondering a bit how far the references with some Bitmap methods will go.
I have an originally Bitmap just like:
Bitmap originalBitmap = BitmapFactory.decodeResource(resources, bitmapID);
and then I send it to an object within its constructor of a basic class:
class Test
{
Bitmap memberBitmap;
Test(Bitmap b)
{
memberBitmap = b;
}
}
This far, I know memberBitmap is still a reference to originalBitmap. But what I would like to do is basically resize this Bitmap using:
memberBitmap = Bitmap.createScaledBitmap(memberBitmap, newWidth, newHeight, filter);
Have I now stored a new Bitmap to the memory or have I changed the originalBitmap?
If it's so that I have created a new Bitmap, would there be any alteration to make it update the originalBitmap instead?
Like:
memberBitmap.createScaledBitmap(memberBitmap, newWidth, newHeight, filter);
By the sound of that API, it's probably creating a new bitmap in memory. You can check this by printing out the toString() on both objects after your constructor runs and see if their memory locations are the same.
For the second question: change the originalBitmap to reference to the new Bitmap.
It is actually creating a new Bitmap. In this case, it will create a new Bitmap and you lose the reference to the old one. If you did the code like so:
memberBitmap = b;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(memberBitmap, newWidth, newHeight, filter);
You'll see that memberBitmap remains unchanged and scaledBitmap is a larger or smaller version. However, one exception is if there is no scaling to be done at all in which case it simply returns the reference to memberBitmap and you'll have two references to the same object.
One way you can remove work is to create the Bitmap smaller in the first place using the BitmapFactory.Options parameters. For example:
BitmapFactory.Options o = new BitmapFactory.Options();
o.inSampleSize = 2;
Bitmap originalBitmap = BitmapFactory.decodeResource(resources, bitmapID);
This will create a Bitmap that's half the size (width and height) of the original image. Unfortunately, this is very limiting and the value of inSampleSize must be in powers of 2.
I am working on an Android game that is pretty heavy in terms of image resources. One of the challenges that I am facing is that I need to manually scale the graphics to match the different screen sizes. The reason for not allowing Android to auto-scale is that the game is dependent upon the graphics not getting stretched or skewed. I will be potentially switching in 3-4 bitmaps per screen press, so performance is key (I already have a caching operation set up to help w/ this).
After considering the problem and the diversity of Android devices out there, I decided on the following approach to target as many devices as possible:
Make density-specific graphics (via http://developer.android.com/guide/topics/resources/providing-resources.html and the other suggestions listed in dev.android.com )
Rather than making many versions of an image for the different screen sizes, create one very large image that covers all target screens and use manual scaling to reduce its size to match the device.
So far, this approach is going well, but I need to squeeze more performance out of my custom BitmapScaler class. I have leveraged the code base found here to suit my own needs: http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/ .
With that being said, here are my questions: Does anyone have any comments on the viability of my approach for solving the screen destiny / screen size problem? I am aware that I am trading performance for convenience here. Can anyone suggest a way to squeeze out better performance in my bitmap scaling operation (Below)?
public class BitmapScaler {
private Bitmap scaledBitmap;
public Bitmap getScaledBitmap()
{
return scaledBitmap;
}
/* IMPORANT NOTES:
* The process of scaling bitmaps for Android is not a straightforward process. In order to
* help preserve memory on the phone, you need to go through several steps:
*
* 1. Decode the target resource, but use the inJustDecodeBounds option. This allows you to "sample"
* the resource without actually incurring the hit of loading the entire resource. If set to true, the decoder will return null
* (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate
* the memory for its pixels.
* 2. Determine the new aspects of your bitmap, particularly the scale and sample. If the sample size is set to a value > 1,
* requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number
* of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns
* an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1.
* Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what
* has been requested. Also, powers of 2 are often faster/easier for the decoder to honor.
* 3. Prescale the bitmap as much as possible, rather than trying to fully decode it in memory. This is a less
* expensive operation and allows you to "right size" your image.
* 4. Create your new bitmap, applying a matrix to "fine tune" the final resize.
*
* Partial Ref: http://zerocredibility.wordpress.com/2011/01/27/android-bitmap-scaling/
*/
public BitmapScaler(Resources resources, int targetResourceID, int targetWidth, int targetHeight)
{
BitmapInfo originalInfo = getOriginalBitmapInfo(resources, targetResourceID);
BitmapInfo newInfo = getScaledBitmapInfo(targetHeight, targetWidth, originalInfo);
prescaleScaledBitmap(resources, targetResourceID, newInfo);
scaleScaledBitmap(newInfo);
}
private void scaleScaledBitmap(BitmapInfo newInfo)
{
int ScaledHeight = scaledBitmap.getHeight();
int ScaledWidth = scaledBitmap.getWidth();
float MatrixWidth = ((float)newInfo.width) / ScaledWidth;
float MatrixHeight = ((float)newInfo.height) / ScaledHeight;
Matrix matrix = new Matrix();
matrix.postScale(MatrixWidth, MatrixHeight);
scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, ScaledWidth, ScaledHeight, matrix, true);
}
private void prescaleScaledBitmap(Resources resources, int targetResourceID, BitmapInfo newInfo)
{
BitmapFactory.Options scaledOpts = new BitmapFactory.Options();
scaledOpts.inSampleSize = newInfo.sample;
scaledBitmap = BitmapFactory.decodeResource(resources, targetResourceID, scaledOpts);
}
private BitmapInfo getOriginalBitmapInfo(Resources resources, int targetResourceID)
{
BitmapFactory.Options bitOptions = new BitmapFactory.Options();
bitOptions.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, targetResourceID, bitOptions);
return new BitmapInfo(bitOptions.outHeight,bitOptions.outWidth);
}
private BitmapInfo getScaledBitmapInfo(int targetHeight, int targetWidth, BitmapInfo originalBitmapInfo)
{
float HeightRatio = targetHeight / (float)originalBitmapInfo.height;
float WidthRatio = targetWidth / (float)originalBitmapInfo.width;
BitmapInfo newInfo = new BitmapInfo(0,0);
if (HeightRatio > WidthRatio)
{
newInfo.scale = WidthRatio;
newInfo.width = targetWidth;
newInfo.height = (int)(newInfo.scale * originalBitmapInfo.height);
} else {
newInfo.scale = HeightRatio;
newInfo.height = targetHeight;
newInfo.width = (int)(newInfo.scale * originalBitmapInfo.width);
}
newInfo.sample = 1;
int SampleHeight = originalBitmapInfo.height;
int SampleWidth = originalBitmapInfo.width;
while (true) {
if (SampleWidth / 2 < newInfo.width || SampleHeight / 2 < newInfo.height) {
break;
}
SampleWidth /= 2;
SampleHeight /= 2;
newInfo.sample *= 2;
}
return newInfo;
}
}
Thanks!