62

I decode bitmaps from the SD card using BitmapFactory.decodeFile. Sometimes the bitmaps are bigger than what the application needs or that the heap allows, so I use BitmapFactory.Options.inSampleSize to request a subsampled (smaller) bitmap.

The problem is that the platform does not enforce the exact value of inSampleSize, and I sometimes end up with a bitmap either too small, or still too big for the available memory.

From http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize:

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.

How should I decode bitmaps from the SD card to get a bitmap of the exact size I need while consuming as little memory as possible to decode it?

Edit:

Current source code:

BitmapFactory.Options bounds = new BitmapFactory.Options();
this.bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, bounds);
if (bounds.outWidth == -1) { // TODO: Error }
int width = bounds.outWidth;
int height = bounds.outHeight;
boolean withinBounds = width <= maxWidth && height <= maxHeight;
if (!withinBounds) {
    int newWidth = calculateNewWidth(int width, int height);
    float sampleSizeF = (float) width / (float) newWidth;
    int sampleSize = Math.round(sampleSizeF);
    BitmapFactory.Options resample = new BitmapFactory.Options();
    resample.inSampleSize = sampleSize;
    bitmap = BitmapFactory.decodeFile(filePath, resample);
}
hpique
  • 119,096
  • 131
  • 338
  • 476
  • 4
    Are you using only powers of 2? I can completely understand why something like this would prefer that, but I'm not sure I've ever seen an API reference like that - "we'll try to give you what you asked for, but it might just be something else entirely". – MusiGenesis Apr 14 '10 at 23:35
  • You also might want to exlicity set the outWidth and outHeight properties of your bitmap, in order to control the size. – MusiGenesis Apr 14 '10 at 23:37
  • I'm not using powers of 2. Powers of two are too limiting and the resulting bitmaps are either too small or too big. Maybe I should be using another API? – hpique Apr 14 '10 at 23:40
  • 3
    Heck, inSampleSize being an integer is already limiting. – hpique Apr 14 '10 at 23:43
  • outWidth and outHeight are "readonly". Setting them does not affect the decoding, it's the decoder who sets them. I tried. – hpique Apr 14 '10 at 23:47
  • I'm planning on dabbling in Android soon, but I don't know any more than what I read in your link. That API displeases me greatly, though. You can do all sorts of crazy low-level bitmap manipulation in Windows Mobile, so it's not a limitation of the devices at all. Is that API absolutely the only way to deal with images in Android, or are there lower-level options? – MusiGenesis Apr 16 '10 at 03:43
  • This API is a wrapper of native methods, so I believe this is a lower-level as you get using the SDK. Still, I wouldn't mind using something different as long as it works. :) – hpique Apr 16 '10 at 10:12
  • Code seems to be OK. Maybe you could also post a sample image that is not being scaled correctly? – Fedor Apr 23 '10 at 12:03
  • Has this worked for u?I'm facing the same issue...have u found a solution?Thanks – adrian Sep 02 '11 at 07:21
  • What if the image is coming from inputstream? – Anuj Dec 11 '14 at 11:09

11 Answers11

47

You are on the right track, however you are trying to do two things at once: read the file in and scale it to the appropriate size.

The first step is to read the file to a Bitmap slightly bigger than you require, using BitmapFactory.Options.inSampleSize to ensure that you do not consume excessive memory reading a large bitmap when all you want is a smaller thumbnail or screen resolution image.

The second step is to call Bitmap.createScaledBitmap() to create a new bitmap to the exact resolution you require.

Make sure you clean up after the temporary bitmap to reclaim its memory. (Either let the variable go out of scope and let the GC deal with it, or call .recycle() on it if you are loading lots of images and are running tight on memory.)

sirdodger
  • 1,044
  • 11
  • 7
18

You may want to use inJustDecodeBounds. Set it to TRUE and load the file as it is.

The image won't be loaded into memory. But the outheight and outwidth properties of BitmapFactory.Options will contain the actual size params of the image specified. Calculate how much u want to subsample it. i.e. 1/2 or 1/4 or 1/8 etc. and assign 2/4/8 etc. accordingly to the inSampleSize.

Now set inJustDecodeBounds to FALSE and call BitmapFactory.decodeFile() to load the image of the exact size as calculated above.

TheCodeArtist
  • 21,479
  • 4
  • 69
  • 130
  • That's what I'm doing already. The problem is not finding the right value for inSampleSize. The problem is that inSampleSize is not always honored by the platform, so I might get a smaller or larger bitmap than needed anyway. – hpique Apr 17 '10 at 17:09
  • inSampleSize ought to return U a sub-sampled image. i.e. the exact dimensions would depend on the original image U are loading. it would be half/quarter/one-eighth etc. relative to the original image. U could load the image-subsample thats closest to ur needs and appropriately stretch it while displaying. What are U doing next to display the image?? maybe i could help there. Hmmm??... – TheCodeArtist Apr 18 '10 at 05:40
  • That's exactly what I'm doing right now, and because inSampleSize is not always honored the results are not always optimal. I'm looking for an alternative, if possible. – hpique Apr 18 '10 at 15:09
  • what do U mean by non-optimal. Are the image being stretched too awkwardly or what??... – TheCodeArtist Apr 18 '10 at 15:32
  • No, the bitmap simply isn't of the requested size sometimes. It's either too big, or too small. If it's too big, the device might run out of memory. If it's too small, the resolution is below what's expected. – hpique Apr 18 '10 at 15:47
  • could U post ur code somewhere and link it here. I'll go thru it and get back to U ASAP... – TheCodeArtist Apr 18 '10 at 15:57
  • may i know what you are trying inside calculateNewWidth() function?? i.e. how does it calculate the newWidth param that it returns?? – TheCodeArtist Apr 23 '10 at 10:30
8

First you need to sample the image to nearest sampling value so that bitmap become memory efficient, which you have described perfectly. Once it's done you can scale it up to fit your screen.

// This function taking care of sampling
backImage =decodeSampledBitmapFromResource(getResources(),R.drawable.back, width, height);

// This will scale it to your screen width and height. you need to pass screen width and height.
backImage = Bitmap.createScaledBitmap(backImage, width, height, false); 
Ravi Bhojani
  • 1,032
  • 1
  • 13
  • 24
1
ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filesPath), THUMBSIZE, THUMBSIZE))

You can directly set the THUMBSIZE depending upon your requirement,however i am not sure about the lower level implementation details,output image quality and its performance but i usually prefer it when i need to show thumbnail.

Ankur Gautam
  • 1,412
  • 5
  • 15
  • 27
  • This can free the bitmap for you `ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(filesPath), THUMBSIZE, THUMBSIZE), ThumbnailUtils.OPTIONS_RECYCLE_INPUT)` – Arst Aug 07 '16 at 12:23
1

Since the API already states that the sample size may or may not be honored. So I guess ur out of luck while using BitmapFactory .

But the way out would be using your own bitmap reader. But I would suggest that you stick with BitmapFactory as its fairly well tested and standardized.

I would try and not worry too much about little extra memory consumption, but try and find out why its not honoring the values. Sadly I have not idea about that :(

the100rabh
  • 4,077
  • 4
  • 32
  • 40
  • Maybe there's an alternative using BitmapFactory, but in a different way. Is it possible to load a Bitmap in chunks, rescale them in memory and then join them? It would be slower, sure, but it would provide an exact result. – hpique Apr 18 '10 at 15:24
  • What do u want to optimize, space or resolution ?? – the100rabh Apr 18 '10 at 18:34
  • Resolution and heap memory are the main concerns. Obtaining the desired bitmap should take as little memory as possible (aside from the bitmap, of course). – hpique Apr 18 '10 at 22:47
  • I would suggest you to not worry too much as these libraries often have to use extra space to optimised for speed – the100rabh Apr 20 '10 at 18:42
1

For those who are looking for exact size image from resources.

For exact width pass reqHight=0 and for exact height pass reqWidth=0

public static Bitmap decodeBitmapResource(Context context, int resId, int reqWidth, int reqHeight)
{
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(context.getResources(), resId, options);
    float scale = 1;
    if (reqWidth != 0 && reqHeight == 0)
    {
        options.inSampleSize = (int) Math.ceil(options.outWidth / (float) reqWidth);
        scale = (float) options.outWidth / (float) reqWidth;
    }
    else if (reqHeight != 0 && reqWidth == 0)
    {
        options.inSampleSize = (int) Math.ceil(options.outHeight / (float) reqHeight);
        scale = (float) options.outHeight / (float) reqHeight;
    }
    else if (reqHeight != 0 && reqWidth != 0)
    {
        float x = (float) options.outWidth / (float) reqWidth;
        float y = (float) options.outHeight / (float) reqHeight;
        scale = x > y ? x : y;
    }
    reqWidth = (int) ((float) options.outWidth / scale);
    reqHeight = (int) ((float) options.outHeight / scale);
    options.inJustDecodeBounds = false;
    Bitmap tmp = BitmapFactory.decodeResource(context.getResources(), resId, options);
    Bitmap out = Bitmap.createScaledBitmap(tmp, reqWidth, reqHeight, false);
    tmp.recycle();
    tmp = null;
    return out;
}
Amit Padekar
  • 259
  • 1
  • 6
0

Since I use inSampleSize I never face out of memory issues any more. So inSampleSize works quite stable for me. I would recommend you to double check the issue. The size may be not exacly the same as you requested. But it should be reduced anyway and there should be no more "Out of memory". I would also recommend to post your code snippet here, maybe there's something wrong with it.

Fedor
  • 43,261
  • 10
  • 79
  • 89
0

As the100rabh said, if you use BitmapFactory you don't really have much choice. However, bitmap decoding is really simple, and depending on the scaling algorithm you want to use, it's usually fairly simple to scale it on-the-fly without having to read large parts of the original bitmap into memory (in general, you'll only need double the memory required for 1-2 lines of the original bitmap).

Tal Pressman
  • 7,199
  • 2
  • 30
  • 33
0

When inScaled flag is set (by default it is TRUE), if inDensity and inTargetDensity are not 0, the bitmap will be scaled to match inTargetDensity when loaded, rather than relying on the graphics system scaling it each time it is drawn to a Canvas. So simply set the inScaled to false will do.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
ucMedia
  • 1
  • 1
0

I was able to approximately get the "right size" by rescaling the decoded image file to 70% its bitmap size.

Bitmap myBitmap = BitmapFactory.decodeFile(path);
if(myBitmap != null)
{
  //reduce to 70% size; bitmaps produce larger than actual image size
  Bitmap rescaledMyBitmap = Bitmap.createScaledBitmap(
                                                  myBitmap, 
                                                  myBitmap.getWidth()/10*7, 
                                                  myBitmap.getHeight()/10*7, 
                                                  false);

  image.setImageBitmap(rescaledMyBitmap);
}                

I hope this helps you out somehow! Peace!

masarapmabuhay
  • 466
  • 1
  • 9
  • 15
0

Documentation is very self-explaining. Firstly I thought to make a small clip out of there but found out that full read is better for understanding and required for not making some fails with memory. For the last, it's pretty small.

RevakoOA
  • 601
  • 1
  • 9
  • 20