How to keep aspect ratio and position of rectangle inside another rectangle?

8.3k Views Asked by At

This problem has plagued me for many years.

Given two rectangles how do I resize the first to fit into the second while preserving the aspect ratio? The second rectangle could be bigger or smaller than the first.

Here are the variables I have to work with where the first rectangle is represented as an image:

// these variables contain the image and container sizes
imageWidth
imageHeight
targetWidth
targetHeight
adjustedImageWidth
adjustedImageHeight

imageAspectRatio = imageWidth/imageHeight;
targetAspectRatio = targetWidth/targetHeight;

What I have found so far as the next step (from open source code):

if (imageAspectRatio > targetAspectRatio) {
    adjustedHeight = targetWidth / imageAspectRatio;
else
    adjustedWidth = targetHeight * imageAspectRatio;
}

// define size and position of image
// to fit inside second rectangle

I do not know what to do next.

2

There are 2 best solutions below

1
On BEST ANSWER

I do not understand what you mean by keeping the position the same, so I will just find the largest scaling of rectangle 1 that fits into rectangle 2. Basically you just find the scaling that works for the width and the scaling that works for the height and take the minimum. If the first rectangle is $h_1 \times w_1$ and the second is $h_2 \times w_2$ you write

vert scale $=\frac {h_2}{h_1}$
horiz scale $= \frac {w_2}{w_1}$
$\text{scale}=\min($horiz scale, vert scale)
new dimensions $=\text{scale} \cdot h_1 \times \text{scale} \cdot w_1$
The new dimensions will fit within the second rectangle, touching on two sides.

If you want to keep the center point (say) of rectangle $1$ and find the largest scaling possible, you do the same as above, but compute four scaling factors, one for each side, then take the minimum. It will touch on one side only.

9
On

Thanks to Ross I was able to put the "code" example of this together:

// these contain the image sizes and container sizes
imageWidth          = image.width;
imageHeight         = image.height;
targetWidth         = container.width; // or desired width
targetHeight        = container.height; // or desired height

// get the aspect ratios in case we need to expand or shrink to fit
imageAspectRatio    = imageWidth/imageHeight;
targetAspectRatio   = targetWidth/targetHeight;

adjustedWidth       = targetWidth;
adjustedHeight      = targetHeight;

// get the larger aspect ratio of the two
// if aspect ratio is 1 then no adjustment needed
if (imageAspectRatio > targetAspectRatio) {
    adjustedHeight  = targetWidth / imageAspectRatio;
else if (imageAspectRatio < targetAspectRatio) {
    adjustedWidth   = targetHeight * imageAspectRatio;
}

// set the adjusted size (same if square)
image.width         = adjustedWidth;
image.height        = adjustedHeight;

// center the image in the container (optional)
image.x             = (targetWidth / 2) - (adjustedWidth / 2);
image.y             = (targetHeight / 2) - (adjustedHeight / 2);

Note:
The x and y equation may be incorrect

Update
I've created a code pen that shows passing in multiple values.

Here is the code from that pen including a function to return the adjusted size:

function getSizeToFit(currentWidth, currentHeight, desiredWidth, desiredHeight, showInConsole = true) {

    // get the aspect ratios in case we need to expand or shrink to fit
    var imageAspectRatio    = currentWidth/currentHeight;
    var targetAspectRatio   = desiredWidth/desiredHeight;

    // no need to adjust the size if current size is square
    var adjustedWidth       = desiredWidth;
    var adjustedHeight      = desiredHeight;

    // get the larger aspect ratio of the two
    // if aspect ratio is 1 then no adjustment needed
    if (imageAspectRatio > targetAspectRatio) {
      adjustedHeight = desiredWidth / imageAspectRatio;
    }
    else if (imageAspectRatio < targetAspectRatio) {
      adjustedWidth = desiredHeight * imageAspectRatio;
    }

    // set the adjusted size (same if square)
    var newSizes = {};
    newSizes.computedWidth = adjustedWidth;
    newSizes.computedHeight = adjustedHeight;

    if (showInConsole) {
      var info = "Image size: " + currentWidth + "x" + currentHeight;
      info += ", Desired size: " + desiredWidth + "x" + desiredHeight;
      info += ", Computed size: " + adjustedWidth + "x" + adjustedHeight; 
      info += ", Image aspect ratio: " + imageAspectRatio;
      info += ", Desired size aspect ratio: " + targetAspectRatio; 
      console.log(info);
    }

    return newSizes;
}

  // smaller square to larger square
  var squareSizes = getSizeToFit(100, 100, 200, 200);
  //console.log("New size:" + squareSizes.computedWidth + "x" + squareSizes.computedHeight);

  // smaller to larger
  var smallerToLarger = getSizeToFit(50, 100, 200, 200);

  // larger square to smaller square
  var largerToSmaller = getSizeToFit(200, 200, 100, 100);

  // larger rectangle to smaller square
  var largerRectangleToSmaller = getSizeToFit(500, 1000, 100, 100);

  // larger rectangle to smaller rectangle
  var largerRectangleToSmaller = getSizeToFit(1024, 768, 400, 200);

  // smaller rectangle to larger rectangle
  var largerRectangleToSmaller = getSizeToFit(320, 200, 1024, 768);

Output

"Image size: 100x100, Desired size: 200x200, Computed size: 200x200, Image aspect ratio: 1, Desired size aspect ratio: 1"

"Image size: 50x100, Desired size: 200x200, Computed size: 100x200, Image aspect ratio: 0.5, Desired size aspect ratio: 1"

"Image size: 200x200, Desired size: 100x100, Computed size: 100x100, Image aspect ratio: 1, Desired size aspect ratio: 1"

"Image size: 500x1000, Desired size: 100x100, Computed size: 50x100, Image aspect ratio: 0.5, Desired size aspect ratio: 1"

"Image size: 1024x768, Desired size: 400x200, Computed size: 266.66666666666663x200, Image aspect ratio: 1.3333333333333333, Desired size aspect ratio: 2"

"Image size: 320x200, Desired size: 1024x768, Computed size: 1024x640, Image aspect ratio: 1.6, Desired size aspect ratio: 1.3333333333333333"

https://codepen.io/anon/pen/gBqqNB?editors=0012