Most memory efficient way to resize bitmaps on android?
This answer is summarised from Loading large bitmaps Efficiently
which explains how to use inSampleSize to load a down-scaled bitmap
version.In particular Pre-scaling bitmaps explains the details of various
methods, how to combine them, and which are the most memory efficient.
There are three dominant ways to resize a bitmap on Android which have different memory properties:
createScaledBitmap API
This API will take in an existing bitmap, and create a NEW bitmap with the exact dimensions you’ve selected.
On the plus side, you can get exactly the image size you’re looking for (regardless of how it looks). But the downside, is that this API requires an existing bitmap in order to work. Meaning the image would have to be loaded, decoded, and a bitmap created, before being able to create a new, smaller version. This is ideal in terms of getting your exact dimensions, but horrible in terms of additional memory overhead. As such, this is kind-of a deal breaker for most app developers who tend to be memory conscious
inSampleSize flag
BitmapFactory.Options
has a property noted as inSampleSize
that will resize your image while decoding it, to avoid the need to decode to a temporary bitmap. This integer value used here will load an image at a 1/x reduced size. For example, setting inSampleSize
to 2 returns an image that’s half the size, and Setting it to 4 returns an image that’s 1/ 4th the size. Basically image sizes will always be some power-of-two smaller than your source size.
From a memory perspective, using inSampleSize
is a really fast operation. Effectively, it will only decode every Xth pixel of your image into your resulting bitmap. There’s two main issues with inSampleSize
though:
It doesn’t give you exact resolutions. It only decreases the size of your bitmap by some power of 2.
It doesn’t produce the best quality resize. Most resizing filters produce good looking images by reading blocks of pixels, and then weighting them to produce the resized pixel in question.
inSampleSize
avoids all this by just reading every few pixels. The result is quite performant, and low memory, but quality suffers.
If you're only dealing with shrinking your image by some pow2 size, and filtering isn't an issue, then you can't find a more memory efficient (or performance efficient) method than inSampleSize
.
inScaled, inDensity, inTargetDensity flags
If you need to scale an image to a dimension that’s not equal to a power of two, then you’ll need the inScaled
, inDensity
and inTargetDensity
flags of BitmapOptions
. When inScaled
flag has been set, the system will derive the scaling value to apply to your bitmap by dividing the inTargetDensity
by the inDensity
values.
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
Using this method will re-size your image, and also apply a ‘resizing filter’ to it, that is, the end result will look better because some additional math has been taken into account during the resizing step. But be warned: that extra filter step, takes extra processing time, and can quickly add up for big images, resulting in slow resizes, and extra memory allocations for the filter itself.
It’s generally not a good idea to apply this technique to an image that’s significantly larger than your desired size, due to the extra filtering overhead.
Magic Combination
From a memory and performance perspective, you can combine these options for the best results. (setting the inSampleSize
, inScaled
, inDensity
and inTargetDensity
flags)
inSampleSize
will first be applied to the image, getting it to the next power-of-two LARGER than your target size. Then, inDensity
& inTargetDensity
are used to scale the result to exact dimensions that you want, applying a filter operation to clean up the image.
Combining these two is a much faster operation, since the inSampleSize
step will reduce the number of pixels that the resulting Density-based step will need to apply it’s resizing filter on.
mBitmapOptions.inScaled = true;
mBitmapOptions.inSampleSize = 4;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth * mBitmapOptions.inSampleSize;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeFile(fileName, mBitmapOptions);
If you're needing to fit an image to specific dimensions, and some nicer filtering, then this technique is the best bridge to getting the right size, but done in a fast, low-memory footprint operation.
Getting image dimensions
Getting the image size without decoding the whole image
In order to resize your bitmap, you’ll need to know the incoming dimensions. You can use the inJustDecodeBounds
flag to help you get the dimensions of the image, w/o needing to actually decode the pixel data.
// Decode just the boundaries
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fileName, mBitmapOptions);
srcWidth = mBitmapOptions.outWidth;
srcHeight = mBitmapOptions.outHeight;
//now go resize the image to the size you want
You can use this flag to decode the size first, and then calculate the proper values for scaling to your target resolution.
Memory efficient image resize in Android
When you decode the bitmap with the BitmapFactory, pass in a BitmapFactory.Options object and specify inSampleSize. This is the best way to save memory when decoding an image.
Here's a sample code Strange out of memory issue while loading an image to a Bitmap object
How to make a bitmap take less memory on Android?
It is not possible without reducing image dimensions.
All images with the same dimensions require same amount of RAM, regardless it size on disk and compression. Graphics adapter don't understand different image types and compression and it needs only uncompressed raw array of pixels. And it size is constant
For example
size = width * height * 4; // for RGBA_8888
size = width * height * 2; // for RGB_565
So you should reduce image dimensions or use caching on the disk and remove bitmaps from the RAM that are currently invisible and reload from disk when needed.
Handling large Bitmaps
There is an option in BitmapFactory.Options
class (one I overlooked) named inJustDecodeBounds
, javadoc of which reads:
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.
I used it to find out the actual size of the Bitmap and then chose to down sample it using inSampleSize
option. This at least avoids any OOM errors while decoding the file.
Reference:
1. Handling larger Bitmaps
2. How do I get Bitmap info before I decode
How to Resize a Bitmap in Android?
Change:
profileImage.setImageBitmap(
BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.length)
To:
Bitmap b = BitmapFactory.decodeByteArray(imageAsBytes, 0, imageAsBytes.length)
profileImage.setImageBitmap(Bitmap.createScaledBitmap(b, 120, 120, false));
Scale down Bitmap from resource in good quality and memory efficient
private Bitmap decodeImage(File f) {
Bitmap b = null;
try {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
float sc = 0.0f;
int scale = 1;
//if image height is greater than width
if (o.outHeight > o.outWidth) {
sc = o.outHeight / 400;
scale = Math.round(sc);
}
//if image width is greater than height
else {
sc = o.outWidth / 400;
scale = Math.round(sc);
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
} catch (IOException e) {
}
return b;
}
Here '400' is the new width (in case image is in portrait mode) or new height (in case image is in landscape mode). You can set the value of your own choice.. Scaled bitmap will not take much memory space..
Is there any efficient way in Android to downscale large image from file and save it to new file without OOM exception
There is a class in the Android framework that can be used to decode only small regions of an image at a time: BitmapRegionDecoder
. As @emandt mentioned, resizing it this way may give you some undesirable results, although you could experiment with picking different region sizes (perhaps some multiple of the rescale percentage could improve the results). Depending on your requirements these artifacts may not be a problem.
Resize bitmaps vs multiple Images folders for Android resolutions
Android does it's best to resize images when missing in the proper folder.
For quality sake you should alway scale down, not up.
So, it could be enough if you use the highest resolution image in its folder.
Better if you use it the proper dpi for that resolution. i.e.: 480 dpi for a drawable-xxhdpi image.
Now here is my way to manage graphics.
I prefer using as little images as possible, ad put them in each folder (each at proper dpi, since I really dislike 72 dpi images).
For instance, to fill a background, I use tiled bitmaps instead of full size ones, which saves me a lot of bytes.
I also take advantage of gradients and layer-lists, to combine a tiled image and a gradient, so to say.
I also use drawables (which are scalable, since they are drawn at runtime) and 9 patches (these ones are selectively stretchable).
And I also take advantage of Unicode glyphs (the few that Android supports): they are scalable by definition, since they are part of a TTF font.
Sometimes I also use SVG graphics (which are vectorial), through the use of an external library.
The other side of the medal is that you better keep the images simple, since they are stored in memory and you have to calculate the size in pixels when creating the bitmaps from them.
And sometimes the library isn't very fast and optimized, so it may introduce some lags.
I think I've been quite logorroic, but I gave you a rough idea about how to design an UI that can be nice but not necessarily heavy.
Related Topics
Where Do I Create and Use Scheduledthreadpoolexecutor, Timertask, or Handler
Android: Scrollview VS Nestedscrollview
Menuitem Tinting on Appcompat Toolbar
How to Change Toolbar Home Icon Color
Highlighting Text Color Using HTML.Fromhtml() in Android
How to Align Android Toolbar Menu/Icons to the Left Like in Google Maps App
How to Show a Marker in Maps Launched by Geo Uri Intent
Android Fragments: When to Use Hide/Show or Add/Remove/Replace
Start Activity with an Animation
How to Retrieve Key Alias and Key Password for Signed APK in Android Studio(Migrated from Eclipse)
How to Calculate Dp from Pixels in Android Programmatically
Android Imageview Adjusting Parent's Height and Fitting Width
Gradle and Multi-Project Structure
Detect If App Was Downloaded from Android Market
Android 4.3: How to Connect to Multiple Bluetooth Low Energy Devices
How to Display More Than 3- Levels of Expandable List View
Android Java.Lang.Illegalstateexception: Couldn't Read Row 0, Col 0 from Cursorwindow