Resize a large bitmap file to scaled output file on Android

232,061

Solution 1

No. I'd love for someone to correct me, but I accepted the load/resize approach you tried as a compromise.

Here are the steps for anyone browsing:

  1. Calculate the maximum possible inSampleSize that still yields an image larger than your target.
  2. Load the image using BitmapFactory.decodeFile(file, options), passing inSampleSize as an option.
  3. Resize to the desired dimensions using Bitmap.createScaledBitmap().

Solution 2

Justin answer translated to code (works perfect for me):

private Bitmap getBitmap(String path) {

Uri uri = getImageUri(path);
InputStream in = null;
try {
    final int IMAGE_MAX_SIZE = 1200000; // 1.2MP
    in = mContentResolver.openInputStream(uri);

    // Decode image size
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();



    int scale = 1;
    while ((options.outWidth * options.outHeight) * (1 / Math.pow(scale, 2)) > 
          IMAGE_MAX_SIZE) {
       scale++;
    }
    Log.d(TAG, "scale = " + scale + ", orig-width: " + options.outWidth + ", 
       orig-height: " + options.outHeight);

    Bitmap resultBitmap = null;
    in = mContentResolver.openInputStream(uri);
    if (scale > 1) {
        scale--;
        // scale to max possible inSampleSize that still yields an image
        // larger than target
        options = new BitmapFactory.Options();
        options.inSampleSize = scale;
        resultBitmap = BitmapFactory.decodeStream(in, null, options);

        // resize to desired dimensions
        int height = resultBitmap.getHeight();
        int width = resultBitmap.getWidth();
        Log.d(TAG, "1th scale operation dimenions - width: " + width + ",
           height: " + height);

        double y = Math.sqrt(IMAGE_MAX_SIZE
                / (((double) width) / height));
        double x = (y / height) * width;

        Bitmap scaledBitmap = Bitmap.createScaledBitmap(resultBitmap, (int) x, 
           (int) y, true);
        resultBitmap.recycle();
        resultBitmap = scaledBitmap;

        System.gc();
    } else {
        resultBitmap = BitmapFactory.decodeStream(in);
    }
    in.close();

    Log.d(TAG, "bitmap size - width: " +resultBitmap.getWidth() + ", height: " + 
       resultBitmap.getHeight());
    return resultBitmap;
} catch (IOException e) {
    Log.e(TAG, e.getMessage(),e);
    return null;
}

Solution 3

This is 'Mojo Risin's and 'Ofir's solutions "combined". This will give you a proportionally resized image with the boundaries of max width and max height.

  1. It only reads meta data to get the original size (options.inJustDecodeBounds)
  2. It uses a rought resize to save memory (itmap.createScaledBitmap)
  3. It uses a precisely resized image based on the rough Bitamp created earlier.

For me it has been performing fine on 5 MegaPixel images an below.

try
{
    int inWidth = 0;
    int inHeight = 0;

    InputStream in = new FileInputStream(pathOfInputImage);

    // decode image size (decode metadata only, not the whole image)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeStream(in, null, options);
    in.close();
    in = null;

    // save width and height
    inWidth = options.outWidth;
    inHeight = options.outHeight;

    // decode full image pre-resized
    in = new FileInputStream(pathOfInputImage);
    options = new BitmapFactory.Options();
    // calc rought re-size (this is no exact resize)
    options.inSampleSize = Math.max(inWidth/dstWidth, inHeight/dstHeight);
    // decode full image
    Bitmap roughBitmap = BitmapFactory.decodeStream(in, null, options);

    // calc exact destination size
    Matrix m = new Matrix();
    RectF inRect = new RectF(0, 0, roughBitmap.getWidth(), roughBitmap.getHeight());
    RectF outRect = new RectF(0, 0, dstWidth, dstHeight);
    m.setRectToRect(inRect, outRect, Matrix.ScaleToFit.CENTER);
    float[] values = new float[9];
    m.getValues(values);

    // resize bitmap
    Bitmap resizedBitmap = Bitmap.createScaledBitmap(roughBitmap, (int) (roughBitmap.getWidth() * values[0]), (int) (roughBitmap.getHeight() * values[4]), true);

    // save image
    try
    {
        FileOutputStream out = new FileOutputStream(pathOfOutputImage);
        resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
    }
    catch (Exception e)
    {
        Log.e("Image", e.getMessage(), e);
    }
}
catch (IOException e)
{
    Log.e("Image", e.getMessage(), e);
}

Solution 4

Acknowledging the other excellent answer so far, the best code I've seen yet for this is in the documentation for the photo taking tool.

See the section entitled "Decode a Scaled Image".

http://developer.android.com/training/camera/photobasics.html

The solution it proposes is a resize then scale solution like the others here, but it's quite neat.

I've copied the code below as a ready-to-go function for convenience.

private void setPic(String imagePath, ImageView destination) {
    int targetW = destination.getWidth();
    int targetH = destination.getHeight();
    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(imagePath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, bmOptions);
    destination.setImageBitmap(bitmap);
}

Solution 5

Why not use the API?

int h = 48; // height in pixels
int w = 48; // width in pixels    
Bitmap scaled = Bitmap.createScaledBitmap(largeBitmap, w, h, true);
Share:
232,061

Related videos on Youtube

Manuel
Author by

Manuel

Updated on January 29, 2020

Comments

  • Manuel
    Manuel over 4 years

    I have a large bitmap (say 3888x2592) in a file. Now, I want to resize that bitmap to 800x533 and save it to another file. I normally would scale the bitmap by calling Bitmap.createBitmap method but it needs a source bitmap as the first argument, which I can't provide because loading the original image into a Bitmap object would of course exceed the memory (see here, for example).

    I also can't read the bitmap with, for example, BitmapFactory.decodeFile(file, options), providing a BitmapFactory.Options.inSampleSize, because I want to resize it to an exact width and height. Using inSampleSize would resize the bitmap to 972x648 (if I use inSampleSize=4) or to 778x518 (if I use inSampleSize=5, which isn't even a power of 2).

    I would also like to avoid reading the image using inSampleSize with, for example, 972x648 in a first step and then resizing it to exactly 800x533 in a second step, because the quality would be poor compared to a direct resizing of the original image.

    To sum up my question: Is there a way to read a large image file with 10MP or more and save it to a new image file, resized to a specific new width and height, without getting an OutOfMemory exception?

    I also tried BitmapFactory.decodeFile(file, options) and setting the Options.outHeight and Options.outWidth values manually to 800 and 533, but it doesn't work that way.

  • Manuel
    Manuel almost 14 years
    I tried to avoid that. So there's no way to directly resize a large image in only one step?
  • Justin
    Justin almost 14 years
    Not to my knowledge, but don't let that stop you from exploring this further.
  • Manuel
    Manuel almost 14 years
    Since inSampleSize is an Integer, you would only very seldomly get the exact pixel width and height that you want to get. You may get close sometimes, but you also may be far away from it, depending on the decimals.
  • Manuel
    Manuel almost 14 years
    Alright, I will take this for my accepted answer so far. If I find out any other methods, I will let you know.
  • Manuel
    Manuel over 13 years
    Because it wouldn't solve my problem. Which is: "...it needs a source bitmap as the first argument, which I can't provide because loading the original image into a Bitmap object would of course exceed the memory." So, I can't pass a Bitmap to the .createScaledBitmap method either, because I still would need to load a large image into a Bitmap object first.
  • Bostone
    Bostone over 13 years
    Right. I re-read your question and basically (if I understand it right) it boild down to "can I resize image to exact dimensions without loading original file into memory?" If so - I don't know enough about intricacies of image processing to answer it but something tells me that 1. it's not available from API, 2. it will not be 1-liner. I will mark this as favorite - it would be interesting to see if you (or someone else) will solve this.
  • RRTW
    RRTW over 12 years
    Morning, I did tried your code (post above in this thread), but seems not working, where do I did wrong ? Any suggests are welcome :-)
  • Oliver Dixon
    Oliver Dixon almost 12 years
    Makes it hard to read when you use variables like "b" but good answer non the less.
  • Biginner
    Biginner almost 11 years
    @Ofir : getImageUri(path); what i have to pass in this method?
  • Fattie
    Fattie over 9 years
    the power calculation for the scale is simply wrong here; just use the calculation on the android doco page.
  • david.perez
    david.perez over 9 years
    Instead of (wh)/Math.pow(scale, 2) it is more efficient to use (wh) >> scale.
  • Ostkontentitan
    Ostkontentitan over 9 years
    Very nice! Using inDensity instead of Bitmap.createScaledBitmap saved me a lot of memory heap. Even better combined with inSamplesize.
  • user276648
    user276648 about 9 years
    As PSIXO mentioned in an answer, you may also want to use android:largeHeap if you still have issues after using inSampleSize.
  • gw0
    gw0 about 9 years
    Don't call System.gc() please
  • JoeCoolman
    JoeCoolman over 8 years
    Thanks @Ofir but this transformation doesn't conserve the image orientation :-/
  • cybergen
    cybergen about 8 years
    First you're deviding integers what will floor the result. Second the code crashes with targetW or targetH being 0 (although this doesn't make much sense I know). Third inSampleSize should be a power of 2.
  • cybergen
    cybergen about 8 years
    Don't get me wrong. This will definiteley load an image but if flooring the ints is indended, it doesn't look so. And this is also definitely not the right answer because the image will not be scaled as expected. It will not do anything until the image view is half the size of the image or smaller. Then nothing happens until the image view is 1/4 the size of the image. And so on with powers of two!
  • Ravit
    Ravit almost 8 years
    Please notice that bmOptions.inPurgeable = true; is deprecated.
  • Hamza
    Hamza over 6 years
    it did work for me because i am getting uri and converting to bitmap so scaling them is easy for me 1+ for the simplest one.
  • Prasad
    Prasad about 6 years
    bitmap variable was getting empty
  • Phong Nguyen
    Phong Nguyen almost 6 years
    thank a lot, it works fine. btw instead of new instance, it could be bitmapOptions.inJustDecodeBounds = false to decode bitmap