Resize Image to fit in bounding box

Tried Mr. Warren's code, but it didn't produce reliable results.

For example,

ExpandToBound(new Size(640,480), new Size(66, 999)).Dump();
// {Width=66, Height=49}

ExpandToBound(new Size(640,480), new Size(999,50)).Dump();
// {Width=66, Height=50}

You can see, height = 49 and height = 50 in another.

Here's mine (based version of Mr. Warren's code) without the discrepancy and a slight refactor:

// Passing null for either maxWidth or maxHeight maintains aspect ratio while
//        the other non-null parameter is guaranteed to be constrained to
//        its maximum value.
//
//  Example: maxHeight = 50, maxWidth = null
//    Constrain the height to a maximum value of 50, respecting the aspect
//    ratio, to any width.
//
//  Example: maxHeight = 100, maxWidth = 90
//    Constrain the height to a maximum of 100 and width to a maximum of 90
//    whichever comes first.
//
private static Size ScaleSize( Size from, int? maxWidth, int? maxHeight )
{
   if ( !maxWidth.HasValue && !maxHeight.HasValue ) throw new ArgumentException( "At least one scale factor (toWidth or toHeight) must not be null." );
   if ( from.Height == 0 || from.Width == 0 ) throw new ArgumentException( "Cannot scale size from zero." );

   double? widthScale = null;
   double? heightScale = null;

   if ( maxWidth.HasValue )
   {
       widthScale = maxWidth.Value / (double)from.Width;
   }
   if ( maxHeight.HasValue )
   {
       heightScale = maxHeight.Value / (double)from.Height;
   }

   double scale = Math.Min( (double)(widthScale ?? heightScale),
                            (double)(heightScale ?? widthScale) );

   return new Size( (int)Math.Floor( from.Width * scale ), (int)Math.Ceiling( from.Height * scale ) );
}

Find which is smaller: MaxWidth / w or MaxHeight / h Then multiply w and h by that number

Explanation:

You need to find the scaling factor which makes the image fit.

To find the scaling factor, s, for the width, then s must be such that: s * w = MaxWidth. Therefore, the scaling factor is MaxWidth / w.

Similarly for height.

The one that requires the most scaling (smaller s) is the factor by which you must scale the whole image.


Based on Eric's suggestion I'd do something like this:

private static Size ExpandToBound(Size image, Size boundingBox)
{       
    double widthScale = 0, heightScale = 0;
    if (image.Width != 0)
        widthScale = (double)boundingBox.Width / (double)image.Width;
    if (image.Height != 0)
        heightScale = (double)boundingBox.Height / (double)image.Height;                

    double scale = Math.Min(widthScale, heightScale);

    Size result = new Size((int)(image.Width * scale), 
                        (int)(image.Height * scale));
    return result;
}

I might have gone a bit overboard on the casts, but I was just trying to preserve precision in the calculations.


To perform an aspect fill instead of an aspect fit, use the larger ratio instead. That is, change Matt's code from Math.Min to Math.Max.

(An aspect fill leaves none of the bounding box empty but may put some of the image outside the bounds, while an aspect fit leaves none of the image outside the bounds but may leave some of the bounding box empty.)