I have an image on sdcard and need to show on the image view
The problem is that after it has been decoded, it seems the quality is deteriorated. Are there any ways to keep the quality and at the same time preserve the memory?
Or, if I used an larger image, are there any ways to preserve the memory (avoid too large bitmap to load) with scaling? (I need to keep the size of the original image)
Thanks for helping.
public Bitmap decodeFile(String pubKey, int bookPageID, int type)
throws IOException {
Bitmap b = null;
File f = null;
String uri = null;
FileInputStream fis = null;
Log.d(TAG,"pageID to read: " + bookPageID);
IRIssue issue = Broker.model.issueDataStore.getIRIssue(pubKey);
String imageFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey);
// pageID - 1 since the page is an array (start at 0) , but page ID start at 1
if (type == 2){
uri = imageFolder + issue.vol[0].pages[bookPageID - 1].graphicUri;
}else {
uri = imageFolder + issue.vol[0].pages[bookPageID - 1].textUri;
}
f = new File(uri);
Log.d(TAG,"is file: " + uri + " exist?" + f.exists());
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, options);
fis.close();
return b;
}
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!).