I have an ImageView and I need to getImageResource() based on user GPS position.
There are 6 images and as the distance between 2 points decrease I replace the image with a new resource.
I'm testing the app on the Galaxy S4 and the problem is that after a very small random number of loading the app crashes because of OutOfMemory.
Is there a good way to cache the images? (Maybe I need to load them by using an AsyncTask)
The images are 400x400px png- 24 bit with transparency.
Thank you
Try using this:
public static Bitmap decodeSampledBitmapFromResource(String uri,
int reqWidth, int reqHeight, int orientation) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(uri, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap decodeFile = BitmapFactory.decodeFile(uri, options);
int rotate = 0;
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_270:
rotate = 270;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
rotate = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_90:
rotate = 90;
break;
}
Matrix matrix = new Matrix();
// matrix.postScale(scaleWidth, scaleHeight);
matrix.postRotate(rotate);
Bitmap rotatedBitmap = Bitmap.createBitmap(decodeFile, 0, 0,
decodeFile.getWidth(), decodeFile.getHeight(), matrix, true);
return rotatedBitmap;
}
private 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;
}
Galaxy S4 most likely works with the xxhdpi drawables, so you putting everything in mdpi will have the system scale up your images to match S4's dpi level, hence the OutOfMemory error. Try scaling and placing drawables in their respective folders depending on the dpi (including xhdpi and xxhdpi) and after that maybe optimize your code.
Related
I'm new to this forum, so please bear with me and gently point out mistakes if any,
So I'm working on a project where I'm uploading images to server, now I want to limit the size of images, I'm giving an option to "Click image" where my code will open default camera intent and clicks the pic, or "Choose from gallery".
My question is regarding "Click image", Now when user clicks an image, can I preset the image max size which can be clicked?
You can resize your image in onActivityResult method,try following code snippet
public static Bitmap handleSamplingAndRotationBitmap(Context context, Uri selectedImage)
throws IOException {
int MAX_HEIGHT = 1024;
int MAX_WIDTH = 1024;
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);
BitmapFactory.decodeStream(imageStream, null, options);
imageStream.close();
options.inSampleSize = calculateInSampleSizes(options, MAX_WIDTH, MAX_HEIGHT);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
imageStream = context.getContentResolver().openInputStream(selectedImage);
Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);
img = rotateImageIfRequired(context, img, selectedImage);
return img;
}
private static int calculateInSampleSizes(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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((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;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
private static Bitmap rotateImageIfRequired(Context context, Bitmap img, Uri selectedImage) throws IOException {
InputStream input = context.getContentResolver().openInputStream(selectedImage);
ExifInterface ei;
if (Build.VERSION.SDK_INT > 23)
ei = new ExifInterface(input);
else
ei = new ExifInterface(selectedImage.getPath());
int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
return rotateImage(img, 90);
case ExifInterface.ORIENTATION_ROTATE_180:
return rotateImage(img, 180);
case ExifInterface.ORIENTATION_ROTATE_270:
return rotateImage(img, 270);
default:
return img;
}
}
private static Bitmap rotateImage(Bitmap img, int degree) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
img.recycle();
return rotatedImg;
}
you just need invoke handleSamplingAndRotationBitmap method,you'll get a Bitmap which size can be set by yourself.
PS: Case some pictures captured by sumsung's phone 's rotation is incorrect,so we need handle picture's orientation too,hope that can help you.
You can simply get the size of the file. You need to store the image when u take for it. after that u can get size using below code segment
String imagePath = Environment.getExternalStorageDirectory() + "/yourImagefile.png";
File imageFile = new File(imagePath );
long filelength = imageFile .length();
length = filelength/1024;
this length give you size in KB. then you can add if condition like below
if(length>sizeyouwant){
//delete image and toast message with info
if(imageFile.exists()) {
imageFile.delete();
}
Toast.makeText(getApplicationContext(),
"Image is not saved due to image size exceeds limit....",
Toast.LENGTH_SHORT).show();
}
I know, there are a lot of similar questions in Stackoverflow. But I could not solve my problem.
I have some kind of jigsaw puzzle. The pictures in the Drawable-nodpi folder are 2500x1250.
I am trying to resize the images with the following code suggested:
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);
}
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
I call it like this:
(I call these codes several times.)
private Bitmap resizeImg (int rsm) {
Bitmap bmp = decodeSampledBitmapFromResource(getResources(),rsm,wdth,hght);
return bmp;
}
I need to use Matrix as the scaling of Imageview. But it's not the size I want. It works right without the Matrix.
I'm solving the Matrix problem like this:
Bitmap.createScaledBitmap
private Bitmap resizeImg (int rsm) {
Bitmap bmp = decodeSampledBitmapFromResource(getResources(),rsm,wdth,hght);
// return bmp;
Bitmap resized = Bitmap.createScaledBitmap(bmp, wdth,hght,true); //I get OutOfMemoryError this line.
return resized;
}
This insertion really solves my problem. But every day I get lots of OutOfMemoryError errors.
What do I need to do to fix this error? Please help me. Thank you.
1] Add largeHeap as true in android manifest to your application tag:
android:largeHeap="true"
2] Recycle your all used bitmaps,
Example:
bmp.recycle();
resized.recycle();
I'm facing a crash every time with a Galaxy S5 when trying to show a background image.
This background is located in xxhdpi resource folder, the size is the same as the S5 screen (1080x1920) so I don't need to call "createScaledBitmap" for scaling it. The resolution of this image is JPG 96dpi.
And when calling decodeResource... crash!!! How is this possible? Is the only bitmap I'm loading in this "super-powerful" device.
Thanks!!!
Below my code (scale = 1 for S5):
public static Bitmap decodeBitmapFromResource(Resources res, int resId, float scale) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options,
(int)(options.outWidth*scale),
(int)(options.outHeight*scale));
options.inJustDecodeBounds = false;
if (scale > 1) {
Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
return Bitmap.createScaledBitmap(bitmap, (int)(options.outWidth*scale),
(int)(options.outHeight*scale), true);
}
return BitmapFactory.decodeResource(res, resId, options);
}
i too faced this problem many times...
try using this code..
private Bitmap decodeFile(File f) throws IOException {
Bitmap b = null;
DisplayMetrics metrics = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay()
.getMetrics(metrics);
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inDither = false; // Disable Dithering mode
o.inPurgeable = true; // Tell to gc that whether it needs free memory,
// the Bitmap can be cleared
o.inInputShareable = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > metrics.heightPixels
|| o.outWidth > metrics.widthPixels) {
scale = (int) Math.pow(
2,
(int) Math.ceil(Math.log(metrics.heightPixels
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
and take care of few things like make every bitmap null after its use etc.
try this
public static Bitmap decodeBitmapFromResource(String pathName, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
// return BitmapFactory.decodeResource(res, resId, options);
return BitmapFactory.decodeFile(pathName, options);
}
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) {
// Calculate ratios of height and width to requested height and
// width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((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;
}
return inSampleSize;
}
Add this line in your Manifest file in the application tag. It doesn't solve the problem just allows your app to have more memory:
android:largeHeap="true"
UPDATE:
However using largeHeap is not a good solution. here is the google's doc about this.
However, the ability to request a large heap is intended only for a
small set of apps that can justify the need to consume more RAM (such
as a large photo editing app). Never request a large heap simply
because you've run out of memory and you need a quick fix—you should
use it only when you know exactly where all your memory is being
allocated and why it must be retained. Yet, even when you're confident
your app can justify the large heap, you should avoid requesting it to
whatever extent possible. Using the extra memory will increasingly be
to the detriment of the overall user experience because garbage
collection will take longer and system performance may be slower when
task switching or performing other common operations.
And about loading bitmaps:
When you load a bitmap, keep it in RAM only at the resolution you need
for the current device's screen, scaling it down if the original
bitmap is a higher resolution. Keep in mind that an increase in bitmap
resolution results in a corresponding (increase2) in memory needed,
because both the X and Y dimensions increase.
It's not bad to take a look at this page, it explains ways of managing memory:
How Your App Should Manage Memory
So I think my last answer is not a good solution and You might rethink your strategy in loading images. Hope this answer helps you ;)
I've written a multimedia app. In this I have made a gallery with a viewpager, and a FragmentStatePagerAdapter. I used FragmentStatePagerAdapter so that each time I'have in memory only two images and each time the user scrolls the pager the old picture is destroyed and the new is decoded in bitmap.
I have read lots of tutorials of how to efficiently load a bitmap using in a smart way the inSampleSize option and I've implemented it in such a way. In my phone runs great, but in some phones I get an out of memory exception so this is how I decode my ImageView in each fragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
try {
ImageArray=org.apache.commons.io.FileUtils.readFileToByteArray(new File(_imagePaths.get(mImageNum)));
if(MyApplication.getDeviceWidth() !=0 || MyApplication.getDeviceHeight()!=0)
bitmap=decodeSampledBitmapFromResource(ImageArray,MyApplication.getDeviceWidth(), MyApplication.getDeviceHeight());
else{
DisplayMetrics metrics = MyApplication.getAppContext().getResources().getDisplayMetrics();
bitmap=decodeSampledBitmapFromResource(ImageArray,metrics.widthPixels, metrics.heightPixels);
}
mImageView.setImageBitmap(bitmap);
} catch (IOException e) {
Log.e("ImageDetailFragment",e.getLocalizedMessage());
}
}
public static Bitmap decodeSampledBitmapFromResource(byte[] array,int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap b =BitmapFactory.decodeByteArray(array, 0, array.length,options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
if(b!=null)
b.recycle();
Log.i("inSampleSize", String.valueOf(options.inSampleSize));
return BitmapFactory.decodeByteArray(array, 0, array.length,options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth,int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((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;
}
Log.i("inSample size", String.valueOf(inSampleSize));
if(sampleLimit==-1)
return 2;
return inSampleSize;
}
So as you can see (I think) I load each bitmap efficiently and if a screen is smaller inSampleSize goes to 2
My question is how can I compute inSampleSize not depending on screen size only but also on the memory that is available to my app, cause in my phone I get 64MB but i've come across tablets with 38MB or less
I'm getting image data from a server and I'm converting it to byte[] using Base64.decode. My code works fine for small image sizes but for a particular image of size 9.2MB, it crashes. I have read about down sampling in various posts but before I could get to the sampling section of code, I'm getting a out of memory exception while reading the bytes in the following line of code.
byte[] data = Base64.decode(attchData[i].getBytes(),0);
Please help me out.
You can simply use this and get better solution:
public Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// "RECREATE" THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
return resizedBitmap; }
Use this methood, May work for you
decodeSampledBitmapFromPath(src, reqWidth, reqHeight);
use this implementation
public 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;
}
public Bitmap decodeSampledBitmapFromPath(String path, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
return bmp;
}
Wrap the input stream you are reading from (when reading data from the server) in a Base64InputStream instead. This should reduce the amount of memory required during the base64 decoding phase.
But you should check if you really have to send images of that size to the client. Maybe the images can be scaled on the server side?