Out of Memory Error - Bitmap Size - android

I want to display this image and upload it to a server.
I'm using the camera app to take a photo and returning the file path of the photo to an activity. I run into an Out of Memory error on some phones because I have a constant image size that I try and import between devices.
How can I pull in the largest image size possible to upload to the server while still working within the memory constraints of the phone?
Code Follows:
request to load Aync
GetBitmapTask GBT = new GetBitmapTask(dataType, path, 1920, 1920, loader);
GBT.addAsyncTaskListener(new AsyncTaskDone()
{
#Override
public void loaded(Object resp)
{
crop.setImageBitmap((Bitmap)resp);
crop.setScaleType(ScaleType.MATRIX);
}
#Override
public void error() {
}
});
GBT.execute();
The Async Task that throws the OOM error
public class GetBitmapTask extends AsyncTask<Void, Integer, Bitmap>
{
...
#Override
public Bitmap doInBackground(Void... params)
{
Bitmap r = null;
if (_dataType.equals("Unkown"))
{
Logger.e(getClass().getName(), "Error: Unkown File Type");
return null;
}
else if (_dataType.equals("File"))
{
Options options = new Options();
options.inJustDecodeBounds = true;
//Logger.i(getClass().getSimpleName(), _path.substring(7));
BitmapFactory.decodeFile(_path.substring(7), options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
Logger.i(getClass().getSimpleName(),
"height: " + options.outHeight +
"\nwidth: " + options.outWidth +
"\nmimetype: " + options.outMimeType +
"\nsample size: " + options.inSampleSize);
options.inJustDecodeBounds = false;
r = BitmapFactory.decodeFile(_path.substring(7), options);
}
else if (_dataType.equals("Http"))
{
r = _loader.downloadBitmap(_path, reqHeight);
Logger.i(getClass().getSimpleName(), "height: " + r.getHeight() +
"\nwidth: " + r.getWidth());
}
return r;
}
public static int calculateInSampleSize( 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;
while (height / inSampleSize > reqHeight || width / inSampleSize > reqWidth)
{
if (height > width)
{
inSampleSize = height / reqHeight;
if (((double)height % (double)reqHeight) != 0)
{
inSampleSize++;
}
}
else
{
inSampleSize = width / reqWidth;
if (((double)width % (double)reqWidth) != 0)
{
inSampleSize++;
}
}
}
return inSampleSize;
}
}

You can give the camera the uri pointing to file you want to save the image into:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mDefaultPhotoUri); // set path to image file
You can then upload that file to your server, so you have the full bitmap size. On the other hand, there is no need (and on many devices no way) to decode and show bitmap 1920x1920 (or similar) in UI, it's just too big.
Hope this helps.

Related

Trying to display large bitmap images in a RecyclerView Android displays an error setDataSource failed

I'm trying to load images on a RecyclerView using Glide, but I keep getting an error, Glide failed to load the resource even when the image URL seems to be there and when I click on the URL I can view the image.
Here is my FiltersViewHolder code;
public class FiltersViewHolder extends RecyclerView.ViewHolder {
public static String PHOTO_URL="";
private Context context;
TrendingFilters trendingFilters;
#BindView(R.id.filters_image)
ImageView filtersImage;
public FiltersViewHolder(Context context, View itemView) {
super(itemView);
this.context = context;
ButterKnife.bind(this, itemView);
}
Here I try to bind the images to the ImageView using Glide;
public void bind(final TrendingFilters trendingFilters) {
this.trendingFilters = trendingFilters;
DiskCacheStrategy strategy = DiskCacheStrategy.NONE;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.id.filters_image, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
Log.e("Bitmap", imageHeight + "" + imageWidth);
String imageType = options.outMimeType;
if(trendingFilters.getEmbedUrl() != null) {
GlideApp.with(context)
.load(Uri.parse(trendingFilters.getEmbedUrl()))
.placeholder(R.drawable.fox_face_mesh_texture)
.apply(RequestOptions.diskCacheStrategyOf(strategy))
.fitCenter()
.dontAnimate()
.into(filtersImage);
filtersImage.setImageBitmap(
decodeSampledBitmapFromResource(context.getResources(), R.id.filters_image, 100, 100));
PHOTO_URL = trendingFilters.getEmbedUrl();
}else{
}
itemView.setOnClickListener(view -> {
Toast.makeText(context, "Item clicked: " + trendingFilters.getId(), Toast.LENGTH_SHORT).show();
});
}
Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
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;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100x100 pixel thumbnail, as shown in the following example code:
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);
}
#OnClick(R.id.filters_image)
public void onViewClicked() {
}
}
And the error on the logcat is as follows;
W/Glide: Load failed for https://www.svrf.com/classic/embed/vr/1041219 with size [1141x284]
class com.bumptech.glide.load.engine.GlideException: Failed to load resource
Cause (1 of 3): class com.bumptech.glide.load.engine.GlideException: Failed LoadPath{ContentLengthInputStream->Object->Drawable}, REMOTE
Cause (1 of 3): class com.bumptech.glide.load.engine.GlideException: Failed DecodePath{ContentLengthInputStream->GifDrawable->Drawable}
Cause (2 of 3): class com.bumptech.glide.load.engine.GlideException: Failed DecodePath{ContentLengthInputStream->Bitmap->Drawable}
Cause (3 of 3): class com.bumptech.glide.load.engine.GlideException: Failed DecodePath{ContentLengthInputStream->BitmapDrawable->Drawable}

outofmemory exception for Large Bitmap

I found a lot of documentation on how to load large Bitmaps and avoid outofmemory exception. but the problem is that I have to take the image from my MediaStore.Images.media so the classical
decodeFile(path,options) indicated in the google documentation does not work to me
As you can see below I decommented the line // Bitmap photo= Mediastore.Images, that is the one that triggers the out of memory. on the other side adding
the line Bitmap bm=BitmapFactory.decodeFile(selectedImageToUri,options) returns null, although the compiler can see both the path in selectedImageToUri (that indicates the content provider where the pics are) than the options value, that I set to 8, because I want to subscale all the images
My question is how can I insert in bm the bitmap that is referring to the image selected by the user in the gallery. in the line BitMap photo does not return null and work really well, but I decommented because after I change a couple of images gives me outofmemory exception.
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable final Bundle savedInstanceState) {
if (flagVariable) {
if (selectedImageToUri != null) {
// BitMap photo = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), Uri.parse(selectedImageToUri));
final BitmapFactory.Options options= new BitmapFactory.Options();
options.inSampleSize=8;
Bitmap bm = BitmapFactory.decodeFile(selectedImageToUri, options);
pic = new BitmapDrawable(bm);
getActivity().getWindow().setBackgroundDrawable(pic);
} else {
getDefaultImageBackground(inflater, container);
}
hiddenList = inflater.inflate(R.layout.fragment_as_list_layout_temp, container, false);
} else {
getDefaultImageBackground(inflater, container);
}
listView = (ListView) hiddenList.findViewById(R.id.list_hidden);
MediaStore.getBitmap is just a simple convienence method, it looks like this:
public static final Bitmap getBitmap(ContentResolver cr, Uri url)
throws FileNotFoundException, IOException {
InputStream input = cr.openInputStream(url);
Bitmap bitmap = BitmapFactory.decodeStream(input);
input.close();
return bitmap;
}
You can create your own method based on this that takes the options and calls a different overload on BitmapFactory:
public static final Bitmap getBitmap(ContentResolver cr,
Uri url,
BitmapFactory.Options options)
throws FileNotFoundException, IOException {
InputStream input = cr.openInputStream(url);
Bitmap bitmap = BitmapFactory.decodeStream(input, null, options);
input.close();
return bitmap;
}
Usage:
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap bm = getBitmap(getActivity().getContentResolver(),
Uri.parse(selectedImageToUri),
options);
I spent a lot of time on this problem, but no one will give me exact answer and finally i solved it. First create method and provide Image URI as argument, and this will return bitmap basically here i calculated image size on bases of, we can manage memory as well as image and get exact image in bitmap form.
you can even display 5000×8000 and 12MiB picture without any error code is tested just copy paste in your class and enjoy.
Use
Bitmap mBitmap = getPhoto(MYIMAGEURI);
Provide URI to method and get Bitmap
Bitmap getPhoto(Uri selectedImage) {
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int height = metrics.heightPixels;
int width = metrics.widthPixels;
Bitmap photoBitmap = null;
InputStream inputStream = null;
try {
inputStream = getContentResolver().openInputStream(selectedImage);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, bitmapOptions);
int imageWidth = bitmapOptions.outWidth;
int imageHeight = bitmapOptions.outHeight;
#SuppressWarnings("unused")
InputStream is = null;
try {
is = getContentResolver().openInputStream(selectedImage);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
float scale = 1.0f;
if (imageWidth < imageHeight) {
if (imageHeight > width * 1.0f) {
scale = width * 1.0f / (imageHeight * 1.0f);
}
} else {
if (imageWidth > width * 1.0f) {
scale = width * 1.0f / (imageWidth * 1.0f);
}
}
photoBitmap = decodeSampledBitmapFromResource(this,
selectedImage, (int) (imageWidth * scale),
(int) (imageHeight * scale));
return photoBitmap;
}
Decode Bitmap Sample using image size
public static Bitmap decodeSampledBitmapFromResource(Context context,
Uri uri, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream is = null;
try {
is = context.getContentResolver().openInputStream(uri);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
BitmapFactory.decodeStream(is, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode editBitmap with inSampleSize set
options.inJustDecodeBounds = false;
InputStream inputs = null;
try {
inputs = context.getContentResolver().openInputStream(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return BitmapFactory.decodeStream(inputs, null, options);
}
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) {
// 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 = Math.min(heightRatio, widthRatio);
// inSampleSize = heightRatio < widthRatio ? heightRatio :
// widthRatio;
}
return inSampleSize;
}
Or may be possible to solved using one line of code in manifiest.xml
is in application tag use this
android:largeHeap="true"

Xamarin forms android image is not getting compressed

I'm working on xamarin forms project. I'm taking images from gallery and uploading those to server. My back-end is parse backend where we can not upload files having size more than 1MB. So, I'm trying to compress the image so that every time the image size is less than 1MB.
Mentioned below is my code :-
protected override async void OnActivityResult (int requestCode, Result resultCode, Intent intent)
{
if (resultCode == Result.Canceled)
return;
try {
var mediafile = await intent.GetMediaFileExtraAsync (Forms.Context);
// get byte[] from file stream
byte[] byteData = ReadFully (mediafile.GetStream ());
byte[] resizedImage = ResizeAndCompressImage (byteData, 60, 60, mediafile);
var imageStream = new ByteArrayContent (resizedImage);
imageStream.Headers.ContentDisposition = new ContentDispositionHeaderValue ("attachment") {
FileName = Guid.NewGuid () + ".Png"
};
var multi = new MultipartContent ();
multi.Add (imageStream);
HealthcareProfessionalDataClass lDataClass = HealthcareProfessionalDataClass.Instance;
lDataClass.Thumbnail = multi;
App.mByteArrayOfImage = byteData;
MessagingCenter.Send<IPictureTaker,string> (this, "picturetaken", mediafile.Path);
} catch (InvocationTargetException e) {
e.PrintStackTrace ();
} catch (Java.Lang.Exception e) {
e.PrintStackTrace ();
}
}
public static byte[] ReadFully (System.IO.Stream input)
{
using (var ms = new MemoryStream ()) {
input.CopyTo (ms);
return ms.ToArray ();
}
}
public static byte[] ResizeAndCompressImage (byte[] imageData, float width, float height, MediaFile file)
{
try {
// Load the bitmap
var options = new BitmapFactory.Options ();
options.InJustDecodeBounds = true;
options.InMutable = true;
BitmapFactory.DecodeFile (file.Path, options);
// Calculate inSampleSize
options.InSampleSize = calculateInSampleSize (options, (int)width, (int)height);
// Decode bitmap with inSampleSize set
options.InJustDecodeBounds = false;
var originalBitMap = BitmapFactory.DecodeByteArray (imageData, 0, imageData.Length, options);
var resizedBitMap = Bitmap.CreateScaledBitmap (originalBitMap, (int)width, (int)height, false);
if (originalBitMap != null) {
originalBitMap.Recycle ();
originalBitMap = null;
}
using (var ms = new MemoryStream ()) {
resizedBitMap.Compress (Bitmap.CompressFormat.Png, 0, ms);
if (resizedBitMap != null) {
resizedBitMap.Recycle ();
resizedBitMap = null;
}
return ms.ToArray ();
}
} catch (Java.Lang.Exception e) {
e.PrintStackTrace ();
return null;
}
}
public static int calculateInSampleSize (BitmapFactory.Options options, int reqWidth, int reqHeight)
{
// Raw height and width of image
int height = options.OutHeight;
int width = options.OutWidth;
int inSampleSize = 16;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
But the problem is my image is not getting compressed.I'm not able to upload an image having size = 2MB and I want to upload images having size at-least 30 MB. Also I've observed that calculateInSampleSize always returns 16 as inSampleSize which is default one.
Please let me know if there's any issue in my code.
This seems like a very complicated and convoluted way of doing it. Here's a more concise sample that should help you resize your images:
protected override void OnActivityResult(int requestCode, Result resultCode, Android.Content.Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
var stream = this.Resize(data.Data, 60, 60);
// Send the stream to Parse
}
private Stream Resize(Android.Net.Uri uri, float maxWidth, float maxHeight)
{
var scale = 1;
using (var rawStream = this.ContentResolver.OpenInputStream(f))
using (var options = new BitmapFactory.Options { InJustDecodeBounds = true })
{
BitmapFactory.DecodeStream(rawStream, null, options);
while(options.OutWidth / scale / 2 > maxWidth ||
options.OutHeight / scale / 2 > maxHeight)
{
scale *= 2;
}
}
using (var options = new BitmapFactory.Options { InSampleSize = scale })
using (var bitmap = f.GetBitmap(options))
{
var memoryStream = new MemoryStream();
bitmap.Compress(Bitmap.CompressFormat.Png, 0, memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
}
Regarding why you are seeing InSampleSize = 16, my guess is that your image's height or width are less than 1920 (which is 60 * 2 * 16) and since you are using && in the while loop, the greater check for that side fails and thus, you never enter the while body.
Additionally, if you are looking to create smaller images, compressing them as Jpeg is a much better approach than using png's.

Release memory after loading the Bitmap

In my app I put two Bitmaps. When the orientation is vertical, show the first image, if it's horizontal, show the second image. Using
Log.i("log","Total memory " + title + " = " + (int) (Runtime.getRuntime().totalMemory()/1024));
I found that after I change my phone's orientation a few times, the total memory grows. However, I would expect the memory to remain the same, it seems like the program is not properly freeing memory.
Here's my code
public Bitmap decodeSampledBitmapFromResource(int path, int reqWidth, int reqHeight, Context ctx){
// Читаем с inJustDecodeBounds=true для определения размеров
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap b = BitmapFactory.decodeResource(ctx.getResources(), path, options);
// Вычисляем inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Читаем с использованием inSampleSize коэффициента
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(ctx.getResources(), path, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {
// Реальные размеры изображения
Log.i("log", "inSampleSize" + reqWidth);
Log.i("log", "inSampleSize" + reqHeight);
final int height = options.outHeight;
Log.i("log", "height" + height);
final int width = options.outWidth;
Log.i("log", "height" + width);
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Вычисляем наибольший inSampleSize, который будет кратным двум
// и оставит полученные размеры больше, чем требуемые
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
Log.i("log", "inSampleSize" + inSampleSize);
}
}
return inSampleSize;
}
private void readImage(int draw) {
//int px = getResources().getDimensionPixelSize(draw);
int pxW = displaymetrics.widthPixels;
int pxH = displaymetrics.heightPixels;
***if(bitmap != null){
bitmap.recycle();
bitmap = null;
}***
bitmap = decodeSampledBitmapFromResource(draw, My_px_W, My_px_H, this);
ivStart.setImageBitmap(bitmap);
}
Here's onCreate
if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
My_px_W = 200;
My_px_H = 150;
readImage(R.drawable.im2);
logMemory("'vertical'");
}
else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
My_px_W = 400;
My_px_H = 200;
readImage(R.drawable.s2);
logMemory("'horizontal'");
}
You need to call System.gc()after you called recycle if you want it to be immediately released.
Recycle on it's own is not enough as the garbage collector is not called.
Free the native object associated with this bitmap, and clear the
reference to the pixel data. This will not free the pixel data
synchronously; it simply allows it to be garbage collected if there
are no other references. The bitmap is marked as "dead", meaning it
will throw an exception if getPixels() or setPixels() is called, and
will draw nothing. This operation cannot be reversed, so it should
only be called if you are sure there are no further uses for the
bitmap. This is an advanced call, and normally need not be called,
since the normal GC process will free up this memory when there are no
more references to this bitmap.
You should call bitmap.recycle() and then call System.gc()
Be sure you aren't using the same bitmap after call recycle()

Sampling a bitmap from url

I am trying to reduce the size of bitmap from a url. I saw many posts, but all were about sampling a local file. I want to sample the image at url. Here is my code:
public Bitmap getScaledFromUrl(String url) {
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1 / 10;
try {
return BitmapFactory.decodeStream((InputStream) new URL(url)
.getContent());
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
Is this approach correct? I am getting out of memory crashes in my app at this function. Any ideas?
This works. I found it at http://blog.vandzi.com/2013/01/get-scaled-image-from-url-in-android.html . Use the following snippet of code, pass params as you like.
private static Bitmap getScaledBitmapFromUrl(String imageUrl, int requiredWidth, int requiredHeight) throws IOException {
URL url = new URL(imageUrl);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(url.openConnection().getInputStream(), null, options);
options.inSampleSize = calculateInSampleSize(options, requiredWidth, requiredHeight);
options.inJustDecodeBounds = false;
//don't use same inputstream object as in decodestream above. It will not work because
//decode stream edit input stream. So if you create
//InputStream is =url.openConnection().getInputStream(); and you use this in decodeStream
//above and bellow it will not work!
Bitmap bm = BitmapFactory.decodeStream(url.openConnection().getInputStream(), null, options);
return bm;
}
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;
}
It's really flexible.. I think you should try it out.
You are using it wrong. You are asking to make the picture 10 times bigger :) You should give the command in normal numbers, not fraction. For example:
final BitmapFactory.Options options2 = new BitmapFactory.Options();
options2.inSampleSize = 8;
b = BitmapFactory.decodeFile(image, options2);
with this configuration you obtain 8 times smaller picture than the original.
UPDATE: To load image from internet add this class to the project and do the following:
ImageLoader loader = new ImageLoader(context);
Bitmap image = loader.getBitmap(URL);

Categories

Resources