I want to scale down a 500x500px resource to fit always a specific size which is determined by the width of the screen.
Currently I use the code from the Android Developers Site (Loading Large Bitmaps Efficiently), but the quality is not as good as I would use the 500x500px resource in a ImageView (as source in xml) and just scale the ImageView and not the Bitmap.
But it's slow and I want to scale the Bitmap, too, to be memory efficient and fast.
Edit: The drawable which I wanna scale is in the drawable folder of my app.
Edit2: My current approaches.
The left image is the method from Loading Large Bitmaps Efficiently without any modifications. The center image is done with the method provided by #Salman Zaidi with this little modification: o.inPreferredConfig = Config.ARGB_8888; and o2.inPreferredConfig = Config.ARGB_8888;
The right image is an imageview where the image source is defined in xml and the quality I wanna reach with a scaled bitmap.
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..
Dudes, inSampleSize param is made for memory optimization, while loading a bitmap from resources or memory. So for your issue you should use this:
Bitmap bmp = BitmapFactory.decode...;
bmp = bmp.createScaledBitmap(bmp, 400, 400, false);
inSampleSizelets lets you to scale bitmap with descret steps. Scale ratios are 2,4 and so on. So when your use decoding with options, where inSampleSize=2 you loads a 250x250 bitmap from memory and then stretch it to 400x400
Check this training:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
It shows how to resize bitmaps efficiently
Related
I am developing a application where , there is a profile page where user can set its image via camera or from gallery.
Now when user sets large image then my app stops unfortunate , i found a reason for that is , we can't set image on image View exceeds some size.
So i decided to reduce image size before setting it on image view.
Here i found many solutions but in all that it also reduces its width and height, but i don't want to reduce its width and height.
I only want to reduce its size in percentage. E.x from 1 mb to 100 kb.
How can I achieve this in Android?
Try this way,hope this will help you to solve your problem.
public Bitmap decodeFile(String path) {
try {
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, o);
// The new size we want to scale to
final int REQUIRED_SIZE = 70;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while (o.outWidth / scale / 2 >= REQUIRED_SIZE && o.outHeight / scale / 2 >= REQUIRED_SIZE)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeFile(path, o2);
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
First off, I have read many posts and articles about out of memory exceptions but none of them have helped with my situation. What I'm trying to do is load an image from the sd card but scale it to an exact pixel size.
I first get the width and height of the image and calculate the sample size:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(backgroundPath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, getWidth(), getHeight());
Here's how I get the sample size (although its not really relevant):
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
// NOTE: we could use Math.floor here for potential better image quality
// however, this also results in more out of memory issues
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
Now that I have a sample size I load the image from disk to an approximate size (sample size):
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPurgeable = true;
Bitmap bmp = BitmapFactory.decodeFile(backgroundPath, options);
Now, I scale this bitmap that I have created to the exact size I need and clean up:
// scale the bitmap to the exact size we need
Bitmap editedBmp = Bitmap.createScaledBitmap(bmp, (int) (width * scaleFactor), (int) (height * scaleFactor), true);
// clean up first bitmap
bmp.recycle();
bmp = null;
System.gc(); // I know you shouldnt do this, but I'm desperate
The above step is usually get my out of memory exception. Does anyone know a way to load an exact size bitmap from disk to avoid having to create two separate bitmaps like above?
Also, it seems like more exceptions occur when the user runs this code for a second time (sets a new image). However, I make sure to unload the drawable that was created from the bitmap which allows it to be garbage collected before this code is run again.
Any suggestions?
Thanks,
Nick
In your case there's no need to create the intermediate bitmap after you've performed the first decode. Since you're drawing to to a Canvas, you can use either the following methods (whichever you find most convenient) to scale the image to the perfect size.
drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)
drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint)
Maybe this method would be helpful, I think I pulled it off of stackoverflow myself. It solved my out of memory exception issue.
private Bitmap decodeFile(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_SIZE=250;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_SIZE && o.outHeight/scale/2>=REQUIRED_SIZE)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
i am loading some bitmap from the gallery using the following code:
bitmap = (BitmapFactory.decodeFile(picturePath)).copy(Bitmap.Config.ARGB_8888, true);
bitmap = Bitmap.createScaledBitmap(bitmap, screenWidth, screenHeight, true);
bitmapCanvas = new Canvas(bitmap);
invalidate(); // refresh the screen
Question:
It seems that it takes so long time to load an image by first decode fully and copy, and then making scaling to fit for the screen width and height. It really actually does not need to load the pic with full density because I would not let the user to enlarge the imported image anyway.
In that way, are there any method to reduce the load time and RAM? (directly load a scaled-down image) How to further modify the above coding?
It may be worth trying RGB_565 instead of ARGB_8888 if you don't have transparency.
just have found the answer for this reducing RAM and load time and avoid outofmemory error from other similar questions.
//get importing bitmap dimension
Options op = new Options();
op.inJustDecodeBounds = true;
Bitmap pic_to_be_imported = BitmapFactory.decodeFile(picturePath, op);
final int x_pic = op.outWidth;
final int y_pic = op.outHeight;
//The new size we want to scale to
final int IMAGE_MAX_SIZE= (int) Math.max(DrawViewWidth, DrawViewHeight);
int scale = 1;
if (op.outHeight > IMAGE_MAX_SIZE || op.outWidth > IMAGE_MAX_SIZE)
{
scale = (int)Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(op.outHeight, op.outWidth)) / Math.log(0.5)));
}
final BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
//Import the file using the o2 options: inSampleSized
bitmap = (BitmapFactory.decodeFile(picturePath, o2));
bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
My Problem
I have a series of Bitmaps that I would like to load up in the correct orientation.
When I save the image I go in and set the orientation attribute using the ExifInterface
ExifInterface exif = new ExifInterface(EXTERNAL_IMAGE_PATH+File.separator+this._currentPhotoName+JPEG_FILE_SUFFIX);
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
Log.v("PhotoManager", "Rotation:"+rotation);
if (rotation > 0) {
exif.setAttribute(ExifInterface.TAG_ORIENTATION,String.valueOf(0));
This works fine and if I was to pull this image off of my device it would be in the correct orientation. However, when I then decode my Bitmap later down the line it stays in the camera's default orientation of left-horizontal even if the image was taken in portrait?
My Question
How can I decode the bitmap and take into account its EXIF information?
I don't want to have to rotate the image after I decode it every time as I would have to create another Bitmap and that is memory I don't have.
Thanks in advance.
For those that are also stuck on this and have oom issues when manipulating multiple bitmaps here is my solution.
Do not change the exif data like I originally thought in the question - We need this later down the line.
When it comes to decoding the image to view, instead of decoding the full size image just decode the image scaled down to what you need. The following code example contains both the decoding of the bitmap to the device screen size and then it also handles the rotation of the bitmap for you.
public static Bitmap decodeFileForDisplay(File f){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
DisplayMetrics metrics = MyApplication.getAppContext().getResources().getDisplayMetrics();
//The new size we want to scale to
//final int REQUIRED_SIZE=180;
int scaleW = o.outWidth / metrics.widthPixels;
int scaleH = o.outHeight / metrics.heightPixels;
int scale = Math.max(scaleW,scaleH);
//Log.d("CCBitmapUtils", "Scale Factor:"+scale);
//Find the correct scale value. It should be the power of 2.
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
Bitmap scaledPhoto = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
try {
ExifInterface exif = new ExifInterface(f.getAbsolutePath());
int rotation = CCDataUtils.exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL));
if (rotation > 0)
scaledPhoto = CCBitmapUtils.convertBitmapToCorrectOrientation(scaledPhoto, rotation);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return scaledPhoto;
} catch (FileNotFoundException e) {}
return null;
}
public static Bitmap convertBitmapToCorrectOrientation(Bitmap photo,int rotation) {
int width = photo.getWidth();
int height = photo.getHeight();
Matrix matrix = new Matrix();
matrix.preRotate(rotation);
return Bitmap.createBitmap(photo, 0, 0, width, height, matrix, false);
}
So the image Bitmap thats returned after calling decodeFileForDisplay(File f); is in the correct orientation and the correct size for you screen saving you tons of memory problems.
I hope it helps someone
How do I get a bitmap with a certain (memory friendly) size from the camera?
I'm starting a camera intent with:
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra("return-data", true);
photoUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "mytmpimg.jpg"));
cameraIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA);
I handle the result here:
// Bitmap photo = (Bitmap) intent.getExtras().get("data");
Bitmap photo = getBitmap(photoUri);
Now if I use the commented line - get the bitmap directly, I get always a 160 x 120 bitmap, and that's too small. If I load it from the URI using some standard stuff I found (method getBitmap), it loads a 2560 x 1920 bitmap (!) and that consumes almost 20 mb memory.
How do I load let's say 480 x 800 (the same size the camera preview shows me)?
Without having to load the 2560 x 1920 into memory and scaling down.
Here is what I came up with, based on a method called getBitmap() from a crop library which was removed from old Android version. I did some modifications:
private Bitmap getBitmap(Uri uri, int width, int height) {
InputStream in = null;
try {
int IMAGE_MAX_SIZE = Math.max(width, height);
in = getContentResolver().openInputStream(uri);
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, o);
in.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//adjust sample size such that the image is bigger than the result
scale -= 1;
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
in = getContentResolver().openInputStream(uri);
Bitmap b = BitmapFactory.decodeStream(in, null, o2);
in.close();
//scale bitmap to desired size
Bitmap scaledBitmap = Bitmap.createScaledBitmap(b, width, height, false);
//free memory
b.recycle();
return scaledBitmap;
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
return null;
}
What this does is load the bitmap using BitmapFactory.Options() + some sample size - this way the original image is not loaded into memory. The problem is that the sample size just works in steps. I get the "min" sample size for my image using some maths I copied - and subtract 1 in order to get the sample size which will produce the min. bitmap bigger than the size I need.
And then in order to get the bitmap with exactly the size requested do normal scaling with Bitmap.createScaledBitmap(b, width, height, false);. And immediatly after it recycle the bigger bitmap. This is important, because, for example, in my case, in order to get 480 x 800 bitmap, the bigger bitmap was 1280 x 960 and that occupies 4.6mb memory.
A more memory friendly way would be to not adjust scale, so a smaller bitmap will be scaled up to match the required size. But this will reduce the quality of the image.