Изменить размер изображения, чтобы поместиться в ограничивающем поле


Легкая проблема, но по какой-то причине я просто не могу понять это сегодня.

Мне нужно изменить размер изображения до максимально возможного размера, который поместится в ограничивающую рамку, сохраняя при этом соотношение сторон.

В основном я ищу код для заполнения этой функции:

void CalcNewDimensions(ref int w, ref int h, int MaxWidth, int MaxHeight);

Где w & h-исходные высота и ширина (in) и новые высота и ширина (out), а MaxWidth и MaxHeight определяют ограничивающую рамку, в которую должно поместиться изображение.

9 35

9 ответов:

Найдите, что меньше: MaxWidth / w или MaxHeight / h Затем умножьте w и h на это число

Пояснение:

Вам нужно найти коэффициент масштабирования, который делает изображение подходящим.

Чтобы найти коэффициент масштабирования, s, для ширины, то s должен быть таким, что: s * w = MaxWidth. Поэтому коэффициент масштабирования равен MaxWidth / w.

Аналогично для высоты.

Тот, который требует наибольшего масштабирования (меньший s), является фактором, с помощью которого вы должны масштабировать целое изображение.

Основываясь на предложении Эрика, я бы сделал что-то вроде этого:

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;
}
Возможно, я немного переборщил с бросками, но я просто пытался сохранить точность в расчетах.

Чтобы выполнить заливку аспекта вместо подгонки аспекта, используйте вместо этого большее соотношение. То есть, изменить код Мэтта от математики.Мин к математике.Макс.

(заполнение аспекта не оставляет ни одного ограничивающего прямоугольника пустым, но может вывести часть изображения за пределы границ, в то время как подгонка аспекта не оставляет ни одного изображения за пределами границ, но может оставить некоторые ограничивающие прямоугольники пустыми.)

Попробовал код Мистера Уоррена, но он не дал надежных результатов.

Например,

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}

Вы можете видеть, высота = 49 и высота = 50 в другом.

Вот мой (основанный на версии кода Мистера Уоррена) без расхождения и небольшого рефактора:

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

Следующий код дает более точные результаты:

    public static Size CalculateResizeToFit(Size imageSize, Size boxSize)
    {
        // TODO: Check for arguments (for null and <=0)
        var widthScale = boxSize.Width / (double)imageSize.Width;
        var heightScale = boxSize.Height / (double)imageSize.Height;
        var scale = Math.Min(widthScale, heightScale);
        return new Size(
            (int)Math.Round((imageSize.Width * scale)),
            (int)Math.Round((imageSize.Height * scale))
            );
    }

Код Python, но, возможно, он укажет вам правильное направление:

def fit_within_box(box_width, box_height, width, height):
    """
    Returns a tuple (new_width, new_height) which has the property
    that it fits within box_width and box_height and has (close to)
    the same aspect ratio as the original size
    """
    new_width, new_height = width, height
    aspect_ratio = float(width) / float(height)

    if new_width > box_width:
        new_width = box_width
        new_height = int(new_width / aspect_ratio)

    if new_height > box_height:
        new_height = box_height
        new_width = int(new_height * aspect_ratio)

    return (new_width, new_height)

Убер просто. :) Вопрос в том, чтобы найти коэффициент, на который нужно умножить ширину и высоту. Решение состоит в том, чтобы попробовать использовать один, и если он не подходит, используйте другой. Так...

private float ScaleFactor(Rectangle outer, Rectangle inner)
{
    float factor = (float)outer.Height / (float)inner.Height;
    if ((float)inner.Width * factor > outer.Width) // Switch!
        factor = (float)outer.Width / (float)inner.Width;
    return factor;
}

Чтобы поместить картинку (pctRect) в окно (wndRect) вызовите вот так

float factor=ScaleFactor(wndRect, pctRect); // Outer, inner
RectangleF resultRect=new RectangleF(0,0,pctRect.Width*factor,pctRect.Height*Factor)

У меня была похожая проблема, и я нашел, что это очень полезно: Статья. Как я правильно понял вам нужно изменить размер изображения?

Основываясь на предыдущих ответах, вот функция Javascript:

/**
* fitInBox
* Constrains a box (width x height) to fit in a containing box (maxWidth x maxHeight), preserving the aspect ratio
* @param width      width of the box to be resized
* @param height     height of the box to be resized
* @param maxWidth   width of the containing box
* @param maxHeight  height of the containing box
* @param expandable (Bool) if output size is bigger than input size, output is left unchanged (false) or expanded (true)
* @return           {width, height} of the resized box
*/
function fitInBox(width, height, maxWidth, maxHeight, expandable) {
    "use strict";

    var aspect = width / height,
        initWidth = width,
        initHeight = height;

    if (width > maxWidth || height < maxHeight) {
        width = maxWidth;
        height = Math.floor(width / aspect);
    }

    if (height > maxHeight || width < maxWidth) {
        height = maxHeight;
        width = Math.floor(height * aspect);
    }

    if (!!expandable === false && (width >= initWidth || height >= initHeight)) {
        width = initWidth;
        height = initHeight;
    }

    return {
        width: width,
        height: height
    };
}