2011-04-20 22 views
11

Me gustaría copiar un área aproximadamente rectangular a un área rectangular. Ejemplo:¿Cómo se dibuja una imagen basada en un polígono simple?

Ambas áreas se definen por sus puntos de esquina. La dirección general se mantiene (sin voltear, etc.).

Simplemente girando la imagen de origen no funciona ya que los lados opuestos pueden ser de diferente longitud.

Hasta ahora no encontré ninguna manera de hacer esto en C# puro (excepto en la copia manual de píxeles), así que supongo que tengo que recurrir a la API de Windows o a una biblioteca de terceros.

Respuesta

3

En términos generales, lo que se quiere hacer es asignar el destino de las coordenadas de la fuente coordina a través de una función de transformación:

for (int y = 0; y < destHeight; y++) { 
    for (x=0; x < destWidth; x++) { 
     Color c = Transform(x, y, sourceImage, sourceTransform); 
     SetPixel(destImage, x, y, c); 
    } 
} 

Asumamos que sourceTransform es un objeto que encapsula una transformación de por lo urce a dest coordenadas (y viceversa).

Trabajar en coordenadas de destino hará que sea más fácil evitar esa curva en su imagen original retransformada y le permitirá mejores antialias, ya que puede asignar las esquinas del píxel de dest a la imagen de origen y muestra dentro e interpolar/extrapolar.

En su caso, va a tener un conjunto de ecuaciones lineales que hacen el mapeo, en este caso esto se conoce como deformación cuadrilátera - see this previous question.

8

Como no pude encontrar una respuesta, escribí una implementación ingenua por mi cuenta. Funciona razonablemente bien.

Ejemplos

me llamaron todos los ejemplos a mano en la pintura, por lo que no son muy exactos - que era justo lo suficiente para poner a prueba algunos conceptos básicos.

a) Giro leve.

Fuente:

source image

Resultado:

resulting image

b) Diversas lados

Fuente:

source 2

Resultado:

result 2

c) Perspectiva

Fuente:

source 3

Resultado:

result 3

Código

(se especializó en mi caso de uso, pero debe ser fácil de adaptar):

// _Corners are, well, the 4 corners in the source image 
// _Px is an array of pixels extracted from the source image 

public void Rescale() 
{ 
    RescaleImage (
     _Corners[0], 
     _Corners[1], 
     _Corners[3], 
     _Corners[2], 
     100, 
     100); 
} 

private void RescaleImage (PointF TL, PointF TR, PointF LL, PointF LR, int sx, int sy) 
{ 
    var bmpOut = new Bitmap (sx, sy); 

    for (int x = 0; x < sx; x++) { 
     for (int y = 0; y < sy; y++) { 
      /* 
      * relative position 
      */ 
      double rx = (double) x/sx; 
      double ry = (double) y/sy; 

      /* 
      * get top and bottom position 
      */ 
      double topX = TL.X + rx * (TR.X - TL.X); 
      double topY = TL.Y + rx * (TR.Y - TL.Y); 
      double bottomX = LL.X + rx * (LR.X - LL.X); 
      double bottomY = LL.Y + rx * (LR.Y - LL.Y); 

      /* 
      * select center between top and bottom point 
      */ 
      double centerX = topX + ry * (bottomX - topX); 
      double centerY = topY + ry * (bottomY - topY); 

      /* 
      * store result 
      */ 
      var c = PolyColor (centerX, centerY); 
      bmpOut.SetPixel (x, y, c); 
     } 
    } 

    bmpOut.Save (_Path + "out5 rescale out.bmp"); 
} 

private Color PolyColor (double x, double y) 
{ 
    // get fractions 
    double xf = x - (int) x; 
    double yf = y - (int) y; 

    // 4 colors - we're flipping sides so we can use the distance instead of inverting it later 
    Color cTL = _Px[(int) y + 1, (int) x + 1]; 
    Color cTR = _Px[(int) y + 1, (int) x + 0]; 
    Color cLL = _Px[(int) y + 0, (int) x + 1]; 
    Color cLR = _Px[(int) y + 0, (int) x + 0]; 

    // 4 distances 
    double dTL = Math.Sqrt (xf * xf + yf * yf); 
    double dTR = Math.Sqrt ((1 - xf) * (1 - xf) + yf * yf); 
    double dLL = Math.Sqrt (xf * xf + (1 - yf) * (1 - yf)); 
    double dLR = Math.Sqrt ((1 - xf) * (1 - xf) + (1 - yf) * (1 - yf)); 

    // 4 parts 
    double factor = 1.0/(dTL + dTR + dLL + dLR); 
    dTL *= factor; 
    dTR *= factor; 
    dLL *= factor; 
    dLR *= factor; 

    // accumulate parts 
    double r = dTL * cTL.R + dTR * cTR.R + dLL * cLL.R + dLR * cLR.R; 
    double g = dTL * cTL.G + dTR * cTR.G + dLL * cLL.G + dLR * cLR.G; 
    double b = dTL * cTL.B + dTR * cTR.B + dLL * cLL.B + dLR * cLR.B; 

    Color c = Color.FromArgb ((int) (r + 0.5), (int) (g + 0.5), (int) (b + 0.5)); 

    return c; 
} 
+0

¡Eso es genial! Después de leer esto, la única palabra que puedo pensar es "anti-skew". Buen trabajo :) – Tom

+1

sé que esta es una publicación anterior, pero ¿a qué te refieres con '* estamos volteando lados para poder usar la distancia en lugar de invertirla más adelante *' en tu 'PolyColor' porque si tu imagen es 800x600 obtendrá un IndexOutOfRangeExeption – WiiMaxx

Cuestiones relacionadas