I have a problem that I have 80 to 150 images of very High Resolution and I have to show them in a view pager. I had written the simple mechanism of decoding but get OutofMemory Error after the 2nd Page. I tried very much but unable to find the exact solution to avoid that. My own suggestion is that if we will able to load a image in parts on an Image View then perhaps we avoid this but I don't know how to achieve this. Please suggest any solution regarding this.
Code:
private class MyPagerAdapter extends PagerAdapter {
#Override
public int getCount() {
return Integer.parseInt(pagesCount);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0==((ImageView)arg1);
}
#Override
public void destroyItem(View container, int position, Object object) {
View view = (View)object;
((ViewPager) container).removeView(view);
view = null;
}
#Override
public View instantiateItem(View container, final int position) {
final ImageView imgPage = new ImageView(getApplicationContext());
/*imgPage.setImageURI(Uri.withAppendedPath(
Uri.parse(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName), "" + (issueName+position)+".png"));*/
//imgPage.setImageBitmap(BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+issueName+"/"+(issueName+position)));
//destroyItem(container, position, imgPage);
imgPage.setScaleType(android.widget.ImageView.ScaleType.FIT_XY);
imgPage.setImageDrawable(getImageFromSdCard(issueName+position));
//imgPage.setImageResource(getImageFromSdCard(issueName+position));
((ViewPager) container).addView(imgPage,0);
return imgPage;
}
}
public Drawable getImageFromSdCard(String imageName) {
Drawable d = null;
try {
String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
bitmap = BitmapFactory.decodeFile(path + "/" + imageName
+ ".png");
// d = new BitmapDrawable(bitmap);
d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
// bitmap.recycle();
bitmap = null;
} catch (IllegalArgumentException e) {
bitmap = null;
}
return d;
}
//decodes image and scales it to reduce memory consumption
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=550;
//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;
}
Thanks in advance.
inJustDecodeBounds & inSampleSize
You are decoding high resolution images at each PagerAdapter call, and if you are willing to reduce the decoded bitmap memory by powers of 2 (x/2, x/4 ... x : original bitmap size), then go for this method
Bitmap.recycle()
A very handy method, when used at the right place, can work wonders. I am assuming that you are setting a BitmapDrawable or calling setImageBitmap to an ImageView. Add the following snippet in destroyItem() callback of PagerAdapter.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
View view = (View) object;
ImageView imageView = (ImageView) view.findViewById(R.id.image_view);
Drawable drawable = imageView.getDrawable();
if(drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable != null) {
Bitmap bitmap = bitmapDrawable.getBitmap();
if(bitmap != null && !bitmap.isRecycled()) bitmap.recycle();
}
}
((ViewPager) container).removeView(view);
}
The idea is that when any child view is removed from Pager, its bitmap should get recycled, that way you would not depend on GC calls to clear memory for you.
largeHeap = true
Add this property in Manifest.xml <application> tag. Wherever largeHeap is supported, it will increase heap memory to some large value, even goes till 8 times.
Also, in general don't hold any silly references to any bitmap, just decode them and assign it to View, rest will be taken care of.
Hope that helps. :)
I'll suggest you to look at official android developers sample code called BitmapFun and use it for your purpose. Actually you missed o2.inPurgable=true; Also there is no need to use o and o2, o is good enough.
//decodes image and scales it to reduce memory consumption
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=550;
//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();
o.inSampleSize=scale;
o.inJustDecodeBounds = false;
o.inPurgealbe = true;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o);
} catch (FileNotFoundException e) {}
return null;
}
Remove the line bitmap = BitmapFactory.decodeFile(path + "/" + imageName
+ ".png"); from your Drawable getImageFromSdCard(String imageName) method to begin with. Also remove the bitmap variable in that method since it´s unused, now you have removed unnecessary allocations, the problem you experience could be that the GC simply does not cope with the amount of garbage / allocations that was made since you did twice as many that was needed. The method should look something like:
public Drawable getImageFromSdCard(String imageName) {
Drawable d = null;
String path = Environment.getExternalStorageDirectory()+"/MAGZ/"+magName+"/"+magName+issueName;
d = new BitmapDrawable(decodeFile(new File(path+"/"+imageName+".png")));
return d;
}
Also reuse your o variable in decodeFile method just remember to flip the boolean inJustDecodeBounds after the first call.
Also do not catch runtime exceptions, bad practice, they're there because of just runtime errors. You´re hiding the real problem by catching them.
BitmapDrawable(Bitmap bitmap) constructor has been deprecated since api version 4, checkout BitmapDrawable for what constructor you should be using.
Related
I'm implementing ListView with Custom Adapter that extends BaseAdapter. In my app images are downloaded from URLs and then set as bitmap images in Listview.
The problem is that after complete downloading of 2 images java.Lang.OutOfMemoryError occurs and app crashes. I know this is memory issue but I don't know what steps should I take to avoid this... I worked with compressed images and they worked fine.
Below is my Code for getView() of CustomAdapter and doInBackground() of AsyncTask.
Thankx in advance...
Any suggestions will be appreciated..
public View getView(final int position, View convertView, final ViewGroup parent) {
if (convertView == null){
convertView = inflater.inflate(R.layout.row, parent, false);
}
img_name = "test" + position;
image = (ImageView) convertView.findViewById(R.id.imageView1);
Log.d("URL", ""+values[position]);
AsyncTaskRunner runner = new AsyncTaskRunner();
final int x = (int) getItemId(position);
runner.execute(values[x] , image , img_name);
return convertView;
}
doInBackground(Object... params)
protected ImageView doInBackground(Object... params) {
//publishProgress("Calculating..."); // Calls onProgressUpdate()
URL imageURL = null;
try {
url = (String) params[0];
img = (ImageView) params[1];
name = (String) params[2] + ".png";
imageURL = new URL(url);
Log.d("URL", ""+params[0]);
}
catch (MalformedURLException e) {
e.printStackTrace();
}
try {
HttpURLConnection connection= (HttpURLConnection)imageURL.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream inputStream = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(inputStream);
resized = Bitmap.createScaledBitmap(bitmap, 200, 200, true);
}
catch (IOException e) {
e.printStackTrace();
}
return img;
}
OnPostExecute(ImageView result)
protected void onPostExecute(ImageView result) {
result.setImageBitmap(resized);
}
java.Lang.OutOfMemoryError
Reason:
Each process in android is assigned a max heap size which varies from
device to device.(approx. 16 MB on avg.).And high resolution images when used in application occupies large space of this heap.So when a new instance of the bitmap is created and if total size exceeds the allocated heap size, this is error is thrown by the JVM.
Solution:
Android provide a way to handle this problem.Before decoding bitmap we just decode it with options.inJustDecodeBounds = true. where options is the instance of BitmapFactory. It does not load bitmap into memory but it help us to find the width and height of a bitmap so that we can reduce the height and width according to our device.
Then down-scale your bitmap to a create a smaller sized image which would in turn take up less space on the heap.
Here is the way to do it.
BitmapFactory.Options bmpBuffer = new BitmapFactory.Options();
bmpBuffer.inSampleSize = 3;
Bitmap bmp = BitmapFactory.decodeFile(path, bmpBuffer);
This would make your bitmaps 1/3 rd of the original size and hence
would take up 1/3rd space as well.
Eg.
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);
}
Method to Calculate sample size:
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;
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;
}
Note:
One additional thing you can do is use following ,in case there is no other option available. However I want to caution you that it would affect other applications on your device. Hence I would not recommend you to use this.
<application
android:largeHeap="true">
</application>
largeHeap="true" will allow the application to use more heap if it is available.However your app will spend more time during garbage collection.Other apps on the device might get kicked out of memory.
CommonsWare has explained it here
You can use Picasso Library to download images from a url efficiently besides having more options to resize image size, cache, etc. ..
use this bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
public static boolean rotateBitmapByExifAndSave(File targetFile){
if (targetFile==null || !targetFile.exists() || !targetFile.canRead() || !targetFile.canWrite())
return false;
boolean isSucceed = false;
// detect if photo is need to be rotated
try {
final Matrix matrix = new Matrix();
ExifInterface exifReader = new ExifInterface(targetFile.getAbsolutePath());
int orientation = exifReader.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
boolean isRotationNeeded = true;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.postRotate(90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.postRotate(180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.postRotate(270);
break;
default: // ExifInterface.ORIENTATION_NORMAL
// Do nothing. The original image is fine.
isRotationNeeded = false;
isSucceed = true;
break;
}
if (isRotationNeeded){
BitmapFactory.Options bmfOtions = new BitmapFactory.Options();
Bitmap bitmap = null;
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(targetFile);
bitmap = BitmapFactory.decodeStream(fileInputStream,null,bmfOtions);
} catch (FileNotFoundException e){
isSucceed = false;
}
finally {
if (fileInputStream != null)
try {
fileInputStream.close();
} catch (IOException e) {}
}
if (bitmap!=null){
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
isSucceed = ImageUtils.saveBitmapToFile(bitmap, targetFile, 100);
bitmap.recycle();
}
}
} catch (IOException e) {
Log.e("ImageUtils", e);
} catch (Exception e) {
// like there is no EXIF support?
Log.e("ImageUtils", e);
} catch (Throwable e) {
// stupid Out of VM's memory
Log.e("ImageUtils", e.toString());
}
return isSucceed;
}
I use this method to rotate original photos made by device's camera. Nowadays camera could be bigger than 8MPix (Samsung Galaxy S4 has 13 Mega pixel camera). And even with less MPix camera (mine is 5 MP, 2592 x 1944 pixels which in conjunction of ARGB_888 takes 19Mb of RAM according to official docs) I already got OutOfMemory. So the question is how to rotate the photo WITHOUT loss of it's initial resolution and thus quality?
Since there was no answer I assume there is no answer or maybe I just had asked the question a bit incorrectly. It looks like the only option here is to increase the app's heap size
UPDATE:
There is also another option - to work with bitmaps via NDK/JNI like here or to use Android Image-Magic lib. The Image Magic lib is pretty cool, to rotate an image all you need is:
ImageInfo imageInfo = new ImageInfo(imageFile.getAbsolutePath());
MagickImage magickImage = new MagickImage(imageInfo);
magickImage.setCompression(100); // to minimize loss
magickImage.rotateImage(90.0f).writeImage(imageInfo);
MagickImage has many other image manipulating options as well. Blur, matte, scale, charcoal and many more. However its libraries size is noticable. Authors made a great job and they covered all possible plaforms: arm64-v8a, armeabi, armeabi-v7a, mips, mips64, x86, x86_64 and final size of all these libs is over 36Mb. So you should think before adding all the libs into one apk, maybe packaging 6 different versions using manifest to filter by chipset/platform is the right way.
UPDATE
Another option is to convert Immutable Bitmap into Mutable (wrap bitmaps into MappedByteBuffer)
Make a method name decode file:
public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){
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_WIDTH=WIDTH;
final int REQUIRED_HIGHT=HIGHT;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
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;
}
then call this method like this (You can call this method in button click listener)
Bitmap bi = decodeFile(new File(path),1280,800);
Where path is the path of image where you save your image..
in my case it is
String path = Environment.getExternalStorageDirectory().toString() + "/nature.jpg";
In case of any problem - ask :) Hope this helps.
I've got very critical problem.
Only Android 4.1, Bitmap is recycled automatically!
I didn't call recycle() in my code!
My project works fine in other OS versions( ~ 4.0.3) with any resolutions.
Other projects have same problem, too.
All image files are in drawable-nodpi folder.
I resized them to fit for resolution of any devices, always.
public Bitmap GetBitmap(int resource){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither = true;
options.inPurgeable = true;
Bitmap tmp = null;
try{
tmp = BitmapFactory.decodeResource(mResources, resource, options);
}catch(OutOfMemoryError e){
options.inSampleSize = 2;
tmp = BitmapFactory.decodeResource(mResources, resource, options);
}
return tmp;
}
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter){
Bitmap tmp = GetBitmap(resource);
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
tmp.recycle();
tmp = null;
return img;
}
In my testing,
Same bitmap instance, but the problem occurs depending on resizing value.
ex)
int width = 100;
Bitmap imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width , true); -> returns recycled instance.
width = 200;
imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width, true); -> returns normal instance.
In different resolutions, imgStar works fine, but the problem occurs in other bitmap instance.
Similarly, When I change resizing value, it works fine.
In same resolution, the problem occurs in other bitmap instance, if I change the name of image files folder.
drawable-nodpi -> drawable -> drawable-ldpi, ..., drawable-xdpi.
Same resizing value, it works fine if I put other resource id.
ex)
int width = 100;
Bitmap imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.star, width, width , true); -> returns recycled instance.
imgStar = MyResourceManager.getInstance().GetScaledBitmap(R.drawable.diamond, width, width, true); -> returns normal instance.
Please... what can I do?! T ^ T
The reason you're getting different results from different sizes may be because createScaledBitmap will return the original object if it is the same size you are scaling to.
I had the same problem, doing the same thing you are. I was able to fix it this way:
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter) {
Bitmap tmp = GetBitmap(resource);
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
//copy the image to be sure you are not using the same object as the tmp bitmap
img=img.copy (Bitmap.Config.RGB_565,false);
tmp.recycle();
tmp = null;
return img;
}
Here I copied the bitmap to make sure it wasn't just a refernce to the tmp bitmap object before I recycled the tmp bitmap. Of course you can use any bitmap config you need.
I believe XdebugX was correct in his finding although you don't need to create a copy. Simply check if the memory locations are the same between your resized and original bitmaps.
public Bitmap GetScaledBitmap(int resource, int width, int height, boolean filter){
Bitmap tmp = GetBitmap(resource);
if (tmp == null) {
return null;
}
Bitmap img = Bitmap.createScaledBitmap(tmp, width, height, filter);
/***
* Bitmap#createScaledBitmap will return the original object
* if it is the same size you are scaling to.
*/
if (tmp != img) {
Log.d(TAG, "Full size image recycled");
tmp.recycle();
} else {
Log.w(TAG, "Resized bitmap was the same as the fullsize bitmap");
}
return img;
}
I'd rather suggest to try checking if the bitmap is already recycled before to recycle:
if (!tmp.isRecycled()) tmp.recycle();
I am trying to load some very small images (average size is 90kb) into a gridview in Android. Whenever I load more than 9 images then I am getting memory issues. I have tried scaling the images to a smaller size and although this works to a certain extent it is not really a true solution as the picture quality is awful.
The code is below
private Context mContext;
private ArrayList<Bitmap> photos = new ArrayList<Bitmap>();
public Bitmap [] mThumbIds;
public ImageAdapter(Context c) {
mContext = c;
}
public Object getItem(int position) {
return null;
}
public long getItemId(int position) {
return 0;
}
public Bitmap scaleBitmap(String imagePath) {
Bitmap resizedBitmap = null;
try {
int inWidth = 0;
int inHeight = 0;
InputStream in;
in = new FileInputStream(imagePath);
// decode image size (decode metadata only, not the whole image)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, options);
in.close();
in = null;
// save width and height
inWidth = options.outWidth;
inHeight = options.outHeight;
// decode full image pre-resized
in = new FileInputStream(imagePath);
options = new BitmapFactory.Options();
// calc rought re-size (this is no exact resize)
options.inSampleSize = Math.max(inWidth/300, inHeight/300);
// decode full image
Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);
// calc exact destination size
Matrix m = new Matrix();
RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
RectF outRect = new RectF(0, 0, 300, 300);
m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
float[] values = new float[9];
m.getValues(values);
// resize bitmap
resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return resizedBitmap;
}
public void populateGrid() {
File sdDir = new File("mnt/sdcard/Pictures");
File[] sdDirFiles = sdDir.listFiles();
for(File singleFile : sdDirFiles) {
String filePath = singleFile.getAbsolutePath();
Bitmap bmp = scaleBitmap(filePath);
photos.add(bmp);
}
mThumbIds = photos.toArray(new Bitmap[(photos.size())]);
}
// create a new ImageView for each item referenced by the Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView;
if (convertView == null) { // if it's not recycled, initialize some attributes
imageView = new ImageView(mContext);
imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(8, 8, 8, 8);
} else {
imageView = (ImageView) convertView;
}
imageView.setImageBitmap(mThumbIds[position]);
return imageView;
}
#Override
public int getCount() {
return mThumbIds.length;
}
}
Two considerations:
The 90kb compressed image size doesn't really matter. The memory use is dictated by the actual resolution of the bitmap -- in this case, 300*300*4bpp, so about 360k per bitmap.
Gingerbread has some flaws because of the combination of the fact that Bitmap memory is stored in a native array (rather than on the Java heap) combined with the fact that garbage collection occurs concurrently. Because of this fact, it sometimes takes the memory manager longer to realize that Bitmap memory can be re-used.
So, the ramifications of this are:
Consider the actual decompressed Bitmap size when estimating memory usage.
Recycle intermediate bitmaps as much as possible, to help get the memory reclaimed faster. For example, if you are scaling a bitmap, save a reference to the pre-scaled source, and recycle the source after scaling is complete (compare to the result of the scaling, since it's possible that the same Bitmap is returned).
If you can, test your code on an ICS device. You can then use the heap inspection tools to get a sense about where the most memory is being used. Sine ICS allocates bitmap memory on the Java heap, you'll have an accurate picture of the bitmap memory usage.
Bitmap use a lot of memory, if you have the image saved into memory already it would probably be better to just use the path of the file and push it on an ImaveView.
ImageView img = new ImageView(getApplicationContext());;
img.setImageBitmap(BitmapFactory.decodeFile(media.getThumbPath()));
img.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
myLinearLayout.addView(img);
Something like this might work better. this way you are not storing all the Bitmaps into your heap.
So i've got a Uri of an image the user chooses out of images off his SD card. And i'd like to display a thumbnail of that image, because obviously, the image could be huge and take up the whole screen. Anyone know how?
You can simply create thumbnail video and image using ThumnailUtil class of java
Bitmap resized = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(file.getPath()), width, height);
public static Bitmap createVideoThumbnail (String filePath, int kind)
Added in API level 8
Create a video thumbnail for a video. May return null if the video is corrupt or the format is not supported.
Parameters
filePath the path of video file
kind could be MINI_KIND or MICRO_KIND
For more Source code of Thumbnail Util class
Developer.android.com
This code will do the job:
Bitmap getPreview(URI uri) {
File image = new File(uri);
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inJustDecodeBounds = true;
BitmapFactory.decodeFile(image.getPath(), bounds);
if ((bounds.outWidth == -1) || (bounds.outHeight == -1))
return null;
int originalSize = (bounds.outHeight > bounds.outWidth) ? bounds.outHeight
: bounds.outWidth;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = originalSize / THUMBNAIL_SIZE;
return BitmapFactory.decodeFile(image.getPath(), opts);
}
You may want to calculate the nearest power of 2 to use for inSampleSize, because it's said to be faster.
I believe this code is fastest way to generate thumbnail from file on SD card:
public static Bitmap decodeFile(String file, int size) {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file, o);
//Find the correct scale value. It should be the power of 2.
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = (int)Maths.pow(2, (double)(scale-1));
while (true) {
if (width_tmp / 2 < size || height_tmp / 2 < size) {
break;
}
width_tmp /= 2;
height_tmp /= 2;
scale++;
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeFile(file, o2);
}
A few trying I could not get the thumbnail path of image from SD.
i am resolved this problem getting an android image bitmap, before I create an image view in adapter for gridview (or where you need). So i call method imageView.setImageBitmap(someMethod(Context context, imageID))
Bitmap someMethod(Context context, long imageId){
Bitmap bitmap = Media.Images.Thumbnails.getThumbnail(context.getAplicationContext.getContentResolver(), imageid, MediaStore.Images.Thumbnails.MINI_KIND, null);
return bitmap;
}
You can get image ID from your SD using this guide (Get list of photo galleries on Android)
If you like HQ thumbnails, so use [RapidDecoder][1] library. It is simple as follow:
import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
.scale(width, height)
.useBuiltInDecoder(true)
.decode();
Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result.
I tested it in API Level 8 :)
This package will let you access image URI to receive image size, large Bitmap data, sampling image to any smaller size for saving memory and maximize performance.
It uses InputStream and BitmapFactory:
public int[] getImageSize(Uri uri){
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream input = this.getContentResolver().openInputStream(uri);
BitmapFactory.decodeStream(input, null, options); input.close();
return new int[]{options.outWidth, options.outHeight};
}
catch (Exception e){}
return new int[]{0,0};
}
public Bitmap BitmapImage(Uri uri){return BitmapImage(uri,-1,-1);}
public Bitmap BitmapImage(Uri uri, int maxSize){return BitmapImage(uri,maxSize,maxSize);}
public Bitmap BitmapImage(Uri uri, int Wmax, int Hmax){
try {
InputStream input = this.getContentResolver().openInputStream(uri);
double ratio=1;
if ((Wmax>-1)&&(Hmax>-1)){
int[] wh=getImageSize(uri); double w=wh[0], h=wh[1];
if (w/Wmax>1){ratio=Wmax/w; if (h*ratio>Hmax){ratio=Hmax/h;}}
else if (h/Hmax>1){ratio=Hmax/h;}
}
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = (int)Math.ceil(1/ratio);
bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
input.close();
return bitmap;
}
catch (Exception e){}
return null;
}
Four functions for different use case:
/*
getImageSize(Uri uri): return int[]{ width, height}
BitmapImage(Uri uri): return Bitmap in full size
BitmapImage(Uri uri, int maxSize): return sampled Bitmap which is limited in square size
BitmapImage(Uri uri, int Wmax, int Hmax): return sampled Bitmap which is limited width and height
*/