70

I have a large sized image. At runtime, I want to read the image from storage and scale it so that its weight and size gets reduced and I can use it as a thumbnail. When a user clicks on the thumbnail, I want to display the full-sized image.

Raul Rene
  • 10,014
  • 9
  • 53
  • 75
d-man
  • 57,473
  • 85
  • 212
  • 296

9 Answers9

136

Try this

Bitmap ThumbImage = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(imagePath), THUMBSIZE, THUMBSIZE);

This Utility is available from API_LEVEl 8. [Source]

Vishnu
  • 2,024
  • 3
  • 28
  • 34
Dax
  • 6,908
  • 5
  • 23
  • 30
  • 4
    Using this code you are actually loading in memory a copy of the large bitmap, so this is not a good way of managing big images. – GuillermoMP Nov 16 '15 at 11:21
  • 11
    The correct way to go is decoding a downsampled version of the file. Other answers offer this approach. Also the official docs explain the process well: https://developer.android.com/intl/es/training/displaying-bitmaps/load-bitmap.html . Since this one is the most voted answer (due its simplicity), i just wanted to warn everyone that this is not the best way. – GuillermoMP Nov 16 '15 at 17:18
  • How I can apply this to a drawable resource? – JCarlosR Jul 30 '16 at 23:58
  • 2
    Does this solution keeping the aspect ratio? – Matan Tubul Sep 20 '17 at 07:16
  • Whats the import for this? – Jithin Angel'z Jas'z Nov 26 '21 at 08:08
51

My Solution

byte[] imageData = null;

        try     
        {

            final int THUMBNAIL_SIZE = 64;

            FileInputStream fis = new FileInputStream(fileName);
            Bitmap imageBitmap = BitmapFactory.decodeStream(fis);

            imageBitmap = Bitmap.createScaledBitmap(imageBitmap, THUMBNAIL_SIZE, THUMBNAIL_SIZE, false);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();  
            imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            imageData = baos.toByteArray();

        }
        catch(Exception ex) {

        }
kakopappa
  • 5,023
  • 5
  • 54
  • 73
  • 1
    My files were to large, so I had to use sub sampling in the `BitmapFactory.decodeStream(fis);` step. See [docs](http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inSampleSize) for more detail about sub sampling. – Diederik Jan 23 '13 at 07:42
  • 7
    It's better to use Android's `ThumbnailUils` class like the answer below. – afollestad Oct 29 '14 at 02:30
  • 2
    @afollestad Not really, this approach is the correct one. Using ThumbnailUtils is only a good idea when you are 100% sure you are working with small files. – GuillermoMP Nov 16 '15 at 18:23
  • This solution has a BIG bug: imageBitmap = Bitmap.createScaledBitmap(imageBitmap, ...); you will lose the first imageBitmap WITHOUT RECYCLING IT! Sooner or later it will bring to an OutOfMemoryError... – Massimo Feb 12 '16 at 10:31
  • if (!imageBitmap.isRecycled()) imageBitmap.recycle(); – kakopappa Sep 02 '16 at 05:20
  • 1
    This solution nonetheless loads the whole Bitmap into the memory and then scales it. – Madeyedexter Dec 05 '16 at 03:50
  • This solution does not preserve aspect ratio. – Zanna Aug 24 '18 at 09:28
14

The best solution I found is the following. Compared with the other solutions this one does not need to load the full image for creating a thumbnail, so it is more efficient! Its limit is that you can not have a thumbnail with exact width and height but the solution as near as possible.

File file = ...; // the image file

Options bitmapOptions = new Options();
bitmapOptions.inJustDecodeBounds = true; // obtain the size of the image, without loading it in memory
BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOptions);

// find the best scaling factor for the desired dimensions
int desiredWidth = 400;
int desiredHeight = 300;
float widthScale = (float)bitmapOptions.outWidth/desiredWidth;
float heightScale = (float)bitmapOptions.outHeight/desiredHeight;
float scale = Math.min(widthScale, heightScale);

int sampleSize = 1;
while (sampleSize < scale) {
    sampleSize *= 2;
}
bitmapOptions.inSampleSize = sampleSize; // this value must be a power of 2,
                                         // this is why you can not have an image scaled as you would like
bitmapOptions.inJustDecodeBounds = false; // now we want to load the image

// Let's load just the part of the image necessary for creating the thumbnail, not the whole image
Bitmap thumbnail = BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOptions);

// Save the thumbnail
File thumbnailFile = ...;
FileOutputStream fos = new FileOutputStream(thumbnailFile);
thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, fos);
fos.flush();
fos.close();

// Use the thumbail on an ImageView or recycle it!
thumbnail.recycle();
Massimo
  • 3,436
  • 4
  • 40
  • 68
10

Here is a more complete solution to scaling down a Bitmap to thumbnail size. It expands on the Bitmap.createScaledBitmap solution by maintaining the aspect ratio of the images and also padding them to the same width so that they look good in a ListView.

Also, it would be best to do this scaling once and store the resulting Bitmap as a blob in your Sqlite database. I have included a snippet on how to convert the Bitmap to a byte array for this purpose.

public static final int THUMBNAIL_HEIGHT = 48;
public static final int THUMBNAIL_WIDTH = 66;

imageBitmap = BitmapFactory.decodeByteArray(mImageData, 0, mImageData.length);
Float width  = new Float(imageBitmap.getWidth());
Float height = new Float(imageBitmap.getHeight());
Float ratio = width/height;
imageBitmap = Bitmap.createScaledBitmap(imageBitmap, (int)(THUMBNAIL_HEIGHT*ratio), THUMBNAIL_HEIGHT, false);

int padding = (THUMBNAIL_WIDTH - imageBitmap.getWidth())/2;
imageView.setPadding(padding, 0, padding, 0);
imageView.setImageBitmap(imageBitmap);



ByteArrayOutputStream baos = new ByteArrayOutputStream();  
imageBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] byteArray = baos.toByteArray();
charles young
  • 2,269
  • 2
  • 23
  • 38
  • Is there a reason you switched arguments order for `THUMBNAIL_HEIGHT` and `THUMBNAIL_HEIGHT` for `createScaledBitmap`? – jayeffkay Nov 22 '16 at 13:10
6

Use BitmapFactory.decodeFile(...) to get your Bitmap object and set it to an ImageView with ImageView.setImageBitmap().

On the ImageView set the layout dimensions to something small, eg:

android:layout_width="66dip" android:layout_height="48dip"

Add an onClickListener to the ImageView and launch a new activity, where you display the image in full size with

android:layout_width="wrap_content" android:layout_height="wrap_content"

or specify some larger size.

Vasily Kabunov
  • 6,511
  • 13
  • 49
  • 53
Jim Blackler
  • 22,946
  • 12
  • 85
  • 101
  • 11
    When having multiple images you should consider scaling it down to the thumb size beforehand. otherwise that could slow down the performance when moving the set. – Moritz Apr 05 '10 at 07:17
  • 5
    Indeed, and to do that you would use Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, false); – Jim Blackler Apr 05 '10 at 08:34
  • 1
    dones following method also reduce the weight of the image ? Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, false) – d-man Apr 06 '10 at 11:05
3
/**
 * Creates a centered bitmap of the desired size.
 *
 * @param source original bitmap source
 * @param width targeted width
 * @param height targeted height
 * @param options options used during thumbnail extraction
 */
public static Bitmap extractThumbnail(
        Bitmap source, int width, int height, int options) {
    if (source == null) {
        return null;
    }

    float scale;
    if (source.getWidth() < source.getHeight()) {
        scale = width / (float) source.getWidth();
    } else {
        scale = height / (float) source.getHeight();
    }
    Matrix matrix = new Matrix();
    matrix.setScale(scale, scale);
    Bitmap thumbnail = transform(matrix, source, width, height,
            OPTIONS_SCALE_UP | options);
    return thumbnail;
}
user1546570
  • 287
  • 3
  • 13
  • where is transform(matrix, source, width, height, OPTIONS_SCALE_UP | options) Method – Andy Nov 08 '13 at 05:32
  • https://code.google.com/p/kontalk/source/browse/src/org/kontalk/xmpp/util/ThumbnailUtils.java?repo=androidclient&name=xmpp&r=f879e85d21d6e0a8a6f736ebe90c03e851b51436 – user1546570 Nov 11 '13 at 07:06
2

I found an easy way to do this

Bitmap thumbnail = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(mPath),200,200)

Syntax

Bitmap thumbnail = ThumbnailUtils.extractThumbnail(Bitmap source,int width,int height)

OR

use Picasso dependancy

compile 'com.squareup.picasso:picasso:2.5.2'

Picasso.with(context)
    .load("file:///android_asset/DvpvklR.png")
    .resize(50, 50)
    .into(imageView2);

Reference Picasso

Community
  • 1
  • 1
Sushin Pv
  • 1,826
  • 3
  • 22
  • 36
0

If you want high quality result, so use [RapidDecoder][1] library. It is simple as follow:

import rapid.decoder.BitmapDecoder;
...
Bitmap bitmap = BitmapDecoder.from(getResources(), R.drawable.image)
                             .scale(width, height)
                             .useBuiltInDecoder(true)
                             .decode();

Don't forget to use builtin decoder if you want to scale down less than 50% and a HQ result.

S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59
0

This answer is based on the solution presented in https://developer.android.com/topic/performance/graphics/load-bitmap.html (without using of external libraries) with some changes by me to make its functionality better and more practical.

Some notes about this solution:

  1. It is assumed that you want to keep the aspect ratio. In other words:

    finalWidth / finalHeight == sourceBitmap.getWidth() / sourceBitmap.getWidth() (Regardless of casting and rounding issues)

  2. It is assumed that you have two values (maxWidth & maxHeight) that you want any of the dimensions of your final bitmap doesn't exceed its corresponding value. In other words:

    finalWidth <= maxWidth && finalHeight <= maxHeight

    So minRatio has been placed as the basis of calculations (See the implementation). UNLIKE the basic solution that has placed maxRatio as the basis of calculations in actual. Also, the calculation of inSampleSize has been so much better (more logic, brief and efficient).

  3. It is assumed that you want to (at least) one of the final dimensions has exactly the value of its corresponding maxValue (each one was possible, by considering the above assumptions). In other words:

    finalWidth == maxWidth || finalHeight == maxHeight

    The final additional step in compare to the basic solution (Bitmap.createScaledBitmap(...)) is for this "exactly" constraint. The very important note is you shouldn't take this step at first (like the accepted answer), because of its significant consumption of memory in case of huge images!

  4. It is for decoding a file. You can change it like the basic solution to decode a resource (or everything that BitmapFactory supports).

The implementation:

public static Bitmap decodeSampledBitmap(String pathName, int maxWidth, int maxHeight) {
    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(pathName, options);

    final float wRatio_inv = (float) options.outWidth / maxWidth,
          hRatio_inv = (float) options.outHeight / maxHeight; // Working with inverse ratios is more comfortable
    final int finalW, finalH, minRatio_inv /* = max{Ratio_inv} */;

    if (wRatio_inv > hRatio_inv) {
        minRatio_inv = (int) wRatio_inv;
        finalW = maxWidth;
        finalH = Math.round(options.outHeight / wRatio_inv);
    } else {
        minRatio_inv = (int) hRatio_inv;
        finalH = maxHeight;
        finalW = Math.round(options.outWidth / hRatio_inv);
    }

    options.inSampleSize = pow2Ceil(minRatio_inv); // pow2Ceil: A utility function that comes later
    options.inJustDecodeBounds = false; // Decode bitmap with inSampleSize set

    return Bitmap.createScaledBitmap(BitmapFactory.decodeFile(pathName, options),
          finalW, finalH, true);
}

/**
 * @return the largest power of 2 that is smaller than or equal to number. 
 * WARNING: return {0b1000000...000} for ZERO input.
 */
public static int pow2Ceil(int number) {
    return 1 << -(Integer.numberOfLeadingZeros(number) + 1); // is equivalent to:
    // return Integer.rotateRight(1, Integer.numberOfLeadingZeros(number) + 1);
}

Sample Usage, in case of you have an imageView with a determined value for layout_width (match_parent or a explicit value) and a indeterminate value for layout_height (wrap_content) and instead a determined value for maxHeight:

imageView.setImageBitmap(decodeSampledBitmap(filePath, 
        imageView.getWidth(), imageView.getMaxHeight()));
Mir-Ismaili
  • 13,974
  • 8
  • 82
  • 100