2009-07-09 10 views
34

Un problema fácil, pero por alguna razón no puedo resolverlo hoy.Cambiar el tamaño de la imagen para que quepa en el cuadro delimitador

Necesito cambiar el tamaño de una imagen al tamaño máximo posible que cabe en un cuadro delimitador mientras se mantiene la relación de aspecto.

Básicamente estoy buscando el código para llenar esta función:

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

Donde w & h son la altura y anchura (en) y la nueva altura y anchura (fuera) y MaxWidth y original MaxHeight define el cuadro delimitador en el que debe encajar la imagen.

+7

Por favor, no abusar de los árbitros por el estilo. Es mucho mejor hacer un rectángulo struct_immutable_ que tenga un ancho y un campo de altura, y luego escribir un método ExpandToBound que tome dos rectángulos y devuelva el rectángulo resultante. Es mucho más fácil razonar acerca de las funciones cuando las implementa como _funciones_. Argumentos entran, salen los resultados; las funciones no cambian el estado que no poseen. –

+0

@Eric Lippert - De acuerdo, el ejemplo no fue la función que realmente implementé, solo una versión reducida para evitar confundir el problema con las estructuras Rectangle u otras cosas que no forman parte del núcleo del problema. –

Respuesta

74

Encuentra que es más pequeño: MaxWidth/w o MaxHeight/h luego multiplicar w y h por ese número

Explicación:

es necesario encontrar el factor de escala que hace que el ajuste de la imagen.

Para hallar el factor de escala, s, para el ancho, entonces s debe ser tal que: s * w = MaxWidth. Por lo tanto, el factor de escala es MaxWidth/w.

De forma similar para la altura.

El que requiere más escala (más pequeño s) es el factor por el que debe escalar toda la imagen.

+0

Si lo hace con flotador, es probable que encuentre que la dimensión que coincide con el cuadro delimitador está un poco apagada (a veces, de manera impredecible). Cuando haya identificado qué dimensión no coincide, ¿sería mejor simplemente asumir que la otra es exactamente lo que debería ser? –

+4

+1 Tres años más tarde sentado en la parte superior de mis resultados de Google. Sin despeinarse sin problemas. ¡Gracias! – chaosTechnician

+1

Me gusta cómo su respuesta no está escrita en c, así que puedo aplicarla a python –

0

Tuve un problema similar y lo encontré muy útil: article. Como entendí correctamente, ¿necesita cambiar el tamaño de la imagen?

2

código Python, pero tal vez te orientará en la dirección correcta:

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

Su código funciona si la imagen debe reducirse, no si debe expandirse para ajustarse al contenedor. Creo que se debería actualizar el condicionales así: ' si new_width> box_width o new_height box_height o new_width

26

Sobre la base de la sugerencia de Eric me gustaría hacer algo como esto:

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

Me podría haber ido un poco por la borda en los moldes, pero solo estaba tratando de preservar la precisión en los cálculos.

+0

no se olvide de 'return resultado;' Usted –

6

Para realizar un relleno de aspecto en lugar de un ajuste de aspecto, utilice la relación más grande en su lugar. Es decir, cambie el código de Matt de Math.Min a Math.Max.

(Un relleno de aspecto no deja ningún cuadro delimitador vacío pero puede poner parte de la imagen fuera de los límites, mientras que un aspecto no deja ninguna imagen fuera de los límites pero puede dejar vacío el recuadro delimitador)

6

Probé el código del Sr. Warren, pero no produjo resultados confiables.

Por ejemplo,

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} 

Se puede ver, altura = 49 y la altura = 50 en otro.

Aquí está el mío (versión del Sr.Código de Warren) sin la discrepancia y una ligera 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

Para usar Math.Round en ambos casos en la última línea, como en tu código obtenemos resultados incorrectos. – Thomas

4

siguiente código produce resultados más precisos:

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

Basado en las respuestas anteriores, he aquí una función de 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

Muy simple. :) El problema es encontrar un factor por el cual se necesita multiplicar el ancho y la altura. La solución es intentar usar uno y si no encaja, usar el otro. Así que ...

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

Para compensar la imagen (pctRect) a la ventana (wndRect) llamada como ésta

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