Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android - calculateInSampleSize, why does Math.round deal with height( height / reqHeight) when width > height?

I'm looking on 'developer.android.com' to scale down my bitmap file and I found one thing that I don't understand. so I appreciate you give me a little help.

Here's a snippet from developer.android.com

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

in if statement, when " if(width > height)" why do they calculate "(float)height / (float)reqHeight " ?

for example, width=600, height=800, reqWidth=100, reqHeight=100.

In this situation, inSampleSize would be 6 and the dimensions calculated are width=100, height=133. height is still above reqHeight..

so, can anyone explain me about this, please? sorry for complicated explanation but I hope someone give me a idea. :)

like image 681
user1874389 Avatar asked Dec 04 '12 04:12

user1874389


4 Answers

All I can say is that their logic looks wrong :( Anyway this method is fairly simple so it shouldn't be that much of a problem for you to reimplement it with the correct conditions ! I mean when you take a look at decodeSampledBitmapFromResource, it only wants to reduce the Bitmap to make it fit into the desired bounds, so this has to be an error.

EDIT :: That looks even worse as to me it won't work for some cases. Let's say you have width = 200 and height = 600. You set your max bounds at width = 100 and height = 500. You have height > width, yet if you want them both to fit the return result inSampleSize has to be 200/100 and not 600/500. So basically if you reimplement the method, I would do it this way :

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 stretch_width = Math.round((float)width / (float)reqWidth);
    int stretch_height = Math.round((float)height / (float)reqHeight);

    if (stretch_width <= stretch_height) 
        return stretch_height;
    else 
        return stretch_width;
}

But that looks like too many issues with their code for me to believe I understood its point correctly !

like image 144
PeterGriffin Avatar answered Nov 09 '22 04:11

PeterGriffin


Code in above question is seriously outdated. As documented in BitmapFactory.Options reference (since March 8, 2013) inSampleSize will be rounded down to the nearest power of 2.

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder uses a final value based on powers of 2, any other value will be rounded down to the nearest power of 2.

BitmapFactory.Options reference from March 8, 2013

So proper code to calculate inSampleSize would be Loading Large Bitmaps

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

However above code can be additionally optimized. since halfWidth and halfHeight are already divided by 2, while loop can be rewritten to return image larger or equal to requested size bounds.

        while ((halfHeight / inSampleSize) >= reqHeight
                && (halfWidth / inSampleSize) >= reqWidth) {
            inSampleSize *= 2;
        }

Original code for image sized 800x800 pix that has to fit into 100x100 pix will return inSampleSize of 4 -> returned image will be 200x200 pix, and modified code will return inSampleSize of 8 -> returned image will be 100x100 pix.

Note: Primary function of down-sampling image with inSampleSize and BitmapFactory.decodeXXX methods is to preserve memory while loading images that are significantly larger than needed for display purposes. Those methods in combination with above code will always give you smallest image (scaled by power of 2) that is larger (or equal) to the requested bounds, not image that will fit inside that bounds.

like image 25
Dalija Prasnikar Avatar answered Nov 09 '22 03:11

Dalija Prasnikar


Well, after having many problems with the scaling-stuff I think i got my own answer:

Using "Loading Large Bitmaps Efficiently" from http://developer.android.com/training/displaying-bitmaps/load-bitmap.html I want to post some of my results (Original Code below):

// 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);

I am currently working on my Galaxy-Tab 7.7 with 1280 x 752 resolution in landscape mode. Imagine an Image with the following specs:

1920 x 1200 .. what will happen?

heighRatio = 1920/1280 = 1.5 and widthRatio = 1200/752 = 1.59

Both numbers get roundet to 2 so the Image will be scaled down by the factor 2 if i understand everything right. This will result in the Image to be 1920/2 = 960 * 600 which is less then my requiered Resolution of 1280 * 752.

I solved this with replacing round by floor:

// Calculate ratios of height and width to requested height and width
final int heightRatio = (int)Math.floor((float) height / (float) reqHeight);
final int widthRatio = (int)Math.floor((float) width / (float) reqWidth);

This actually prevents some of my images get scaled down too much. I am currently still investigating the parameter inSampleSize to see if using "fractions" would be an option. Currently all Images up to the size of 1280x752 (*2) = 2560*1504 will not get scaled. The imageView I am writing this is an detailed view of the image so it should not be too much of an issue right now.

I use the modified version of the in code in conjunction with:

returnview.setAdjustViewBounds(true);

This will prevent images larger then my screen to get a messed up bounding box. You can see it if you set an color-backround to the actual image. Furthermore with the now fixed code I can implement some onClick Handler to detect if the user clicks outside of my image to close the image.

like image 3
cimba007 Avatar answered Nov 09 '22 02:11

cimba007


Their logic is not only hamfisted it's actually wrong. The thing you need in the image isn't shrink it until one parameter would be less than the required value if it were divided in half, but rather make sure no dimension is greater than 2048. Above that, a lot of times you can't render the texture or display the image. And if you had an image that was something like 4000x1300 and you said you wanted at least 768. It cannot actually cut the 1300 in half without going below that value. So it does nothing and exits. Trying to load a 4000 wide image which fails.

public static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
    int height = options.outHeight;
    int width = options.outWidth;
    int inSampleSize = 1;
    while (height > maxHeight || width > maxWidth) {
        height /= 2;
        width /= 2;
        inSampleSize *= 2;
    }
    return inSampleSize;
}
like image 1
Tatarize Avatar answered Nov 09 '22 03:11

Tatarize