How do I scale a streaming bitmap in-place without reading the whole image first?
This method will read the header information from the image to determine its size, then read the image and scale it to the desired size in place without allocating memory for the full original sized image.
It also uses BitmapFactory.Options.inPurgeable, which seems to be a sparsely documented but desirable option to prevent OoM exceptions when using lots of bitmaps. UPDATE: no longer uses inPurgeable, see this note from Romain
It works by using a BufferedInputStream to read the header information for the image before reading the entire image in via the InputStream.
/**
* Read the image from the stream and create a bitmap scaled to the desired
* size. Resulting bitmap will be at least as large as the
* desired minimum specified dimensions and will keep the image proportions
* correct during scaling.
*/
protected Bitmap createScaledBitmapFromStream( InputStream s, int minimumDesiredBitmapWith, int minimumDesiredBitmapHeight ) {
final BufferedInputStream is = new BufferedInputStream(s, 32*1024);
try {
final Options decodeBitmapOptions = new Options();
// For further memory savings, you may want to consider using this option
// decodeBitmapOptions.inPreferredConfig = Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel
if( minimumDesiredBitmapWidth >0 && minimumDesiredBitmapHeight >0 ) {
final Options decodeBoundsOptions = new Options();
decodeBoundsOptions.inJustDecodeBounds = true;
is.mark(32*1024); // 32k is probably overkill, but 8k is insufficient for some jpgs
BitmapFactory.decodeStream(is,null,decodeBoundsOptions);
is.reset();
final int originalWidth = decodeBoundsOptions.outWidth;
final int originalHeight = decodeBoundsOptions.outHeight;
// inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings
decodeBitmapOptions.inSampleSize= Math.max(1,Math.min(originalWidth / minimumDesiredBitmapWidth, originalHeight / minimumDesiredBitmapHeight));
}
return BitmapFactory.decodeStream(is,null,decodeBitmapOptions);
} catch( IOException e ) {
throw new RuntimeException(e); // this shouldn't happen
} finally {
try {
is.close();
} catch( IOException ignored ) {}
}
}
Memory efficient way to scale an image
I'm glad you asked, I was about to investigate this for my own project.
It looks like BitmapFactory.Options is your friend here, specifically BitmapFactory.Options.inSampleSize enter link description here. You can use BitmapFactory.decodeStream to get the image dimensions without creating a Bitmap.
Googling revealed the com.bristle.javalib.awt ImgUtil classes; these appear to be open source (don't know the license), and are AWT based, so may work on Android. The interesting method is ImgUtil.scalueImageToOutputStreamAsJPEG, which works with InputSteam/OutputStream, so may be memory efficient.
UPDATE
An alternative is to write a JNI wrapper around /system/lib/libjpeg.so, which appears to be standard on Android devices. This library works with scanlines, so can be memory friendly. Another plus is that using JNI should be faster than pure java.
Hope this helps,
Phil Lello
Resize a large bitmap file to scaled output file on Android
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()
.
Resizing bitmap from InputStream
Actually you have two different BufferedInputStream
but they internally use the only one InputStream
object because BufferedInputStream
is only a wrapper for InputStream
.
So you can't just call two times BitmapFactory.decodeStream
method on the same stream, it will definitely fail because the second time it wouldn't start decoding from the beginning of the stream. You need to reset your stream if it is supported or reopen it.
Preserve the image quality when decode stream in android
The following code uses several concepts from Displaying Bitmaps Efficiently
First off the bitmap reading is done in a background thread, I'm using mark / reset on inputStream
(wrapped with BufferedInputstream
) to not read more than necessary from stream when we try to find out the size of the image to use when calculating scale factor. The example code below subsamples the image to match a size of 320x240 pixles. In a non example code one could have simple callback interface send the bitmap from onPostExecute
to implementing class (callback interface implementer). Or provide the view as a member tot the AsyncTask
directly and set the bitmap in onPostExecute
.
Call the code with (example downloaded image on my device):
BitmapTask task = new BitmapTask(getContentResolver());
task.execute(Uri.parse("file:///storage/emulated/0/Download/download.jpg"));
The classes in question
private static class BitmapTask extends AsyncTask<Uri, Void, Bitmap> {
// prevent mem leaks
private WeakReference<ContentResolver> mWeakContentResolver;
public BitmapTask(ContentResolver resolver) {
mWeakContentResolver = new WeakReference<ContentResolver>(resolver);
}
@Override
protected Bitmap doInBackground(Uri... params) {
Bitmap bitmap = null;
ContentResolver resolver = mWeakContentResolver.get();
if (resolver != null) {
BufferedInputStream stream = null;
try {
stream = new BufferedInputStream(
resolver.openInputStream(params[0]));
stream.mark(1 * 1024);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// Find out size of image
BitmapFactory.decodeStream(stream, null, options);
try {
stream.reset();
} catch (IOException e) {
Log.d(TAG, "reset failed");
}
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Log.d(TAG, "w, h, mime " + imageWidth + " , " + imageHeight
+ " , " + imageType);
options.inJustDecodeBounds = false;
// Calculate down scale factor
options.inSampleSize = calculateInSampleSize(options, 320,
240);
return BitmapFactory.decodeStream(stream, null, options);
} catch (FileNotFoundException e) {
bitmap = null;
} finally {
IOUtils.closeStreamSilently(stream);
}
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
Log.d(TAG,
"bitmap result: "
+ ((result != null) ? "" + result.getByteCount()
: "0"));
result.recycle();
}
}
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) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Edit: For large inputstreams there might be a problem with the mark/reset technique, SkImageDecoder::Factory returned null
can be seen in logs sometimes, resulting in null bitmap, se other SO question on the matter: SkImageDecoder::Factory returned null. It can be fixed by reiniting the stream variable again stream = new resolver.openInputStream(params[0]));
before return in doInBackground
Edit 2: If you have to preserve the image size but wan't to limit memory usage you could use the options.inPreferredConfig = Bitmap.Config.RGB_565;
that halves the memory per pixel, but bare in mind that the images might not have great quality anymore (experiment!).
Trying to scale an image and return a bitmap from android gallery
You need to seek back to the start of the InputStream after reading the bounds before calling decodeStream for a second time, otherwise you will end up with an invalid Bitmap. The easiest way is just to close the stream and open it again.
Try this code (note function is no longer static to allow calling getContentResolver() and you have pass in a Uri not an InputStream):
public Bitmap getBitmapFromReturnedImage(Uri selectedImage, int reqWidth, int reqHeight) throws IOException {
InputStream inputStream = getContentResolver().openInputStream(selectedImage);
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// close the input stream
inputStream.close();
// reopen the input stream
inputStream = getContentResolver().openInputStream(selectedImage);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeStream(inputStream, null, options);
}
call like this
Uri selectedImage = data.getData();
imageBitmap = getBitmapFromReturnedImage(selectedImage, 800, 800);
Related Topics
Change Navigation Bar Icon Color on Android
Retain The Fragment Object While Rotating
Fragment in Viewpager Not Restored After Popbackstack
End Incoming Call Programmatically
How to Fix Android Studio Getting Stuck Executing Gradle Tasks
Using Espresso to Unit Test Google Maps
How to Change Android:Windowsoftinputmode Value from Java Class
Detecting Outgoing Call and Call Hangup Event in Android
Detecting The Scrolling Direction in The Adapter (Up/Down)
Android: Is There Any Free PDF Library for Android
Using Google Places API in Android
Android - How to Set Background Color of All Screens
How to Make Space Between Linearlayout Children
Animation for Expandablelistview
Eclipse: Won't Let Me Use Android Sdk, Wrongly Claims My Adt Is Out of Date
To Run Dex in Process, The Gradle Daemon Needs a Larger Heap. It Currently Has Approximately 910 Mb