2009-07-09 17 views
34

Łatwy problem, ale z jakiegoś powodu po prostu nie mogę tego dzisiaj zrozumieć.Zmień rozmiar obrazu, aby zmieścił się w ramce ograniczającej

Potrzebuję zmienić rozmiar obrazu na maksymalny możliwy rozmiar, który będzie pasował do ramki ograniczającej, zachowując proporcje.

Basicly Szukam kodu do wypełnienia tej funkcji:

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

gdzie w & h to oryginalna wysokość i szerokość (w), a nowa wysokość i szerokość (na zewnątrz) i maxwidth i MaxHeight definiuje ramkę ograniczającą, w której obraz musi się mieścić.

+7

Proszę nie nadużywać piśmiennictwa w tym stylu. Znacznie lepiej zrobić prostokątny struct Rectangle, który ma pole szerokości i wysokości, a następnie napisać metodę ExpandToBound, która pobiera dwa prostokąty i zwraca wynikowy prostokąt. O wiele łatwiej jest wnioskować o funkcjach, gdy implementujesz je jako _functions_. Pojawiają się argumenty, wyniki wychodzą; funkcje nie modyfikują stanu, który nie jest ich właścicielem. –

+0

@Eric Lippert - Zgoda, przykładem nie była funkcja, którą faktycznie zaimplementowałem, tylko uproszczona wersja, aby uniknąć mylenia problemu ze strukturami Rectangle lub innymi rzeczami, które nie są częścią rdzenia problemu. –

Odpowiedz

74

Znajdź który jest mniejszy: MaxWidth/w lub MaxHeight/h następnie pomnożyć w i h przez tego numeru

Objaśnienie:

Trzeba znaleźć współczynnik skalowania, który sprawia, że ​​dopasowanie obrazu.

Aby znaleźć współczynnik skalowania, s, dla szerokości, to s musi być takie, aby: s * w = MaxWidth. Dlatego współczynnik skalowania to MaxWidth/w.

Podobnie dla wzrostu.

Ten, który wymaga większego skalowania (mniejszy s) jest czynnikiem, za pomocą którego należy skalować cały obraz.

+0

Jeśli zrobisz to z floatem, prawdopodobnie uznasz, że wymiar dopasowujący się do ramki ograniczającej jest nieznacznie wyłączony (czasami w nieprzewidywalny sposób). Kiedy rozpoznasz, który wymiar nie pasuje, czy najlepiej jest po prostu założyć, że drugi jest dokładnie tym, jaki powinien być? –

+4

+1 Trzy lata później znajduję się u góry moich wyników Google. Bez musów, bez zamieszania. Dzięki! – chaosTechnician

+1

Podobnie jak odpowiedź nie jest zapisana wc, więc mogę zastosować ją do python –

0

Podobny problem i znalazłem to bardzo pomocne: article. Jak dobrze zrozumiałem, musisz zmienić rozmiar obrazu?

2

kod Python, ale być może będzie to punkt, który w dobrym kierunku:

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) 
+0

Twój kod działa, jeśli obraz musi zostać zmniejszony, a nie, jeśli musi zostać rozszerzony, aby pasował do pojemnika. Myślę, że należy zaktualizować warunkowe tak: 'jeśli new_width> box_width lub new_height box_height lub new_width

26

podstawie sugestii Erica zrobiłbym coś takiego:

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

mogę poszły trochę za burtę, ale starałem się zachować precyzję w obliczeniach.

+0

Nie zapomnij 'return result;' –

6

Aby wykonać wypełnienie kształtu zamiast kształtu, należy użyć większego współczynnika. To znaczy, zmień kod Matta z Math.Min na Math.Max.

(Wypełnienie aspektu nie pozostawia pustego pola obramowania, ale może umieścić część obrazu poza obrębem, podczas gdy dopasowanie do aspektu nie pozostawia żadnego obrazu poza granicami, ale może pozostawić część obwiedni pustej.)

6

Próbowałem kodu pana Warrena, ale nie dało to wiarygodnych wyników.

Na przykład

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} 

Można zobaczyć, wysokość = 49 height = 50, a w drugim.

Oto moja (oparta wersja panaKod Warrena) bez rozbieżności i lekkim 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)); 
} 
+0

You shoud używa Math.Round w obu przypadkach w ostatnim wierszu, tak jak w Twoim kodzie, otrzymujemy błędne wyniki. – Thomas

4

następujący kod produkuje dokładniejsze wyniki:

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

na podstawie poprzedniej odpowiedzi, oto funkcja 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 
    }; 
} 
1

Über prosty. :) Problem polega na znalezieniu współczynnika, przez który należy pomnożyć szerokość i wysokość. Rozwiązaniem jest próba użycia jednego, a jeśli nie pasuje, użyj drugiego. Więc ...

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

Aby dopasować obraz (pctRect) do okna (wndRect) nazywamy takie jak ta

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