MNM
MNM

Reputation: 2743

Change the size of a bitmap in android to avoid OOM

I have been working on changing the size of the bitmap that I download from my server because I know that this is the issue with my OOM error I have been getting. I have tried the follow other examples and this one as well https://developer.android.com/training/displaying-bitmaps/load-bitmap.html but I cannot make heads or tails about how to use it and where to use it either. I want to scale my bitmaps down to screen resolution but can't seam to figure that out. Thank you for any help with this.

Also not that this is being done in a AsyncTask if that makes a difference.

Here is my code for setting up the bitmap:

public class ImageDownloaderTask extends AsyncTask<String, Void, Bitmap> {

BitmapFactory.Options options = new BitmapFactory.Options();

private final WeakReference<ImageView> imageViewReference;
Resources resources;

public ImageDownloaderTask(ImageView imageView) {
    imageViewReference = new WeakReference<ImageView>(imageView);
}


@Override
protected Bitmap doInBackground(String... params)
{
    return downloadBitmap(params[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
    if (isCancelled()) {
        bitmap = null;
        Log.d("HTTPS No go", bitmap.toString());
    }

    if (imageViewReference != null) {
        ImageView imageView = imageViewReference.get();
        if (imageView != null) {
            if (bitmap != null) {
                //Scale the bitmap to a smaller size
                imageView.setImageBitmap(bitmap);
            } else {
                Log.d("Downloading the image: ", "No Image found");
            }
        }

    }
}

//URL connection to download the image
private Bitmap downloadBitmap(String url) {

    HttpURLConnection urlConnection = null;
            try{
            URL uri = new URL(url);
            urlConnection = (HttpURLConnection) uri.openConnection();
            urlConnection.setRequestMethod("GET");
            int statusCode = urlConnection.getResponseCode();
            //check if the HTTP status code is equal to 200, which means that it is ok
            if (statusCode != 200) {
                return null;
            }

            InputStream inputStream = urlConnection.getInputStream();
            if (inputStream != null) {

                /*
                options.inJustDecodeBounds = true;
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                int imageHeight = options.outHeight;
                int imageWidth = options.outWidth;
                String imageType = options.outMimeType;
                int sampleSize = calculateInSampleSize(options, imageWidth, imageHeight);
                */
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                return bitmap;
            }
            }catch (ProtocolException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } {}

    return null;
}

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;
}

}

UPDATE:

This fixed it a bit. It allows me to log out just once but when I logout a second time it crashes with the good old OOM error.

InputStream inputStream = urlConnection.getInputStream();
            if (inputStream != null) {

                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize=1; //try to decrease decoded image

                Bitmap bitmap = BitmapFactory.decodeStream(inputStream , null, options);
                return bitmap;
            }
            }catch (ProtocolException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } {}

Upvotes: 0

Views: 1326

Answers (4)

Turkhan Badalov
Turkhan Badalov

Reputation: 934

I faced with the same issue several days ago. Theoretically there are two ways to do this. Either you completely download image and then resize image, or you must ask your server to do this instead of app. I preferred second solution, where I send required Width, Height and needed image. Server calculates possible scale, then reduces size and returns back, prints image. After which I just download bitmap using HttpURLConnection and create bitmap from input stream of my connection without any problem.

What about your error, maybe you are trying to calculate first from stream and then create it. Of course it will cause a crash, because you are trying to read input stream second time. Your cursor has already moved into stream while reading meta data of image to learn size. Now when it tries to create Bitmap it fails, cause it doesn't start from 0th byte of stream. But somewhere in the middle, where your cursor stopped last time. So if it is necessary to read stream twice, you need to copy your input stream first somewhere to be able to read stream twice. Hope it helps you.

Upvotes: 2

MNM
MNM

Reputation: 2743

Thanks everyone that helped out. I found my issue was not the size of the downloading bitmap because I am resizing them automatically when i add them into my recycle view each time. It was caused by a gif I had playing on the Login Page which took just that much memory that it would kill the device once any other thing occupied the memory at all.

I really appreciate everyone here and thank you. I thought myself that the issue was with the downloading of the image because that is what the usual culperat is.

Upvotes: 0

L. Swifter
L. Swifter

Reputation: 3237

I think your need change your image's size before you read it into memory.

private Bitmap downloadBitmap(String url) {

    HttpURLConnection urlConnection = null;
            try{
            URL uri = new URL(url);
            urlConnection = (HttpURLConnection) uri.openConnection();
            urlConnection.setRequestMethod("GET");
            int statusCode = urlConnection.getResponseCode();
            //check if the HTTP status code is equal to 200, which means that it is ok
            if (statusCode != 200) {
                return null;
            }

            InputStream inputStream = urlConnection.getInputStream();
            if (inputStream != null) {

                //scale down the image and load
                options.inJustDecodeBounds = true;
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                options.inSampleSize = calculateInSampleSize(options, 100, 100);
                options.inJustDecodeBounds = false;
                return BitmapFactory.decodeStream(inputStream, null, options);   //I'm not sure here, because the inputStream used twice.
            }
            }catch (ProtocolException e) {
                e.printStackTrace();
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } {}

    return null;
}

Upvotes: 1

liminal
liminal

Reputation: 1164

I used this code:

    Bitmap outBitmap;

    // Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(photoPath, o);

    // The new size we want to scale to ensure memory usage is optimal
    int targetWidth;
    int targetHeight;
    if (o.outHeight > o.outWidth) {
        targetWidth = getResources().getInteger(R.integer.pic_width_px);
        targetHeight = getResources().getInteger(R.integer.pic_height_px);
    } else if (o.outHeight == o.outWidth) {
        targetWidth = targetHeight = getResources().getInteger(R.integer.pic_width_px);
    } else {
        targetWidth = getResources().getInteger(R.integer.pic_width_px);
        targetHeight = getResources().getInteger(R.integer.pic_height_px);
    }

    if (o.outWidth <= targetWidth && o.outHeight <= targetHeight) {
        // Return image as is without any additional scaling
        Bitmap origBitmap = BitmapFactory.decodeFile(photoPath, null);
        outBitmap = Bitmap.createBitmap(origBitmap, 0, 0, o.outWidth, o.outHeight, m, true);
        origBitmap.recycle();

        return outBitmap;
    }

    // Find the correct scale value. It should be the power of 2.
    int scale = 1;
    while(o.outWidth / scale / 2 >= targetWidth &&
            o.outHeight / scale / 2 >= targetHeight) {
        scale *= 2;
    }

    // Decode with inSampleSize
    BitmapFactory.Options scaleOptions = new BitmapFactory.Options();
    scaleOptions.inSampleSize = scale;

    Bitmap scaledBitmap = BitmapFactory.decodeFile(photoPath, scaleOptions);
    return Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), m, true);

Upvotes: 1

Related Questions