Преоразмерете изображението, за да се побере в ограничителната кутия

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

Трябва да преоразмеря изображение до максималния възможен размер, който ще се побере в ограничителна кутия, като същевременно поддържа съотношението на страните.

По принцип търся кода за попълване на тази функция:

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

Където w & h са оригиналната височина и ширина (навътре) и новата височина и ширина (навън), а MaxWidth и MaxHeight определят ограничителната кутия, в която трябва да се побере изображението.


person Eric Petroelje    schedule 09.07.2009    source източник
comment
Моля, не злоупотребявайте с реферите по този начин. Много по-добре е да направите неизменим struct Rectangle, който има поле за ширина и височина, и след това да напишете метод ExpandToBound, който взема два Rectangle и връща получения правоъгълник. Много по-лесно е да разсъждавате относно функциите, когато ги имплементирате като функции. Влизат аргументи, излизат резултати; функциите не мутират състояние, което не притежават.   -  person Eric Lippert    schedule 10.07.2009
comment
@Eric Lippert – Съгласен съм, примерът не беше функцията, която реално внедрих, а просто сведена версия, за да се избегне объркването на проблема с правоъгълни структури или други неща, които не са част от същината на проблема.   -  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