56

I need help with math / algorithm to take an image of known size and fit to one of two screen dimensions:

720 x 480 or 1280 x 1024.

The image dimensions are coming from an XML file, however those dimensions are the web dimensions, I also get a selection of images from the XML that may be of higher and lower resolution than the web dimensions.

What I want is to use the aspect ration of the web dimensions to display the higher resolution image, if available, on an HD (1280x720) screen, or, if the user is on an SD screen (720x480) display the image on that screen.

Other things that would be useful for this, but lower priority, would be, if I know the resolution of the image is smaller in both dimensions than an SD screen (in this case, all I know is the web dimension, and the horizontal dimension of the image file), to display it as actual size on that screen.

pppery
  • 3,731
  • 22
  • 33
  • 46
alphablender
  • 2,168
  • 5
  • 27
  • 42
  • Could you post sample data and what would you want the result to be? – svick Jul 03 '11 at 21:48
  • Ok, well, here is some real sample data: web dimension= width="340" height="517" where there is an image available that the x dimension is actually 1280. I want to resize this so that it's height is no higher than 720, and resize the width proportionally so the image does not get distorted, so I can display it on an HD screen, or if the user is on an SD screen, that would be the dimension I would scale to. The output would be targetrect={x,y,xx,yy) – alphablender Jul 03 '11 at 22:04
  • https://math.stackexchange.com/a/180809/210893 – Stack Underflow Jul 26 '19 at 13:55

4 Answers4

180

Generic as can be:

Image data: (wi, hi) and define ri = wi / hi
Screen resolution: (ws, hs) and define rs = ws / hs

Scaled image dimensions:

rs > ri ? (wi * hs/hi, hs) : (ws, hi * ws/wi)

So for example:

         20
|------------------|
    10
|---------|

--------------------     ---   ---
|         |        |      | 7   |
|         |        |      |     | 10
|----------        |     ---    |
|                  |            |
--------------------           ---

ws = 20
hs = 10
wi = 10
hi = 7

20/10 > 10/7 ==> (wi * hs/hi, hs) = (10 * 10/7, 10) = (100/7, 10) ~ (14.3, 10)

Which as you can see clearly scales to the screen size, because the height is that of the screen but clearly keeps aspect ratio since 14.3/10 ~ 10/7

UPDATE

Center the image as follows:

call (wnew, hnew) the new dimensions.

top = (hs - hnew)/2
left = (ws - wnew)/2
davin
  • 44,863
  • 9
  • 78
  • 78
  • I'm not totally familiar with the notation you are using, does the subscripted i refer to a member of an array, like x[i] where x is an array of size, so i refers to an image and w[i] means we are operating on an array of images? Sorry if that makes me seem like an idiot. – alphablender Jul 03 '11 at 23:37
  • 2
    @alphablender, no, no array, the `i` was supposed to mean image, and `s` screen. – davin Jul 03 '11 at 23:42
  • Does this look kind of like what you are describing?: imagewidth=x imageheight=y imageratio= imagewidth / imageheight screenwidth=1280 screenheight=720 screenratio=screenwidth / screenheight if screenratio > imageratio then resultwidth=imagewidth * screenheight / imageheight resultheight=screenheight else resultwidth=screenwidth resultheight=imageheight * screenwidth / imagewidth end if – alphablender Jul 04 '11 at 00:50
  • What would be the way to compute centering the image on a given screen size once I have calculated the dimension? – alphablender Jul 04 '11 at 03:42
  • This is great, and quite clear. I think it might be more clear to use 9 instead of one of the 10s, but I like the ascii drawings. – Thunder Rabbit Sep 20 '12 at 13:50
  • That expression just explains everything. Neat and useful! – Au Ris Sep 18 '13 at 19:00
  • Thanks! I mashed up this code here in Swift: http://stackoverflow.com/a/30519019/8047... but I found that by reversing the `>`, I was able to get "fill" instead of "letterbox." Is this an artifact of really bad testing, or is this correct? – Dan Rosenstark May 29 '15 at 00:01
  • this implemented in javascript https://gist.github.com/thecotne/d7630dec23ebb1aad24a5d5d0e355a4c – thecotne Feb 26 '19 at 14:39
  • If you reverse the condition like this `rs < ri` it will do `fill` `fit` --> `rs > ri ? (wi * hs/hi, hs) : (ws, hi * ws/wi)` `fill` --> `rs < ri ? (wi * hs/hi, hs) : (ws, hi * ws/wi)` – mehdok Dec 01 '21 at 15:29
30

I understand the accepted answer and it works, but I've always found the following method to be simpler and succinct for "best fit":

// prep
let maxWidth = 190,
    maxHeight = 150;
let imgWidth = img.width,
    imgHeight = img.height;

// calc
let widthRatio = maxWidth / imgWidth,
    heightRatio = maxHeight / imgHeight;
let bestRatio = Math.min(widthRatio, heightRatio);

// output
let newWidth = imgWidth * bestRatio,
    newHeight = imgHeight * bestRatio;

... which of course can be distilled down to:

const maxWidth = 190, maxHeight = 150;
const bestRatio = Math.min(maxWidth / img.width, maxHeight / img.height);
img.width *= bestRatio;
img.height *= bestRatio;
pstanton
  • 35,033
  • 24
  • 126
  • 168
12

Here it is in straightforward C.

You want to scale both coordinates by the returned scale factor.

/* For a rectangle inside a screen, get the scale factor that permits the rectangle
   to be scaled without stretching or squashing. */
float 
aspect_correct_scale_for_rect(const float screen[2], const float rect[2])
{
    float screenAspect = screen[0] / screen[1];
    float rectAspect = rect[0] / rect[1];

    float scaleFactor;
    if (screenAspect > rectAspect)
        scaleFactor = screen[1] / rect[1];
    else
        scaleFactor = screen[0] / rect[0];

    return scaleFactor;
}

Michael Labbé
  • 12,017
  • 4
  • 27
  • 36
5

Aspect ratio correction with letterboxing or fit-to-screen

I wrote up a method recently to handle this exact problem in iOS. I'm using the Eigen matrix library to do scaling, but the the principle (scaling factor) is the same without matrices.

Eigen::Matrix4x4f aspectRatioCorrection(bool fillScreen, const Eigen::Vector2f &screenSize, const Eigen::Vector2f &imageSize)
{
    Eigen::Matrix4x4f scalingMatrix(Eigen::Matrix4x4f::Identity());

    float screenWidth = screenSize.x();
    float screenHeight = screenSize.y();
    float screenAspectRatio = screenWidth / screenHeight;
    float imageWidth = imageSize.x();
    float imageHeight = imageSize.y();
    float imageAspectRatio = imageWidth / imageHeight;

    float scalingFactor;
    if (fillScreen) {
        if (screenAspectRatio > imageAspectRatio) {
            scalingFactor = screenWidth / imageWidth;
        } else {
            scalingFactor = screenHeight / imageHeight;
        }
    } else {
        if (screenAspectRatio > imageAspectRatio) {
            scalingFactor =  screenHeight / imageHeight;
        } else {
            scalingFactor = screenWidth / imageWidth;
        }
    }

    scalingMatrix(0, 0) = scalingFactor;
    scalingMatrix(1, 1) = scalingFactor;

    return scalingMatrix;
}
Cameron Lowell Palmer
  • 21,528
  • 7
  • 125
  • 126