Resize a large bitmap file to scaled output file on Android
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:
- Calculate the maximum possible
inSampleSize
that still yields an image larger than your target. - Load the image using
BitmapFactory.decodeFile(file, options)
, passing inSampleSize as an option. - 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.
- It only reads meta data to get the original size (options.inJustDecodeBounds)
- It uses a rought resize to save memory (itmap.createScaledBitmap)
- 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);
Related videos on Youtube
Manuel
Updated on January 29, 2020Comments
-
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 aBitmapFactory.Options.inSampleSize
, because I want to resize it to an exact width and height. UsinginSampleSize
would resize the bitmap to 972x648 (if I useinSampleSize=4
) or to 778x518 (if I useinSampleSize=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.-
rds over 13 yearsNo, the outHeight and outWidth are out parameters from the decode method. That being said, I have the same issue than you, and I'm not very satisfied with the 2 steps approach ever.
-
Fattie almost 10 yearsoften, thank goodness, you can use one line of code .. stackoverflow.com/a/17733530/294884
-
Fattie over 9 yearsReaders, please note this absolutely critical QA !!! stackoverflow.com/a/24135522/294884
-
Fattie over 9 yearsPls note that this question is now 5 years old, and the full solution is .. stackoverflow.com/a/24135522/294884 Cheers!
-
Vince over 9 yearsThere is now an official documentation on that topic: developer.android.com/training/displaying-bitmaps/…
-
jww over 5 years
-
-
Manuel almost 14 yearsI tried to avoid that. So there's no way to directly resize a large image in only one step?
-
Justin almost 14 yearsNot to my knowledge, but don't let that stop you from exploring this further.
-
Manuel almost 14 yearsSince 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 almost 14 yearsAlright, I will take this for my accepted answer so far. If I find out any other methods, I will let you know.
-
Manuel over 13 yearsBecause 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 over 13 yearsRight. 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 over 12 yearsMorning, 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 almost 12 yearsMakes it hard to read when you use variables like "b" but good answer non the less.
-
Biginner almost 11 years@Ofir : getImageUri(path); what i have to pass in this method?
-
Fattie over 9 yearsthe power calculation for the scale is simply wrong here; just use the calculation on the android doco page.
-
david.perez over 9 yearsInstead of (wh)/Math.pow(scale, 2) it is more efficient to use (wh) >> scale.
-
Ostkontentitan over 9 yearsVery nice! Using inDensity instead of Bitmap.createScaledBitmap saved me a lot of memory heap. Even better combined with inSamplesize.
-
user276648 about 9 yearsAs PSIXO mentioned in an answer, you may also want to use android:largeHeap if you still have issues after using inSampleSize.
-
gw0 about 9 yearsDon't call
System.gc()
please -
JoeCoolman over 8 yearsThanks @Ofir but this transformation doesn't conserve the image orientation :-/
-
cybergen about 8 yearsFirst 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 about 8 yearsDon'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 almost 8 yearsPlease notice that bmOptions.inPurgeable = true; is deprecated.
-
Hamza over 6 yearsit 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 about 6 yearsbitmap variable was getting empty
-
Phong Nguyen almost 6 yearsthank a lot, it works fine. btw instead of new instance, it could be bitmapOptions.inJustDecodeBounds = false to decode bitmap