Search code examples
androidimagememoryscaleimage-size

Android. How to scale images without memory exception?


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:

  1. We scale image using quick algorihtm as match as possible, but image should still has sizes more then required.
  2. 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.


Solution

  • 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;
      }