I am trying to store a bitmap (that i have previously read from a file) after decoding it in a preferable size using BitmapFactory.Options.inSampleSize. The problem is that the file size of the stored bitmap is at least double the size of the original file. I have searched a lot and could not find how i can deal with this, since i don't want it to happen for memmory efficiency (later i reuse the stored bitmap). Here is my method that does what i describe:
private Bitmap decodeFileToPreferredSize(File f) {
Bitmap b = null;
try {
// Decode image size
Log.i("Bitmap", "Imported image size: " + f.length() + " bytes");
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(f.getAbsolutePath(), o);
//Check if the user has defined custom image size
int scale = 1;
if(pref_height != -1 && pref_width != -1) {
if (o.outHeight > pref_height || o.outWidth > pref_width) {
scale = (int) Math.pow(
2,
(int) Math.round(Math.log(pref_width
/ (double) Math.max(o.outHeight, o.outWidth))
/ Math.log(0.5)));
}
}
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
b = BitmapFactory.decodeFile(f.getAbsolutePath(), o2);
String name = "Image_" + System.currentTimeMillis() + ".jpg";
File file = new File(Environment.getExternalStorageDirectory(), name);
FileOutputStream out = new FileOutputStream(file);
b.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.close();
Log.i("Bitmap", "Exported image size: " + file.length() + " bytes");
} catch (Exception e) {
b = null;
}
return b;
}
UPDATE I saw the densities on the two image files. The one that came from camera intent has 72 dpi density for width and height. The image file created from my above method has 96 dpi density for width and height. This explains why a 0.5 MByte image that came form the camera is resized in approximatelly 2.5 MByte with my above method since the rescale factor is (96/72) * 2 ~= 2.5. For some reason, the bitmap i create does not take the density of the image that came from the camera. I tried to set the density with all variation of BitmapFactory.Options.inDensity but no luck. Also i tried to change the bitmap density with bitmap.setDensity(int dpi); but still no effect. So, my new question is if there is a way to define the density of the bitmap when the image is stored.
Thanks in advance.
I had a similar issue. When I downloaded images from web they used more space on the SD than they did when downloaded to my PC from browser. I think the issue is simply that BitmapFactory saves the images in a non optimzed format of some sort.
My workaround was to use following instead of the bitmapfactory:
try {
try {
is = yourinputstream;
// Consider reading stream twice and return the bitmap.
os = youroutputstream;
byte data[] = new byte[4096];
int count;
while ((count = is.read(data)) != -1) {
os.write(data, 0, count);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(is != null) {
is.close();
}
if(os != null) {
os.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
You are correct that the problem is in a density change.
This thread revealed a solution: BitmapFactory returns bigger image than source
Before decoding also disable inScaled:
options.inSampleSize = 2; //or however you desire, power of 2
options.inScaled = false;
This will keep the density from changing and you will see the decrease in size you were looking for.
When BitmapFactory.Options.inSampleSize = 0.5, it does 100% / 0.5 = 200%.
What you want I think is BitmapFactory.Options.inSampleSize = 2, it does 100% / 2 = 50%
Related
The scope: I want to take a picture via intent and save the picture to the internal storage of my app.
Then I want to load a scaled version into a byte array (from inputstream), save this scaled image as byte array into SQLight.
After saving it to the database I want to delete the picture.
(This question only is about saving the image to internal storage, the scope is only here because there is always someone that ask about it)
The problem: I'm stuck at saving the picture to the internal storage.
I'll add examples from my debugging session as comments behind the variables to show the values i got while testing.
I have an ImageView which has an onClickListener that starts the takePictureIntent:
With following global attributes:
Uri mCurrentPhotoUri; //URI to file
File mCurrentPicture; //the current picture don't know if I need it somewhere but for complete understanding of code
imageView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Intent for the on-board camera
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//device has camera
if(takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
//create a file with path the code below
photoFile = createImageFile(); //sets photoFile to: /data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
} catch (IOException e) {
e.printStackTrace();
}
//file has been created, set members and add Extra to intent, then start intent.
if(photoFile != null) {
mCurrentPicture = photoFile; // well, same as above
mCurrentPhotoUri = Uri.fromFile(photoFile); // this looks somehow wrong, but I don't know much about URIs: file:///data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile)); //same URI as above that extra should be needed to tell the cam that I don't want to save to the default path but my app path
startActivityForResult(takePictureIntent, 10); //start the intent and use requestcode 10 for onActivityResult ...
}
}
}
});
The creation of the file path:
//code from google developers with some changes.
private File createImageFile() throws IOException {
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); //from today value: 20151105_092219
String imageFilename = "JPEG_" + timestamp + "_"; // concat is this: JPEG_20151105_092219_
File storageDir = this.getDir("photo", MODE_PRIVATE); //String path is: /data/data/my.app.project/app_photo
storageDir.mkdirs();
File image = File.createTempFile(imageFilename, ".jpg", storageDir); //String path is: /data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
mCurrentPhotoPath = "file:" + image.getAbsolutePath(); //here I put the absolute path into static mCurrentPhotoPath, concate with the "file:" from googledeveloper guide: file:/data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
return image;
}
So the camera opens and I can take a picture and I'm ask if I want to save that picture (all from the build-in camera app, device is a samsung galaxy note).
Then my onActivityResult-Method is called:
I used data as parameter because I used the mini byte array for something, but with the custom storage this returns null and it isn't used anymore.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
switch(requestcode) {
...
case 10:
setImageView(ivPreview1, data, 0);
ivPreview.setVisibility(View.VISIBLE);
break;
...
}
...
}
}
Method setImageView:
private void setImageView(ImageView iv, Intent data, int index) {
try {
Uri u = mCurrentPhotoUri; //sets u to: file:///data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
File file = new File(u.getPath()); //sets file to: /data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
Bitmap bm = null;
ByteArrayOutputStream baos = null;
int orientation = 0;
if (file.exists()) { //this is true
//found that somewhere in the developer training:
ExifInterface exif = null;
try {
exif = new ExifInterface(photoUri.getPath());
} catch (IOException e) {
e.printStackTrace();
}
if(exif != null)
orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0); //is 0 (i didn't rotate the tablet)
//resulution I want to resize the image to:
int reqWidth = 960, reqHeight = 1280;
//exchange values if orientation doesn't match landscape
if (orientation == 0 || orientation == 270) {
int temp = reqWidth;
reqWidth = reqHeight;
reqHeight = temp;
}
//this I used before I changed to internal storage to change the size of the image code below
bm = ImageManager.decodeSampledBitmapFromFile(u.getPath(), reqWidth, reqHeight); // returns null because of this everything following is null too.
if (orientation == 90 || orientation == 180 || orientation == 270) {
Matrix matrix = new Matrix();
// rotate the Bitmap
if (orientation == 90)
matrix.postRotate(90F);
else if (orientation == 270)
matrix.postRotate(-90F);
else
matrix.postRotate(180F);
// recreate the new Bitmap
bm = Bitmap.createBitmap(bm, 0, 0,
bm.getWidth(), bm.getHeight(), matrix, true);
}
baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 50, baos);
}
iv.setImageBitmap(bm);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "Could not take Photo: ", e);
}
}
The following methods that I used to decode the file (customisation of: http://developer.android.com/downloads/samples/DisplayingBitmaps.zip ):
The line with BitmapFactory.decodeFile(filename, options); also creates a log entry: D/skia: --- SkImageDecoder::Factory returned null
public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth, int reqHeight) {
//this gets parameters:
// reqHeight: 960, reqWidth: 1280 and filename: /data/data/my.app.project/app_photo/JPEG_20151105_092219_-1434131481.jpg
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, options); // this adds outHeight and outWidth to -1 (variables from options)
//this also creates a log entry: D/skia: --- SkImageDecoder::Factory returned null
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(filename, options);
return bmp;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// BEGIN_INCLUDE (calculate_sample_size)
// Raw height and width of image
final int height = options.outHeight; //is -1
final int width = options.outWidth; //is -1
int inSampleSize = 1;
//because its obviously smaller in both statements code will not be executed so it returns 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;
}
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
long totalPixels = width * height / inSampleSize;
// Anything more than 2x the requested pixels we'll sample down further
final long totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels > totalReqPixelsCap) {
inSampleSize *= 2;
totalPixels /= 2;
}
}
return inSampleSize;
// END_INCLUDE (calculate_sample_size)
}
I'm stuck at this for several days now I don't have any ideas that could solve my problem. This is also due to lack of android knowledge and the fact that i can't use emulators on my pc so i can't even look in the app folder to see if a picture was taken.
Try to get the path for storing temporary image like below.which will return your app folder location.and add the permission as well.
File dir = context.getExternalFilesDir(null)+"/"+"photo";
Add uses-feature for camera access too.
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
Official documentation.
Days, I've spent working on this. Weeks, perhaps. Literally. :(
So I've got an image on an SD card that more than likely came out of the built-in camera. I want to take that image and downsample it to an arbitrary size (but always smaller and never larger). My code uses standard Android Bitmap methods to decode, resize, recompress, and save the image. Everything works fine as long as the final image is smaller than 3MP or so. If the image is larger, or if I try to do several of these at once, the application crashes with an OutOfMemoryError. I know why that's happening, and I know it's happening for a perfectly legitimate reason, I just want it to not happen anymore.
Look, I'm not trying to launch a rocket here. All I want to do is resize a camera image and dump it to an OutputStream or even a temporary file. Surely someone out there must have done such a thing. I don't need you to write my code for me, and I don't need my hand held. But between my various programming abortions and days of obsessed Googling, I don't even know which direction to head in. Roughly speaking, does anyone know how to decode a JPEG, downsample it, re-compress it in JPEG, and send it out on an OutputStream without allocating a massive amount of memory?
Ok I know it's a little bit late but, I had this problem and I found solution. It is actually easy and I am sure it supports back to api 10(I have no idea about before 10). I tried this with my phone. It is a samsung galaxy s2 with an 8mp camera and the code perfectly resized camera images to the 168x168 as well as images i found on web. I checked the images by using file manager too. I never tried resizing images to bigger resoulation.
private Bitmap resize(Bitmap bp, int witdh, int height){
return Bitmap.createScaledBitmap(bp, width, height, false);
}
you can save it like this
private void saveBitmap(Bitmap bp) throws FileNotFoundException{
String state = Environment.getExternalStorageState();
File folder;
//if there is memory card available code choose that
if (Environment.MEDIA_MOUNTED.equals(state)) {
folder=Environment.getExternalStorageDirectory();
}else{
folder=Environment.getDataDirectory();
}
folder=new File(folder, "/aaaa");
if(!folder.exists()){
folder.mkdir();
}
File file=new File(folder, (int)(Math.random()*10000)+".jpg");
FileOutputStream os=new FileOutputStream(file);
bp.compress(Bitmap.CompressFormat.JPEG, 90, os);
}
thanks to this link
The following code is from my previous project. Key point is "options.inSampleSize".
public static Bitmap makeBitmap(String fn, int minSideLength, int maxNumOfPixels) {
BitmapFactory.Options options;
try {
options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(fn, options);
if (options.mCancel || options.outWidth == -1
|| options.outHeight == -1) {
return null;
}
options.inSampleSize = computeSampleSize(
options, minSideLength, maxNumOfPixels);
options.inJustDecodeBounds = false;
//Log.e(LOG_TAG, "sample size=" + options.inSampleSize);
options.inDither = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeFile(fn, options);
} catch (OutOfMemoryError ex) {
Log.e(LOG_TAG, "Got oom exception ", ex);
return null;
}
}
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
double w = options.outWidth;
double h = options.outHeight;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
(int) Math.min(Math.floor(w / minSideLength),
Math.floor(h / minSideLength));
if (upperBound < lowerBound) {
// return the larger one when there is no overlapping zone.
return lowerBound;
}
if ((maxNumOfPixels == UNCONSTRAINED) &&
(minSideLength == UNCONSTRAINED)) {
return 1;
} else if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
return upperBound;
}
}
I have read many related posts concerning memory allocation problems with decoding bitmaps, but am still unable to find the solution to the following problem even after using the code provided in the official website.
Here is my code:
public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
InputStream is2 = new ByteArrayInputStream(baos.toByteArray());
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is1, null, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(is2, null, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
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) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600);
I am getting "Out of memory error on a 3250016 - byte allocation" in this line:
return BitmapFactory.decodeStream(is2, null, options);
It would seem to me that 3.2 MB is small enough to be allocated. Where am I going wrong? How can I solve this?
EDIT
After looking into this solution HERE by N-Joy it works fine with Required size 300 but my required size is 800, so i am still getting the error.
The method decodeSampledBitmapFromResource is not memory efficient because it uses 3 streams: the ByteArrayOutputStream baos, ByteArrayInputStream is1 and ByteArrayInputStream is2, each of those stores the same stream data of the image (one byte array for each).
And when I test with my device (LG nexus 4) to decode an 2560x1600 image on SDcard to target size 800 it takes something like this:
03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999
We can see: too much memory allocated (28.5 MB) just to decode 4096000 a pixel image.
Solution: we read the InputStream and store the data directly into one byte array and use this byte array for the rest work.
Sample code:
public Bitmap decodeSampledBitmapFromResourceMemOpt(
InputStream inputStream, int reqWidth, int reqHeight) {
byte[] byteArr = new byte[0];
byte[] buffer = new byte[1024];
int len;
int count = 0;
try {
while ((len = inputStream.read(buffer)) > -1) {
if (len != 0) {
if (count + len > byteArr.length) {
byte[] newbuf = new byte[(count + len) * 2];
System.arraycopy(byteArr, 0, newbuf, 0, count);
byteArr = newbuf;
}
System.arraycopy(buffer, 0, byteArr, count, len);
count += len;
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArr, 0, count, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);
return BitmapFactory.decodeByteArray(byteArr, 0, count, options);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
The method that does the calculation:
public void onButtonClicked(View v) {
int[] pids = { android.os.Process.myPid() };
MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
long startTime = System.currentTimeMillis();
FileInputStream inputStream;
String filePath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/test2.png";
File file = new File(filePath);
try {
inputStream = new FileInputStream(file);
// mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
800);
ImageView imageView = (ImageView) findViewById(R.id.image);
imageView.setImageBitmap(mBitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
+ " time = " + (System.currentTimeMillis() - startTime));
}
And the result:
03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
This is a common issue which user normally faces while playing with large bitmaps and there are lots a questions discussed on site, here, here, here and here and many more, even though user not able to manipulate the exact solution.
I stumbled upon a library sometime back which manages bitmaps smoothly and others links which I listed below. Hope this helps!
smoothie-Library
Android-BitmapCache
Android-Universal-Image-Loader
Solution for OutOfMemoryError: bitmap size exceeds VM budget
ARGB_8888 uses more memory as it takes Alpha color value so my suggestion is to use RGB_565 as stated HERE
Note: Quality will be little low compared to ARGB_8888.
You're probably holding on to previous bitmap references. I'm guessing that you're executing this code several times and never executing bitmap.recycle(). Memory will inevitably run out.
I had many problems with Bitmap memory usage.
Results:
Most devices have limited heap memory for graphics, most small devices are limited to 16MB for overall apps, not just your app
Use 4 bit or 8 bit or 16 bit bitmaps if applicable
Try to draw shapes from scratch, omit bitmaps if possible.
Use WebView to dynamically load as many images as you like, it's built using NDK (low level) so has no GDI heap memory restrictions.
It works smooth and fast :)
Out of memory problems when decoding bitmaps are not often linked with the image size you are decoding.
Of course if you try to open an image 5000x5000px you will fail with a OutOfMemoryError, but with the size of 800x800px it is totally reasonable and should work fine.
If your device is out of memory with a 3.2 MB image it's likely because you are leaking context somewhere in the app.
It's the first part of this post:
I guess problem is not in your layout, problem is somewhere else in
your code. and probably you are leaking context somewhere.
What it means it's that you are using Activity Context in components that should not, preventing them to be garbage collected. Because there components often are held by activities, those activities are not GC and your java heap will grow very fast and your app will crash at one time or another.
As Raghunandan said, you will have to use MAT to find wich Activity/Component is held and remove the context leak.
The best way I found for now to detect context leak is orientation change.
For example, rotate your ActivityMain multiple times, run MAT and check if you have only one instance of ActivityMain. If you have multiple ones (as much as rotation changes) it means there is a context leak.
I found years ago a good tutorial on using MAT. Maybe there is better one now.
Other posts on memory leaks:
Android - memory leak or?
Out of memory error on android emulator, but not on device
Have a look at this video. http://www.youtube.com/watch?v=_CruQY55HOk. Do not use system.gc() as suggested in the video. Use a MAT Analyzer to find out memory leaks. The returned bitmap is too huge causing memory leak i guess.
It seems you have large image to display.
You can download image and save to sdcard (example) then you can user this
code to display image from sdcard.
i also had same problem earlier.. and i have managed it by using this function where you can get scale as your required width and height.
private Bitmap decodeFile(FileInputStream f)
{
try
{
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(f,null,o);
//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true)
{
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(f, null, o2);
}
catch (FileNotFoundException e) {}
return null;
}
and refer Memory Leak Error Android and error in loading images to gridview android
I have an Android application that is very image intensive. I'm currently using Bitmap.createScaledBitmap() to scale the image to a desired size. However, this method requires that I already have the original bitmap in memory, which can be quite sizable.
How can I scale a bitmap that I'm downloading without first writing the entire thing out to local memory or file system?
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 ) {}
}
}
Here is my version, based on #emmby solution (thanks man!)
I've included a second phase where you take the reduced bitmap and scale it again to match exactly your desired dimensions.
My version takes a file path rather than a stream.
protected Bitmap createScaledBitmap(String filePath, int desiredBitmapWith, int desiredBitmapHeight) throws IOException, FileNotFoundException {
BufferedInputStream imageFileStream = new BufferedInputStream(new FileInputStream(filePath));
try {
// Phase 1: Get a reduced size image. In this part we will do a rough scale down
int sampleSize = 1;
if (desiredBitmapWith > 0 && desiredBitmapHeight > 0) {
final BitmapFactory.Options decodeBoundsOptions = new BitmapFactory.Options();
decodeBoundsOptions.inJustDecodeBounds = true;
imageFileStream.mark(64 * 1024);
BitmapFactory.decodeStream(imageFileStream, null, decodeBoundsOptions);
imageFileStream.reset();
final int originalWidth = decodeBoundsOptions.outWidth;
final int originalHeight = decodeBoundsOptions.outHeight;
// inSampleSize prefers multiples of 2, but we prefer to prioritize memory savings
sampleSize = Math.max(1, Math.max(originalWidth / desiredBitmapWith, originalHeight / desiredBitmapHeight));
}
BitmapFactory.Options decodeBitmapOptions = new BitmapFactory.Options();
decodeBitmapOptions.inSampleSize = sampleSize;
decodeBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; // Uses 2-bytes instead of default 4 per pixel
// Get the roughly scaled-down image
Bitmap bmp = BitmapFactory.decodeStream(imageFileStream, null, decodeBitmapOptions);
// Phase 2: Get an exact-size image - no dimension will exceed the desired value
float ratio = Math.min((float)desiredBitmapWith/ (float)bmp.getWidth(), (float)desiredBitmapHeight/ (float)bmp.getHeight());
int w =(int) ((float)bmp.getWidth() * ratio);
int h =(int) ((float)bmp.getHeight() * ratio);
return Bitmap.createScaledBitmap(bmp, w,h, true);
} catch (IOException e) {
throw e;
} finally {
try {
imageFileStream.close();
} catch (IOException ignored) {
}
}
}
I'm having an odd problem with my map pin sizes. To preserve dynamic-ness, the map pins for different categories are stored on a site's server so that they can be changed at any point even after the app is published.
I'm caching the pins every time I download them and I only ever re-download them if the server sends back a bit saying that one has changed since last I downloaded it. The first time I grab the pins, I use the bitmaps before I save them to files and the map markers are the correct size. Every time after that I'm loading a saved version of the pins straight from the image file. These are displaying considerably smaller than they are when using the bitmaps from the first download.
At first, I thought it was a problem with the way I'm saving the PNGs, but their sizes are correct (64 x 64). Is this a dip/px issue or do I need to decompress the image files with some sort of option?
Here's how I grab the images the first time:
public static Bitmap loadMapPin(String category, int width, int height) {
URL imageUrl;
category = category.toLowerCase().replace(" ", "");
try {
imageUrl = new URL(PIN_URL+category+".png");
InputStream is = (InputStream) imageUrl.getContent();
Options options = new Options();
options.inJustDecodeBounds = true; //Only find the dimensions
//Decode without downloading to find dimensions
BitmapFactory.decodeStream(is, null, options);
boolean scaleByHeight = Math.abs(options.outHeight - height) >= Math.abs(options.outWidth - width);
if(options.outHeight * options.outWidth >= width * height){
// Load, scaling to smallest power of 2 that'll get it <= desired dimensions
double sampleSize = scaleByHeight
? options.outHeight / height
: options.outWidth / width;
options.inSampleSize =
(int)Math.pow(2d, Math.floor(
Math.log(sampleSize)/Math.log(2d)));
}
options.inJustDecodeBounds = false; //Download image this time
is.close();
is = (InputStream) imageUrl.getContent();
Bitmap img = BitmapFactory.decodeStream(is, null, options);
return img;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
And here's how I'm loading them from the cached file:
BitmapFactory.decodeFile(filepath);
Thanks in advance!
I've found that, by default, decompressing an image to a bitmap doesn't scale with high density screens. You have to set the density to none. In other words, you specify that the image is meant for an unknown density.
Solution:
Bitmap b = BitmapFactory.decodeFile(filepath);
b.setDensity(Bitmap.DENSITY_NONE);