1

I have some code that takes two images and merges the middle section of them together into one image. The code works, but uses quite a lot of memory, so it fails on some devices that don't have enough memory.

Is there some way to make this code more memory efficient?

The code

private void convertImages(){
    ImageView imageView = (ImageView) findViewById(R.id.imageView);

    File leftFile = new File(Environment.getExternalStorageDirectory(), "images/left.jpg");
    File rightFile = new File(Environment.getExternalStorageDirectory(), "images/right.jpg");

    Bitmap left = BitmapFactory.decodeFile(leftFile.getAbsolutePath());
    Bitmap right = BitmapFactory.decodeFile(rightFile.getAbsolutePath());

    Rect srcRect = new Rect( (int)(left.getWidth()*0.25), 0, (int)(left.getWidth()*0.75), left.getHeight() );
    Rect dstRectLeft = new Rect( 0, 0, (int)(srcRect.width()/2), srcRect.height() );
    Rect dstRectRight = new Rect( (int)(srcRect.width()/2), 0, srcRect.width(), srcRect.height() );

    Bitmap outBitmap = Bitmap.createBitmap(srcRect.width(), srcRect.height(), Bitmap.Config.ARGB_8888);
    Canvas outCanvas = new Canvas(outBitmap);
    outCanvas.drawBitmap(left, srcRect, dstRectLeft, null);
    outCanvas.drawBitmap(right, srcRect, dstRectRight, null);

    imageView.setImageBitmap( outBitmap );
}
Magnus
  • 17,157
  • 19
  • 104
  • 189
  • It seems that the two source images are guaranteed to be of the same dimensions. So, You can load the first image to construct the rects, then release the image (by setting it to null then invoking the gc). Then load the images again, but in a one-by-one manner (that is releasing the image after each use). Then crop each image to the new dimensions, writing each of them to a file in each step. Then lastly, load the cropped images to create the final image. – Ahmed Khalaf Apr 28 '16 at 19:00
  • You could simply remove one image in memory by doing 1) load left 2) draw left to output 3) load right, 4) draw right to output. – flanglet Apr 29 '16 at 18:43

2 Answers2

0

You can try compression. Try this helpful imagecompressor class, copied and pasted for your convenience into this thread:

Android: Compressing images creates black borders on left and top margin

The compressor java will compress your pictures to about 1/10 of their size so for a 3mb picture, it will become 300kb. Hopefully that will save you some memory.

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.media.ExifInterface;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import timber.log.Timber;

//http://voidcanvas.com/whatsapp-like-image-compression-in-android/
public class ImageCompressor {

    public ImageCompressor() {}

    public static String compressImage(String imagePath, Context context) {
        Bitmap scaledBitmap = null;
        String filename = "compressed_" +imagePath.substring(imagePath.lastIndexOf("/")+1);

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

//      by setting this field as true, the actual bitmap pixels are not loaded in the memory. Just the bounds are loaded. If
//      you try the use the bitmap here, you will get null.
        options.inJustDecodeBounds = true;
        Timber.e( "imagePath "+imagePath);
        Timber.e("filename "+filename);
        Bitmap bmp = BitmapFactory.decodeFile(imagePath, options);
        if (options == null) {
            Timber.e("zero bitmap");
        }
        int actualHeight = options.outHeight;
        int actualWidth = options.outWidth;
        float imgRatio = actualWidth / actualHeight;

        float maxHeight = actualHeight * 10/20;
        float maxWidth = actualWidth * 10/20;
        float maxRatio = maxWidth / maxHeight;

//      width and height values are set maintaining the aspect ratio of the image

        if (actualHeight > maxHeight || actualWidth > maxWidth) {
            if (imgRatio < maxRatio) {

                imgRatio = maxHeight / actualHeight;
                actualWidth = (int) (imgRatio * actualWidth);
                actualHeight = (int) maxHeight;

            } else if (imgRatio > maxRatio) {

                imgRatio = maxWidth / actualWidth;
                actualHeight = (int) (imgRatio * actualHeight);
                actualWidth = (int) maxWidth;

            } else {

                actualHeight = (int) maxHeight;
                actualWidth = (int) maxWidth;

            }
        }

//      setting inSampleSize value allows to load a scaled down version of the original image

        options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);

//      inJustDecodeBounds set to false to load the actual bitmap
        options.inJustDecodeBounds = false;

//      this options allow android to claim the bitmap memory if it runs low on memory
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inTempStorage = new byte[16 * 1024];

        try {
//          load the bitmap from its path
            bmp = BitmapFactory.decodeFile(imagePath, options);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();

        }
        try {
            scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();
        }

        float ratioX = actualWidth / (float) options.outWidth;
        float ratioY = actualHeight / (float) options.outHeight;
        float middleX = actualWidth / 2.0f;
        float middleY = actualHeight / 2.0f;

        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);

        Canvas canvas = new Canvas(scaledBitmap);
        canvas.setMatrix(scaleMatrix);
        canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));

//      check the rotation of the image and display it properly
        ExifInterface exif;
        try {
            exif = new ExifInterface(imagePath);

            int orientation = exif.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION, 0);
            Timber.e("Exif: " + orientation);
            Matrix matrix = new Matrix();
            if (orientation == 6) {
                matrix.postRotate(90);
                Timber.e( "Exif: " + orientation);
            } else if (orientation == 3) {
                matrix.postRotate(180);
                Timber.e( "Exif: " + orientation);
            } else if (orientation == 8) {
                matrix.postRotate(270);
                Timber.e( "Exif: " + orientation);
            }
            scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,
                    scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix,
                    true);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
        }

        FileOutputStream out = null;

        try {

            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);

            out.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
            return filename;
        }

    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) {
            final int heightRatio = Math.round((float) height/ (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;      }
        final float totalPixels = width * height;
        final float totalReqPixelsCap = reqWidth * reqHeight * 2;
        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
            inSampleSize++;
        }
        return inSampleSize;
    }

}
}
Community
  • 1
  • 1
Simon
  • 19,658
  • 27
  • 149
  • 217
0

You can use the Bitmap.createScaledBitmap(src, dstWidth, dstHeight, filter); method to scale down bitmaps so its size will be much more smaller

Blerim Blerii
  • 223
  • 3
  • 16