It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I have spent much time to understand how to scale images without Out Of Memory exception, and I want to share my knowledge, I wrote function that scale images(from big to small) to exactly size. The Algorithm is:
We scale image using quick algorihtm as match as possible, but image should still has sizes more then required.
We scale image to exactly result size.
The function is good commented, so it is easy to undrstand. If you will find errors or you know how make it more correct and more quick, please leave comment.
Enjoy!
//this function change image size(from big to small) using low memory(as possible)
//the bigger side of result image will equals to IMAGE_BIGGER_SIDE_SIZE
//and second side will be scaled proportionally
//for example if you have image 800x400 and call decodeBitmapSize(bm, 200)
//you will receive image 200x100
//algorithm:
//1. we scale image using quick algorihtm as match as possible,
// but image will still has sizes more then required
//2. we scale image to exactly result size
public static Bitmap decodeBitmapSize(Bitmap bm, int IMAGE_BIGGER_SIDE_SIZE){
//we will return this Bitmap
Bitmap b = null;
//convert Bitmap to byte[]
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
//We need to know image width and height,
//for it we create BitmapFactory.Options object and do BitmapFactory.decodeByteArray
//inJustDecodeBounds = true - means that we do not need load Bitmap to memory
//but we need just know width and height of it
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opt);
int CurrentWidth = opt.outWidth;
int CurrentHeight = opt.outHeight;
//there is function that can quick scale images
//but we need to give in scale parameter, and scale - it is power of 2
//for example 0,1,2,4,8,16...
//what scale we need? for example our image 1000x1000 and we want it will be 100x100
//we need scale image as match as possible but should leave it more then required size
//in our case scale=8, we receive image 1000/8 = 125 so 125x125,
//scale = 16 is incorrect in our case, because we receive 1000/16 = 63 so 63x63 image
//and it is less then 100X100
//this block of code calculate scale(we can do it another way, but this way it more clear to read)
int scale = 1;
int PowerOf2 = 0;
int ResW = CurrentWidth;
int ResH = CurrentHeight;
if (ResW > IMAGE_BIGGER_SIDE_SIZE || ResH > IMAGE_BIGGER_SIDE_SIZE) {
while(1==1)
{
PowerOf2++;
scale = (int) Math.pow(2,PowerOf2);
ResW = (int)((double)opt.outWidth / (double)scale);
ResH = (int)((double)opt.outHeight / (double)scale);
if(Math.max(ResW,ResH ) < IMAGE_BIGGER_SIDE_SIZE)
{
PowerOf2--;
scale = (int) Math.pow(2,PowerOf2);
ResW = (int)((double)opt.outWidth / (double)scale);
ResH = (int)((double)opt.outHeight / (double)scale);
break;
}
}
}
//Decode our image using scale that we calculated
BitmapFactory.Options opt2 = new BitmapFactory.Options();
opt2.inSampleSize = scale;
b = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opt2);
//calculating new width and height
int w = b.getWidth();
int h = b.getHeight();
if(w>=h)
{
w = IMAGE_BIGGER_SIDE_SIZE;
h =(int)( (double)b.getHeight() * ((double)w/b.getWidth()));
}
else
{
h = IMAGE_BIGGER_SIDE_SIZE;
w =(int)( (double)b.getWidth() * ((double)h/b.getHeight()));
}
//if we lucky and image already has correct sizes after quick scaling - return result
if(opt2.outHeight==h && opt2.outWidth==w)
{
return b;
}
//we scaled our image as match as possible using quick method
//and now we need to scale image to exactly size
b = Bitmap.createScaledBitmap(b, w,h,true);
return b;
}
I've got a serious performance issue in my app, when loading up bitmaps it seems to take up way to much memory.
I have a drawable folder which contains the bitmap sizes for all android devices, these bitmaps are of high quality. Basically it goes though each bitmap and makes a new one for the device depending on the size. (Decided to do it this way because it supports the correct orientation and any device). It works but it's taking up way to much memory and takes along time to load. Can anyone make any suggestions on the following code.
public Bitmap getBitmapSized(String name, int percentage, int screen_dimention, int frames, int rows, Object params)
{
if(name != "null")
{
_tempInt = _context.getResources().getIdentifier(name, "drawable", _context.getPackageName());
_tempBitmap = (BitmapFactory.decodeResource(_context.getResources(), _tempInt, _BM_options_temp));
}
else
{
_tempBitmap = (Bitmap) params;
}
_bmWidth = _tempBitmap.getWidth() / frames;
_bmHeight = _tempBitmap.getHeight() / rows;
_newWidth = (screen_dimention / 100.0f) * percentage;
_newHeight = (_newWidth / _bmWidth) * _bmHeight;
//Round up to closet factor of total frames (Stops juddering within animation)
_newWidth = _newWidth * frames;
//Output the created item
/*
Log.w(name, "Item");
Log.w(Integer.toString((int)_newWidth), "new width");
Log.w(Integer.toString((int)_newHeight), "new height");
*/
//Create new item and recycle bitmap
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, false);
_tempBitmap.recycle();
return newBitmap;
}
There's an excellent guide over on the Android Training site:
http://developer.android.com/training/displaying-bitmaps/load-bitmap.html
It's about efficient loading of bitmap images - highly recommended!
This will save space. If not using Alpha colors it would be better not to use one with the A channel.
Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// or Bitmap.Config.RGB_565 ;
// or Bitmap.Config.ARGB_4444 ;
Bitmap newBitmap = Bitmap.createScaledBitmap(_tempBitmap, (int)_newWidth, (int)_newHeight, options);
I know this must be one of the most asked things at SO, but none of the other answers gave me a solution. But from reading the other answers, looks like I'll need to redesign the way the App is working.
It's like this, we have a ScrollView, which will inflate some views. A ListView can't be used in this situation, because to behave the way we want it would require extending the ListView, and this is something we don't want to do (even though this seems to be our only solution to our current way of showing items, because of this OOM exception). The list can have a lot of columns per row, and the bigger the screen, more columns it will have.
Each inflated View has a layout displaying some info from the database, including a picture. This picture is stored through a byte array. It's any picture taken with the device camera. Currently every photo (byte array) is taking 800kb to 1mb, which seems a lot to me. Now the list have 30+ items. I took photos until the OOM happened, and it happened when I took a total of 6 photos (occasionally 7). That would be 8mb-9mb of data. Everytime I go to other Activity, and go back to the Activity the ScrollView is in, the list needs to be repopulated.
This is the snippet of the PopulateList method:
if (item.getImg() != null) {
if (App.debug) {
Log.d(TAG, "Setting bmp.");
}
Bitmap bmp = App.byteArrayToBmp(item.getImg());
imgV.setImageBitmap(bmp);
}
Every inflated View will open an 'Advanced Dialog', which will contain other info. Maybe the Image could be there instead on the list (meaning that there would be only 1 bitmap, as every inflated View shares the same advanced dialog). Or I could extend the ListView and benefit from it recycling method (It's not a good solution as I though it would be considering more than 6 items can be at the screen). Another thing that bothers me is every picture having 800kb. Seems like a lot for a 128x128.
This is the setup for the size:
cameraParams.setPictureSize(App.pxToDpi(128), App.pxToDpi(128));
cameraParams.setPictureFormat(PixelFormat.JPEG);
camera.setParameters(cameraParams);
public static int pxToDpi(int px) {
final int scale = app.getApplicationContext().getResources().getDisplayMetrics().densityDpi;
int pixels = (int) px * (scale / 160);
return pixels;
}
So, do you think there is a solution to my issue keeping the current model of my App, or will I need to reformulate?
EDIT: The bitmap method:
public static Bitmap byteArrayToBmp(byte[] byteArray) {
Bitmap img = null;
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
img = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length, opts);
return img;
}
You might want to look at the Official Android Training docs, they've just been updated:
Check out Displaying Bitmaps Efficiently with the lesson: Loading Large Bitmaps Efficiently that goes over this.
Basically you can decode the image using sampleSize to decode it to the width and height you want:
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) {
if (width > height) {
inSampleSize = Math.round((float)height / (float)reqHeight);
} else {
inSampleSize = Math.round((float)width / (float)reqWidth);
}
}
return inSampleSize;
}
Explained in much great detail in the links above
I'm loading a bitmap into an ImageView, and seeing this error. I gather this limit relates to a size limit for OpenGL hardware textures (2048x2048). The image I need to load is a pinch-zoom image of about 4,000 pixels high.
I've tried turning off hardware acceleration in the manifest, but no joy.
<application
android:hardwareAccelerated="false"
....
>
Is it possible to load an image larger than 2048 pixels into an ImageView?
This isn't a direct answer to the question (loading images >2048), but a possible solution for anyone experiencing the error.
In my case, the image was smaller than 2048 in both dimensions (1280x727 to be exact) and the issue was specifically experienced on a Galaxy Nexus. The image was in the drawable folder and none of the qualified folders. Android assumes drawables without a density qualifier are mdpi and scales them up or down for other densities, in this case scaled up 2x for xhdpi. Moving the culprit image to drawable-nodpi to prevent scaling solved the problem.
I have scaled down the image in this way:
ImageView iv = (ImageView)waypointListView.findViewById(R.id.waypoint_picker_photo);
Bitmap d = new BitmapDrawable(ctx.getResources() , w.photo.getAbsolutePath()).getBitmap();
int nh = (int) ( d.getHeight() * (512.0 / d.getWidth()) );
Bitmap scaled = Bitmap.createScaledBitmap(d, 512, nh, true);
iv.setImageBitmap(scaled);
All rendering is based on OpenGL, so no you can't go over this limit (GL_MAX_TEXTURE_SIZE depends on the device, but the minimum is 2048x2048, so any image lower than 2048x2048 will fit).
With such big images, if you want to zoom in out, and in a mobile, you should setup a system similar to what you see in google maps for example. With the image split in several pieces, and several definitions.
Or you could scale down the image before displaying it (see user1352407's answer on this question).
And also, be careful to which folder you put the image into, Android can automatically scale up images. Have a look at Pilot_51's answer below on this question.
Instead of spending hours upon hours trying to write/debug all this downsampling code manually, why not use Picasso? It was made for dealing with bitmaps of all types and/or sizes.
I have used this single line of code to remove my "bitmap too large...." problem:
Picasso.load(resourceId).fit().centerCrop().into(imageView);
Addition of the following 2 attributes in (AndroidManifest.xml) worked for me:
android:largeHeap="true"
android:hardwareAccelerated="false"
Changing the image file to drawable-nodpi folder from drawable folder worked for me.
I used Picasso and had the same problem. image was too large at least in on size, width or height. finally I found the solution here. you can scale the large image down according to display size and also keep the aspect ratio:
public Point getDisplaySize(Display display) {
Point size = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(size);
} else {
int width = display.getWidth();
int height = display.getHeight();
size = new Point(width, height);
}
return size;
}
and use this method for loading image by Picasso:
final Point displySize = getDisplaySize(getWindowManager().getDefaultDisplay());
final int size = (int) Math.ceil(Math.sqrt(displySize.x * displySize.y));
Picasso.with(this)
.load(urlSource)
.resize(size, size)
.centerInside()
.into(imageViewd);
also for better performance you can download the image according to width and height of the display screen, not whole the image:
public String reviseImageUrl(final Integer displayWidth, final Integer displayHeight,
final String originalImageUrl) {
final String revisedImageUrl;
if (displayWidth == null && displayHeight == null) {
revisedImageUrl = originalImageUrl;
} else {
final Uri.Builder uriBuilder = Uri.parse(originalImageUrl).buildUpon();
if (displayWidth != null && displayWidth > 0) {
uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_WIDTH, String.valueOf(displayWidth));
}
if (displayHeight != null && displayHeight > 0) {
uriBuilder.appendQueryParameter(QUERY_KEY_DISPLAY_HEIGHT, String.valueOf(displayHeight));
}
revisedImageUrl = uriBuilder.toString();
}
return revisedImageUrl;
}
final String newImageUlr = reviseImageUrl(displySize.x, displySize.y, urlSource);
and then:
Picasso.with(this)
.load(newImageUlr)
.resize(size, size)
.centerInside()
.into(imageViewd);
EDIT: getDisplaySize()
display.getWidth()/getHeight() is deprecated. Instead of Display use DisplayMetrics.
public Point getDisplaySize(DisplayMetrics displayMetrics) {
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
return new Point(width, height);
}
BitmapRegionDecoder does the trick.
You can override onDraw(Canvas canvas), start a new Thread and decode the area visible to the user.
As pointed by Larcho, starting from API level 10, you can use BitmapRegionDecoder to load specific regions from an image and with that, you can accomplish to show a large image in high resolution by allocating in memory just the needed regions. I've recently developed a lib that provides the visualisation of large images with touch gesture handling. The source code and samples are available here.
View level
You can disable hardware acceleration for an individual view at runtime with the following code:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
I ran through same problem, here is my solution. set the width of image same as android screen width and then scales the height
Bitmap myBitmap = BitmapFactory.decodeFile(image.getAbsolutePath());
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
Log.e("Screen width ", " "+width);
Log.e("Screen height ", " "+height);
Log.e("img width ", " "+myBitmap.getWidth());
Log.e("img height ", " "+myBitmap.getHeight());
float scaleHt =(float) width/myBitmap.getWidth();
Log.e("Scaled percent ", " "+scaleHt);
Bitmap scaled = Bitmap.createScaledBitmap(myBitmap, width, (int)(myBitmap.getWidth()*scaleHt), true);
myImage.setImageBitmap(scaled);
This is better for any size android screen. let me know if it works for you.
Scale down image:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// Set height and width in options, does not return an image and no resource taken
BitmapFactory.decodeStream(imagefile, null, options);
int pow = 0;
while (options.outHeight >> pow > reqHeight || options.outWidth >> pow > reqWidth)
pow += 1;
options.inSampleSize = 1 << pow;
options.inJustDecodeBounds = false;
image = BitmapFactory.decodeStream(imagefile, null, options);
The image will be scaled down at the size of reqHeight and reqWidth. As I understand inSampleSize only take in a power of 2 values.
Use Glide library instead of directly loading into imageview
Glide : https://github.com/bumptech/glide
Glide.with(this).load(Uri.parse(filelocation))).into(img_selectPassportPic);
I tried all the solutions above, one-after-the-other, for quite many hours, and none seemed to work! Finally, I decided to look around for an official example concerning capturing images with Android's camera, and displaying them. The official example (here), finally gave me the only method that worked. Below I present the solution I found in that example app:
public void setThumbnailImageAndSave(final ImageView imgView, File imgFile) {
/* There isn't enough memory to open up more than a couple camera photos */
/* So pre-scale the target bitmap into which the file is decoded */
/* Get the size of the ImageView */
int targetW = imgView.getWidth();
int targetH = imgView.getHeight();
/* Get the size of the image */
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
/* Figure out which way needs to be reduced less */
int scaleFactor = 1;
if ((targetW > 0) || (targetH > 0)) {
scaleFactor = Math.min(photoW/targetW, photoH/targetH);
}
/* Set bitmap options to scale the image decode target */
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;
/* Decode the JPEG file into a Bitmap */
Bitmap bitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bmOptions);
/* Associate the Bitmap to the ImageView */
imgView.setImageBitmap(bitmap);
imgView.setVisibility(View.VISIBLE);
}
NOTE FOR THOSE WHO WANT TO PUT IMAGES OF SMALL SIZE:
Pilot_51's solution (moving your images to drawable-nodpi folder) works, but has another problem:
It makes images TOO SMALL on screen unless the images are resized to a very large (like 2000 x 3800) resolution to fit screen -- then it makes your app heavier.
SOLUTION: put your image files in drawable-hdpi -- It worked like a charm for me.
Using the correct drawable subfolder solved it for me. My solution was to put my full resolution image (1920x1200) into the drawable-xhdpi folder, instead of the drawable folder.
I also put a scaled down image (1280x800) into the drawable-hdpi folder.
These two resolutions match the 2013 and 2012 Nexus 7 tablets I'm programming. I also tested the solution on some other tablets.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
///*
if (requestCode == PICK_FROM_FILE && resultCode == RESULT_OK && null != data){
uri = data.getData();
String[] prjection ={MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(uri,prjection,null,null,null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(prjection[0]);
ImagePath = cursor.getString(columnIndex);
cursor.close();
FixBitmap = BitmapFactory.decodeFile(ImagePath);
ShowSelectedImage = (ImageView)findViewById(R.id.imageView);
// FixBitmap = new BitmapDrawable(ImagePath);
int nh = (int) ( FixBitmap.getHeight() * (512.0 / FixBitmap.getWidth()) );
FixBitmap = Bitmap.createScaledBitmap(FixBitmap, 512, nh, true);
// ShowSelectedImage.setImageBitmap(BitmapFactory.decodeFile(ImagePath));
ShowSelectedImage.setImageBitmap(FixBitmap);
}
}
This code is work
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);