math/algorithm Fit image to screen retain aspect ratio

AlgorithmImageMathGraphicsResize

Algorithm Problem Overview


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.

Hope that is clear enough.

Thanks!

Algorithm Solutions


Solution 1 - Algorithm

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

Solution 2 - Algorithm

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;

Solution 3 - Algorithm

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;

}

Solution 4 - Algorithm

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionalphablenderView Question on Stackoverflow
Solution 1 - AlgorithmdavinView Answer on Stackoverflow
Solution 2 - AlgorithmpstantonView Answer on Stackoverflow
Solution 3 - AlgorithmMichael LabbéView Answer on Stackoverflow
Solution 4 - AlgorithmCameron Lowell PalmerView Answer on Stackoverflow