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

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

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

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

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

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


person Eric Petroelje    schedule 09.07.2009    source источник
comment
Пожалуйста, не злоупотребляйте подобным образом рефери. Намного лучше создать неизменяемую структуру Rectangle, которая имеет поле ширины и высоты, а затем написать метод ExpandToBound, который принимает два Rectangles и возвращает результирующий Rectangle. Намного легче рассуждать о функциях, если вы реализуете их как functions. Аргументы приходят, результаты приходят; функции не изменяются, они не принадлежат.   -  person Eric Lippert    schedule 10.07.2009
comment
@Eric Lippert - Согласен, пример - это не та функция, которую я фактически реализовал, а просто упрощенная версия, чтобы не путать проблему со структурами Rectangle или другими вещами, которые не являются частью проблемы.   -  person Eric Petroelje    schedule 22.12.2009


Ответы (9)


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

Объяснение:

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

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

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

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

person Shawn J. Goff    schedule 09.07.2009
comment
Если вы сделаете это с помощью float, вы, вероятно, можете обнаружить, что размер, соответствующий ограничивающей рамке, немного отклоняется (иногда непредсказуемым образом). Когда вы определили, какое измерение не совпадает, может быть лучше просто предположить, что другое именно то, что должно быть? - person Marcus Downing; 20.08.2009
comment
+1 Три года спустя занимаю первое место в моих результатах Google. Ни суеты, ни суеты. Спасибо! - person chaosTechnician; 04.06.2012
comment
Например, ваш ответ не написан на c, поэтому я могу применить его к python - person Luke Taylor; 29.06.2015
comment
Спасибо за то, что вы предоставили алгоритм и объяснение, а не просто код! Я бы хотел, чтобы больше решений было написано так. - person delrocco; 14.10.2017
comment
Спасибо, это хорошо работает. объяснение тоже действительно помогло - person Jacob Schneider; 05.01.2020

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

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

Я мог бы немного переборщить с забросами, но я просто пытался сохранить точность расчетов.

person Matt Warren    schedule 10.07.2009
comment
не забудьте return result; - person Brian Chavez; 20.03.2011

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

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

person vocaro    schedule 19.08.2009
comment
Отличная информация. Мне не было интуитивно понятно, глядя на другие ответы - person Felipe; 19.01.2020

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

Например,

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 ) );
}
person Brian Chavez    schedule 20.03.2011
comment
Вы должны использовать Math.Round в обоих случаях в последней строке, так как в вашем коде мы получаем неправильные результаты. - person Thomas; 28.12.2017

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

    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))
            );
    }
person M. Mennan Kara    schedule 29.07.2012

Код 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)
person Jason Creighton    schedule 09.07.2009
comment
Ваш код работает, если изображение нужно сжать, а не если его нужно расширить до размеров контейнера. Я думаю, вам следует обновить свои условия следующим образом: if new_width > box_width or new_height < box_height: И: if new_height > box_height or new_width < box_width: - person Nicolas Le Thierry d'Ennequin; 14.03.2012

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

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)
person Tomaž Štih    schedule 06.03.2015

Основываясь на предыдущих ответах, вот функция 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
    };
}
person Nicolas Le Thierry d'Ennequin    schedule 25.01.2014

Код Python для этой задачи основан на ответе Джейсона с исправлением для масштабирования и изменения порядка аргументов для обычной передачи аргументов с помощью img.shape.

def fit_within_box(box_height, box_width, height, width):
    """
    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 or new_height < box_height:
        new_width = box_width
        new_height = int(new_width / aspect_ratio)

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

    return new_height, new_width
person vozman    schedule 05.06.2020