Shrinking android bitmaps without saving them as a jpeg file to avoid out of memory errors

293 Views Asked by At

I'm still wrapping my head around how bitmaps work on Android. My current situation is - I'm trying to display a gridview of images but I get out of memory errors around 8 or so pictures.

The way I do it now is I'm using a custom camera (extending surfaceview and implementing surfaceholder.callback) to take a picture, decoding the byte array from the onPreviewFrame method using the YUV420SP conversion function into an int[] array and then creating a bitmap from that array. There are two reasons I'm doing this, 1. to save the stream into a blob within a sqlite db to transfer to a server at a later time, and 2. to avoid saving it as a jepg in the phone that can be altered with.

If there are better ways to do this I'm open to hearing about it, but for now I'm looking for an answer that'll help me shrink down the image size when I display it after calling it from the database, or before saving it to the database. I've looked at other threads on this topic but everything I've seen so far requires saving it as a jpeg and then using bitmapfactory.options to decode the file location into a bitmap. Does anyone know a way to use the options directly with the byte array parameter given or even after converting it using YUV420SP?

Let me know if any more clarification is needed.

1

There are 1 best solutions below

1
On

So the problem is you did not scale down the image before displaying them into GridView, so while you create Bitmap from those int[], you should create a scale down version of the Bitmap before displaying them.

There will be something like this:

public static byte[] intArraysToByteArrays(int[] data){
    ByteBuffer byteBuffer = ByteBuffer.allocate(data.length * 4);        
    IntBuffer intBuffer = byteBuffer.asIntBuffer();
    intBuffer.put(data);

    return byteBuffer.array();
}

public static Bitmap decodeSampleBitmapFromByteStream(byte[] data , int reqWidth, int reqHeight){
     // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeByteArray(data, 0, data.length, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}


/**
 * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding
 * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates
 * the closest inSampleSize that will result in the final decoded bitmap having a width and
 * height equal to or larger than the requested width and height. This implementation does not
 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
 * results in a larger bitmap which isn't as useful for caching purposes.
 *
 * @param options An options object with out* params already populated (run through a decode*
 *            method with inJustDecodeBounds==true
 * @param reqWidth The requested width of the resulting bitmap
 * @param reqHeight The requested height of the resulting bitmap
 * @return The value to be used for inSampleSize
 */
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;

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

NOTE: I have NOT tested the code, but I am pretty confident it should work fine. For better performance, all these should be ran in the background thread.