Android. How to scale images without memory exception? [closed] - android

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I have spent much time to understand how to scale images without Out Of Memory exception, and I want to share my knowledge, I wrote function that scale images(from big to small) to exactly size. The Algorithm is:
We scale image using quick algorihtm as match as possible, but image should still has sizes more then required.
We scale image to exactly result size.
The function is good commented, so it is easy to undrstand. If you will find errors or you know how make it more correct and more quick, please leave comment.

Enjoy!
//this function change image size(from big to small) using low memory(as possible)
//the bigger side of result image will equals to IMAGE_BIGGER_SIDE_SIZE
//and second side will be scaled proportionally
//for example if you have image 800x400 and call decodeBitmapSize(bm, 200)
//you will receive image 200x100
//algorithm:
//1. we scale image using quick algorihtm as match as possible,
// but image will still has sizes more then required
//2. we scale image to exactly result size
public static Bitmap decodeBitmapSize(Bitmap bm, int IMAGE_BIGGER_SIDE_SIZE){
//we will return this Bitmap
Bitmap b = null;
//convert Bitmap to byte[]
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
//We need to know image width and height,
//for it we create BitmapFactory.Options object and do BitmapFactory.decodeByteArray
//inJustDecodeBounds = true - means that we do not need load Bitmap to memory
//but we need just know width and height of it
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opt);
int CurrentWidth = opt.outWidth;
int CurrentHeight = opt.outHeight;
//there is function that can quick scale images
//but we need to give in scale parameter, and scale - it is power of 2
//for example 0,1,2,4,8,16...
//what scale we need? for example our image 1000x1000 and we want it will be 100x100
//we need scale image as match as possible but should leave it more then required size
//in our case scale=8, we receive image 1000/8 = 125 so 125x125,
//scale = 16 is incorrect in our case, because we receive 1000/16 = 63 so 63x63 image
//and it is less then 100X100
//this block of code calculate scale(we can do it another way, but this way it more clear to read)
int scale = 1;
int PowerOf2 = 0;
int ResW = CurrentWidth;
int ResH = CurrentHeight;
if (ResW > IMAGE_BIGGER_SIDE_SIZE || ResH > IMAGE_BIGGER_SIDE_SIZE) {
while(1==1)
{
PowerOf2++;
scale = (int) Math.pow(2,PowerOf2);
ResW = (int)((double)opt.outWidth / (double)scale);
ResH = (int)((double)opt.outHeight / (double)scale);
if(Math.max(ResW,ResH ) < IMAGE_BIGGER_SIDE_SIZE)
{
PowerOf2--;
scale = (int) Math.pow(2,PowerOf2);
ResW = (int)((double)opt.outWidth / (double)scale);
ResH = (int)((double)opt.outHeight / (double)scale);
break;
}
}
}
//Decode our image using scale that we calculated
BitmapFactory.Options opt2 = new BitmapFactory.Options();
opt2.inSampleSize = scale;
b = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opt2);
//calculating new width and height
int w = b.getWidth();
int h = b.getHeight();
if(w>=h)
{
w = IMAGE_BIGGER_SIDE_SIZE;
h =(int)( (double)b.getHeight() * ((double)w/b.getWidth()));
}
else
{
h = IMAGE_BIGGER_SIDE_SIZE;
w =(int)( (double)b.getWidth() * ((double)h/b.getHeight()));
}
//if we lucky and image already has correct sizes after quick scaling - return result
if(opt2.outHeight==h && opt2.outWidth==w)
{
return b;
}
//we scaled our image as match as possible using quick method
//and now we need to scale image to exactly size
b = Bitmap.createScaledBitmap(b, w,h,true);
return b;
}

Related

Out of Memory Bitmap Android

I used Volley library and I got this error. I used ListView to display. The application doesn't stop if I don't scroll down my list view.
I have read almost all threads about "OOM" in stackoverflow. I didn't find any useful. My application gets json string which is base 64 encoded image from web. I decode the string into byte array and I decode via bitmapfactory. I display the bitmap.
if you want to display multiple bitmap in one activity you should only display thumbnails of these bitmaps in order to avoid Out Of Memory Error. You could do something like this :
Bitmap getThumbnailFromBitmap(bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int max = Math.max(width, height);
if (max>512) {
int thumbWidth = Math.round((512f/max)* width);
int thumbHeight = Math.round((512f/max)* height);
Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, thumbWidth , thumbHeight);
bitamp.recycle();
return thumbnail ;
} else {
return bitmap ;
}
}

Android memory handling with Bitmap in Xamarin.Android

Fellow developer, i seek your help.
I have a problem which is memory related. I don't really know how to tackle this so i will just present my code snippets. Bear in mind that, although it is on Xamarin.Android, it will also apply to normal Android.
I use a library which just starts a camera intent. The library (or component) i am using is : http://components.xamarin.com/view/xamarin.mobile. That is not really relevant, but maybe you can point me to other insights why i should or shouldn't be using this library.
Anyway, to start the camera and capture the input. I use the following code :
private void StartCamera() {
var picker = new MediaPicker (this);
if (! picker.IsCameraAvailable) {
Toast.MakeText (this, "No available camera found!", ToastLength.Short).Show ();
} else {
var intent = picker.GetTakePhotoUI (new StoreCameraMediaOptions{
Name = "photo.jpg",
Directory = "photos"
});
StartActivityForResult (intent, 1);
}
}
The onActivityForResult() method is called when i return from this camera intent. In this method, i do the following :
protected override async void OnActivityResult (int requestCode, Result resultCode, Intent data)
{
// User canceled
if (resultCode == Result.Canceled)
return;
System.GC.Collect ();
dialog = new ProgressDialog (this);
dialog.SetProgressStyle (ProgressDialogStyle.Spinner);
dialog.SetIconAttribute (Android.Resource.Attribute.DialogIcon);
dialog.SetTitle (Resources.GetString(Resource.String.dialog_picture_sending_title));
dialog.SetMessage (Resources.GetString(Resource.String.dialog_picture_sending_text));
dialog.SetCanceledOnTouchOutside (false);
dialog.SetCancelable (false);
dialog.Show ();
MediaFile file = await data.GetMediaFileExtraAsync (this);
await ConvertingAndSendingTask ( file );
dialog.Hide();
await SetupView ();
}
Then, in my ConvertingAndSendingTask() i convert the picture into the desired dimensions with a scaled bitmap. The code is as follows :
public async Task ConvertingAndSendingTask(MediaFile file) {
try{
System.GC.Collect();
int targetW = 1600;
int targetH = 1200;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InJustDecodeBounds = true;
Bitmap b = BitmapFactory.DecodeFile (file.Path, options);
int photoW = options.OutWidth;
int photoH = options.OutHeight;
int scaleFactor = Math.Min(photoW/targetW, photoH/targetH);
options.InJustDecodeBounds = false;
options.InSampleSize = scaleFactor;
options.InPurgeable = true;
Bitmap bitmap = BitmapFactory.DecodeFile(file.Path, options);
float resizeFactor = CalculateInSampleSize (options, 1600, 1200);
Bitmap bit = Bitmap.CreateScaledBitmap(bitmap, (int)(bitmap.Width/resizeFactor),(int)(bitmap.Height/resizeFactor), false);
bitmap.Recycle();
System.GC.Collect();
byte[] data = BitmapToBytes(bit);
bit.Recycle();
System.GC.Collect();
await app.api.SendPhoto (data, app.ChosenAlbum.foreign_id);
bitmap.Recycle();
System.GC.Collect();
} catch(Exception e) {
System.Diagnostics.Debug.WriteLine (e.StackTrace);
}
}
Well, this method sends it good on newer devices with more memory but on lower end devices it ends up in a Out of memory error. Or anyway a nearly OOM. When Somethimes i goes well but when i want to take the second or third picture, it always ends up in OOM errors.
I realize that what i am doing is memory intensive. For example :
First i want the initial Width and Height of the original image.
Then it is sampled down (i don't really know if it is done well).
Then i load the sampled Bitmap into the memory.
When i loaded it into memory, my scaled bitmap has to be loaded in memory aswell prior to Recycle() the first Bitmap.
Ultimately i need a byte[] for sending the Bitmap over the web. But i need to convert it first prior to releasing my scaled Bitmap.
Then i release my scaled Bitmap and send the byte[].
Then as a final step, the byte[] needs to be released from memory aswell. I already do that on my BitmapToBytes() method as shown below, but i wanted to include it for maybe other insights.
static byte[] BitmapToBytes(Bitmap bitmap) {
byte[] data = new byte[0];
using (MemoryStream stream = new MemoryStream ())
{
bitmap.Compress (Bitmap.CompressFormat.Jpeg, 90, stream);
stream.Close ();
data = stream.ToArray ();
}
return data;
}
Does somebody sees any good parts where i can optimize this process? I know i am loading to much into memory but i can't think of another way.
It should be mentioned that i always want my images to be 1600x1200(Landscape) or 1200x1600(Portrait). I calculate that value in the following way :
public static float CalculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
float inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and
// width
float heightRatio = ((float) height / (float) reqHeight);
float widthRatio = ((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
if(height < reqHeight || width < reqWidth) {
// Calculate ratios of height and width to requested height and
// width
float heightRatio = ((float) reqHeight / (float) height);
float widthRatio = ((float) reqWidth / (float) width);
// Choose the smallest ratio as inSampleSize value, this will
// guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
Does anybody has any recommendations or an alternative workflow?
I would be so much helped with this!
This might be a very delayed reply but may helpful to somebody who got the same issue.
Use the the calculated InSampleSize value (option)to decode file to
bitmap. this itself generates the scaled down bitmap instead of using in CreateScaledBitmap.
If you are expecting High resolution image as output then it is
difficult to handle OutOfMemory issue. Because An image with a higher
resolution does not provide any visible benefit, but still takes up
precious memory and incurs additional performance overhead due to
additional scaling performed by the view.[xamarin doc]
Calculate the bitmap target width and height relating to the imageview height and width. you can calculate this by MeasuredHeight and MeasuredWidth Property. [Note: This works only after complete image draw]
Consider using async method to decode file instead running on main thread [DecodeFileAsync]
For more detail follow this http://appliedcodelog.blogspot.in/2015/07/avoiding-imagebitmap.html

How can I make my images consume less memory?

I am developing a game and I was working with the graphics, I run into some force closes. So my question is : How can we make images to consume less memory in android?
I´ll explain my game, it´s a logic game with a few small images and a background. I´m testing the app in a galaxy note, 1 GB of RAM, and I thought it could take the high resolution but if I use big image for the background, it force closes after going to the pause layout and back.
So I have lowered the graphics and done with no force closes. Anyway is there some way to avoid this memory issue? I´m setting the images directly on the xml is that wrong?
Solved
I have decided to go with this method, thanks to Durairaj Packirisamy for the answer
Here is my code:
Bitmap unscaledimgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on);
ImageView switch1 = (ImageView) findViewById(R.id.switch1);
int viewheight = screenheight / 10;
int imgheight = unscaledimgswitch.getHeight();
switch1.getLayoutParams().height = viewheight;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = ScaledFactor.getScaleFactor(imgheight ,viewheight); // de esta forma cargo la imagen del tamaño exacto necesario
options.inJustDecodeBounds = false;
imgswitch = BitmapFactory.decodeResource(getResources(), R.drawable.switch1on, options);
switch1.setImageBitmap(imgswitch);
And in an other class
static int getScaleFactor(int imgheight, int viewheight) {
int result;
result = imgheight / viewheight ;
return result;
}
You need to use a proper BitmapFactory.options.inSampleSize value, If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
Also have a look here:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

Loading multiple high quality bitmaps and scaling them

I've got a serious performance issue in my app, when loading up bitmaps it seems to take up way to much memory.
I have a drawable folder which contains the bitmap sizes for all android devices, these bitmaps are of high quality. Basically it goes though each bitmap and makes a new one for the device depending on the size. (Decided to do it this way because it supports the correct orientation and any device). It works but it's taking up way to much memory and takes along time to load. Can anyone make any suggestions on the following code.
public Bitmap getBitmapSized(String name, int percentage, int screen_dimention, int frames, int rows, Object params)
{
if(name != "null")
{
_tempInt = _context.getResources().getIdentifier(name, "drawable", _context.getPackageName());
_tempBitmap = (BitmapFactory.decodeResource(_context.getResources(), _tempInt, _BM_options_temp));
}
else
{
_tempBitmap = (Bitmap) params;
}
_bmWidth = _tempBitmap.getWidth() / frames;
_bmHeight = _tempBitmap.getHeight() / rows;
_newWidth = (screen_dimention / 100.0f) * percentage;
_newHeight = (_newWidth / _bmWidth) * _bmHeight;
//Round up to closet factor of total frames (Stops juddering within animation)
_newWidth = _newWidth * frames;
//Output the created item
/*
Log.w(name, "Item");
Log.w(Integer.toString((int)_newWidth), "new width");
Log.w(Integer.toString((int)_newHeight), "new height");
*/
//Create new item and recycle bitmap
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, false);
_tempBitmap.recycle();
return newBitmap;
}
There's an excellent guide over on the Android Training site:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
It's about efficient loading of bitmap images - highly recommended!
This will save space. If not using Alpha colors it would be better not to use one with the A channel.
Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// or Bitmap.Config.RGB_565 ;
// or Bitmap.Config.ARGB_4444 ;
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, options);

Android -- Map markers displaying differently (dip/px or decompression)

I'm having an odd problem with my map pin sizes. To preserve dynamic-ness, the map pins for different categories are stored on a site's server so that they can be changed at any point even after the app is published.
I'm caching the pins every time I download them and I only ever re-download them if the server sends back a bit saying that one has changed since last I downloaded it. The first time I grab the pins, I use the bitmaps before I save them to files and the map markers are the correct size. Every time after that I'm loading a saved version of the pins straight from the image file. These are displaying considerably smaller than they are when using the bitmaps from the first download.
At first, I thought it was a problem with the way I'm saving the PNGs, but their sizes are correct (64 x 64). Is this a dip/px issue or do I need to decompress the image files with some sort of option?
Here's how I grab the images the first time:
public static Bitmap loadMapPin(String category, int width, int height) {
URL imageUrl;
category = category.toLowerCase().replace(" ", "");
try {
imageUrl = new URL(PIN_URL+category+".png");
InputStream is = (InputStream) imageUrl.getContent();
Options options = new Options();
options.inJustDecodeBounds = true; //Only find the dimensions
//Decode without downloading to find dimensions
BitmapFactory.decodeStream(is, null, options);
boolean scaleByHeight = Math.abs(options.outHeight - height) >= Math.abs(options.outWidth - width);
if(options.outHeight * options.outWidth >= width * height){
// Load, scaling to smallest power of 2 that'll get it <= desired dimensions
double sampleSize = scaleByHeight
? options.outHeight / height
: options.outWidth / width;
options.inSampleSize =
(int)Math.pow(2d, Math.floor(
Math.log(sampleSize)/Math.log(2d)));
}
options.inJustDecodeBounds = false; //Download image this time
is.close();
is = (InputStream) imageUrl.getContent();
Bitmap img = BitmapFactory.decodeStream(is, null, options);
return img;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And here's how I'm loading them from the cached file:
BitmapFactory.decodeFile(filepath);
Thanks in advance!
I've found that, by default, decompressing an image to a bitmap doesn't scale with high density screens. You have to set the density to none. In other words, you specify that the image is meant for an unknown density.
Solution:
Bitmap b = BitmapFactory.decodeFile(filepath);
b.setDensity(Bitmap.DENSITY_NONE);

Categories

Resources